@stack-spot/citric-react 0.42.0-beta.0 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/CHANGELOG.md +13 -13
  2. package/dist/citric.css +2849 -3081
  3. package/dist/components/Accordion.d.ts +1 -1
  4. package/dist/components/Accordion.js +1 -1
  5. package/dist/components/Alert.d.ts +1 -1
  6. package/dist/components/Alert.js +1 -1
  7. package/dist/components/AsyncContent.d.ts +1 -1
  8. package/dist/components/AsyncContent.js +1 -1
  9. package/dist/components/Avatar.d.ts +1 -1
  10. package/dist/components/Avatar.js +1 -1
  11. package/dist/components/AvatarGroup.d.ts +1 -1
  12. package/dist/components/AvatarGroup.js +1 -1
  13. package/dist/components/Badge.d.ts +1 -1
  14. package/dist/components/Badge.js +1 -1
  15. package/dist/components/Blockquote.d.ts +1 -1
  16. package/dist/components/Blockquote.js +1 -1
  17. package/dist/components/Breadcrumb.d.ts +1 -1
  18. package/dist/components/Breadcrumb.js +1 -1
  19. package/dist/components/Button.d.ts +1 -1
  20. package/dist/components/Button.js +1 -1
  21. package/dist/components/ButtonLink.d.ts +1 -1
  22. package/dist/components/ButtonLink.js +1 -1
  23. package/dist/components/Card.d.ts +1 -1
  24. package/dist/components/Card.js +1 -1
  25. package/dist/components/Checkbox.d.ts +1 -1
  26. package/dist/components/Checkbox.d.ts.map +1 -1
  27. package/dist/components/Checkbox.js +2 -2
  28. package/dist/components/Checkbox.js.map +1 -1
  29. package/dist/components/CheckboxGroup.d.ts +1 -1
  30. package/dist/components/CheckboxGroup.js +1 -1
  31. package/dist/components/Circle.d.ts +1 -1
  32. package/dist/components/Circle.js +1 -1
  33. package/dist/components/CitricComponent.d.ts +1 -1
  34. package/dist/components/CitricComponent.d.ts.map +1 -1
  35. package/dist/components/Divider.d.ts +1 -1
  36. package/dist/components/Divider.js +1 -1
  37. package/dist/components/ErrorBoundary.d.ts +1 -1
  38. package/dist/components/ErrorBoundary.js +1 -1
  39. package/dist/components/ErrorMessage.d.ts +1 -1
  40. package/dist/components/ErrorMessage.js +1 -1
  41. package/dist/components/FallbackBoundary.d.ts +1 -1
  42. package/dist/components/FallbackBoundary.js +1 -1
  43. package/dist/components/Favorite.d.ts +1 -1
  44. package/dist/components/Favorite.js +1 -1
  45. package/dist/components/FieldGroup.d.ts +1 -1
  46. package/dist/components/FieldGroup.js +1 -1
  47. package/dist/components/Form.d.ts +2 -2
  48. package/dist/components/Form.js +1 -1
  49. package/dist/components/FormGroup.d.ts +1 -1
  50. package/dist/components/FormGroup.js +1 -1
  51. package/dist/components/Icon.d.ts +1 -1
  52. package/dist/components/Icon.js +1 -1
  53. package/dist/components/IconBox.d.ts +3 -3
  54. package/dist/components/IconBox.js +1 -1
  55. package/dist/components/ImageBox.d.ts +3 -3
  56. package/dist/components/ImageBox.js +1 -1
  57. package/dist/components/ImageWithFallback.d.ts +1 -1
  58. package/dist/components/ImageWithFallback.js +1 -1
  59. package/dist/components/Input.d.ts +1 -1
  60. package/dist/components/Input.js +1 -1
  61. package/dist/components/Link.d.ts +1 -1
  62. package/dist/components/Link.js +1 -1
  63. package/dist/components/LoadingPanel.d.ts +1 -1
  64. package/dist/components/LoadingPanel.js +1 -1
  65. package/dist/components/MenuOverlay/Menu.d.ts +1 -1
  66. package/dist/components/MenuOverlay/Menu.js +1 -1
  67. package/dist/components/MenuOverlay/index.d.ts +1 -1
  68. package/dist/components/MenuOverlay/index.js +1 -1
  69. package/dist/components/Overlay/index.d.ts +1 -1
  70. package/dist/components/Overlay/index.js +1 -1
  71. package/dist/components/Pagination.d.ts +1 -1
  72. package/dist/components/Pagination.js +1 -1
  73. package/dist/components/ProgressBar.d.ts +1 -1
  74. package/dist/components/ProgressBar.js +1 -1
  75. package/dist/components/ProgressCircular.d.ts +1 -1
  76. package/dist/components/ProgressCircular.js +1 -1
  77. package/dist/components/RadioGroup.d.ts +1 -1
  78. package/dist/components/RadioGroup.js +1 -1
  79. package/dist/components/Rating.d.ts +1 -1
  80. package/dist/components/Rating.js +1 -1
  81. package/dist/components/Select/MultiSelect.d.ts +1 -1
  82. package/dist/components/Select/MultiSelect.js +1 -1
  83. package/dist/components/Select/RichSelect.d.ts +1 -1
  84. package/dist/components/Select/RichSelect.js +1 -1
  85. package/dist/components/Select/SimpleSelect.d.ts +1 -1
  86. package/dist/components/Select/SimpleSelect.js +1 -1
  87. package/dist/components/Select/index.d.ts +1 -1
  88. package/dist/components/Select/index.js +1 -1
  89. package/dist/components/SelectBox.d.ts +9 -1
  90. package/dist/components/SelectBox.d.ts.map +1 -1
  91. package/dist/components/SelectBox.js +6 -5
  92. package/dist/components/SelectBox.js.map +1 -1
  93. package/dist/components/Skeleton.d.ts +1 -1
  94. package/dist/components/Skeleton.js +1 -1
  95. package/dist/components/Slider.d.ts +1 -1
  96. package/dist/components/Slider.js +1 -1
  97. package/dist/components/SmartTable.d.ts +1 -1
  98. package/dist/components/SmartTable.js +1 -1
  99. package/dist/components/Stepper.d.ts +1 -1
  100. package/dist/components/Stepper.js +1 -1
  101. package/dist/components/Table.d.ts +3 -3
  102. package/dist/components/Table.js +1 -1
  103. package/dist/components/Tabs/index.d.ts +1 -1
  104. package/dist/components/Tabs/index.js +1 -1
  105. package/dist/components/Textarea.d.ts +1 -1
  106. package/dist/components/Textarea.js +1 -1
  107. package/dist/components/Tooltip.d.ts +1 -1
  108. package/dist/components/Tooltip.js +1 -1
  109. package/dist/context/CitricProvider.d.ts +1 -1
  110. package/dist/context/CitricProvider.js +1 -1
  111. package/dist/index.d.ts +1 -1
  112. package/dist/index.d.ts.map +1 -1
  113. package/dist/index.js +1 -1
  114. package/dist/index.js.map +1 -1
  115. package/dist/overlay.js +1 -1
  116. package/dist/theme.css +415 -415
  117. package/dist/utils/css.js +1 -1
  118. package/dist/utils/css.js.map +1 -1
  119. package/package.json +1 -1
  120. package/scripts/build-css.ts +49 -49
  121. package/src/components/Accordion.tsx +130 -130
  122. package/src/components/Alert.tsx +24 -24
  123. package/src/components/AsyncContent.tsx +75 -75
  124. package/src/components/Avatar.tsx +45 -45
  125. package/src/components/AvatarGroup.tsx +49 -49
  126. package/src/components/Badge.tsx +47 -47
  127. package/src/components/Blockquote.tsx +18 -18
  128. package/src/components/Breadcrumb.tsx +33 -33
  129. package/src/components/Button.tsx +105 -105
  130. package/src/components/ButtonLink.tsx +45 -45
  131. package/src/components/Card.tsx +68 -68
  132. package/src/components/Checkbox.tsx +51 -52
  133. package/src/components/CheckboxGroup.tsx +153 -153
  134. package/src/components/Circle.tsx +43 -43
  135. package/src/components/CitricComponent.ts +47 -47
  136. package/src/components/Divider.tsx +24 -24
  137. package/src/components/ErrorBoundary.tsx +75 -75
  138. package/src/components/ErrorMessage.tsx +11 -11
  139. package/src/components/FallbackBoundary.tsx +40 -40
  140. package/src/components/Favorite.tsx +57 -57
  141. package/src/components/FieldGroup.tsx +46 -46
  142. package/src/components/Form.tsx +36 -36
  143. package/src/components/FormGroup.tsx +57 -57
  144. package/src/components/Icon.tsx +35 -35
  145. package/src/components/IconBox.tsx +134 -134
  146. package/src/components/ImageBox.tsx +125 -125
  147. package/src/components/ImageWithFallback.tsx +65 -65
  148. package/src/components/Input.tsx +49 -49
  149. package/src/components/Link.tsx +55 -55
  150. package/src/components/LoadingPanel.tsx +12 -12
  151. package/src/components/MenuOverlay/Menu.tsx +158 -158
  152. package/src/components/MenuOverlay/context.ts +20 -20
  153. package/src/components/MenuOverlay/index.tsx +55 -55
  154. package/src/components/MenuOverlay/keyboard.ts +60 -60
  155. package/src/components/MenuOverlay/types.ts +171 -171
  156. package/src/components/Overlay/context.ts +10 -10
  157. package/src/components/Overlay/index.tsx +182 -182
  158. package/src/components/Overlay/types.ts +75 -75
  159. package/src/components/Pagination.tsx +133 -133
  160. package/src/components/ProgressBar.tsx +45 -45
  161. package/src/components/ProgressCircular.tsx +45 -45
  162. package/src/components/RadioGroup.tsx +147 -147
  163. package/src/components/Rating.tsx +98 -98
  164. package/src/components/Select/MultiSelect.tsx +217 -217
  165. package/src/components/Select/RichSelect.tsx +128 -128
  166. package/src/components/Select/SimpleSelect.tsx +73 -73
  167. package/src/components/Select/hooks.ts +133 -133
  168. package/src/components/Select/index.tsx +35 -35
  169. package/src/components/Select/types.ts +134 -134
  170. package/src/components/SelectBox.tsx +181 -167
  171. package/src/components/Skeleton.tsx +53 -53
  172. package/src/components/Slider.tsx +89 -89
  173. package/src/components/SmartTable.tsx +227 -227
  174. package/src/components/Stepper.tsx +163 -163
  175. package/src/components/Table.tsx +234 -234
  176. package/src/components/Tabs/TabController.ts +54 -54
  177. package/src/components/Tabs/index.tsx +106 -106
  178. package/src/components/Tabs/types.ts +67 -67
  179. package/src/components/Tabs/utils.ts +6 -6
  180. package/src/components/Text.ts +111 -111
  181. package/src/components/Textarea.tsx +27 -27
  182. package/src/components/Tooltip.tsx +83 -83
  183. package/src/components/layout.tsx +101 -101
  184. package/src/context/CitricContext.tsx +4 -4
  185. package/src/context/CitricProvider.tsx +14 -14
  186. package/src/context/hooks.ts +6 -6
  187. package/src/index.ts +58 -59
  188. package/src/overlay.ts +348 -348
  189. package/src/types.ts +235 -235
  190. package/src/utils/ValueController.ts +28 -28
  191. package/src/utils/acessibility.ts +92 -92
  192. package/src/utils/checkbox.ts +121 -121
  193. package/src/utils/css.ts +119 -119
  194. package/src/utils/options.ts +9 -9
  195. package/src/utils/radio.ts +93 -93
  196. package/src/utils/react.ts +6 -6
  197. package/src/utils/time.ts +5 -5
  198. package/tsconfig.json +10 -10
  199. package/dist/components/Autocomplete/Autocomplete.d.ts +0 -211
  200. package/dist/components/Autocomplete/Autocomplete.d.ts.map +0 -1
  201. package/dist/components/Autocomplete/Autocomplete.js +0 -409
  202. package/dist/components/Autocomplete/Autocomplete.js.map +0 -1
  203. package/dist/components/Autocomplete/index.d.ts +0 -3
  204. package/dist/components/Autocomplete/index.d.ts.map +0 -1
  205. package/dist/components/Autocomplete/index.js +0 -2
  206. package/dist/components/Autocomplete/index.js.map +0 -1
  207. package/src/components/Autocomplete/Autocomplete.tsx +0 -794
  208. package/src/components/Autocomplete/index.ts +0 -3
