@stack-spot/citric-react 0.37.1 → 0.39.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.
- package/CHANGELOG.md +13 -0
- package/dist/citric.css +2844 -2844
- package/dist/components/Accordion.d.ts +1 -1
- package/dist/components/Accordion.js +1 -1
- package/dist/components/Alert.d.ts +1 -1
- package/dist/components/Alert.js +1 -1
- package/dist/components/AsyncContent.d.ts +1 -1
- package/dist/components/AsyncContent.js +1 -1
- package/dist/components/Avatar.d.ts +1 -1
- package/dist/components/Avatar.js +1 -1
- package/dist/components/AvatarGroup.d.ts +1 -1
- package/dist/components/AvatarGroup.js +1 -1
- package/dist/components/Badge.d.ts +1 -1
- package/dist/components/Badge.js +1 -1
- package/dist/components/Blockquote.d.ts +1 -1
- package/dist/components/Blockquote.js +1 -1
- package/dist/components/Breadcrumb.d.ts +1 -1
- package/dist/components/Breadcrumb.js +1 -1
- package/dist/components/Button.d.ts +1 -1
- package/dist/components/Button.js +1 -1
- package/dist/components/ButtonLink.d.ts +1 -1
- package/dist/components/ButtonLink.js +1 -1
- package/dist/components/Card.d.ts +1 -1
- package/dist/components/Card.js +1 -1
- package/dist/components/Checkbox.d.ts +1 -1
- package/dist/components/Checkbox.js +1 -1
- package/dist/components/CheckboxGroup.d.ts +1 -1
- package/dist/components/CheckboxGroup.d.ts.map +1 -1
- package/dist/components/CheckboxGroup.js +2 -2
- package/dist/components/CheckboxGroup.js.map +1 -1
- package/dist/components/Circle.d.ts +1 -1
- package/dist/components/Circle.js +1 -1
- package/dist/components/Divider.d.ts +1 -1
- package/dist/components/Divider.js +1 -1
- package/dist/components/ErrorBoundary.d.ts +1 -1
- package/dist/components/ErrorBoundary.js +1 -1
- package/dist/components/ErrorMessage.d.ts +1 -1
- package/dist/components/ErrorMessage.js +1 -1
- package/dist/components/FallbackBoundary.d.ts +1 -1
- package/dist/components/FallbackBoundary.js +1 -1
- package/dist/components/Favorite.d.ts +1 -1
- package/dist/components/Favorite.js +1 -1
- package/dist/components/FieldGroup.d.ts +1 -1
- package/dist/components/FieldGroup.js +1 -1
- package/dist/components/Form.d.ts +2 -2
- package/dist/components/Form.js +1 -1
- package/dist/components/FormGroup.d.ts +1 -1
- package/dist/components/FormGroup.js +1 -1
- package/dist/components/Icon.d.ts +1 -1
- package/dist/components/Icon.js +1 -1
- package/dist/components/IconBox.d.ts +3 -3
- package/dist/components/IconBox.js +1 -1
- package/dist/components/ImageBox.d.ts +3 -3
- package/dist/components/ImageBox.js +1 -1
- package/dist/components/ImageWithFallback.d.ts +1 -1
- package/dist/components/ImageWithFallback.js +1 -1
- package/dist/components/Input.d.ts +1 -1
- package/dist/components/Input.js +1 -1
- package/dist/components/Link.d.ts +1 -1
- package/dist/components/Link.js +1 -1
- package/dist/components/LoadingPanel.d.ts +1 -1
- package/dist/components/LoadingPanel.js +1 -1
- package/dist/components/MenuOverlay/Menu.d.ts +1 -1
- package/dist/components/MenuOverlay/Menu.js +1 -1
- package/dist/components/MenuOverlay/index.d.ts +1 -1
- package/dist/components/MenuOverlay/index.js +1 -1
- package/dist/components/Overlay/index.d.ts +4 -1
- package/dist/components/Overlay/index.d.ts.map +1 -1
- package/dist/components/Overlay/index.js +4 -1
- package/dist/components/Overlay/index.js.map +1 -1
- package/dist/components/Pagination.d.ts +1 -1
- package/dist/components/Pagination.js +1 -1
- package/dist/components/ProgressBar.d.ts +1 -1
- package/dist/components/ProgressBar.js +1 -1
- package/dist/components/ProgressCircular.d.ts +1 -1
- package/dist/components/ProgressCircular.js +1 -1
- package/dist/components/RadioGroup.d.ts +1 -1
- package/dist/components/RadioGroup.js +1 -1
- package/dist/components/Rating.d.ts +1 -1
- package/dist/components/Rating.js +1 -1
- package/dist/components/Select/MultiSelect.d.ts +1 -1
- package/dist/components/Select/MultiSelect.js +1 -1
- package/dist/components/Select/RichSelect.d.ts +1 -1
- package/dist/components/Select/RichSelect.js +1 -1
- package/dist/components/Select/SimpleSelect.d.ts +1 -1
- package/dist/components/Select/SimpleSelect.js +1 -1
- package/dist/components/Select/index.d.ts +1 -1
- package/dist/components/Select/index.js +1 -1
- package/dist/components/SelectBox.d.ts +1 -1
- package/dist/components/SelectBox.js +1 -1
- package/dist/components/Skeleton.d.ts +1 -1
- package/dist/components/Skeleton.js +1 -1
- package/dist/components/Slider.d.ts +1 -1
- package/dist/components/Slider.js +1 -1
- package/dist/components/SmartTable.d.ts +1 -1
- package/dist/components/SmartTable.js +1 -1
- package/dist/components/Stepper.d.ts +1 -1
- package/dist/components/Stepper.js +1 -1
- package/dist/components/Table.d.ts +3 -3
- package/dist/components/Table.js +1 -1
- package/dist/components/Tabs/index.d.ts +1 -1
- package/dist/components/Tabs/index.js +1 -1
- package/dist/components/Textarea.d.ts +1 -1
- package/dist/components/Textarea.js +1 -1
- package/dist/components/Tooltip.d.ts +1 -1
- package/dist/components/Tooltip.js +1 -1
- package/dist/context/CitricProvider.d.ts +1 -1
- package/dist/context/CitricProvider.js +1 -1
- package/dist/overlay.js +1 -1
- package/dist/theme.css +415 -415
- package/package.json +2 -1
- package/scripts/build-css.ts +49 -49
- package/src/components/Accordion.tsx +130 -130
- package/src/components/Alert.tsx +24 -24
- package/src/components/AsyncContent.tsx +70 -70
- package/src/components/Avatar.tsx +45 -45
- package/src/components/AvatarGroup.tsx +49 -49
- package/src/components/Badge.tsx +47 -47
- package/src/components/Blockquote.tsx +18 -18
- package/src/components/Breadcrumb.tsx +33 -33
- package/src/components/Button.tsx +105 -105
- package/src/components/ButtonLink.tsx +45 -45
- package/src/components/Card.tsx +68 -68
- package/src/components/Checkbox.tsx +51 -51
- package/src/components/CheckboxGroup.tsx +153 -152
- package/src/components/Circle.tsx +43 -43
- package/src/components/CitricComponent.ts +47 -47
- package/src/components/Divider.tsx +24 -24
- package/src/components/ErrorBoundary.tsx +75 -75
- package/src/components/ErrorMessage.tsx +11 -11
- package/src/components/FallbackBoundary.tsx +40 -40
- package/src/components/Favorite.tsx +57 -57
- package/src/components/FieldGroup.tsx +46 -46
- package/src/components/Form.tsx +36 -36
- package/src/components/FormGroup.tsx +57 -57
- package/src/components/Icon.tsx +35 -35
- package/src/components/IconBox.tsx +134 -134
- package/src/components/ImageBox.tsx +125 -125
- package/src/components/ImageWithFallback.tsx +65 -65
- package/src/components/Input.tsx +49 -49
- package/src/components/Link.tsx +55 -55
- package/src/components/LoadingPanel.tsx +8 -8
- package/src/components/MenuOverlay/Menu.tsx +158 -158
- package/src/components/MenuOverlay/context.ts +20 -20
- package/src/components/MenuOverlay/index.tsx +55 -55
- package/src/components/MenuOverlay/keyboard.ts +60 -60
- package/src/components/MenuOverlay/types.ts +171 -171
- package/src/components/Overlay/context.ts +10 -10
- package/src/components/Overlay/index.tsx +167 -164
- package/src/components/Overlay/types.ts +70 -70
- package/src/components/Pagination.tsx +133 -133
- package/src/components/ProgressBar.tsx +45 -45
- package/src/components/ProgressCircular.tsx +45 -45
- package/src/components/RadioGroup.tsx +146 -146
- package/src/components/Rating.tsx +98 -98
- package/src/components/Select/MultiSelect.tsx +217 -217
- package/src/components/Select/RichSelect.tsx +128 -128
- package/src/components/Select/SimpleSelect.tsx +73 -73
- package/src/components/Select/hooks.ts +133 -133
- package/src/components/Select/index.tsx +35 -35
- package/src/components/Select/types.ts +134 -134
- package/src/components/SelectBox.tsx +167 -167
- package/src/components/Skeleton.tsx +53 -53
- package/src/components/Slider.tsx +89 -89
- package/src/components/SmartTable.tsx +227 -227
- package/src/components/Stepper.tsx +163 -163
- package/src/components/Table.tsx +234 -234
- package/src/components/Tabs/TabController.ts +54 -54
- package/src/components/Tabs/index.tsx +87 -87
- package/src/components/Tabs/types.ts +54 -54
- package/src/components/Tabs/utils.ts +6 -6
- package/src/components/Text.ts +111 -111
- package/src/components/Textarea.tsx +27 -27
- package/src/components/Tooltip.tsx +72 -72
- package/src/components/layout.tsx +101 -101
- package/src/context/CitricContext.tsx +4 -4
- package/src/context/CitricProvider.tsx +14 -14
- package/src/context/hooks.ts +6 -6
- package/src/index.ts +58 -58
- package/src/overlay.ts +341 -341
- package/src/types.ts +216 -216
- package/src/utils/ValueController.ts +28 -28
- package/src/utils/acessibility.ts +92 -92
- package/src/utils/checkbox.ts +121 -121
- package/src/utils/css.ts +119 -119
- package/src/utils/options.ts +9 -9
- package/src/utils/radio.ts +93 -93
- package/src/utils/react.ts +6 -6
- package/tsconfig.json +10 -10
package/src/overlay.ts
CHANGED
|
@@ -1,341 +1,341 @@
|
|
|
1
|
-
import { createRoot } from 'react-dom/client'
|
|
2
|
-
import { HTMLTag, WithDataAttributes } from './types'
|
|
3
|
-
import { styleObjectToCssString } from './utils/css'
|
|
4
|
-
|
|
5
|
-
export type RelativePosition = 'top' | 'bottom' | 'left' | 'right'
|
|
6
|
-
|
|
7
|
-
export interface OverlayOptions<T extends keyof HTMLTag> {
|
|
8
|
-
/**
|
|
9
|
-
* The tag to render when creating the overlay element.
|
|
10
|
-
*
|
|
11
|
-
* @default 'div'
|
|
12
|
-
*/
|
|
13
|
-
tag?: T,
|
|
14
|
-
/**
|
|
15
|
-
* The content of the overlay element.
|
|
16
|
-
*
|
|
17
|
-
* When this is a string, a number or a boolean, the content will be appended to the overlay element. Otherwise, a new React concurrent
|
|
18
|
-
* instance will be created to render the React tree node inside the overlay element.
|
|
19
|
-
*/
|
|
20
|
-
content: React.ReactNode,
|
|
21
|
-
/**
|
|
22
|
-
* Which element will be used as a reference to position the overlay element.
|
|
23
|
-
* This can be either an HTMLElement or an event. If this is a event, the element in `event.target` will be used unless `reference` is
|
|
24
|
-
* set to `'mouse'`, in this case, the reference will be the current cursor position.
|
|
25
|
-
*/
|
|
26
|
-
target: Event | HTMLElement,
|
|
27
|
-
/**
|
|
28
|
-
* Takes effect when `target` is a MouseEvent. If this is set to `'mouse'`, then the position to be used as a reference to place the
|
|
29
|
-
* overlay will be the current cursor position.
|
|
30
|
-
*
|
|
31
|
-
* @default 'element'
|
|
32
|
-
*/
|
|
33
|
-
reference?: 'element' | 'mouse',
|
|
34
|
-
/**
|
|
35
|
-
* Where, relative to `target`, should we place the overlay?
|
|
36
|
-
*
|
|
37
|
-
* @default 'top'
|
|
38
|
-
*/
|
|
39
|
-
position?: RelativePosition,
|
|
40
|
-
/**
|
|
41
|
-
* Affects the positioning of the overlay relative to `target`. While `position` defines the overlay position in the main axis,
|
|
42
|
-
* `alignment` defines the position in the cross axis.
|
|
43
|
-
*
|
|
44
|
-
* @default 'center'
|
|
45
|
-
*/
|
|
46
|
-
alignment?: 'start' | 'center' | 'end',
|
|
47
|
-
/**
|
|
48
|
-
* TODO: implement this (currently, only 'fade' works).
|
|
49
|
-
*
|
|
50
|
-
* - fade: the overlay fades in to appear and fades out to disappear.
|
|
51
|
-
* - accordion: the element grows to appear and shrinks to disappear. The direction of the growth depends on `position`.
|
|
52
|
-
* - bubble: grows from scale(0) to scale(1) from its center to appear. Shrinks from scale(1) to scale(0) to disappear.
|
|
53
|
-
* - none: no animation is used.
|
|
54
|
-
*
|
|
55
|
-
* @default 'fade'
|
|
56
|
-
*/
|
|
57
|
-
animation?: 'fade' | 'accordion' | 'bubble' | 'none',
|
|
58
|
-
/**
|
|
59
|
-
* The attributes for the HTMLElement that will be created for the overlay.
|
|
60
|
-
*/
|
|
61
|
-
attributes?: JSX.IntrinsicElements[T] & WithDataAttributes & { inert?: boolean },
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
interface Position {
|
|
65
|
-
top: number,
|
|
66
|
-
left: number,
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
interface PositionWithRelativeData extends Position {
|
|
70
|
-
relativeTo: RelativePosition,
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
type CalculatePosOptions = Pick<Required<OverlayOptions<any>>, 'position' | 'target' | 'reference' | 'alignment'>
|
|
74
|
-
& { overlay: HTMLElement }
|
|
75
|
-
|
|
76
|
-
const animationDurationMS = 300
|
|
77
|
-
|
|
78
|
-
function hasMargins(element: HTMLElement) {
|
|
79
|
-
const s = element.style
|
|
80
|
-
return s.margin || s.marginTop || s.marginBottom || s.marginLeft || s.marginRight
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function calculateAlignment(alignment: CalculatePosOptions['alignment'], offset: number, size: number, scroll: number) {
|
|
84
|
-
switch (alignment) {
|
|
85
|
-
case 'center': return offset + size / 2 + scroll
|
|
86
|
-
case 'end': return offset + size + scroll
|
|
87
|
-
case 'start': return offset + scroll
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function calculateAlignmentOffset(alignment: CalculatePosOptions['alignment'], overlaySize: number) {
|
|
92
|
-
switch (alignment) {
|
|
93
|
-
case 'center': return overlaySize / 2
|
|
94
|
-
case 'end': return overlaySize
|
|
95
|
-
case 'start': return 0
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function calculatePosition({
|
|
100
|
-
overlay,
|
|
101
|
-
reference,
|
|
102
|
-
target,
|
|
103
|
-
position: relativePosition,
|
|
104
|
-
alignment,
|
|
105
|
-
}: CalculatePosOptions): Position & { overlayWidth: number, overlayHeight: number } {
|
|
106
|
-
const overlayDimensions = overlay.getBoundingClientRect()
|
|
107
|
-
if (hasMargins(overlay)) {
|
|
108
|
-
const style = overlay.computedStyleMap()
|
|
109
|
-
const mt = parseInt(style.get('margin-top')?.toString() ?? '0')
|
|
110
|
-
const mb = parseInt(style.get('margin-bottom')?.toString() ?? '0')
|
|
111
|
-
const ml = parseInt(style.get('margin-left')?.toString() ?? '0')
|
|
112
|
-
const mr = parseInt(style.get('margin-right')?.toString() ?? '0')
|
|
113
|
-
if (mt) overlayDimensions.height += mt
|
|
114
|
-
if (mb) overlayDimensions.height += mb
|
|
115
|
-
if (ml) overlayDimensions.width += ml
|
|
116
|
-
if (mr) overlayDimensions.width += mr
|
|
117
|
-
}
|
|
118
|
-
const referencePosition = { top: 0, left: 0, overlayWidth: overlayDimensions.width, overlayHeight: overlayDimensions.height }
|
|
119
|
-
if (reference === 'mouse' && target instanceof MouseEvent) {
|
|
120
|
-
referencePosition.top = target.clientY
|
|
121
|
-
referencePosition.left = target.clientX
|
|
122
|
-
} else {
|
|
123
|
-
const element = target instanceof Event ? target.target : target
|
|
124
|
-
if (!(element instanceof HTMLElement)) return referencePosition
|
|
125
|
-
const elementDimensions = element.getBoundingClientRect()
|
|
126
|
-
switch (relativePosition) {
|
|
127
|
-
case 'top':
|
|
128
|
-
referencePosition.top = elementDimensions.top + window.scrollY
|
|
129
|
-
referencePosition.left = calculateAlignment(alignment, elementDimensions.left, elementDimensions.width, window.scrollX)
|
|
130
|
-
break
|
|
131
|
-
case 'bottom':
|
|
132
|
-
referencePosition.top = elementDimensions.bottom + window.scrollY
|
|
133
|
-
referencePosition.left = calculateAlignment(alignment, elementDimensions.left, elementDimensions.width, window.scrollX)
|
|
134
|
-
break
|
|
135
|
-
case 'left':
|
|
136
|
-
referencePosition.top = calculateAlignment(alignment, elementDimensions.top, elementDimensions.height, window.scrollY)
|
|
137
|
-
referencePosition.left = elementDimensions.left + window.scrollX
|
|
138
|
-
break
|
|
139
|
-
case 'right':
|
|
140
|
-
referencePosition.top = calculateAlignment(alignment, elementDimensions.top, elementDimensions.height, window.scrollY)
|
|
141
|
-
referencePosition.left = elementDimensions.right + window.scrollX
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
const position = { ...referencePosition }
|
|
145
|
-
switch (relativePosition) {
|
|
146
|
-
case 'top':
|
|
147
|
-
position.top -= overlayDimensions.height
|
|
148
|
-
position.left -= calculateAlignmentOffset(alignment, overlayDimensions.width)
|
|
149
|
-
break
|
|
150
|
-
case 'bottom':
|
|
151
|
-
position.left -= calculateAlignmentOffset(alignment, overlayDimensions.width)
|
|
152
|
-
break
|
|
153
|
-
case 'left':
|
|
154
|
-
position.top -= calculateAlignmentOffset(alignment, overlayDimensions.height)
|
|
155
|
-
position.left -= overlayDimensions.width
|
|
156
|
-
break
|
|
157
|
-
case 'right':
|
|
158
|
-
position.top -= calculateAlignmentOffset(alignment, overlayDimensions.height)
|
|
159
|
-
}
|
|
160
|
-
return position
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function getSafeOverlayPosition(
|
|
164
|
-
options: Omit<CalculatePosOptions, 'position'>, positionPriority: RelativePosition[], fallback?: PositionWithRelativeData,
|
|
165
|
-
): PositionWithRelativeData {
|
|
166
|
-
if (!positionPriority.length) return fallback ?? { top: 0, left: 0, relativeTo: 'top' }
|
|
167
|
-
const [relativePosition, ...remainingRelativePositions] = positionPriority
|
|
168
|
-
const position = { ...calculatePosition({ ...options, position: relativePosition }), relativeTo: relativePosition }
|
|
169
|
-
switch (relativePosition) {
|
|
170
|
-
case 'top':
|
|
171
|
-
if (position.left < 0) position.left = 0
|
|
172
|
-
if (position.left + position.overlayWidth > document.body.clientWidth) {
|
|
173
|
-
position.left = document.body.clientWidth - position.overlayWidth
|
|
174
|
-
}
|
|
175
|
-
if (position.top < 0) return getSafeOverlayPosition(options, remainingRelativePositions, fallback ?? position)
|
|
176
|
-
break
|
|
177
|
-
case 'bottom':
|
|
178
|
-
if (position.left < 0) position.left = 0
|
|
179
|
-
if (position.left + position.overlayWidth > document.body.clientWidth) {
|
|
180
|
-
position.left = document.body.clientWidth - position.overlayWidth
|
|
181
|
-
}
|
|
182
|
-
if (position.top + position.overlayHeight > document.body.clientHeight) {
|
|
183
|
-
return getSafeOverlayPosition(options, remainingRelativePositions, fallback ?? position)
|
|
184
|
-
}
|
|
185
|
-
break
|
|
186
|
-
case 'left':
|
|
187
|
-
if (position.top < 0) position.top = 0
|
|
188
|
-
if (position.top + position.overlayHeight > document.body.clientHeight) {
|
|
189
|
-
position.top = document.body.clientHeight - position.overlayHeight
|
|
190
|
-
}
|
|
191
|
-
if (position.left < 0) return getSafeOverlayPosition(options, remainingRelativePositions, fallback ?? position)
|
|
192
|
-
break
|
|
193
|
-
case 'right':
|
|
194
|
-
if (position.top < 0) position.top = 0
|
|
195
|
-
if (position.top + position.overlayHeight > document.body.clientHeight) {
|
|
196
|
-
position.top = document.body.clientHeight - position.overlayHeight
|
|
197
|
-
}
|
|
198
|
-
if (position.left + position.overlayWidth > document.body.clientWidth) {
|
|
199
|
-
return getSafeOverlayPosition(options, remainingRelativePositions, fallback ?? position)
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
return position
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function invert(position: RelativePosition): RelativePosition {
|
|
206
|
-
switch (position) {
|
|
207
|
-
case 'bottom': return 'top'
|
|
208
|
-
case 'top': return 'bottom'
|
|
209
|
-
case 'left': return 'right'
|
|
210
|
-
case 'right': return 'left'
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function oppositeAxis(position: RelativePosition): [RelativePosition, RelativePosition] {
|
|
215
|
-
return (position === 'top' || position === 'bottom') ? ['left', 'right'] : ['top', 'bottom']
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function reactAttributeToHTML(attribute: string) {
|
|
219
|
-
return attribute === 'className' ? 'class' : attribute
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function setElementAttributes(element: HTMLElement, attributes: Record<string, any> | undefined, ignore: string[] = []) {
|
|
223
|
-
for (const attr in attributes) {
|
|
224
|
-
if (attributes[attr] !== undefined && !ignore.includes(attr)) element.setAttribute(reactAttributeToHTML(attr), attributes[attr])
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function getClosestScrollable(element: HTMLElement, limit: HTMLElement = document.body): HTMLElement | null | undefined {
|
|
229
|
-
if (element === limit) return
|
|
230
|
-
return element.clientHeight === element.scrollHeight ? getClosestScrollable(element.parentElement!, limit) : element
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
function isElementVisible(element: HTMLElement, scrollable: HTMLElement) {
|
|
234
|
-
const elementRect = element.getBoundingClientRect()
|
|
235
|
-
const scrollableRect = scrollable.getBoundingClientRect()
|
|
236
|
-
const diffX = elementRect.left - scrollableRect.left + scrollable.scrollLeft
|
|
237
|
-
const isVisibleX = diffX + elementRect.width <= scrollable.scrollLeft + scrollableRect.width &&
|
|
238
|
-
diffX >= scrollable.scrollLeft
|
|
239
|
-
const diffY = elementRect.top - scrollableRect.top + scrollable.scrollTop
|
|
240
|
-
const isVisibleY = diffY + elementRect.height <= scrollable.scrollTop + scrollableRect.height &&
|
|
241
|
-
diffY >= scrollable.scrollTop
|
|
242
|
-
return isVisibleX && isVisibleY
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* The tooltip may be inside a scrollable element. If this is the case, we must update its position whenever the container is scrolled.
|
|
247
|
-
* If the container is scrolled enough to hide the element that triggered the overlay, we hide the overlay.
|
|
248
|
-
*/
|
|
249
|
-
function attachScrollEffects(target: OverlayOptions<any>['target'], overlay: HTMLElement, hide: () => void) {
|
|
250
|
-
const element = target instanceof Event ? target.target as HTMLElement : target
|
|
251
|
-
const closestScrollableFromTarget = getClosestScrollable(element, overlay.parentNode as HTMLElement)
|
|
252
|
-
if (closestScrollableFromTarget) {
|
|
253
|
-
let lastScrollX = closestScrollableFromTarget.scrollLeft
|
|
254
|
-
let lastScrollY = closestScrollableFromTarget.scrollTop
|
|
255
|
-
const updatePosition = () => {
|
|
256
|
-
if (!isElementVisible(element, closestScrollableFromTarget)) return hide()
|
|
257
|
-
const diffX = closestScrollableFromTarget.scrollLeft - lastScrollX
|
|
258
|
-
const diffY = closestScrollableFromTarget.scrollTop - lastScrollY
|
|
259
|
-
overlay.style.left = `${parseInt(overlay.style.left) - diffX}px`
|
|
260
|
-
overlay.style.top = `${parseInt(overlay.style.top) - diffY}px`
|
|
261
|
-
lastScrollX = closestScrollableFromTarget.scrollLeft
|
|
262
|
-
lastScrollY = closestScrollableFromTarget.scrollTop
|
|
263
|
-
}
|
|
264
|
-
closestScrollableFromTarget.addEventListener('scroll', updatePosition)
|
|
265
|
-
return () => closestScrollableFromTarget.removeEventListener('scroll', updatePosition)
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Appends a new HTML Element to the tag "body". This element is absolutely positioned and its position is calculated according to the
|
|
271
|
-
* options passed as parameter.
|
|
272
|
-
*
|
|
273
|
-
* This function returns both the newly created HTML Element and a function to remove it.
|
|
274
|
-
* @param options {@link OverlayOptions}.
|
|
275
|
-
* @returns an object with two keys: "overlay" (the HTML Element created) and "hide" (a function to remove the element from the document).
|
|
276
|
-
*/
|
|
277
|
-
export function showOverlay<T extends keyof HTMLTag = 'div'>(
|
|
278
|
-
{ tag, content, target, reference = 'element', position = 'top', alignment = 'center', attributes }: OverlayOptions<T>,
|
|
279
|
-
) {
|
|
280
|
-
let removeScrollEffects: (() => void) | undefined
|
|
281
|
-
const overlay = document.createElement(tag || 'div')
|
|
282
|
-
overlay.style = `z-index: 9999; pointer-events: none; position: absolute; opacity: 0; transition: opacity ${animationDurationMS / 1000}s; ${styleObjectToCssString(attributes?.style)}`
|
|
283
|
-
overlay.inert = true
|
|
284
|
-
setElementAttributes(overlay, attributes, ['style', 'inert'])
|
|
285
|
-
document.body.append(overlay)
|
|
286
|
-
let unmount: (() => void) | undefined
|
|
287
|
-
if (['string', 'number', 'boolean'].includes(typeof content)) {
|
|
288
|
-
overlay.append(`${content}`)
|
|
289
|
-
unmount = () => document.body.removeChild(overlay)
|
|
290
|
-
} else {
|
|
291
|
-
const root = createRoot(overlay)
|
|
292
|
-
root.render(content)
|
|
293
|
-
unmount = () => {
|
|
294
|
-
root.unmount()
|
|
295
|
-
document.body.removeChild(overlay)
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const hide = () => new Promise<void>((resolve) => {
|
|
300
|
-
overlay.style.opacity = '0'
|
|
301
|
-
overlay.style.pointerEvents = 'none'
|
|
302
|
-
overlay.inert = true
|
|
303
|
-
setTimeout(() => {
|
|
304
|
-
try {
|
|
305
|
-
removeScrollEffects?.()
|
|
306
|
-
unmount()
|
|
307
|
-
} catch { /* empty */ }
|
|
308
|
-
resolve()
|
|
309
|
-
}, animationDurationMS)
|
|
310
|
-
})
|
|
311
|
-
|
|
312
|
-
setTimeout(() => {
|
|
313
|
-
const overlayPos = getSafeOverlayPosition(
|
|
314
|
-
{ alignment, overlay, reference, target },
|
|
315
|
-
[position, invert(position), ...oppositeAxis(position)],
|
|
316
|
-
)
|
|
317
|
-
overlay.style = `z-index: 9999; position: absolute; opacity: 1; transition: opacity ${animationDurationMS / 1000}s; top: ${overlayPos.top}px; left: ${overlayPos.left}px; ${styleObjectToCssString(attributes?.style)}`
|
|
318
|
-
if (attributes && 'inert' in attributes && attributes.inert) overlay.style.pointerEvents = 'none'
|
|
319
|
-
else overlay.inert = false
|
|
320
|
-
overlay.classList.add(overlayPos.relativeTo, `align-${alignment}`)
|
|
321
|
-
removeScrollEffects = attachScrollEffects(target, overlay, hide)
|
|
322
|
-
}, 0)
|
|
323
|
-
|
|
324
|
-
return {
|
|
325
|
-
/**
|
|
326
|
-
* The overlay element created.
|
|
327
|
-
*/
|
|
328
|
-
overlay,
|
|
329
|
-
/**
|
|
330
|
-
* Removes the overlay element.
|
|
331
|
-
* @returns a promise that completes when the element is fully removed (after any animation).
|
|
332
|
-
*/
|
|
333
|
-
hide,
|
|
334
|
-
/**
|
|
335
|
-
* Returns a promise that resolves as soon as the overlay finishes the animation to show up.
|
|
336
|
-
*/
|
|
337
|
-
ready: new Promise<void>((resolve) => {
|
|
338
|
-
setTimeout(resolve, animationDurationMS)
|
|
339
|
-
}),
|
|
340
|
-
}
|
|
341
|
-
}
|
|
1
|
+
import { createRoot } from 'react-dom/client'
|
|
2
|
+
import { HTMLTag, WithDataAttributes } from './types'
|
|
3
|
+
import { styleObjectToCssString } from './utils/css'
|
|
4
|
+
|
|
5
|
+
export type RelativePosition = 'top' | 'bottom' | 'left' | 'right'
|
|
6
|
+
|
|
7
|
+
export interface OverlayOptions<T extends keyof HTMLTag> {
|
|
8
|
+
/**
|
|
9
|
+
* The tag to render when creating the overlay element.
|
|
10
|
+
*
|
|
11
|
+
* @default 'div'
|
|
12
|
+
*/
|
|
13
|
+
tag?: T,
|
|
14
|
+
/**
|
|
15
|
+
* The content of the overlay element.
|
|
16
|
+
*
|
|
17
|
+
* When this is a string, a number or a boolean, the content will be appended to the overlay element. Otherwise, a new React concurrent
|
|
18
|
+
* instance will be created to render the React tree node inside the overlay element.
|
|
19
|
+
*/
|
|
20
|
+
content: React.ReactNode,
|
|
21
|
+
/**
|
|
22
|
+
* Which element will be used as a reference to position the overlay element.
|
|
23
|
+
* This can be either an HTMLElement or an event. If this is a event, the element in `event.target` will be used unless `reference` is
|
|
24
|
+
* set to `'mouse'`, in this case, the reference will be the current cursor position.
|
|
25
|
+
*/
|
|
26
|
+
target: Event | HTMLElement,
|
|
27
|
+
/**
|
|
28
|
+
* Takes effect when `target` is a MouseEvent. If this is set to `'mouse'`, then the position to be used as a reference to place the
|
|
29
|
+
* overlay will be the current cursor position.
|
|
30
|
+
*
|
|
31
|
+
* @default 'element'
|
|
32
|
+
*/
|
|
33
|
+
reference?: 'element' | 'mouse',
|
|
34
|
+
/**
|
|
35
|
+
* Where, relative to `target`, should we place the overlay?
|
|
36
|
+
*
|
|
37
|
+
* @default 'top'
|
|
38
|
+
*/
|
|
39
|
+
position?: RelativePosition,
|
|
40
|
+
/**
|
|
41
|
+
* Affects the positioning of the overlay relative to `target`. While `position` defines the overlay position in the main axis,
|
|
42
|
+
* `alignment` defines the position in the cross axis.
|
|
43
|
+
*
|
|
44
|
+
* @default 'center'
|
|
45
|
+
*/
|
|
46
|
+
alignment?: 'start' | 'center' | 'end',
|
|
47
|
+
/**
|
|
48
|
+
* TODO: implement this (currently, only 'fade' works).
|
|
49
|
+
*
|
|
50
|
+
* - fade: the overlay fades in to appear and fades out to disappear.
|
|
51
|
+
* - accordion: the element grows to appear and shrinks to disappear. The direction of the growth depends on `position`.
|
|
52
|
+
* - bubble: grows from scale(0) to scale(1) from its center to appear. Shrinks from scale(1) to scale(0) to disappear.
|
|
53
|
+
* - none: no animation is used.
|
|
54
|
+
*
|
|
55
|
+
* @default 'fade'
|
|
56
|
+
*/
|
|
57
|
+
animation?: 'fade' | 'accordion' | 'bubble' | 'none',
|
|
58
|
+
/**
|
|
59
|
+
* The attributes for the HTMLElement that will be created for the overlay.
|
|
60
|
+
*/
|
|
61
|
+
attributes?: JSX.IntrinsicElements[T] & WithDataAttributes & { inert?: boolean },
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface Position {
|
|
65
|
+
top: number,
|
|
66
|
+
left: number,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface PositionWithRelativeData extends Position {
|
|
70
|
+
relativeTo: RelativePosition,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
type CalculatePosOptions = Pick<Required<OverlayOptions<any>>, 'position' | 'target' | 'reference' | 'alignment'>
|
|
74
|
+
& { overlay: HTMLElement }
|
|
75
|
+
|
|
76
|
+
const animationDurationMS = 300
|
|
77
|
+
|
|
78
|
+
function hasMargins(element: HTMLElement) {
|
|
79
|
+
const s = element.style
|
|
80
|
+
return s.margin || s.marginTop || s.marginBottom || s.marginLeft || s.marginRight
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function calculateAlignment(alignment: CalculatePosOptions['alignment'], offset: number, size: number, scroll: number) {
|
|
84
|
+
switch (alignment) {
|
|
85
|
+
case 'center': return offset + size / 2 + scroll
|
|
86
|
+
case 'end': return offset + size + scroll
|
|
87
|
+
case 'start': return offset + scroll
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function calculateAlignmentOffset(alignment: CalculatePosOptions['alignment'], overlaySize: number) {
|
|
92
|
+
switch (alignment) {
|
|
93
|
+
case 'center': return overlaySize / 2
|
|
94
|
+
case 'end': return overlaySize
|
|
95
|
+
case 'start': return 0
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function calculatePosition({
|
|
100
|
+
overlay,
|
|
101
|
+
reference,
|
|
102
|
+
target,
|
|
103
|
+
position: relativePosition,
|
|
104
|
+
alignment,
|
|
105
|
+
}: CalculatePosOptions): Position & { overlayWidth: number, overlayHeight: number } {
|
|
106
|
+
const overlayDimensions = overlay.getBoundingClientRect()
|
|
107
|
+
if (hasMargins(overlay)) {
|
|
108
|
+
const style = overlay.computedStyleMap()
|
|
109
|
+
const mt = parseInt(style.get('margin-top')?.toString() ?? '0')
|
|
110
|
+
const mb = parseInt(style.get('margin-bottom')?.toString() ?? '0')
|
|
111
|
+
const ml = parseInt(style.get('margin-left')?.toString() ?? '0')
|
|
112
|
+
const mr = parseInt(style.get('margin-right')?.toString() ?? '0')
|
|
113
|
+
if (mt) overlayDimensions.height += mt
|
|
114
|
+
if (mb) overlayDimensions.height += mb
|
|
115
|
+
if (ml) overlayDimensions.width += ml
|
|
116
|
+
if (mr) overlayDimensions.width += mr
|
|
117
|
+
}
|
|
118
|
+
const referencePosition = { top: 0, left: 0, overlayWidth: overlayDimensions.width, overlayHeight: overlayDimensions.height }
|
|
119
|
+
if (reference === 'mouse' && target instanceof MouseEvent) {
|
|
120
|
+
referencePosition.top = target.clientY
|
|
121
|
+
referencePosition.left = target.clientX
|
|
122
|
+
} else {
|
|
123
|
+
const element = target instanceof Event ? target.target : target
|
|
124
|
+
if (!(element instanceof HTMLElement)) return referencePosition
|
|
125
|
+
const elementDimensions = element.getBoundingClientRect()
|
|
126
|
+
switch (relativePosition) {
|
|
127
|
+
case 'top':
|
|
128
|
+
referencePosition.top = elementDimensions.top + window.scrollY
|
|
129
|
+
referencePosition.left = calculateAlignment(alignment, elementDimensions.left, elementDimensions.width, window.scrollX)
|
|
130
|
+
break
|
|
131
|
+
case 'bottom':
|
|
132
|
+
referencePosition.top = elementDimensions.bottom + window.scrollY
|
|
133
|
+
referencePosition.left = calculateAlignment(alignment, elementDimensions.left, elementDimensions.width, window.scrollX)
|
|
134
|
+
break
|
|
135
|
+
case 'left':
|
|
136
|
+
referencePosition.top = calculateAlignment(alignment, elementDimensions.top, elementDimensions.height, window.scrollY)
|
|
137
|
+
referencePosition.left = elementDimensions.left + window.scrollX
|
|
138
|
+
break
|
|
139
|
+
case 'right':
|
|
140
|
+
referencePosition.top = calculateAlignment(alignment, elementDimensions.top, elementDimensions.height, window.scrollY)
|
|
141
|
+
referencePosition.left = elementDimensions.right + window.scrollX
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const position = { ...referencePosition }
|
|
145
|
+
switch (relativePosition) {
|
|
146
|
+
case 'top':
|
|
147
|
+
position.top -= overlayDimensions.height
|
|
148
|
+
position.left -= calculateAlignmentOffset(alignment, overlayDimensions.width)
|
|
149
|
+
break
|
|
150
|
+
case 'bottom':
|
|
151
|
+
position.left -= calculateAlignmentOffset(alignment, overlayDimensions.width)
|
|
152
|
+
break
|
|
153
|
+
case 'left':
|
|
154
|
+
position.top -= calculateAlignmentOffset(alignment, overlayDimensions.height)
|
|
155
|
+
position.left -= overlayDimensions.width
|
|
156
|
+
break
|
|
157
|
+
case 'right':
|
|
158
|
+
position.top -= calculateAlignmentOffset(alignment, overlayDimensions.height)
|
|
159
|
+
}
|
|
160
|
+
return position
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function getSafeOverlayPosition(
|
|
164
|
+
options: Omit<CalculatePosOptions, 'position'>, positionPriority: RelativePosition[], fallback?: PositionWithRelativeData,
|
|
165
|
+
): PositionWithRelativeData {
|
|
166
|
+
if (!positionPriority.length) return fallback ?? { top: 0, left: 0, relativeTo: 'top' }
|
|
167
|
+
const [relativePosition, ...remainingRelativePositions] = positionPriority
|
|
168
|
+
const position = { ...calculatePosition({ ...options, position: relativePosition }), relativeTo: relativePosition }
|
|
169
|
+
switch (relativePosition) {
|
|
170
|
+
case 'top':
|
|
171
|
+
if (position.left < 0) position.left = 0
|
|
172
|
+
if (position.left + position.overlayWidth > document.body.clientWidth) {
|
|
173
|
+
position.left = document.body.clientWidth - position.overlayWidth
|
|
174
|
+
}
|
|
175
|
+
if (position.top < 0) return getSafeOverlayPosition(options, remainingRelativePositions, fallback ?? position)
|
|
176
|
+
break
|
|
177
|
+
case 'bottom':
|
|
178
|
+
if (position.left < 0) position.left = 0
|
|
179
|
+
if (position.left + position.overlayWidth > document.body.clientWidth) {
|
|
180
|
+
position.left = document.body.clientWidth - position.overlayWidth
|
|
181
|
+
}
|
|
182
|
+
if (position.top + position.overlayHeight > document.body.clientHeight) {
|
|
183
|
+
return getSafeOverlayPosition(options, remainingRelativePositions, fallback ?? position)
|
|
184
|
+
}
|
|
185
|
+
break
|
|
186
|
+
case 'left':
|
|
187
|
+
if (position.top < 0) position.top = 0
|
|
188
|
+
if (position.top + position.overlayHeight > document.body.clientHeight) {
|
|
189
|
+
position.top = document.body.clientHeight - position.overlayHeight
|
|
190
|
+
}
|
|
191
|
+
if (position.left < 0) return getSafeOverlayPosition(options, remainingRelativePositions, fallback ?? position)
|
|
192
|
+
break
|
|
193
|
+
case 'right':
|
|
194
|
+
if (position.top < 0) position.top = 0
|
|
195
|
+
if (position.top + position.overlayHeight > document.body.clientHeight) {
|
|
196
|
+
position.top = document.body.clientHeight - position.overlayHeight
|
|
197
|
+
}
|
|
198
|
+
if (position.left + position.overlayWidth > document.body.clientWidth) {
|
|
199
|
+
return getSafeOverlayPosition(options, remainingRelativePositions, fallback ?? position)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return position
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function invert(position: RelativePosition): RelativePosition {
|
|
206
|
+
switch (position) {
|
|
207
|
+
case 'bottom': return 'top'
|
|
208
|
+
case 'top': return 'bottom'
|
|
209
|
+
case 'left': return 'right'
|
|
210
|
+
case 'right': return 'left'
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function oppositeAxis(position: RelativePosition): [RelativePosition, RelativePosition] {
|
|
215
|
+
return (position === 'top' || position === 'bottom') ? ['left', 'right'] : ['top', 'bottom']
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function reactAttributeToHTML(attribute: string) {
|
|
219
|
+
return attribute === 'className' ? 'class' : attribute
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function setElementAttributes(element: HTMLElement, attributes: Record<string, any> | undefined, ignore: string[] = []) {
|
|
223
|
+
for (const attr in attributes) {
|
|
224
|
+
if (attributes[attr] !== undefined && !ignore.includes(attr)) element.setAttribute(reactAttributeToHTML(attr), attributes[attr])
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function getClosestScrollable(element: HTMLElement, limit: HTMLElement = document.body): HTMLElement | null | undefined {
|
|
229
|
+
if (element === limit) return
|
|
230
|
+
return element.clientHeight === element.scrollHeight ? getClosestScrollable(element.parentElement!, limit) : element
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function isElementVisible(element: HTMLElement, scrollable: HTMLElement) {
|
|
234
|
+
const elementRect = element.getBoundingClientRect()
|
|
235
|
+
const scrollableRect = scrollable.getBoundingClientRect()
|
|
236
|
+
const diffX = elementRect.left - scrollableRect.left + scrollable.scrollLeft
|
|
237
|
+
const isVisibleX = diffX + elementRect.width <= scrollable.scrollLeft + scrollableRect.width &&
|
|
238
|
+
diffX >= scrollable.scrollLeft
|
|
239
|
+
const diffY = elementRect.top - scrollableRect.top + scrollable.scrollTop
|
|
240
|
+
const isVisibleY = diffY + elementRect.height <= scrollable.scrollTop + scrollableRect.height &&
|
|
241
|
+
diffY >= scrollable.scrollTop
|
|
242
|
+
return isVisibleX && isVisibleY
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* The tooltip may be inside a scrollable element. If this is the case, we must update its position whenever the container is scrolled.
|
|
247
|
+
* If the container is scrolled enough to hide the element that triggered the overlay, we hide the overlay.
|
|
248
|
+
*/
|
|
249
|
+
function attachScrollEffects(target: OverlayOptions<any>['target'], overlay: HTMLElement, hide: () => void) {
|
|
250
|
+
const element = target instanceof Event ? target.target as HTMLElement : target
|
|
251
|
+
const closestScrollableFromTarget = getClosestScrollable(element, overlay.parentNode as HTMLElement)
|
|
252
|
+
if (closestScrollableFromTarget) {
|
|
253
|
+
let lastScrollX = closestScrollableFromTarget.scrollLeft
|
|
254
|
+
let lastScrollY = closestScrollableFromTarget.scrollTop
|
|
255
|
+
const updatePosition = () => {
|
|
256
|
+
if (!isElementVisible(element, closestScrollableFromTarget)) return hide()
|
|
257
|
+
const diffX = closestScrollableFromTarget.scrollLeft - lastScrollX
|
|
258
|
+
const diffY = closestScrollableFromTarget.scrollTop - lastScrollY
|
|
259
|
+
overlay.style.left = `${parseInt(overlay.style.left) - diffX}px`
|
|
260
|
+
overlay.style.top = `${parseInt(overlay.style.top) - diffY}px`
|
|
261
|
+
lastScrollX = closestScrollableFromTarget.scrollLeft
|
|
262
|
+
lastScrollY = closestScrollableFromTarget.scrollTop
|
|
263
|
+
}
|
|
264
|
+
closestScrollableFromTarget.addEventListener('scroll', updatePosition)
|
|
265
|
+
return () => closestScrollableFromTarget.removeEventListener('scroll', updatePosition)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Appends a new HTML Element to the tag "body". This element is absolutely positioned and its position is calculated according to the
|
|
271
|
+
* options passed as parameter.
|
|
272
|
+
*
|
|
273
|
+
* This function returns both the newly created HTML Element and a function to remove it.
|
|
274
|
+
* @param options {@link OverlayOptions}.
|
|
275
|
+
* @returns an object with two keys: "overlay" (the HTML Element created) and "hide" (a function to remove the element from the document).
|
|
276
|
+
*/
|
|
277
|
+
export function showOverlay<T extends keyof HTMLTag = 'div'>(
|
|
278
|
+
{ tag, content, target, reference = 'element', position = 'top', alignment = 'center', attributes }: OverlayOptions<T>,
|
|
279
|
+
) {
|
|
280
|
+
let removeScrollEffects: (() => void) | undefined
|
|
281
|
+
const overlay = document.createElement(tag || 'div')
|
|
282
|
+
overlay.style = `z-index: 9999; pointer-events: none; position: absolute; opacity: 0; transition: opacity ${animationDurationMS / 1000}s; ${styleObjectToCssString(attributes?.style)}`
|
|
283
|
+
overlay.inert = true
|
|
284
|
+
setElementAttributes(overlay, attributes, ['style', 'inert'])
|
|
285
|
+
document.body.append(overlay)
|
|
286
|
+
let unmount: (() => void) | undefined
|
|
287
|
+
if (['string', 'number', 'boolean'].includes(typeof content)) {
|
|
288
|
+
overlay.append(`${content}`)
|
|
289
|
+
unmount = () => document.body.removeChild(overlay)
|
|
290
|
+
} else {
|
|
291
|
+
const root = createRoot(overlay)
|
|
292
|
+
root.render(content)
|
|
293
|
+
unmount = () => {
|
|
294
|
+
root.unmount()
|
|
295
|
+
document.body.removeChild(overlay)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const hide = () => new Promise<void>((resolve) => {
|
|
300
|
+
overlay.style.opacity = '0'
|
|
301
|
+
overlay.style.pointerEvents = 'none'
|
|
302
|
+
overlay.inert = true
|
|
303
|
+
setTimeout(() => {
|
|
304
|
+
try {
|
|
305
|
+
removeScrollEffects?.()
|
|
306
|
+
unmount()
|
|
307
|
+
} catch { /* empty */ }
|
|
308
|
+
resolve()
|
|
309
|
+
}, animationDurationMS)
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
setTimeout(() => {
|
|
313
|
+
const overlayPos = getSafeOverlayPosition(
|
|
314
|
+
{ alignment, overlay, reference, target },
|
|
315
|
+
[position, invert(position), ...oppositeAxis(position)],
|
|
316
|
+
)
|
|
317
|
+
overlay.style = `z-index: 9999; position: absolute; opacity: 1; transition: opacity ${animationDurationMS / 1000}s; top: ${overlayPos.top}px; left: ${overlayPos.left}px; ${styleObjectToCssString(attributes?.style)}`
|
|
318
|
+
if (attributes && 'inert' in attributes && attributes.inert) overlay.style.pointerEvents = 'none'
|
|
319
|
+
else overlay.inert = false
|
|
320
|
+
overlay.classList.add(overlayPos.relativeTo, `align-${alignment}`)
|
|
321
|
+
removeScrollEffects = attachScrollEffects(target, overlay, hide)
|
|
322
|
+
}, 0)
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
/**
|
|
326
|
+
* The overlay element created.
|
|
327
|
+
*/
|
|
328
|
+
overlay,
|
|
329
|
+
/**
|
|
330
|
+
* Removes the overlay element.
|
|
331
|
+
* @returns a promise that completes when the element is fully removed (after any animation).
|
|
332
|
+
*/
|
|
333
|
+
hide,
|
|
334
|
+
/**
|
|
335
|
+
* Returns a promise that resolves as soon as the overlay finishes the animation to show up.
|
|
336
|
+
*/
|
|
337
|
+
ready: new Promise<void>((resolve) => {
|
|
338
|
+
setTimeout(resolve, animationDurationMS)
|
|
339
|
+
}),
|
|
340
|
+
}
|
|
341
|
+
}
|