git.iko.soy/rowtype-yoga/purescript-react-aria
on main help sign in
Initial commit
commitc2b4e4c6eff9240b6255e1ce480d995fa44b88c2 authorMark Eibes <mark.eibes@gmail.com> dateMon 21 Nov 2022, 11:21 (4 years ago)
21 files changed+977
.gitignore+10 LICENSE+16 packages.dhall+105 spago.dhall+34 src/React/Aria/Button.js+50 src/React/Aria/Button.purs+98 src/React/Aria/Combobox.js+3 src/React/Aria/Combobox.purs+127 src/React/Aria/Focus.js+5 src/React/Aria/Focus.purs+27 src/React/Aria/Interactions.js+4 src/React/Aria/Interactions.purs+56 src/React/Aria/JSSet.js+4 src/React/Aria/JSSet.purs+17 src/React/Aria/Listbox.js+6 src/React/Aria/Listbox.purs+112 src/React/Aria/Overlays.js+37 src/React/Aria/Overlays.purs+159 src/React/Aria/Types.purs+81 src/React/Aria/Utils.js+7 src/React/Aria/Utils.purs+19
.gitignorenew file+10
@@ -0,0 +1,10 @@
1+/bower_components/
2+/node_modules/
3+/.pulp-cache/
4+/output/
5+/generated-docs/
6+/.psc-package/
7+/.psc*
8+/.purs*
9+/.psa*
10+/.spago
LICENSEnew file+16
@@ -0,0 +1,16 @@
1+MIT No Attribution
2+
3+Copyright 2021 Mark Eibes, Jan Schulte
4+
5+Permission is hereby granted, free of charge, to any person obtaining a copy of this
6+software and associated documentation files (the "Software"), to deal in the Software
7+without restriction, including without limitation the rights to use, copy, modify,
8+merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
9+permit persons to whom the Software is furnished to do so.
10+
11+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
12+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
13+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
14+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
15+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
16+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
packages.dhallnew file+105
@@ -0,0 +1,105 @@
1+{-
2+Welcome to your new Dhall package-set!
3+
4+Below are instructions for how to edit this file for most use
5+cases, so that you don't need to know Dhall to use it.
6+
7+## Use Cases
8+
9+Most will want to do one or both of these options:
10+1. Override/Patch a package's dependency
11+2. Add a package not already in the default package set
12+
13+This file will continue to work whether you use one or both options.
14+Instructions for each option are explained below.
15+
16+### Overriding/Patching a package
17+
18+Purpose:
19+- Change a package's dependency to a newer/older release than the
20+ default package set's release
21+- Use your own modified version of some dependency that may
22+ include new API, changed API, removed API by
23+ using your custom git repo of the library rather than
24+ the package set's repo
25+
26+Syntax:
27+where `entityName` is one of the following:
28+- dependencies
29+- repo
30+- version
31+-------------------------------
32+let upstream = --
33+in upstream
34+ with packageName.entityName = "new value"
35+-------------------------------
36+
37+Example:
38+-------------------------------
39+let upstream = --
40+in upstream
41+ with halogen.version = "master"
42+ with halogen.repo = "https://example.com/path/to/git/repo.git"
43+
44+ with halogen-vdom.version = "v4.0.0"
45+ with halogen-vdom.dependencies = [ "extra-dependency" ] # halogen-vdom.dependencies
46+-------------------------------
47+
48+### Additions
49+
50+Purpose:
51+- Add packages that aren't already included in the default package set
52+
53+Syntax:
54+where `<version>` is:
55+- a tag (i.e. "v4.0.0")
56+- a branch (i.e. "master")
57+- commit hash (i.e. "701f3e44aafb1a6459281714858fadf2c4c2a977")
58+-------------------------------
59+let upstream = --
60+in upstream
61+ with new-package-name =
62+ { dependencies =
63+ [ "dependency1"
64+ , "dependency2"
65+ ]
66+ , repo =
67+ "https://example.com/path/to/git/repo.git"
68+ , version =
69+ "<version>"
70+ }
71+-------------------------------
72+
73+Example:
74+-------------------------------
75+let upstream = --
76+in upstream
77+ with benchotron =
78+ { dependencies =
79+ [ "arrays"
80+ , "exists"
81+ , "profunctor"
82+ , "strings"
83+ , "quickcheck"
84+ , "lcg"
85+ , "transformers"
86+ , "foldable-traversable"
87+ , "exceptions"
88+ , "node-fs"
89+ , "node-buffer"
90+ , "node-readline"
91+ , "datetime"
92+ , "now"
93+ ]
94+ , repo =
95+ "https://github.com/hdgarrood/purescript-benchotron.git"
96+ , version =
97+ "v7.0.0"
98+ }
99+-------------------------------
100+-}
101+let upstream =
102+ https://github.com/purescript/package-sets/releases/download/psc-0.15.4-20221120/packages.dhall
103+ sha256:b14c3d5c9d32f5fd92bee0b81e087f73f7f7d3ffc6089ca20446f1d00c06311f
104+
105+in upstream
spago.dhallnew file+34
@@ -0,0 +1,34 @@
1+{-
2+Welcome to a Spago project!
3+You can edit this file as you like.
4+
5+Need help? See the following resources:
6+- Spago documentation: https://github.com/purescript/spago
7+- Dhall language tour: https://docs.dhall-lang.org/tutorials/Language-Tour.html
8+
9+When creating a new Spago project, you can use
10+`spago init --no-comments` or `spago init -C`
11+to generate this file without the comments in this block.
12+-}
13+{ name = "react-aria"
14+, license = "MIT"
15+, repository = "https://github.com/rowtype-yoga/purescript-react-aria"
16+, dependencies =
17+ [ "aff"
18+ , "effect"
19+ , "foreign"
20+ , "foreign-object"
21+ , "nullable"
22+ , "ordered-collections"
23+ , "prelude"
24+ , "react-basic"
25+ , "react-basic-dom"
26+ , "react-basic-hooks"
27+ , "unsafe-coerce"
28+ , "untagged-union"
29+ , "web-dom"
30+ , "web-html"
31+ ]
32+, packages = ./packages.dhall
33+, sources = [ "src/**/*.purs", "test/**/*.purs" ]
34+}
src/React/Aria/Button.jsnew file+50
@@ -0,0 +1,50 @@
1+import { useButton } from "@react-aria/button"
2+
3+export const useButtonImpl = useButton
4+
5+export function toDashedProps(psProps) {
6+ const arias = psProps.hasOwnProperty("_aria")
7+ ? Object.fromEntries(
8+ Object.entries(psProps._aria).map(([key, value]) => [
9+ "aria-" + key,
10+ value
11+ ])
12+ )
13+ : {}
14+ const datas = psProps.hasOwnProperty("_data")
15+ ? Object.fromEntries(
16+ Object.entries(psProps._data).map(([key, value]) => [
17+ "data-" + key,
18+ value
19+ ])
20+ )
21+ : {}
22+ const reactProps = Object.assign({}, psProps, arias, datas)
23+ delete reactProps._aria
24+ delete reactProps._data
25+ return reactProps
26+}
27+
28+export function fromDashedProps(reactProps) {
29+ const psProps = {}
30+ let _aria = {}
31+ let _data = {}
32+ Object.entries(reactProps.buttonProps).forEach(([key, value]) => {
33+ if (key.startsWith("aria-")) {
34+ _aria[key.slice(5)] = value
35+ } else if (key.startsWith("data-")) {
36+ _data[key.slice(5)] = value
37+ } else {
38+ psProps[key] = value
39+ }
40+ // console.log(psProps)
41+ })
42+ if (Object.keys(_aria).length !== 0) {
43+ psProps._aria = _aria
44+ }
45+ if (Object.keys(_data).length !== 0) {
46+ psProps._data = _data
47+ }
48+ reactProps.buttonProps = psProps
49+ return reactProps
50+}
src/React/Aria/Button.pursnew file+98
@@ -0,0 +1,98 @@
1+module React.Aria.Button
2+ ( useButton
3+ , UseButton
4+ , ButtonPropsImpl
5+ , pressEvent
6+ , PressEventImpl
7+ ) where
8+
9+import Prelude
10+import Data.Nullable (Nullable)
11+import Effect.Uncurried (EffectFn1, EffectFn2, runEffectFn2)
12+import Foreign (Foreign, unsafeFromForeign, unsafeToForeign)
13+import Foreign.Object (Object)
14+import Prim.Row (class Union)
15+import React.Basic.Events (EventFn, SyntheticEvent, EventHandler, unsafeEventFn)
16+import React.Basic.Hooks (Hook, Ref, unsafeHook)
17+import Unsafe.Coerce (unsafeCoerce)
18+import Untagged.Union (OneOf)
19+import Web.DOM (Node)
20+import Web.HTML (HTMLElement)
21+
22+foreign import data UseButton ∷ Type → Type
23+
24+type PressEventImpl =
25+ { type ∷ String
26+ , pointerType ∷ String
27+ , target ∷ HTMLElement
28+ , shiftKey ∷ Boolean
29+ , ctrlKey ∷ Boolean
30+ , metaKey ∷ Boolean
31+ }
32+
33+pressEvent ∷ EventFn SyntheticEvent PressEventImpl
34+pressEvent = unsafeEventFn unsafeCoerce
35+
36+type ButtonPropsImpl =
37+ ( elementType ∷ String -- button/a/div...
38+ , isDisabled ∷ Boolean
39+ , onPress ∷ EventHandler
40+ , onPressStart ∷ EventHandler
41+ , onPressEnd ∷ EventHandler
42+ , onPressUp ∷ EventHandler
43+ , onPressChange ∷ EffectFn1 Boolean Unit
44+ , preventFocusOnPress ∷ Boolean
45+ , href ∷ String -- for `a`
46+ , target ∷ String -- for `a`
47+ , rel ∷ String -- for `a`
48+ , "aria-expanded" ∷ Boolean
49+ , "aria-haspopup" ∷ OneOf Boolean String
50+ , "aria-controls" ∷ String
51+ , "aria-pressed" ∷ Boolean
52+ , type ∷ String -- button/submit/reset
53+ , excludeFromTabOrder ∷ Boolean
54+ , "aria-label" ∷ String
55+ , "aria-labelledby" ∷ String
56+ , "aria-describedby" ∷ String
57+ , "aria-details" ∷ String
58+ )
59+
60+type ButtonPropsResult =
61+ { _aria ∷ Object String
62+ , disabled ∷ Boolean
63+ , onBlur ∷ EventHandler
64+ , onClick ∷ EventHandler
65+ , onDragStart ∷ EventHandler
66+ , onFocus ∷ EventHandler
67+ , onKeyDown ∷ EventHandler
68+ , onKeyUp ∷ EventHandler
69+ , onMouseDown ∷ EventHandler
70+ , onPointerDown ∷ EventHandler
71+ , onPointerUp ∷ EventHandler
72+ , tabIndex ∷ Int
73+ , type ∷ String
74+ }
75+
76+useButton ∷
77+ ∀ attrsIn attrsIn_.
78+ Union attrsIn attrsIn_ ButtonPropsImpl ⇒
79+ { | attrsIn } →
80+ Ref (Nullable Node) →
81+ Hook UseButton { isPressed ∷ Boolean, buttonProps ∷ ButtonPropsResult }
82+useButton props ref =
83+ unsafeHook
84+ $
85+ runEffectFn2 useButtonImpl
86+ (unsafeToForeign $ props)
87+ ref
88+ <#> (fromDashedProps >>> unsafeFromForeign)
89+
90+foreign import useButtonImpl ∷
91+ EffectFn2
92+ (Foreign)
93+ (Ref (Nullable Node))
94+ (Foreign)
95+
96+foreign import toDashedProps ∷ Foreign → Foreign
97+
98+foreign import fromDashedProps ∷ Foreign → Foreign
src/React/Aria/Combobox.jsnew file+3
@@ -0,0 +1,3 @@
1+import { useComboBox } from "@react-aria/combobox"
2+
3+export const useComboBoxImpl = useComboBox
src/React/Aria/Combobox.pursnew file+127
@@ -0,0 +1,127 @@
1+module React.Aria.Combobox where
2+
3+import Prelude
4+
5+import Data.Nullable (Nullable)
6+import Effect (Effect)
7+import Effect.Uncurried (EffectFn1, EffectFn2, runEffectFn2)
8+import Prim.Row (class Union)
9+import React.Aria.JSSet (JSSet)
10+import React.Aria.Types (FocusStrategy, MenuTriggerAction, Selection, SelectionBehaviour, SelectionMode, ValidationState)
11+import React.Basic (JSX)
12+import React.Basic.Events (EventHandler, SyntheticEvent)
13+import React.Basic.Hooks (Hook, Ref, unsafeHook)
14+import Web.DOM (Node)
15+
16+-- UseComboBox
17+newtype UseComboBox hooks = UseComboBox hooks
18+
19+type AriaComboBoxPropsRequired a r =
20+ ( inputRef ∷ Ref (Nullable Node)
21+ , popoverRef ∷ Ref (Nullable Node)
22+ , listBoxRef ∷ Ref (Nullable Node)
23+ , children ∷ a → JSX -- Must map to either an item or a section
24+ | r
25+ )
26+
27+type AriaComboBoxPropsOptional a =
28+ ( buttonRef ∷ Ref (Nullable Node)
29+ , defaultItems ∷ Array a
30+ , items ∷ Array a
31+ -- , keyboardDelegate :: KeyboardDelegate
32+ , onOpenChange ∷
33+ EffectFn2 Boolean {- isOpen -}
34+ String {- menuTriggerAction: 'focus' | 'input' | 'manual' -}
35+ Unit
36+ , inputValue ∷ String
37+ , defaultInputValue ∷ String
38+ , onInputChange ∷ EffectFn1 String Unit
39+ , allowsCustomValue ∷ Boolean
40+ , menuTrigger ∷ MenuTriggerAction
41+ , shouldFocusWrap ∷ Boolean -- | Whether keyboard navigation is circular.
42+ , disabledKeys ∷
43+ Array String -- | The item keys that are disabled. These items cannot be selected, focused, or otherwise interacted with.
44+ , disallowEmptySelection ∷ Boolean -- | Whether the collection allows empty selection.
45+ , selectedKey ∷ String -- | The currently selected key in the collection (controlled).
46+ , defaultSelectedKey ∷ String -- | The initial selected key in the collection (uncontrolled).
47+ , onSelectionChange ∷ EffectFn1 String Unit
48+ , isDisabled ∷ Boolean
49+ , isReadOnly ∷ Boolean
50+ , placeholder ∷ String
51+ , id ∷ String
52+ , validationState ∷ ValidationState
53+ , isRequired ∷ Boolean
54+ , autoFocus ∷ Boolean
55+ , onFocus ∷ EventHandler
56+ , onBlur ∷ EventHandler
57+ , onFocusChange ∷ EffectFn1 Boolean Unit
58+ , onKeyDown ∷ EventHandler
59+ , onKeyUp ∷ EventHandler
60+ , label ∷ JSX
61+ , description ∷ JSX
62+ , errorMessage ∷ JSX
63+ )
64+
65+type UseComboBoxState =
66+ { inputValue ∷ String
67+ , isFocused ∷ Boolean
68+ , selectedKey ∷ String
69+ , selectedItem ∷ Node
70+ , collection ∷ Array Node
71+ , disabledKeys :: JSSet String
72+ , selectionManager :: SelectionManager
73+ , focusStrategy ∷ FocusStrategy
74+ , isOpen ∷ Boolean
75+ , setInputValue ∷ EffectFn1 String Unit
76+ , commit ∷ Effect Unit
77+ , open ∷ EffectFn2 (Nullable FocusStrategy) MenuTriggerAction Unit
78+ , toggle ∷ EffectFn2 (Nullable FocusStrategy) MenuTriggerAction Unit
79+ , revert ∷ Effect Unit
80+ , setFocused ∷ EffectFn1 Boolean Unit
81+ , setSelectedKey ∷ EffectFn1 String Unit
82+ , close ∷ Effect Unit
83+ }
84+
85+type SelectionManager =
86+ { selectionMode ∷ SelectionMode
87+ , disallowEmptySelection ∷ Boolean
88+ , selectionBehavior ∷ SelectionBehaviour
89+ , isFocused ∷ Boolean
90+ , focusedKey ∷ String
91+ , childFocusStrategy ∷ FocusStrategy
92+ , selectedKeys :: JSSet String
93+ , rawSelection :: Selection
94+ , isEmpty ∷ Boolean
95+ , isSelectAll ∷ Boolean
96+ , firstSelectedKey ∷ Nullable String
97+ , lastSelectedKey ∷ Nullable String
98+ , setSelectionBehavior ∷ EffectFn1 SelectionBehaviour Unit
99+ , setFocused ∷ EffectFn1 Boolean Unit
100+ , setFocusedKey ∷ EffectFn2 String FocusStrategy Unit
101+ , isSelected ∷ EffectFn1 String Boolean
102+ , extendSelection ∷ EffectFn1 String Unit
103+ , toggleSelection ∷ EffectFn1 String Unit
104+ , replaceSelection ∷ EffectFn1 String Unit
105+ , setSelectedKeys ∷ EffectFn1 (Array String) Unit
106+ , selectAll ∷ Effect Unit
107+ , clearSelection ∷ Effect Unit
108+ , toggleSelectAll ∷ Effect Unit
109+ , select ∷ EffectFn2 String SyntheticEvent Unit
110+ , isSelectionEqual :: EffectFn1 (JSSet String) Unit
111+ , canSelectItem ∷ EffectFn1 String Unit
112+ }
113+
114+type ComboBoxAria = { comboBoxProps ∷ { "data-isComboBox" ∷ Boolean } }
115+
116+foreign import useComboBoxImpl
117+ ∷ ∀ props state
118+ . EffectFn2 props state ComboBoxAria
119+
120+useComboBox
121+ ∷ ∀ a props props_
122+ . Union props props_ (AriaComboBoxPropsOptional a)
123+ ⇒ { | AriaComboBoxPropsRequired a props }
124+ → UseComboBoxState
125+ → Hook UseComboBox ComboBoxAria
126+useComboBox p s = unsafeHook do
127+ runEffectFn2 useComboBoxImpl p s
src/React/Aria/Focus.jsnew file+5
@@ -0,0 +1,5 @@
1+import { FocusRing, FocusScope, useFocusRing } from "@react-aria/focus"
2+
3+export const focusRingImpl = FocusRing
4+export const focusScopeImpl = FocusScope
5+export const useFocusRingImpl = useFocusRing
src/React/Aria/Focus.pursnew file+27
@@ -0,0 +1,27 @@
1+module React.Aria.Focus where
2+
3+import Prelude
4+import Effect.Uncurried (EffectFn1, runEffectFn1)
5+import React.Basic (ReactComponent)
6+import React.Basic.Events (EventHandler)
7+import React.Basic.Hooks (Hook, unsafeHook)
8+
9+foreign import focusRingImpl ∷ ∀ props. ReactComponent { | props }
10+
11+foreign import focusScopeImpl ∷ ∀ props. ReactComponent { | props }
12+
13+foreign import useFocusRingImpl ∷ EffectFn1 UseFocusRingInput UseFocusRingResult
14+
15+foreign import data UseFocusRing ∷ Type -> Type
16+
17+type UseFocusRingResult =
18+ { isFocused ∷ Boolean
19+ , isFocusVisible ∷ Boolean
20+ , focusProps ∷ { onBlur ∷ EventHandler, onFocus ∷ EventHandler }
21+ }
22+
23+type UseFocusRingInput =
24+ { within ∷ Boolean, isTextInput ∷ Boolean, autoFocus ∷ Boolean }
25+
26+useFocusRing ∷ UseFocusRingInput -> Hook UseFocusRing UseFocusRingResult
27+useFocusRing = unsafeHook <<< runEffectFn1 useFocusRingImpl
src/React/Aria/Interactions.jsnew file+4
@@ -0,0 +1,4 @@
1+import { useFocusWithin, useFocus } from "@react-aria/interactions"
2+
3+export const useFocusWithinImpl = useFocusWithin
4+export const useFocusImpl = useFocus
src/React/Aria/Interactions.pursnew file+56
@@ -0,0 +1,56 @@
1+module React.Aria.Interactions2 where
2+
3+import Prelude
4+
5+import Effect.Uncurried (EffectFn1, runEffectFn1)
6+import Prim.Row (class Union)
7+import React.Basic.Events (EventHandler)
8+import React.Basic.Hooks (Hook, unsafeHook)
9+
10+-- UseFocusWithin
11+
12+type FocusWithinProps =
13+ ( isDisabled ∷ Boolean
14+ , onFocusWithin ∷ EventHandler
15+ , onBlurWithin ∷ EventHandler
16+ , onFocusWithinChange ∷ EffectFn1 Boolean Unit
17+ )
18+
19+type FocusWithinResult =
20+ { focusWithinProps ∷
21+ { onBlur ∷ EventHandler, onFocus ∷ EventHandler }
22+ }
23+
24+foreign import useFocusWithinImpl ∷ ∀ props. EffectFn1 props FocusWithinResult
25+
26+newtype UseFocusWithin a = UseFocusWithin a
27+
28+useFocusWithin ∷
29+ ∀ props props_.
30+ Union props props_ FocusWithinProps ⇒
31+ { | props } →
32+ Hook UseFocusWithin FocusWithinResult
33+useFocusWithin = unsafeHook <<< runEffectFn1 useFocusWithinImpl
34+
35+-- UseFocus
36+
37+type FocusProps =
38+ ( isDisabled ∷ Boolean
39+ , onFocus ∷ EventHandler
40+ , onBlur ∷ EventHandler
41+ , onFocusChange ∷ EffectFn1 Boolean Unit
42+ )
43+
44+type FocusResult =
45+ { focusProps ∷ { onBlur ∷ EventHandler, onFocus ∷ EventHandler } }
46+
47+foreign import useFocusImpl ∷ ∀ props. EffectFn1 props FocusResult
48+
49+newtype UseFocus a = UseFocus a
50+
51+useFocus ∷
52+ ∀ props props_.
53+ Union props props_ FocusProps ⇒
54+ { | props } →
55+ Hook UseFocus FocusResult
56+useFocus = unsafeHook <<< runEffectFn1 useFocusImpl
src/React/Aria/JSSet.jsnew file+4
@@ -0,0 +1,4 @@
1+export const toArray = Array.from
2+export function fromArray(arr) {
3+ return new Set(arr)
4+}
src/React/Aria/JSSet.pursnew file+17
@@ -0,0 +1,17 @@
1+module React.Aria.JSSet where
2+
3+import Prelude
4+
5+import Data.Set (Set)
6+import Data.Set as Set
7+
8+foreign import data JSSet ∷ Type → Type
9+
10+foreign import toArray ∷ JSSet ~> Array
11+foreign import fromArray ∷ Array ~> JSSet
12+
13+fromJSSet ∷ ∀ a. Ord a ⇒ JSSet a → Set a
14+fromJSSet = toArray >>> Set.fromFoldable
15+
16+toJSSet ∷ Set ~> JSSet
17+toJSSet = Set.toUnfoldable >>> fromArray
src/React/Aria/Listbox.jsnew file+6
@@ -0,0 +1,6 @@
1+import { useListBox, useListBoxSection, useOption } from "@react-aria/listbox"
2+
3+export const useListBoxImpl = useListBox
4+export const useListBoxSectionImpl = useListBoxSection
5+export const useOptionImpl = useOption
6+// exports.getItemIdImpl = listbox.getItemId;
src/React/Aria/Listbox.pursnew file+112
@@ -0,0 +1,112 @@
1+module React.Aria.Listbox where
2+
3+import Prelude
4+
5+import Data.Nullable (Nullable)
6+import Effect.Uncurried (EffectFn1, EffectFn3, runEffectFn3)
7+import Prim.Row (class Union)
8+import React.Aria.Combobox (SelectionManager)
9+import React.Aria.JSSet (JSSet)
10+import React.Aria.Types (FocusStrategy, Selection, SelectionMode)
11+import React.Basic (JSX, Ref)
12+import React.Basic.Events (EventHandler)
13+import React.Basic.Hooks (Hook, unsafeHook)
14+import Web.DOM (Node)
15+
16+type ListState =
17+ { collection ∷ Array Node
18+ , disabledKeys ∷ JSSet String
19+ , selectionManager ∷ SelectionManager
20+ }
21+
22+--##########################################################################
23+
24+type AriaListBoxOptions a =
25+ ( isVirtualized ∷ Boolean
26+ -- , keyboardDelegate :: KeyboardDelegate
27+ , shouldUseVirtualFocus ∷ Boolean
28+ , shouldSelectOnPressUp ∷ Boolean
29+ , shouldFocusOnHover ∷ Boolean
30+ , label ∷ JSX
31+ , autoFocus ∷ FocusStrategy
32+ , shouldFocusWrap ∷ Boolean
33+ , items ∷ Array a
34+ , disabledKeys ∷ Array String
35+ , selectionMode ∷ SelectionMode
36+ , disallowEmptySelection ∷ Boolean
37+ , selectedKeys ∷ Array String
38+ , defaultSelectedKeys ∷ Array String
39+ , onSelectionChange ∷ EffectFn1 Selection Unit
40+ , onFocus ∷ EventHandler
41+ , onBlur ∷ EventHandler
42+ , onFocusChange ∷ EffectFn1 Boolean Unit
43+ , id ∷ String
44+ , "aria-label" ∷ String
45+ , "aria-labelledby" ∷ String
46+ , "aria-describedby" ∷ String
47+ , "aria-details" ∷ String
48+ )
49+
50+type ListBoxAria =
51+ { listBoxProps ∷ {}
52+ , labelProps ∷ {}
53+ }
54+
55+foreign import useListBoxImpl
56+ ∷ ∀ props. EffectFn3 props ListState (Ref (Nullable Node)) ListBoxAria
57+
58+newtype UseListBox hooks = UseListBox hooks
59+
60+useListBox
61+ ∷ ∀ a props props_
62+ . Union props props_ (AriaListBoxOptions a)
63+ ⇒ { | props }
64+ → ListState
65+ → Ref (Nullable Node)
66+ → Hook UseListBox ListBoxAria
67+useListBox p s r = unsafeHook do
68+ runEffectFn3 useListBoxImpl p s r
69+
70+--##########################################################################
71+
72+type AriaOptionPropsOptional = ("aria-label" ∷ String)
73+type AriaOptionPropsRequired r = (key ∷ String | r)
74+type OptionAria =
75+ { optionProps ∷ {}
76+ , labelProps ∷ {}
77+ , descriptionProps ∷ {}
78+ , isFocused ∷ Boolean
79+ , isSelected ∷ Boolean
80+ , isPressed ∷ Boolean
81+ , isDisabled ∷ Boolean
82+ }
83+
84+foreign import useOptionImpl
85+ ∷ ∀ props. EffectFn3 props ListState (Ref (Nullable Node)) OptionAria
86+
87+newtype UseOption hooks = UseOption hooks
88+
89+useOption
90+ ∷ ∀ props props_
91+ . Union props props_ AriaOptionPropsOptional
92+ ⇒ { | AriaOptionPropsRequired props }
93+ → ListState
94+ → (Ref (Nullable Node))
95+ → Hook UseOption OptionAria
96+useOption p s r = unsafeHook do
97+ runEffectFn3 useOptionImpl p s r
98+
99+--##########################################################################
100+
101+type AriaListBoxSectionProps = (heading :: JSX, "aria-label" :: String)
102+type ListBoxSectionAria =
103+ { itemProps ∷ {} -- [FIXME]
104+ , headingProps ∷ {} -- [FIXME]
105+ , groupProps ∷ {} -- [FIXME]
106+ }
107+
108+foreign import useListBoxSectionImpl
109+ ∷ forall props
110+ . EffectFn1 props ListBoxSectionAria
111+
112+newtype UseListBoxSection hooks = UseListBoxSection hooks
src/React/Aria/Overlays.jsnew file+37
@@ -0,0 +1,37 @@
1+import {
2+ useModal,
3+ useOverlay,
4+ useOverlayPosition,
5+ useOverlayTrigger,
6+ usePreventScroll,
7+ OverlayProvider,
8+ OverlayContainer,
9+ DismissButton,
10+ ModalProvider
11+} from "@react-aria/overlays"
12+
13+export const useModalImpl = useModal
14+export const useOverlayImpl = useOverlay
15+export const useOverlayPositionImpl = useOverlayPosition
16+export const useOverlayTriggerImpl = useOverlayTrigger
17+export const usePreventScrollImpl = usePreventScroll
18+export const overlayProvider = OverlayProvider
19+export const overlayContainer = OverlayContainer
20+export const dismissButton = DismissButton
21+export const modalProvider = ModalProvider
22+
23+export function toPSAria(obj) {
24+ let result = {}
25+ Object.keys(obj).forEach((k) => {
26+ if (k.startsWith("aria-")) {
27+ const v = obj[k]
28+ if (v !== undefined && v !== null) {
29+ const newKey = k.substring(5)
30+ result[newKey] = v
31+ }
32+ } else {
33+ console.error(`${k} is not an aria attribute`)
34+ }
35+ })
36+ return result
37+}
src/React/Aria/Overlays.pursnew file+159
@@ -0,0 +1,159 @@
1+module React.Aria.Overlays where
2+
3+import Prelude
4+
5+import Data.Nullable (Nullable)
6+import Effect (Effect)
7+import Effect.Aff.Compat (runEffectFn1)
8+import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, runEffectFn2, runEffectFn3)
9+import Foreign (Foreign)
10+import Foreign.Object (Object)
11+import Prim.Row (class Union)
12+import React.Basic (JSX)
13+import React.Basic.DOM (CSS)
14+import React.Basic.Events (EventHandler)
15+import React.Basic.Hooks (Hook, ReactComponent, Ref, unsafeHook)
16+import Web.DOM (Node)
17+import Web.HTML (HTMLElement)
18+
19+-- UseModal
20+newtype UseModal hooks = UseModal hooks
21+
22+type UseModalOptions = { isDisabled :: Boolean }
23+
24+type ModalAria = { modalProps :: { "data-ismodal" :: Boolean } }
25+
26+foreign import useModalImpl ∷ EffectFn1 UseModalOptions ModalAria
27+
28+useModal :: UseModalOptions -> Hook UseModal ModalAria
29+useModal = unsafeHook <<< runEffectFn1 useModalImpl
30+
31+-- UseOverlay
32+newtype UseOverlay hooks = UseOverlay hooks
33+
34+type UseOverlayProps =
35+ ( isOpen ∷ Boolean
36+ , onClose ∷ Effect Unit
37+ , isDismissable ∷ Boolean
38+ , shouldCloseOnBlur ∷ Boolean
39+ , isKeyboardDismissaDisabled ∷ Boolean
40+ , shouldCloseOnInteractOutside ∷ HTMLElement → Boolean
41+ )
42+
43+type OverlayAria =
44+ { overlayProps ∷ { onKeyDown ∷ EventHandler }
45+ , underlayProps ∷ { onPointerDown ∷ EventHandler }
46+ }
47+
48+foreign import useOverlayImpl
49+ ∷ ∀ useOverlayProps. EffectFn2 useOverlayProps (Ref (Nullable Node)) OverlayAria
50+
51+useOverlay
52+ ∷ ∀ props props_
53+ . Union props props_ UseOverlayProps
54+ ⇒ Record props
55+ → Ref (Nullable Node)
56+ → Hook UseOverlay OverlayAria
57+useOverlay x y = unsafeHook $ runEffectFn2 useOverlayImpl x y
58+
59+-- UseOverlayPosition
60+newtype UseOverlayPosition hooks = UseOverlayPosition hooks
61+
62+type UseAriaPositionPropsRequired r =
63+ ( targetRef ∷ Ref (Nullable Node)
64+ , overlayRef ∷ Ref (Nullable Node)
65+ | r
66+ )
67+
68+type UseAriaPositionPropsOptional =
69+ ( boundaryElement ∷ HTMLElement
70+ , scrollRef ∷ Ref (Nullable Node)
71+ , shouldUpdatePosition ∷ Boolean
72+ , onClose ∷ Effect Unit
73+ , placement ∷ String
74+ , containerPadding ∷ Number
75+ , offset ∷ Number
76+ , crossOffset ∷ Number
77+ , shouldFlip ∷ Boolean
78+ , isOpen ∷ Boolean
79+ )
80+
81+type PositionAria =
82+ { overlayProps :: { style :: CSS }
83+ , arrowProps :: { style :: CSS }
84+ , placement :: String
85+ , updatePosition :: Effect Unit
86+ }
87+
88+foreign import useOverlayPositionImpl
89+ ∷ ∀ props
90+ . EffectFn1 props PositionAria
91+
92+usePosition
93+ ∷ ∀ props props_
94+ . Union props props_ UseAriaPositionPropsOptional
95+ ⇒ Record (UseAriaPositionPropsRequired props)
96+ → Hook UseOverlayPosition PositionAria
97+usePosition = unsafeHook <<< runEffectFn1 useOverlayPositionImpl
98+
99+-- UseOverlayTrigger
100+newtype UseOverlayTrigger hooks = UseOverlayTrigger hooks
101+
102+-- | Type must be one of the following
103+-- | 'dialog'
104+-- | 'menu'
105+-- | 'listbox'
106+-- | 'tree'
107+-- | 'grid'
108+type UseOverlayTriggerProps = { type ∷ String }
109+
110+type UseOverlayTriggerState =
111+ { isOpen ∷ Boolean
112+ , open ∷ Effect Unit
113+ , close ∷ Effect Unit
114+ , toggle ∷ Effect Unit
115+ }
116+
117+type OverlayTriggerAriaImpl =
118+ { triggerProps ∷ Object Foreign
119+ , overlayProps ∷ { id ∷ String }
120+ }
121+
122+type OverlayTriggerAria =
123+ { triggerProps ∷ { _aria ∷ Object String }
124+ , overlayProps ∷ { id ∷ String }
125+ }
126+
127+foreign import useOverlayTriggerImpl ∷
128+ EffectFn3
129+ UseOverlayTriggerProps
130+ UseOverlayTriggerState
131+ (Ref (Nullable Node))
132+ OverlayTriggerAriaImpl
133+
134+useOverlayTrigger
135+ ∷ UseOverlayTriggerProps
136+ → UseOverlayTriggerState
137+ → Ref (Nullable Node)
138+ → Hook UseOverlayTrigger OverlayTriggerAria
139+useOverlayTrigger x y z = unsafeHook do
140+ implProps ← runEffectFn3 useOverlayTriggerImpl x y z
141+ pure (implProps { triggerProps = { _aria: toPSAria implProps.triggerProps } })
142+
143+-- Prevent Scroll
144+newtype UsePreventScroll hooks = UsePreventScroll hooks
145+type UsePreventScrollOptions = { isDisabled :: Boolean }
146+
147+foreign import usePreventScrollImpl ∷ EffectFn1 UsePreventScrollOptions Unit
148+
149+usePreventScroll :: UsePreventScrollOptions -> Hook UsePreventScroll Unit
150+usePreventScroll = unsafeHook <<< runEffectFn1 usePreventScrollImpl
151+
152+-- foreign import ariaHideOutsideImpl ∷ Fn2 Foreign Foreign (Effect Unit)
153+
154+foreign import overlayProvider :: ReactComponent { children :: Array JSX }
155+foreign import overlayContainer :: ReactComponent { children :: Array JSX }
156+foreign import dismissButton :: ReactComponent { onDismiss :: Effect Unit }
157+foreign import modalProvider :: ReactComponent { children :: Array JSX }
158+
159+foreign import toPSAria ∷ Object Foreign → Object String
src/React/Aria/Types.pursnew file+81
@@ -0,0 +1,81 @@
1+module React.Aria.Types where
2+
3+import Prelude
4+
5+import React.Aria.JSSet (JSSet)
6+import Unsafe.Coerce (unsafeCoerce)
7+
8+-- MenuTriggerAction
9+foreign import data MenuTriggerAction ∷ Type
10+
11+menuTriggerActionFocus ∷ MenuTriggerAction
12+menuTriggerActionFocus = unsafeCoerce "focus"
13+
14+menuTriggerActionInput ∷ MenuTriggerAction
15+menuTriggerActionInput = unsafeCoerce "input"
16+
17+menuTriggerActionManual ∷ MenuTriggerAction
18+menuTriggerActionManual = unsafeCoerce "manual"
19+
20+instance Eq MenuTriggerAction where
21+ eq a b = ((unsafeCoerce a) ∷ String) == unsafeCoerce b
22+
23+-- ValidationState
24+foreign import data ValidationState ∷ Type
25+
26+validationStateValid ∷ ValidationState
27+validationStateValid = unsafeCoerce "valid"
28+
29+validationStateInvalid ∷ ValidationState
30+validationStateInvalid = unsafeCoerce "invalid"
31+
32+instance Eq ValidationState where
33+ eq a b = ((unsafeCoerce a) ∷ String) == unsafeCoerce b
34+
35+-- FocusStrategy
36+foreign import data FocusStrategy ∷ Type
37+
38+focusStrategyFirst ∷ FocusStrategy
39+focusStrategyFirst = unsafeCoerce "first"
40+
41+focusStrategyLast ∷ FocusStrategy
42+focusStrategyLast = unsafeCoerce "last"
43+
44+instance Eq FocusStrategy where
45+ eq a b = ((unsafeCoerce a) ∷ String) == unsafeCoerce b
46+
47+-- SelectionMode
48+foreign import data SelectionMode ∷ Type
49+
50+selectionModeNone ∷ SelectionMode
51+selectionModeNone = unsafeCoerce "none"
52+
53+selectionModeSingle ∷ SelectionMode
54+selectionModeSingle = unsafeCoerce "single"
55+
56+selectionModeMultiple ∷ SelectionMode
57+selectionModeMultiple = unsafeCoerce "multiple"
58+
59+instance Eq SelectionMode where
60+ eq a b = ((unsafeCoerce a) ∷ String) == unsafeCoerce b
61+
62+-- SelectionBehaviour
63+foreign import data SelectionBehaviour ∷ Type
64+
65+selectionBehaviourToggle ∷ SelectionBehaviour
66+selectionBehaviourToggle = unsafeCoerce "toggle"
67+
68+selectionBehaviourReplace ∷ SelectionBehaviour
69+selectionBehaviourReplace = unsafeCoerce "replace"
70+
71+instance Eq SelectionBehaviour where
72+ eq a b = ((unsafeCoerce a) ∷ String) == unsafeCoerce b
73+
74+-- Selection
75+foreign import data Selection ∷ Type
76+
77+selectionAll ∷ Selection
78+selectionAll = unsafeCoerce "all"
79+
80+selectionFromSet ∷ JSSet String -> SelectionBehaviour
81+selectionFromSet = unsafeCoerce
src/React/Aria/Utils.jsnew file+7
@@ -0,0 +1,7 @@
1+import { mergeProps, useId } from "@react-aria/utils"
2+
3+export function mergePropsImpl(args) {
4+ return mergeProps(...args)
5+}
6+
7+export const useIdImpl = useId
src/React/Aria/Utils.pursnew file+19
@@ -0,0 +1,19 @@
1+module React.Aria.Utils where
2+
3+import Foreign (Foreign, unsafeFromForeign, unsafeToForeign)
4+import Prim.Row (class Union)
5+import React.Basic.Hooks (Hook, unsafeHook)
6+import Effect (Effect)
7+
8+foreign import useIdImpl ∷ Effect String
9+
10+newtype UseId a = UseId a
11+
12+useId ∷ Hook UseId String
13+useId = unsafeHook useIdImpl
14+
15+foreign import mergePropsImpl ∷ Array Foreign → Foreign
16+
17+mergeProps ∷ ∀ r1 r2 r3. Union r1 r2 r3 ⇒ { | r1 } → { | r2 } → { | r3 }
18+mergeProps r1 r2 = unsafeFromForeign
19+ (mergePropsImpl [ unsafeToForeign r1, unsafeToForeign r2 ])