@@ -1,182 +1,182 @@
1
- import { useEffect, useRef } from 'react'
2
- import { showOverlay } from '../../overlay'
3
- import { HTMLTag } from '../../types'
4
- import { focusFirstChild } from '../../utils/acessibility'
5
- import { delay } from '../../utils/time'
6
- import { OverlayProvider } from './context'
7
- import { OverlayController, OverlayProps } from './types'
8
- export { useOverlayController } from './context'
9
-
10
- /**
11
- * An arbitrary time to wait for the next React render to be performed
12
- */
13
- const arbitraryRenderTime = 20
14
-
15
- /**
16
- * These todos are in order of priority.
17
- *
18
- * TODO: update position when the size changes. Currently, the top position seems out of place whenever the height changes. The same is
19
- * probably true for the left position if the width changes.
20
- * TODO (minor): use React Portal to implement overlays. The current implementation will lose every React context in the tree.
21
- */
22
-
23
- /**
24
- * Creates an overlay for the child component. The overlay can be any React element. The overlay can be triggered by "click" or "hover"
25
- * (default).
26
- *
27
- * The overlay can be hidden from within its content through the hook `useOverlayController()`.
28
- *
29
- * @example
30
- *
31
- * ```
32
- * const overlay = <Card>Hey, this is my overlay!</Card>
33
- *
34
- * return (
35
- * <Overlay content={overlay} attributes={{ style: { margin: '20px' } }}>
36
- * <Button>Hover to see the overlay</Button>
37
- * </Overlay>
38
- * )
39
- * ```
40
- */
41
- export function Overlay<T extends keyof HTMLTag>({
42
- tag,
43
- children,
44
- content,
45
- position = 'top',
46
- triggerOn = 'hover',
47
- alignment = 'center',
48
- attributes,
49
- onRenderChild,
50
- autoFocusBehavior = 'keyboard',
51
- openDelayMS,
52
- closeDelayMS,
53
- ...props
54
- }: OverlayProps<T>,
55
- ) {
56
- const controller = useRef<OverlayController>({ close: () => Promise.resolve() })
57
- const wrapper = useRef<HTMLDivElement | null>(null)
58
- // props that don't require removing and reattaching the event listeners
59
- const dynamic = useRef({ tag, content, position, alignment, attributes })
60
-
61
- useEffect(() => {
62
- dynamic.current = { tag, content, position, alignment, attributes }
63
- }, [tag, content, position, alignment, attributes])
64
-
65
- useEffect(() => {
66
- let visible = false
67
- let hideOnClickOutside: ((event: Event) => void) | undefined
68
- let hideOnEsc: ((event: Event) => void) | undefined
69
- let hideOverlay: ((immediately?: boolean) => Promise<void>) | undefined
70
- let removeRefocusTargetListener: (() => void) | undefined
71
-
72
- function getTarget() {
73
- const target = wrapper.current?.firstChild
74
- return target instanceof HTMLElement ? target : undefined
75
- }
76
-
77
- if (onRenderChild) {
78
- const target = getTarget()
79
- if (target) onRenderChild(target)
80
- }
81
-
82
- async function show(event: Event) {
83
- if (visible) return
84
- visible = true
85
- const target = getTarget()
86
- if (openDelayMS && triggerOn === 'hover') {
87
- await delay(openDelayMS)
88
- if (!target?.matches(':hover, :focus')) {
89
- visible = false
90
- return
91
- }
92
- }
93
- if (!target) return
94
- const { overlay, hide: hideFn } = showOverlay({
95
- tag: dynamic.current.tag,
96
- content: ['string', 'number', 'boolean'].includes(typeof dynamic.current.content)
97
- ? dynamic.current.content
98
- : <OverlayProvider value={controller.current}>{dynamic.current.content}</OverlayProvider>,
99
- target,
100
- position: dynamic.current.position,
101
- alignment: dynamic.current.alignment,
102
- attributes: dynamic.current.attributes,
103
- })
104
- hideOverlay?.(true) // ensures there's no lost opened overlay.
105
- hideOverlay = hideFn
106
-
107
- function onHide(condition: (event: Event) => boolean) {
108
- return (event: Event) => {
109
- if (condition(event)) controller.current.close()
110
- }
111
- }
112
-
113
- if (event.type === 'click') {
114
- hideOnEsc = onHide(e => e instanceof KeyboardEvent && e.key === 'Escape')
115
- setTimeout(() => {
116
- hideOnClickOutside = onHide(e => e instanceof MouseEvent && e.button === 0 && !overlay.contains(e.target as HTMLElement))
117
- document.addEventListener('click', hideOnClickOutside)
118
- }, arbitraryRenderTime)
119
- document.addEventListener('keydown', hideOnEsc)
120
- }
121
-
122
- // focus target when the last overlay element loses focus
123
- function refocusTarget(e: KeyboardEvent) {
124
- if (e.key === 'Tab' && e.target instanceof HTMLElement) {
125
- const allItems = Array.from(e.target.closest('[data-citric="menu"]')?.querySelectorAll('a, button') ?? [])
126
- if (e.target === allItems[allItems.length - 1]) {
127
- getTarget()?.focus()
128
- e.preventDefault()
129
- }
130
- }
131
- }
132
- overlay.addEventListener('keydown', refocusTarget)
133
- removeRefocusTargetListener = () => overlay.removeEventListener('keydown', refocusTarget)
134
-
135
- // auto-focus
136
- const openedWithMouse = event instanceof MouseEvent && event.detail > 0
137
- if (autoFocusBehavior === 'always' || (autoFocusBehavior === 'keyboard' && !openedWithMouse)) {
138
- setTimeout(() => focusFirstChild(overlay), arbitraryRenderTime)
139
- }
140
- }
141
-
142
- controller.current.close = async (immediately = false) => {
143
- if (!immediately && (closeDelayMS ?? 0) > 0) {
144
- await delay(closeDelayMS ?? 0)
145
- }
146
- visible = false
147
- if (hideOnClickOutside) document.removeEventListener('click', hideOnClickOutside)
148
- if (hideOnEsc) document.removeEventListener('keydown', hideOnEsc)
149
- removeRefocusTargetListener?.()
150
- await hideOverlay?.()
151
- }
152
-
153
- if (triggerOn === 'hover') {
154
- const close = () => controller.current.close()
155
- getTarget()?.addEventListener('mouseenter', show)
156
- getTarget()?.addEventListener('mouseleave', close)
157
- getTarget()?.addEventListener('focus', show)
158
- getTarget()?.addEventListener('blur', close)
159
- return () => {
160
- getTarget()?.removeEventListener('mouseenter', show)
161
- getTarget()?.removeEventListener('mouseleave', close)
162
- getTarget()?.removeEventListener('focus', show)
163
- getTarget()?.removeEventListener('blur', close)
164
- }
165
- }
166
-
167
- if (triggerOn === 'click') {
168
- getTarget()?.addEventListener('click', show)
169
- return () => {
170
- controller.current.close()
171
- getTarget()?.removeEventListener('click', show)
172
- if (hideOnClickOutside) document.removeEventListener('click', hideOnClickOutside)
173
- }
174
- }
175
- }, [wrapper.current, triggerOn])
176
-
177
- useEffect(() => () => {
178
- controller.current?.close(true)
179
- }, [])
180
-
181
- return <div ref={wrapper} {...props}>{children}</div>
182
- }
1
+ import { useEffect, useRef } from 'react'
2
+ import { showOverlay } from '../../overlay'
3
+ import { HTMLTag } from '../../types'
4
+ import { focusFirstChild } from '../../utils/acessibility'
5
+ import { delay } from '../../utils/time'
6
+ import { OverlayProvider } from './context'
7
+ import { OverlayController, OverlayProps } from './types'
8
+ export { useOverlayController } from './context'
9
+
10
+ /**
11
+ * An arbitrary time to wait for the next React render to be performed
12
+ */
13
+ const arbitraryRenderTime = 20
14
+
15
+ /**
16
+ * These todos are in order of priority.
17
+ *
18
+ * TODO: update position when the size changes. Currently, the top position seems out of place whenever the height changes. The same is
19
+ * probably true for the left position if the width changes.
20
+ * TODO (minor): use React Portal to implement overlays. The current implementation will lose every React context in the tree.
21
+ */
22
+
23
+ /**
24
+ * Creates an overlay for the child component. The overlay can be any React element. The overlay can be triggered by "click" or "hover"
25
+ * (default).
26
+ *
27
+ * The overlay can be hidden from within its content through the hook `useOverlayController()`.
28
+ *
29
+ * @example
30
+ *
31
+ * ```
32
+ * const overlay = <Card>Hey, this is my overlay!</Card>
33
+ *
34
+ * return (
35
+ * <Overlay content={overlay} attributes={{ style: { margin: '20px' } }}>
36
+ * <Button>Hover to see the overlay</Button>
37
+ * </Overlay>
38
+ * )
39
+ * ```
40
+ */
41
+ export function Overlay<T extends keyof HTMLTag>({
42
+ tag,
43
+ children,
44
+ content,
45
+ position = 'top',
46
+ triggerOn = 'hover',
47
+ alignment = 'center',
48
+ attributes,
49
+ onRenderChild,
50
+ autoFocusBehavior = 'keyboard',
51
+ openDelayMS,
52
+ closeDelayMS,
53
+ ...props
54
+ }: OverlayProps<T>,
55
+ ) {
56
+ const controller = useRef<OverlayController>({ close: () => Promise.resolve() })
57
+ const wrapper = useRef<HTMLDivElement | null>(null)
58
+ // props that don't require removing and reattaching the event listeners
59
+ const dynamic = useRef({ tag, content, position, alignment, attributes })
60
+
61
+ useEffect(() => {
62
+ dynamic.current = { tag, content, position, alignment, attributes }
63
+ }, [tag, content, position, alignment, attributes])
64
+
65
+ useEffect(() => {
66
+ let visible = false
67
+ let hideOnClickOutside: ((event: Event) => void) | undefined
68
+ let hideOnEsc: ((event: Event) => void) | undefined
69
+ let hideOverlay: ((immediately?: boolean) => Promise<void>) | undefined
70
+ let removeRefocusTargetListener: (() => void) | undefined
71
+
72
+ function getTarget() {
73
+ const target = wrapper.current?.firstChild
74
+ return target instanceof HTMLElement ? target : undefined
75
+ }
76
+
77
+ if (onRenderChild) {
78
+ const target = getTarget()
79
+ if (target) onRenderChild(target)
80
+ }
81
+
82
+ async function show(event: Event) {
83
+ if (visible) return
84
+ visible = true
85
+ const target = getTarget()
86
+ if (openDelayMS && triggerOn === 'hover') {
87
+ await delay(openDelayMS)
88
+ if (!target?.matches(':hover, :focus')) {
89
+ visible = false
90
+ return
91
+ }
92
+ }
93
+ if (!target) return
94
+ const { overlay, hide: hideFn } = showOverlay({
95
+ tag: dynamic.current.tag,
96
+ content: ['string', 'number', 'boolean'].includes(typeof dynamic.current.content)
97
+ ? dynamic.current.content
98
+ : <OverlayProvider value={controller.current}>{dynamic.current.content}</OverlayProvider>,
99
+ target,
100
+ position: dynamic.current.position,
101
+ alignment: dynamic.current.alignment,
102
+ attributes: dynamic.current.attributes,
103
+ })
104
+ hideOverlay?.(true) // ensures there's no lost opened overlay.
105
+ hideOverlay = hideFn
106
+
107
+ function onHide(condition: (event: Event) => boolean) {
108
+ return (event: Event) => {
109
+ if (condition(event)) controller.current.close()
110
+ }
111
+ }
112
+
113
+ if (event.type === 'click') {
114
+ hideOnEsc = onHide(e => e instanceof KeyboardEvent && e.key === 'Escape')
115
+ setTimeout(() => {
116
+ hideOnClickOutside = onHide(e => e instanceof MouseEvent && e.button === 0 && !overlay.contains(e.target as HTMLElement))
117
+ document.addEventListener('click', hideOnClickOutside)
118
+ }, arbitraryRenderTime)
119
+ document.addEventListener('keydown', hideOnEsc)
120
+ }
121
+
122
+ // focus target when the last overlay element loses focus
123
+ function refocusTarget(e: KeyboardEvent) {
124
+ if (e.key === 'Tab' && e.target instanceof HTMLElement) {
125
+ const allItems = Array.from(e.target.closest('[data-citric="menu"]')?.querySelectorAll('a, button') ?? [])
126
+ if (e.target === allItems[allItems.length - 1]) {
127
+ getTarget()?.focus()
128
+ e.preventDefault()
129
+ }
130
+ }
131
+ }
132
+ overlay.addEventListener('keydown', refocusTarget)
133
+ removeRefocusTargetListener = () => overlay.removeEventListener('keydown', refocusTarget)
134
+
135
+ // auto-focus
136
+ const openedWithMouse = event instanceof MouseEvent && event.detail > 0
137
+ if (autoFocusBehavior === 'always' || (autoFocusBehavior === 'keyboard' && !openedWithMouse)) {
138
+ setTimeout(() => focusFirstChild(overlay), arbitraryRenderTime)
139
+ }
140
+ }
141
+
142
+ controller.current.close = async (immediately = false) => {
143
+ if (!immediately && (closeDelayMS ?? 0) > 0) {
144
+ await delay(closeDelayMS ?? 0)
145
+ }
146
+ visible = false
147
+ if (hideOnClickOutside) document.removeEventListener('click', hideOnClickOutside)
148
+ if (hideOnEsc) document.removeEventListener('keydown', hideOnEsc)
149
+ removeRefocusTargetListener?.()
150
+ await hideOverlay?.()
151
+ }
152
+
153
+ if (triggerOn === 'hover') {
154
+ const close = () => controller.current.close()
155
+ getTarget()?.addEventListener('mouseenter', show)
156
+ getTarget()?.addEventListener('mouseleave', close)
157
+ getTarget()?.addEventListener('focus', show)
158
+ getTarget()?.addEventListener('blur', close)
159
+ return () => {
160
+ getTarget()?.removeEventListener('mouseenter', show)
161
+ getTarget()?.removeEventListener('mouseleave', close)
162
+ getTarget()?.removeEventListener('focus', show)
163
+ getTarget()?.removeEventListener('blur', close)
164
+ }
165
+ }
166
+
167
+ if (triggerOn === 'click') {
168
+ getTarget()?.addEventListener('click', show)
169
+ return () => {
170
+ controller.current.close()
171
+ getTarget()?.removeEventListener('click', show)
172
+ if (hideOnClickOutside) document.removeEventListener('click', hideOnClickOutside)
173
+ }
174
+ }
175
+ }, [wrapper.current, triggerOn])
176
+
177
+ useEffect(() => () => {
178
+ controller.current?.close(true)
179
+ }, [])
180
+
181
+ return <div ref={wrapper} {...props}>{children}</div>
182
+ }
@@ -1,75 +1,75 @@
1
- import { OverlayOptions } from '../../overlay'
2
- import { HTMLTag } from '../../types'
3
-
4
- export type TriggerOn = 'hover' | 'click'
5
-
6
- export interface BaseOverlayProps<T extends keyof HTMLTag> extends Omit<OverlayOptions<T>, 'target'> {
7
- /**
8
- * When should the overlay element be created? When the child is clicked or when the child is hovered?
9
- *
10
- * Accessibility:
11
- * Click = focus + press enter to open; focus + enter to close OR esc.
12
- * Hover = focus to open; blur to close.
13
- *
14
- * @default 'hover'
15
- */
16
- triggerOn?: TriggerOn,
17
- /**
18
- * Only valid if `triggerOn` is "hover".
19
- *
20
- * Waits the given amount of milliseconds before closing the overlay.
21
- *
22
- * @default 0
23
- */
24
- closeDelayMS?: number,
25
- /**
26
- * Only valid if `triggerOn` is "hover".
27
- *
28
- * Waits the given amount of milliseconds before opening the overlay.
29
- *
30
- * @default 0
31
- */
32
- openDelayMS?: number,
33
- /**
34
- * TODO: not implemented yet.
35
- *
36
- * - Never: the focus won't changes when the overlay opens.
37
- * - All: the focus always changes when the overlay opens (given there's a focusable element in the overlay).
38
- * - Keyboard (default): the focus only changes when the overlay is opened via the keyboard.
39
- *
40
- * The first focusable element in the overlay will be focused as soon as it's rendered. When it's closed, the child element
41
- * (`children`) regains focus.
42
- *
43
- * The focus control will be such that, after the last element in the overlay is focused, the next focus will move to the child element
44
- * (`children`).
45
- *
46
- * If the overlay has no focusable element, this properties makes no difference.
47
- *
48
- * Attention: focusable elements inside the overlay can be ignored by setting `auto-focus` to false.
49
- *
50
- * @default 'keyboard'
51
- */
52
- autoFocusBehavior?: 'never' | 'always' | 'keyboard',
53
- /**
54
- * The element to receive the overlay.
55
- */
56
- children: React.ReactElement,
57
- /**
58
- * Function to run when the child is rendered. It receives the child element as a parameter.
59
- *
60
- * This is useful for easily adding attributes to the element (mainly accessibility ones).
61
- * @param element the child element, the one that receives the overlay.
62
- */
63
- onRenderChild?: (element: HTMLElement) => void,
64
- }
65
-
66
- export type OverlayProps<T extends keyof HTMLTag> = Omit<React.JSX.IntrinsicElements['div'], 'content'> & BaseOverlayProps<T>
67
-
68
- export interface OverlayController {
69
- /**
70
- * Closes the tooltip.
71
- * @param immediately when true, ignores the closeDelayMS parameter.
72
- * @returns a promise that resolves when the overlay is removed from the DOM.
73
- */
74
- close: (immediately?: boolean) => Promise<void>,
75
- }
1
+ import { OverlayOptions } from '../../overlay'
2
+ import { HTMLTag } from '../../types'
3
+
4
+ export type TriggerOn = 'hover' | 'click'
5
+
6
+ export interface BaseOverlayProps<T extends keyof HTMLTag> extends Omit<OverlayOptions<T>, 'target'> {
7
+ /**
8
+ * When should the overlay element be created? When the child is clicked or when the child is hovered?
9
+ *
10
+ * Accessibility:
11
+ * Click = focus + press enter to open; focus + enter to close OR esc.
12
+ * Hover = focus to open; blur to close.
13
+ *
14
+ * @default 'hover'
15
+ */
16
+ triggerOn?: TriggerOn,
17
+ /**
18
+ * Only valid if `triggerOn` is "hover".
19
+ *
20
+ * Waits the given amount of milliseconds before closing the overlay.
21
+ *
22
+ * @default 0
23
+ */
24
+ closeDelayMS?: number,
25
+ /**
26
+ * Only valid if `triggerOn` is "hover".
27
+ *
28
+ * Waits the given amount of milliseconds before opening the overlay.
29
+ *
30
+ * @default 0
31
+ */
32
+ openDelayMS?: number,
33
+ /**
34
+ * TODO: not implemented yet.
35
+ *
36
+ * - Never: the focus won't changes when the overlay opens.
37
+ * - All: the focus always changes when the overlay opens (given there's a focusable element in the overlay).
38
+ * - Keyboard (default): the focus only changes when the overlay is opened via the keyboard.
39
+ *
40
+ * The first focusable element in the overlay will be focused as soon as it's rendered. When it's closed, the child element
41
+ * (`children`) regains focus.
42
+ *
43
+ * The focus control will be such that, after the last element in the overlay is focused, the next focus will move to the child element
44
+ * (`children`).
45
+ *
46
+ * If the overlay has no focusable element, this properties makes no difference.
47
+ *
48
+ * Attention: focusable elements inside the overlay can be ignored by setting `auto-focus` to false.
49
+ *
50
+ * @default 'keyboard'
51
+ */
52
+ autoFocusBehavior?: 'never' | 'always' | 'keyboard',
53
+ /**
54
+ * The element to receive the overlay.
55
+ */
56
+ children: React.ReactElement,
57
+ /**
58
+ * Function to run when the child is rendered. It receives the child element as a parameter.
59
+ *
60
+ * This is useful for easily adding attributes to the element (mainly accessibility ones).
61
+ * @param element the child element, the one that receives the overlay.
62
+ */
63
+ onRenderChild?: (element: HTMLElement) => void,
64
+ }
65
+
66
+ export type OverlayProps<T extends keyof HTMLTag> = Omit<React.JSX.IntrinsicElements['div'], 'content'> & BaseOverlayProps<T>
67
+
68
+ export interface OverlayController {
69
+ /**
70
+ * Closes the tooltip.
71
+ * @param immediately when true, ignores the closeDelayMS parameter.
72
+ * @returns a promise that resolves when the overlay is removed from the DOM.
73
+ */
74
+ close: (immediately?: boolean) => Promise<void>,
75
+ }