@proyecto-viviana/solidaria-components 0.2.5 → 0.3.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/LICENSE +21 -0
- package/README.md +39 -272
- package/dist/ActionBar.d.ts +79 -0
- package/dist/ActionBar.d.ts.map +1 -0
- package/dist/ActionGroup.d.ts +74 -0
- package/dist/ActionGroup.d.ts.map +1 -0
- package/dist/Alert.d.ts +70 -0
- package/dist/Alert.d.ts.map +1 -0
- package/dist/Autocomplete.d.ts +5 -5
- package/dist/Autocomplete.d.ts.map +1 -1
- package/dist/Breadcrumbs.d.ts +27 -8
- package/dist/Breadcrumbs.d.ts.map +1 -1
- package/dist/Button.d.ts +28 -5
- package/dist/Button.d.ts.map +1 -1
- package/dist/Calendar.d.ts +51 -7
- package/dist/Calendar.d.ts.map +1 -1
- package/dist/Checkbox.d.ts +33 -8
- package/dist/Checkbox.d.ts.map +1 -1
- package/dist/Collection.d.ts +130 -0
- package/dist/Collection.d.ts.map +1 -0
- package/dist/Color.d.ts +210 -9
- package/dist/Color.d.ts.map +1 -1
- package/dist/ColorEditor.d.ts +42 -0
- package/dist/ColorEditor.d.ts.map +1 -0
- package/dist/ComboBox.d.ts +146 -16
- package/dist/ComboBox.d.ts.map +1 -1
- package/dist/ContextualHelpTrigger.d.ts +40 -0
- package/dist/ContextualHelpTrigger.d.ts.map +1 -0
- package/dist/DateField.d.ts +35 -8
- package/dist/DateField.d.ts.map +1 -1
- package/dist/DatePicker.d.ts +101 -5
- package/dist/DatePicker.d.ts.map +1 -1
- package/dist/DateRangePickerContext.d.ts +30 -0
- package/dist/DateRangePickerContext.d.ts.map +1 -0
- package/dist/Dialog.d.ts +5 -5
- package/dist/Dialog.d.ts.map +1 -1
- package/dist/Disclosure.d.ts +25 -5
- package/dist/Disclosure.d.ts.map +1 -1
- package/dist/DragAndDrop.d.ts +80 -0
- package/dist/DragAndDrop.d.ts.map +1 -0
- package/dist/DragPreview.d.ts +14 -0
- package/dist/DragPreview.d.ts.map +1 -0
- package/dist/DropZone.d.ts +27 -0
- package/dist/DropZone.d.ts.map +1 -0
- package/dist/FieldError.d.ts +27 -0
- package/dist/FieldError.d.ts.map +1 -0
- package/dist/FileTrigger.d.ts +26 -0
- package/dist/FileTrigger.d.ts.map +1 -0
- package/dist/Focusable.d.ts +27 -0
- package/dist/Focusable.d.ts.map +1 -0
- package/dist/Form.d.ts +41 -0
- package/dist/Form.d.ts.map +1 -0
- package/dist/GridList.d.ts +69 -10
- package/dist/GridList.d.ts.map +1 -1
- package/dist/HiddenDateInput.d.ts +26 -0
- package/dist/HiddenDateInput.d.ts.map +1 -0
- package/dist/HiddenTimeInput.d.ts +25 -0
- package/dist/HiddenTimeInput.d.ts.map +1 -0
- package/dist/Icon.d.ts +57 -0
- package/dist/Icon.d.ts.map +1 -0
- package/dist/Keyboard.d.ts +13 -0
- package/dist/Keyboard.d.ts.map +1 -0
- package/dist/Landmark.d.ts +3 -3
- package/dist/Landmark.d.ts.map +1 -1
- package/dist/Link.d.ts +10 -4
- package/dist/Link.d.ts.map +1 -1
- package/dist/ListBox.d.ts +73 -11
- package/dist/ListBox.d.ts.map +1 -1
- package/dist/ListDropTargetDelegate.d.ts +38 -0
- package/dist/ListDropTargetDelegate.d.ts.map +1 -0
- package/dist/Menu.d.ts +79 -10
- package/dist/Menu.d.ts.map +1 -1
- package/dist/Meter.d.ts +4 -4
- package/dist/Meter.d.ts.map +1 -1
- package/dist/Modal.d.ts +6 -4
- package/dist/Modal.d.ts.map +1 -1
- package/dist/NumberField.d.ts +10 -12
- package/dist/NumberField.d.ts.map +1 -1
- package/dist/Popover.d.ts +32 -7
- package/dist/Popover.d.ts.map +1 -1
- package/dist/Pressable.d.ts +27 -0
- package/dist/Pressable.d.ts.map +1 -0
- package/dist/ProgressBar.d.ts +6 -4
- package/dist/ProgressBar.d.ts.map +1 -1
- package/dist/RadioGroup.d.ts +43 -9
- package/dist/RadioGroup.d.ts.map +1 -1
- package/dist/RangeCalendar.d.ts +39 -7
- package/dist/RangeCalendar.d.ts.map +1 -1
- package/dist/RouterProvider.d.ts +75 -0
- package/dist/RouterProvider.d.ts.map +1 -0
- package/dist/SearchField.d.ts +23 -21
- package/dist/SearchField.d.ts.map +1 -1
- package/dist/Select.d.ts +48 -7
- package/dist/Select.d.ts.map +1 -1
- package/dist/SelectionIndicator.d.ts +30 -0
- package/dist/SelectionIndicator.d.ts.map +1 -0
- package/dist/Separator.d.ts +9 -3
- package/dist/Separator.d.ts.map +1 -1
- package/dist/SharedElementTransition.d.ts +41 -0
- package/dist/SharedElementTransition.d.ts.map +1 -0
- package/dist/Slider.d.ts +15 -8
- package/dist/Slider.d.ts.map +1 -1
- package/dist/StepList.d.ts +90 -0
- package/dist/StepList.d.ts.map +1 -0
- package/dist/Switch.d.ts +11 -5
- package/dist/Switch.d.ts.map +1 -1
- package/dist/Table.d.ts +222 -19
- package/dist/Table.d.ts.map +1 -1
- package/dist/Tabs.d.ts +47 -10
- package/dist/Tabs.d.ts.map +1 -1
- package/dist/TagGroup.d.ts +22 -10
- package/dist/TagGroup.d.ts.map +1 -1
- package/dist/Text.d.ts +10 -0
- package/dist/Text.d.ts.map +1 -0
- package/dist/TextField.d.ts +19 -11
- package/dist/TextField.d.ts.map +1 -1
- package/dist/TimeField.d.ts +32 -7
- package/dist/TimeField.d.ts.map +1 -1
- package/dist/Toast.d.ts +29 -14
- package/dist/Toast.d.ts.map +1 -1
- package/dist/ToggleButton.d.ts +36 -0
- package/dist/ToggleButton.d.ts.map +1 -0
- package/dist/ToggleButtonGroup.d.ts +33 -0
- package/dist/ToggleButtonGroup.d.ts.map +1 -0
- package/dist/Toolbar.d.ts +7 -3
- package/dist/Toolbar.d.ts.map +1 -1
- package/dist/Tooltip.d.ts +58 -7
- package/dist/Tooltip.d.ts.map +1 -1
- package/dist/Tree.d.ts +102 -11
- package/dist/Tree.d.ts.map +1 -1
- package/dist/Virtualizer.d.ts +61 -0
- package/dist/Virtualizer.d.ts.map +1 -0
- package/dist/VirtualizerLayouts.d.ts +82 -0
- package/dist/VirtualizerLayouts.d.ts.map +1 -0
- package/dist/VisuallyHidden.d.ts +4 -2
- package/dist/VisuallyHidden.d.ts.map +1 -1
- package/dist/contexts.d.ts +6 -1
- package/dist/contexts.d.ts.map +1 -1
- package/dist/index.d.ts +73 -39
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23342 -10644
- package/dist/index.js.map +1 -7
- package/dist/index.jsx +18110 -0
- package/dist/index.jsx.map +1 -0
- package/dist/useDragAndDrop.d.ts +93 -0
- package/dist/useDragAndDrop.d.ts.map +1 -0
- package/dist/utils.d.ts +8 -2
- package/dist/utils.d.ts.map +1 -1
- package/dist/virtualizer/Layout.d.ts +79 -0
- package/dist/virtualizer/Layout.d.ts.map +1 -0
- package/package.json +33 -32
- package/src/ActionBar.tsx +251 -0
- package/src/ActionGroup.tsx +277 -0
- package/src/Alert.tsx +152 -0
- package/src/Autocomplete.tsx +39 -44
- package/src/Breadcrumbs.tsx +227 -72
- package/src/Button.tsx +315 -74
- package/src/Calendar.tsx +347 -141
- package/src/Checkbox.tsx +414 -123
- package/src/Collection.tsx +350 -0
- package/src/Color.tsx +1325 -284
- package/src/ColorEditor.tsx +213 -0
- package/src/ComboBox.tsx +644 -245
- package/src/ContextualHelpTrigger.tsx +195 -0
- package/src/DateField.tsx +274 -106
- package/src/DatePicker.tsx +892 -111
- package/src/DateRangePickerContext.tsx +44 -0
- package/src/Dialog.tsx +173 -104
- package/src/Disclosure.tsx +158 -105
- package/src/DragAndDrop.tsx +340 -0
- package/src/DragPreview.tsx +47 -0
- package/src/DropZone.tsx +233 -0
- package/src/FieldError.tsx +89 -0
- package/src/FileTrigger.tsx +83 -0
- package/src/Focusable.tsx +103 -0
- package/src/Form.tsx +140 -0
- package/src/GridList.tsx +542 -128
- package/src/HiddenDateInput.tsx +153 -0
- package/src/HiddenTimeInput.tsx +133 -0
- package/src/Icon.tsx +133 -0
- package/src/Keyboard.tsx +26 -0
- package/src/Landmark.tsx +37 -63
- package/src/Link.tsx +132 -69
- package/src/ListBox.tsx +656 -106
- package/src/ListDropTargetDelegate.ts +283 -0
- package/src/Menu.tsx +1234 -132
- package/src/Meter.tsx +44 -58
- package/src/Modal.tsx +262 -166
- package/src/NumberField.tsx +267 -151
- package/src/Popover.tsx +452 -343
- package/src/Pressable.tsx +108 -0
- package/src/ProgressBar.tsx +54 -59
- package/src/RadioGroup.tsx +533 -121
- package/src/RangeCalendar.tsx +249 -150
- package/src/RouterProvider.tsx +223 -0
- package/src/SearchField.tsx +460 -133
- package/src/Select.tsx +804 -233
- package/src/SelectionIndicator.tsx +108 -0
- package/src/Separator.tsx +47 -49
- package/src/SharedElementTransition.tsx +264 -0
- package/src/Slider.tsx +148 -98
- package/src/StepList.tsx +272 -0
- package/src/Switch.tsx +93 -46
- package/src/Table.tsx +1551 -225
- package/src/Tabs.tsx +377 -123
- package/src/TagGroup.tsx +233 -135
- package/src/Text.tsx +18 -0
- package/src/TextField.tsx +413 -86
- package/src/TimeField.tsx +232 -222
- package/src/Toast.tsx +306 -160
- package/src/ToggleButton.tsx +169 -0
- package/src/ToggleButtonGroup.tsx +141 -0
- package/src/Toolbar.tsx +61 -70
- package/src/Tooltip.tsx +473 -116
- package/src/Tree.tsx +1514 -175
- package/src/Virtualizer.tsx +730 -0
- package/src/VirtualizerLayouts.ts +280 -0
- package/src/VisuallyHidden.tsx +32 -38
- package/src/contexts.ts +29 -36
- package/src/index.ts +972 -620
- package/src/useDragAndDrop.ts +367 -0
- package/src/utils.tsx +69 -50
- package/src/virtualizer/Layout.ts +192 -0
- package/dist/index.ssr.js +0 -9785
- package/dist/index.ssr.js.map +0 -7
package/src/Popover.tsx
CHANGED
|
@@ -8,23 +8,26 @@
|
|
|
8
8
|
import {
|
|
9
9
|
type JSX,
|
|
10
10
|
createContext,
|
|
11
|
+
createEffect,
|
|
11
12
|
createMemo,
|
|
12
13
|
createSignal,
|
|
13
14
|
createUniqueId,
|
|
15
|
+
onCleanup,
|
|
14
16
|
splitProps,
|
|
15
17
|
useContext,
|
|
16
18
|
Show,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
} from 'solid-js'
|
|
20
|
-
import { Portal, isServer } from 'solid-js/web'
|
|
19
|
+
} from "solid-js";
|
|
20
|
+
import { Portal } from "solid-js/web";
|
|
21
21
|
import {
|
|
22
22
|
createOverlayTrigger,
|
|
23
|
+
createPopover,
|
|
23
24
|
FocusScope,
|
|
25
|
+
useUNSAFE_PortalContext,
|
|
26
|
+
visuallyHiddenStyles,
|
|
24
27
|
type Placement,
|
|
25
28
|
type PlacementAxis,
|
|
26
|
-
} from
|
|
27
|
-
import { createOverlayTriggerState } from
|
|
29
|
+
} from "@proyecto-viviana/solidaria";
|
|
30
|
+
import { createOverlayTriggerState } from "@proyecto-viviana/solid-stately";
|
|
28
31
|
import {
|
|
29
32
|
type RenderChildren,
|
|
30
33
|
type ClassNameOrFunction,
|
|
@@ -33,158 +36,177 @@ import {
|
|
|
33
36
|
useRenderProps,
|
|
34
37
|
filterDOMProps,
|
|
35
38
|
dataAttr,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
// ============================================
|
|
40
|
-
// TYPES
|
|
41
|
-
// ============================================
|
|
39
|
+
useIsHydrated,
|
|
40
|
+
} from "./utils";
|
|
41
|
+
import { DialogTriggerContext, PopoverTriggerContext } from "./contexts";
|
|
42
42
|
|
|
43
43
|
export interface PopoverRenderProps {
|
|
44
44
|
/**
|
|
45
45
|
* The name of the component that triggered the popover.
|
|
46
46
|
*/
|
|
47
|
-
trigger: string | null
|
|
47
|
+
trigger: string | null;
|
|
48
48
|
/**
|
|
49
49
|
* The placement of the popover relative to the trigger.
|
|
50
50
|
*/
|
|
51
|
-
placement: PlacementAxis | null
|
|
51
|
+
placement: PlacementAxis | null;
|
|
52
52
|
/**
|
|
53
53
|
* Whether the popover is currently entering (for animations).
|
|
54
54
|
*/
|
|
55
|
-
isEntering: boolean
|
|
55
|
+
isEntering: boolean;
|
|
56
56
|
/**
|
|
57
57
|
* Whether the popover is currently exiting (for animations).
|
|
58
58
|
*/
|
|
59
|
-
isExiting: boolean
|
|
59
|
+
isExiting: boolean;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
export interface PopoverProps extends SlotProps {
|
|
63
63
|
/** The children of the component - can be JSX or render function. */
|
|
64
|
-
children?: RenderChildren<PopoverRenderProps
|
|
64
|
+
children?: RenderChildren<PopoverRenderProps>;
|
|
65
65
|
/** The CSS className for the element. */
|
|
66
|
-
class?: ClassNameOrFunction<PopoverRenderProps
|
|
66
|
+
class?: ClassNameOrFunction<PopoverRenderProps>;
|
|
67
67
|
/** The inline style for the element. */
|
|
68
|
-
style?: StyleOrFunction<PopoverRenderProps
|
|
68
|
+
style?: StyleOrFunction<PopoverRenderProps>;
|
|
69
69
|
/**
|
|
70
70
|
* The name of the component that triggered the popover.
|
|
71
71
|
*/
|
|
72
|
-
trigger?: string
|
|
72
|
+
trigger?: string;
|
|
73
73
|
/**
|
|
74
74
|
* The ref for the element which the popover positions itself with respect to.
|
|
75
75
|
* Required when used standalone (not within a trigger component).
|
|
76
76
|
*/
|
|
77
|
-
triggerRef?: () => Element | null
|
|
77
|
+
triggerRef?: () => Element | null;
|
|
78
78
|
/**
|
|
79
79
|
* The placement of the element with respect to its anchor element.
|
|
80
80
|
* @default 'bottom'
|
|
81
81
|
*/
|
|
82
|
-
placement?: Placement
|
|
82
|
+
placement?: Placement;
|
|
83
83
|
/**
|
|
84
84
|
* The placement padding that should be applied between the element and its
|
|
85
85
|
* surrounding container.
|
|
86
86
|
* @default 12
|
|
87
87
|
*/
|
|
88
|
-
containerPadding?: number
|
|
88
|
+
containerPadding?: number;
|
|
89
89
|
/**
|
|
90
90
|
* The additional offset applied along the main axis between the element and its
|
|
91
91
|
* anchor element.
|
|
92
92
|
* @default 8
|
|
93
93
|
*/
|
|
94
|
-
offset?: number
|
|
94
|
+
offset?: number;
|
|
95
95
|
/**
|
|
96
96
|
* The additional offset applied along the cross axis between the element and its
|
|
97
97
|
* anchor element.
|
|
98
98
|
* @default 0
|
|
99
99
|
*/
|
|
100
|
-
crossOffset?: number
|
|
100
|
+
crossOffset?: number;
|
|
101
101
|
/**
|
|
102
102
|
* Whether the element should flip its orientation when there is insufficient room.
|
|
103
103
|
* @default true
|
|
104
104
|
*/
|
|
105
|
-
shouldFlip?: boolean
|
|
105
|
+
shouldFlip?: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* The max height of the popover.
|
|
108
|
+
*/
|
|
109
|
+
maxHeight?: number;
|
|
110
|
+
/**
|
|
111
|
+
* A boundary element for placement calculations.
|
|
112
|
+
*/
|
|
113
|
+
boundaryElement?: Element;
|
|
114
|
+
/**
|
|
115
|
+
* A ref for the popover arrow element.
|
|
116
|
+
*/
|
|
117
|
+
arrowRef?: () => Element | null;
|
|
118
|
+
/**
|
|
119
|
+
* A ref for the scrollable popover element.
|
|
120
|
+
*/
|
|
121
|
+
scrollRef?: () => Element | null;
|
|
106
122
|
/**
|
|
107
123
|
* Whether the popover is non-modal (allows interaction outside).
|
|
108
124
|
*/
|
|
109
|
-
isNonModal?: boolean
|
|
125
|
+
isNonModal?: boolean;
|
|
110
126
|
/**
|
|
111
127
|
* Whether pressing Escape to close should be disabled.
|
|
112
128
|
*/
|
|
113
|
-
isKeyboardDismissDisabled?: boolean
|
|
129
|
+
isKeyboardDismissDisabled?: boolean;
|
|
114
130
|
/**
|
|
115
131
|
* Filter for which outside interactions should close the popover.
|
|
116
132
|
*/
|
|
117
|
-
shouldCloseOnInteractOutside?: (element: Element) => boolean
|
|
133
|
+
shouldCloseOnInteractOutside?: (element: Element) => boolean;
|
|
118
134
|
/** Whether the popover is open (controlled). */
|
|
119
|
-
isOpen?: boolean
|
|
135
|
+
isOpen?: boolean;
|
|
120
136
|
/** Whether the popover opens by default (uncontrolled). */
|
|
121
|
-
defaultOpen?: boolean
|
|
137
|
+
defaultOpen?: boolean;
|
|
122
138
|
/** Handler called when the popover's open state changes. */
|
|
123
|
-
onOpenChange?: (isOpen: boolean) => void
|
|
139
|
+
onOpenChange?: (isOpen: boolean) => void;
|
|
140
|
+
/**
|
|
141
|
+
* Whether focus should move to the popover container on open.
|
|
142
|
+
* @default true
|
|
143
|
+
*/
|
|
144
|
+
autoFocus?: boolean;
|
|
124
145
|
/** Whether the popover is entering (for animations). */
|
|
125
|
-
isEntering?: boolean
|
|
146
|
+
isEntering?: boolean;
|
|
126
147
|
/** Whether the popover is exiting (for animations). */
|
|
127
|
-
isExiting?: boolean
|
|
148
|
+
isExiting?: boolean;
|
|
128
149
|
}
|
|
129
150
|
|
|
130
151
|
export interface PopoverTriggerProps {
|
|
131
152
|
/** The children - should include a trigger and popover content. */
|
|
132
|
-
children: JSX.Element
|
|
153
|
+
children: JSX.Element;
|
|
133
154
|
/** Whether the popover is open (controlled). */
|
|
134
|
-
isOpen?: boolean
|
|
155
|
+
isOpen?: boolean;
|
|
135
156
|
/** Whether the popover is open by default (uncontrolled). */
|
|
136
|
-
defaultOpen?: boolean
|
|
157
|
+
defaultOpen?: boolean;
|
|
137
158
|
/** Callback when open state changes. */
|
|
138
|
-
onOpenChange?: (isOpen: boolean) => void
|
|
159
|
+
onOpenChange?: (isOpen: boolean) => void;
|
|
139
160
|
}
|
|
140
161
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
162
|
+
export {
|
|
163
|
+
PopoverTriggerContext,
|
|
164
|
+
usePopoverTrigger,
|
|
165
|
+
type PopoverTriggerContextValue,
|
|
166
|
+
} from "./contexts";
|
|
144
167
|
|
|
145
|
-
|
|
146
|
-
|
|
168
|
+
interface PopoverContextValue {
|
|
169
|
+
placement: () => PlacementAxis | null;
|
|
170
|
+
arrowProps: () => JSX.HTMLAttributes<HTMLElement>;
|
|
171
|
+
}
|
|
147
172
|
|
|
148
|
-
|
|
149
|
-
|
|
173
|
+
export const PopoverContext = createContext<PopoverContextValue | null>(null);
|
|
174
|
+
const PopoverGroupContext = createContext<(() => HTMLElement | null) | null>(null);
|
|
150
175
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
176
|
+
function PopoverDismissButton(props: { onDismiss: () => void }): JSX.Element {
|
|
177
|
+
return (
|
|
178
|
+
<button
|
|
179
|
+
type="button"
|
|
180
|
+
aria-label="Dismiss"
|
|
181
|
+
tabIndex={-1}
|
|
182
|
+
onClick={props.onDismiss}
|
|
183
|
+
style={visuallyHiddenStyles}
|
|
184
|
+
/>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
154
187
|
|
|
155
188
|
/**
|
|
156
189
|
* A PopoverTrigger opens a popover when a trigger element is pressed.
|
|
157
190
|
* Children should include a trigger element (e.g. Button) and the Popover.
|
|
158
191
|
*/
|
|
159
192
|
export function PopoverTrigger(props: PopoverTriggerProps): JSX.Element {
|
|
160
|
-
const [local] = splitProps(props, [
|
|
193
|
+
const [local] = splitProps(props, ["isOpen", "defaultOpen", "onOpenChange"]);
|
|
161
194
|
|
|
162
|
-
// Create overlay trigger state
|
|
163
195
|
const state = createOverlayTriggerState({
|
|
164
196
|
get isOpen() {
|
|
165
|
-
return local.isOpen
|
|
197
|
+
return local.isOpen;
|
|
166
198
|
},
|
|
167
199
|
get defaultOpen() {
|
|
168
|
-
return local.defaultOpen
|
|
200
|
+
return local.defaultOpen;
|
|
169
201
|
},
|
|
170
202
|
onOpenChange: local.onOpenChange,
|
|
171
|
-
})
|
|
203
|
+
});
|
|
172
204
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const triggerId = createUniqueId()
|
|
205
|
+
let triggerRef: HTMLElement | null = null;
|
|
206
|
+
const triggerId = createUniqueId();
|
|
176
207
|
|
|
177
|
-
|
|
178
|
-
createOverlayTrigger(
|
|
179
|
-
{ type: 'dialog' },
|
|
180
|
-
state,
|
|
181
|
-
() => triggerRef
|
|
182
|
-
)
|
|
208
|
+
const triggerAria = createOverlayTrigger({ type: "dialog" }, state, () => triggerRef);
|
|
183
209
|
|
|
184
|
-
// Track if triggerRef has been set (to prevent buttons inside Popover from overwriting it)
|
|
185
|
-
let triggerRefSet = false
|
|
186
|
-
|
|
187
|
-
// Context value
|
|
188
210
|
const contextValue = createMemo(() => ({
|
|
189
211
|
state: {
|
|
190
212
|
isOpen: () => state.isOpen(),
|
|
@@ -192,349 +214,436 @@ export function PopoverTrigger(props: PopoverTriggerProps): JSX.Element {
|
|
|
192
214
|
close: () => state.close(),
|
|
193
215
|
toggle: () => state.toggle(),
|
|
194
216
|
},
|
|
195
|
-
triggerRef: () =>
|
|
196
|
-
return triggerRef
|
|
197
|
-
},
|
|
217
|
+
triggerRef: () => triggerRef,
|
|
198
218
|
setTriggerRef: (el: HTMLElement | null) => {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
triggerRef = el
|
|
203
|
-
triggerRefSet = true
|
|
219
|
+
if (!el) return;
|
|
220
|
+
if (!triggerRef || !triggerRef.isConnected) {
|
|
221
|
+
triggerRef = el;
|
|
204
222
|
}
|
|
205
223
|
},
|
|
206
224
|
triggerId,
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
// detect the context and use it for open state.
|
|
213
|
-
//
|
|
214
|
-
// IMPORTANT: In SolidJS, JSX children are lazily evaluated when they're part
|
|
215
|
-
// of JSX expression. So {props.children} here means the children (Button, Popover)
|
|
216
|
-
// will be evaluated inside the Provider context.
|
|
225
|
+
triggerProps: triggerAria.triggerProps,
|
|
226
|
+
overlayProps: triggerAria.overlayProps,
|
|
227
|
+
trigger: "PopoverTrigger",
|
|
228
|
+
}));
|
|
229
|
+
|
|
217
230
|
return (
|
|
218
231
|
<PopoverTriggerContext.Provider value={contextValue()}>
|
|
219
232
|
{props.children}
|
|
220
233
|
</PopoverTriggerContext.Provider>
|
|
221
|
-
)
|
|
234
|
+
);
|
|
222
235
|
}
|
|
223
236
|
|
|
224
|
-
// ============================================
|
|
225
|
-
// POPOVER COMPONENT
|
|
226
|
-
// ============================================
|
|
227
|
-
|
|
228
237
|
/**
|
|
229
238
|
* A popover is an overlay element positioned relative to a trigger.
|
|
230
239
|
*/
|
|
231
240
|
export function Popover(props: PopoverProps): JSX.Element {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
241
|
+
// Note: do NOT early-return on the server. Returning `null` on the server and a
|
|
242
|
+
// full <Show>/<Portal> tree on the client desyncs Solid's hydration walk (the
|
|
243
|
+
// server emits no marker for the <Show>), which surfaces as "Hydration Mismatch /
|
|
244
|
+
// getNextElement" in the parent (e.g. Picker). Instead, run the same structure on
|
|
245
|
+
// both and gate the Portal on `useIsHydrated()` so the overlay only mounts on the
|
|
246
|
+
// client after hydration — the server + first client render both produce an empty
|
|
247
|
+
// <Show> marker, so hydration aligns.
|
|
237
248
|
const [local, rest] = splitProps(props, [
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const [
|
|
264
|
-
|
|
265
|
-
//
|
|
249
|
+
"class",
|
|
250
|
+
"style",
|
|
251
|
+
"trigger",
|
|
252
|
+
"triggerRef",
|
|
253
|
+
"placement",
|
|
254
|
+
"containerPadding",
|
|
255
|
+
"offset",
|
|
256
|
+
"crossOffset",
|
|
257
|
+
"shouldFlip",
|
|
258
|
+
"maxHeight",
|
|
259
|
+
"boundaryElement",
|
|
260
|
+
"arrowRef",
|
|
261
|
+
"scrollRef",
|
|
262
|
+
"isNonModal",
|
|
263
|
+
"isKeyboardDismissDisabled",
|
|
264
|
+
"shouldCloseOnInteractOutside",
|
|
265
|
+
"isOpen",
|
|
266
|
+
"defaultOpen",
|
|
267
|
+
"onOpenChange",
|
|
268
|
+
"autoFocus",
|
|
269
|
+
"isEntering",
|
|
270
|
+
"isExiting",
|
|
271
|
+
]);
|
|
272
|
+
|
|
273
|
+
let popoverRef!: HTMLDivElement;
|
|
274
|
+
const [groupRef, setGroupRef] = createSignal<HTMLDivElement | null>(null);
|
|
275
|
+
// False on the server and during hydration; true after onMount. Gates the Portal
|
|
276
|
+
// so overlay content only ever renders client-side, post-hydration.
|
|
277
|
+
const isHydrated = useIsHydrated();
|
|
278
|
+
|
|
279
|
+
const triggerContext = useContext(PopoverTriggerContext);
|
|
280
|
+
const dialogTriggerContext = useContext(DialogTriggerContext);
|
|
281
|
+
const popoverGroupContext = useContext(PopoverGroupContext);
|
|
282
|
+
const resolvedTrigger = () =>
|
|
283
|
+
local.trigger ??
|
|
284
|
+
triggerContext?.trigger ??
|
|
285
|
+
(dialogTriggerContext ? "DialogTrigger" : undefined);
|
|
286
|
+
const isSubPopover = () => resolvedTrigger() === "SubmenuTrigger" && popoverGroupContext != null;
|
|
287
|
+
|
|
288
|
+
const [internalOpen, setInternalOpen] = createSignal(local.defaultOpen ?? false);
|
|
289
|
+
|
|
266
290
|
const isOpen = (): boolean => {
|
|
267
|
-
if (local.isOpen !== undefined) return local.isOpen
|
|
291
|
+
if (local.isOpen !== undefined) return local.isOpen;
|
|
268
292
|
if (triggerContext) {
|
|
269
|
-
return triggerContext.state.isOpen()
|
|
293
|
+
return triggerContext.state.isOpen();
|
|
294
|
+
}
|
|
295
|
+
if (dialogTriggerContext) {
|
|
296
|
+
return dialogTriggerContext.state.isOpen();
|
|
270
297
|
}
|
|
271
|
-
return internalOpen()
|
|
272
|
-
}
|
|
298
|
+
return internalOpen();
|
|
299
|
+
};
|
|
273
300
|
|
|
274
301
|
const close = () => {
|
|
275
302
|
if (local.isOpen !== undefined) {
|
|
276
|
-
local.onOpenChange?.(false)
|
|
303
|
+
local.onOpenChange?.(false);
|
|
277
304
|
} else if (triggerContext) {
|
|
278
|
-
triggerContext.state.close()
|
|
279
|
-
local.onOpenChange?.(false)
|
|
305
|
+
triggerContext.state.close();
|
|
306
|
+
local.onOpenChange?.(false);
|
|
307
|
+
} else if (dialogTriggerContext) {
|
|
308
|
+
dialogTriggerContext.state.close();
|
|
309
|
+
local.onOpenChange?.(false);
|
|
280
310
|
} else {
|
|
281
|
-
setInternalOpen(false)
|
|
282
|
-
local.onOpenChange?.(false)
|
|
311
|
+
setInternalOpen(false);
|
|
312
|
+
local.onOpenChange?.(false);
|
|
283
313
|
}
|
|
284
|
-
}
|
|
314
|
+
};
|
|
285
315
|
|
|
286
|
-
// Get trigger ref
|
|
287
316
|
const getTriggerRef = () => {
|
|
288
|
-
if (local.triggerRef) return local.triggerRef()
|
|
289
|
-
if (triggerContext) return triggerContext.triggerRef()
|
|
290
|
-
return
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
// Signal to track placement after positioning
|
|
294
|
-
const [placement, setPlacement] = createSignal<PlacementAxis | null>(null)
|
|
295
|
-
// Signal to track position styles
|
|
296
|
-
// Start with visibility hidden, then show after positioning
|
|
297
|
-
const [positionStyles, setPositionStyles] = createSignal({
|
|
298
|
-
top: '0px',
|
|
299
|
-
left: '0px',
|
|
300
|
-
visibility: 'hidden' as 'hidden' | 'visible',
|
|
301
|
-
})
|
|
302
|
-
|
|
303
|
-
// Handle keyboard dismiss (Escape key)
|
|
304
|
-
createEffect(() => {
|
|
305
|
-
if (!isOpen()) return
|
|
306
|
-
if (local.isKeyboardDismissDisabled) return
|
|
307
|
-
|
|
308
|
-
const handleKeyDown = (e: KeyboardEvent) => {
|
|
309
|
-
if (e.key === 'Escape') {
|
|
310
|
-
e.preventDefault()
|
|
311
|
-
e.stopPropagation()
|
|
312
|
-
close()
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
document.addEventListener('keydown', handleKeyDown)
|
|
317
|
-
onCleanup(() => document.removeEventListener('keydown', handleKeyDown))
|
|
318
|
-
})
|
|
319
|
-
|
|
320
|
-
// Handle click outside to dismiss popover
|
|
321
|
-
createEffect(() => {
|
|
322
|
-
if (!isOpen()) return
|
|
323
|
-
if (local.isNonModal) return // Non-modal popovers don't auto-dismiss on outside click
|
|
324
|
-
|
|
325
|
-
const handleClickOutside = (e: MouseEvent) => {
|
|
326
|
-
const target = e.target as Element
|
|
327
|
-
const trigger = getTriggerRef()
|
|
328
|
-
|
|
329
|
-
// Don't close if clicking inside the popover
|
|
330
|
-
if (popoverRef && popoverRef.contains(target)) {
|
|
331
|
-
return
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// Don't close if clicking the trigger (it will toggle)
|
|
335
|
-
if (trigger && trigger.contains(target)) {
|
|
336
|
-
return
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// Check custom filter
|
|
340
|
-
if (local.shouldCloseOnInteractOutside && !local.shouldCloseOnInteractOutside(target)) {
|
|
341
|
-
return
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
close()
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// Use capture phase to catch clicks before they bubble
|
|
348
|
-
// Small delay to avoid closing on the same click that opened it
|
|
349
|
-
const timeoutId = setTimeout(() => {
|
|
350
|
-
document.addEventListener('mousedown', handleClickOutside, true)
|
|
351
|
-
}, 0)
|
|
352
|
-
|
|
353
|
-
onCleanup(() => {
|
|
354
|
-
clearTimeout(timeoutId)
|
|
355
|
-
document.removeEventListener('mousedown', handleClickOutside, true)
|
|
356
|
-
})
|
|
357
|
-
})
|
|
358
|
-
|
|
359
|
-
// Calculate position based on trigger element
|
|
360
|
-
// Using position: fixed so we use viewport coordinates directly from getBoundingClientRect
|
|
361
|
-
const updatePosition = () => {
|
|
362
|
-
const trigger = getTriggerRef()
|
|
363
|
-
const popover = popoverRef
|
|
364
|
-
if (!trigger || !popover) return
|
|
365
|
-
|
|
366
|
-
const triggerRect = trigger.getBoundingClientRect()
|
|
367
|
-
// Use offsetWidth/offsetHeight which are more reliable than getBoundingClientRect
|
|
368
|
-
// when the element might be positioned off-screen initially
|
|
369
|
-
const popoverWidth = popover.offsetWidth
|
|
370
|
-
const popoverHeight = popover.offsetHeight
|
|
371
|
-
const offset = local.offset ?? 8
|
|
372
|
-
|
|
373
|
-
let top = 0
|
|
374
|
-
let left = 0
|
|
375
|
-
const placementValue = local.placement ?? 'bottom'
|
|
376
|
-
|
|
377
|
-
// Using viewport coordinates for position: fixed
|
|
378
|
-
switch (placementValue) {
|
|
379
|
-
case 'top':
|
|
380
|
-
case 'top start':
|
|
381
|
-
case 'top end':
|
|
382
|
-
top = triggerRect.top - popoverHeight - offset
|
|
383
|
-
left = triggerRect.left + (triggerRect.width - popoverWidth) / 2
|
|
384
|
-
setPlacement('top')
|
|
385
|
-
break
|
|
386
|
-
case 'bottom':
|
|
387
|
-
case 'bottom start':
|
|
388
|
-
case 'bottom end':
|
|
389
|
-
top = triggerRect.bottom + offset
|
|
390
|
-
left = triggerRect.left + (triggerRect.width - popoverWidth) / 2
|
|
391
|
-
setPlacement('bottom')
|
|
392
|
-
break
|
|
393
|
-
case 'left':
|
|
394
|
-
case 'left top':
|
|
395
|
-
case 'left bottom':
|
|
396
|
-
top = triggerRect.top + (triggerRect.height - popoverHeight) / 2
|
|
397
|
-
left = triggerRect.left - popoverWidth - offset
|
|
398
|
-
setPlacement('left')
|
|
399
|
-
break
|
|
400
|
-
case 'right':
|
|
401
|
-
case 'right top':
|
|
402
|
-
case 'right bottom':
|
|
403
|
-
top = triggerRect.top + (triggerRect.height - popoverHeight) / 2
|
|
404
|
-
left = triggerRect.right + offset
|
|
405
|
-
setPlacement('right')
|
|
406
|
-
break
|
|
407
|
-
default:
|
|
408
|
-
top = triggerRect.bottom + offset
|
|
409
|
-
left = triggerRect.left + (triggerRect.width - popoverWidth) / 2
|
|
410
|
-
setPlacement('bottom')
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
setPositionStyles({
|
|
414
|
-
top: `${top}px`,
|
|
415
|
-
left: `${left}px`,
|
|
416
|
-
visibility: 'visible',
|
|
417
|
-
})
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// Set up positioning effect - runs when open and trigger ref is available
|
|
421
|
-
createEffect(() => {
|
|
422
|
-
if (!isOpen()) return
|
|
317
|
+
if (local.triggerRef) return local.triggerRef();
|
|
318
|
+
if (triggerContext) return triggerContext.triggerRef();
|
|
319
|
+
if (dialogTriggerContext) return dialogTriggerContext.triggerRef();
|
|
320
|
+
return null;
|
|
321
|
+
};
|
|
423
322
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
323
|
+
const popoverAria = createPopover(
|
|
324
|
+
{
|
|
325
|
+
triggerRef: getTriggerRef,
|
|
326
|
+
popoverRef: () => popoverRef ?? null,
|
|
327
|
+
groupRef: () => (isSubPopover() ? (popoverGroupContext?.() ?? null) : groupRef()),
|
|
328
|
+
get placement() {
|
|
329
|
+
return local.placement;
|
|
330
|
+
},
|
|
331
|
+
get containerPadding() {
|
|
332
|
+
return local.containerPadding;
|
|
333
|
+
},
|
|
334
|
+
get offset() {
|
|
335
|
+
return local.offset ?? 8;
|
|
336
|
+
},
|
|
337
|
+
get crossOffset() {
|
|
338
|
+
return local.crossOffset;
|
|
339
|
+
},
|
|
340
|
+
get shouldFlip() {
|
|
341
|
+
return local.shouldFlip;
|
|
342
|
+
},
|
|
343
|
+
get maxHeight() {
|
|
344
|
+
return local.maxHeight;
|
|
345
|
+
},
|
|
346
|
+
get boundaryElement() {
|
|
347
|
+
return local.boundaryElement;
|
|
348
|
+
},
|
|
349
|
+
get arrowRef() {
|
|
350
|
+
return local.arrowRef;
|
|
351
|
+
},
|
|
352
|
+
get scrollRef() {
|
|
353
|
+
return local.scrollRef;
|
|
354
|
+
},
|
|
355
|
+
get isNonModal() {
|
|
356
|
+
return local.isNonModal;
|
|
357
|
+
},
|
|
358
|
+
get isKeyboardDismissDisabled() {
|
|
359
|
+
return local.isKeyboardDismissDisabled;
|
|
360
|
+
},
|
|
361
|
+
get shouldCloseOnInteractOutside() {
|
|
362
|
+
return local.shouldCloseOnInteractOutside;
|
|
363
|
+
},
|
|
364
|
+
get trigger() {
|
|
365
|
+
return resolvedTrigger();
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
isOpen,
|
|
370
|
+
open: () => {
|
|
371
|
+
if (local.isOpen !== undefined) {
|
|
372
|
+
local.onOpenChange?.(true);
|
|
373
|
+
} else if (triggerContext) {
|
|
374
|
+
triggerContext.state.open();
|
|
375
|
+
local.onOpenChange?.(true);
|
|
376
|
+
} else if (dialogTriggerContext) {
|
|
377
|
+
dialogTriggerContext.state.open();
|
|
378
|
+
local.onOpenChange?.(true);
|
|
379
|
+
} else {
|
|
380
|
+
setInternalOpen(true);
|
|
381
|
+
local.onOpenChange?.(true);
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
close,
|
|
385
|
+
toggle: () => {
|
|
386
|
+
if (isOpen()) close();
|
|
387
|
+
else if (local.isOpen !== undefined) {
|
|
388
|
+
local.onOpenChange?.(true);
|
|
389
|
+
} else if (triggerContext) {
|
|
390
|
+
triggerContext.state.toggle();
|
|
391
|
+
} else if (dialogTriggerContext) {
|
|
392
|
+
dialogTriggerContext.state.toggle();
|
|
393
|
+
} else {
|
|
394
|
+
setInternalOpen(true);
|
|
395
|
+
local.onOpenChange?.(true);
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
);
|
|
442
400
|
|
|
443
|
-
// Render props values
|
|
444
401
|
const renderValues = createMemo<PopoverRenderProps>(() => ({
|
|
445
|
-
trigger:
|
|
446
|
-
placement: placement(),
|
|
402
|
+
trigger: resolvedTrigger() ?? null,
|
|
403
|
+
placement: popoverAria.placement(),
|
|
447
404
|
isEntering: local.isEntering ?? false,
|
|
448
405
|
isExiting: local.isExiting ?? false,
|
|
449
|
-
}))
|
|
406
|
+
}));
|
|
450
407
|
|
|
451
|
-
// Resolve render props
|
|
452
408
|
const renderProps = useRenderProps(
|
|
453
409
|
{
|
|
454
410
|
children: props.children,
|
|
455
411
|
class: local.class,
|
|
456
412
|
style: local.style,
|
|
457
|
-
defaultClassName:
|
|
413
|
+
defaultClassName: "solidaria-Popover",
|
|
458
414
|
},
|
|
459
|
-
renderValues
|
|
460
|
-
)
|
|
415
|
+
renderValues,
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
const [triggerWidth, setTriggerWidth] = createSignal<string | undefined>();
|
|
419
|
+
const hasExplicitTriggerWidth = () => {
|
|
420
|
+
const style = renderProps.style() as (JSX.CSSProperties & Record<string, unknown>) | undefined;
|
|
421
|
+
return style?.["--trigger-width"] != null;
|
|
422
|
+
};
|
|
423
|
+
const updateTriggerWidth = () => {
|
|
424
|
+
const trigger = getTriggerRef();
|
|
425
|
+
if (!trigger || hasExplicitTriggerWidth()) return;
|
|
426
|
+
setTriggerWidth(`${trigger.getBoundingClientRect().width}px`);
|
|
427
|
+
};
|
|
428
|
+
createEffect(() => {
|
|
429
|
+
if (!isOpen()) return;
|
|
430
|
+
updateTriggerWidth();
|
|
431
|
+
|
|
432
|
+
const trigger = getTriggerRef();
|
|
433
|
+
if (!trigger || hasExplicitTriggerWidth() || typeof ResizeObserver === "undefined") return;
|
|
434
|
+
|
|
435
|
+
const observer = new ResizeObserver(updateTriggerWidth);
|
|
436
|
+
observer.observe(trigger);
|
|
437
|
+
onCleanup(() => observer.disconnect());
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
const domProps = createMemo(() =>
|
|
441
|
+
filterDOMProps(rest as Record<string, unknown>, { global: true }),
|
|
442
|
+
);
|
|
443
|
+
const overlayId = () => {
|
|
444
|
+
const restId = (rest as Record<string, unknown>).id as string | undefined;
|
|
445
|
+
return (
|
|
446
|
+
restId ??
|
|
447
|
+
(triggerContext?.overlayProps?.id as string | undefined) ??
|
|
448
|
+
(dialogTriggerContext?.overlayProps?.id as string | undefined)
|
|
449
|
+
);
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
const cleanPopoverProps = () => {
|
|
453
|
+
const {
|
|
454
|
+
style: _style,
|
|
455
|
+
ref: _ref,
|
|
456
|
+
...remaining
|
|
457
|
+
} = popoverAria.popoverProps as Record<string, unknown>;
|
|
458
|
+
return remaining;
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
const mergedStyle = (): JSX.CSSProperties => {
|
|
462
|
+
const ariaStyle = (popoverAria.popoverProps as Record<string, unknown>).style as
|
|
463
|
+
| JSX.CSSProperties
|
|
464
|
+
| undefined;
|
|
465
|
+
const renderStyle = (renderProps.style() || {}) as JSX.CSSProperties & Record<string, unknown>;
|
|
466
|
+
return {
|
|
467
|
+
...ariaStyle,
|
|
468
|
+
...renderStyle,
|
|
469
|
+
"--trigger-width": renderStyle["--trigger-width"] ?? triggerWidth(),
|
|
470
|
+
};
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
const shouldBeDialog = () => !local.isNonModal || resolvedTrigger() === "SubmenuTrigger";
|
|
474
|
+
const portalContext = useUNSAFE_PortalContext();
|
|
475
|
+
const portalContainer = () => {
|
|
476
|
+
if (isSubPopover()) {
|
|
477
|
+
return popoverGroupContext?.() ?? portalContext.getContainer?.() ?? undefined;
|
|
478
|
+
}
|
|
479
|
+
return portalContext.getContainer?.() ?? undefined;
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
// Match React Aria Components: focus the popover container only when no
|
|
483
|
+
// descendant has already moved focus during mount.
|
|
484
|
+
createEffect(() => {
|
|
485
|
+
if (!isOpen() || !shouldBeDialog()) return;
|
|
486
|
+
if ((local.autoFocus ?? true) === false) return;
|
|
487
|
+
if (!popoverRef) return;
|
|
488
|
+
if (resolvedTrigger() === "SubmenuTrigger") return;
|
|
489
|
+
|
|
490
|
+
let timeout: number | undefined;
|
|
491
|
+
let frame: number | undefined;
|
|
492
|
+
|
|
493
|
+
const focusIfNeeded = () => {
|
|
494
|
+
if (!isOpen() || !shouldBeDialog()) return;
|
|
495
|
+
if (!popoverRef || resolvedTrigger() === "SubmenuTrigger") return;
|
|
496
|
+
if (document.activeElement === popoverRef || popoverRef.contains(document.activeElement)) {
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
popoverRef.focus();
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
const scheduleFocus = () => {
|
|
503
|
+
timeout = window.setTimeout(focusIfNeeded, 0);
|
|
504
|
+
};
|
|
461
505
|
|
|
462
|
-
|
|
463
|
-
|
|
506
|
+
if (typeof window.requestAnimationFrame === "function") {
|
|
507
|
+
frame = window.requestAnimationFrame(scheduleFocus);
|
|
508
|
+
} else {
|
|
509
|
+
scheduleFocus();
|
|
510
|
+
}
|
|
464
511
|
|
|
465
|
-
|
|
466
|
-
|
|
512
|
+
onCleanup(() => {
|
|
513
|
+
if (frame !== undefined) {
|
|
514
|
+
window.cancelAnimationFrame(frame);
|
|
515
|
+
}
|
|
516
|
+
if (timeout !== undefined) {
|
|
517
|
+
window.clearTimeout(timeout);
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
// Fallback Escape handling for environments where focus is not moved into the popover.
|
|
523
|
+
createEffect(() => {
|
|
524
|
+
if (!isOpen()) return;
|
|
525
|
+
if (local.isKeyboardDismissDisabled) return;
|
|
526
|
+
|
|
527
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
528
|
+
if (event.key !== "Escape") return;
|
|
529
|
+
if (event.defaultPrevented) return;
|
|
530
|
+
close();
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
document.addEventListener("keydown", onKeyDown);
|
|
534
|
+
onCleanup(() => document.removeEventListener("keydown", onKeyDown));
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
const overlay = () => (
|
|
538
|
+
<PopoverContext.Provider
|
|
539
|
+
value={{ placement: popoverAria.placement, arrowProps: () => popoverAria.arrowProps }}
|
|
540
|
+
>
|
|
541
|
+
<FocusScope contain={shouldBeDialog()} restoreFocus>
|
|
542
|
+
<div
|
|
543
|
+
{...domProps()}
|
|
544
|
+
{...cleanPopoverProps()}
|
|
545
|
+
ref={popoverRef}
|
|
546
|
+
id={overlayId()}
|
|
547
|
+
role={shouldBeDialog() ? "dialog" : undefined}
|
|
548
|
+
tabIndex={shouldBeDialog() ? -1 : undefined}
|
|
549
|
+
class={renderProps.class()}
|
|
550
|
+
style={mergedStyle()}
|
|
551
|
+
data-trigger={resolvedTrigger()}
|
|
552
|
+
data-placement={popoverAria.placement()}
|
|
553
|
+
data-entering={dataAttr(local.isEntering)}
|
|
554
|
+
data-exiting={dataAttr(local.isExiting)}
|
|
555
|
+
>
|
|
556
|
+
<Show when={!local.isNonModal}>
|
|
557
|
+
<PopoverDismissButton onDismiss={close} />
|
|
558
|
+
</Show>
|
|
559
|
+
{renderProps.renderChildren()}
|
|
560
|
+
<PopoverDismissButton onDismiss={close} />
|
|
561
|
+
</div>
|
|
562
|
+
</FocusScope>
|
|
563
|
+
</PopoverContext.Provider>
|
|
564
|
+
);
|
|
565
|
+
|
|
566
|
+
const underlay = () => (
|
|
567
|
+
<div
|
|
568
|
+
data-testid="underlay"
|
|
569
|
+
{...(popoverAria.underlayProps as unknown as JSX.HTMLAttributes<HTMLDivElement>)}
|
|
570
|
+
style={{ position: "fixed", inset: 0 }}
|
|
571
|
+
/>
|
|
572
|
+
);
|
|
467
573
|
|
|
468
574
|
return (
|
|
469
|
-
<Show when={isOpen() || local.isExiting}>
|
|
470
|
-
<Portal>
|
|
471
|
-
<
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
style={{
|
|
480
|
-
position: 'fixed',
|
|
481
|
-
'z-index': 100000,
|
|
482
|
-
...positionStyles(),
|
|
483
|
-
...renderProps.style(),
|
|
484
|
-
}}
|
|
485
|
-
data-trigger={local.trigger ?? triggerContext?.trigger}
|
|
486
|
-
data-placement={placement()}
|
|
487
|
-
data-entering={dataAttr(local.isEntering)}
|
|
488
|
-
data-exiting={dataAttr(local.isExiting)}
|
|
489
|
-
>
|
|
490
|
-
{renderProps.renderChildren()}
|
|
575
|
+
<Show when={isHydrated() && (isOpen() || local.isExiting)}>
|
|
576
|
+
<Portal mount={portalContainer()}>
|
|
577
|
+
<Show when={!local.isNonModal && !isSubPopover() && isOpen()}>{underlay()}</Show>
|
|
578
|
+
<Show
|
|
579
|
+
when={isSubPopover()}
|
|
580
|
+
fallback={
|
|
581
|
+
<div ref={setGroupRef} style={{ display: "contents" }}>
|
|
582
|
+
<PopoverGroupContext.Provider value={() => groupRef()}>
|
|
583
|
+
{overlay()}
|
|
584
|
+
</PopoverGroupContext.Provider>
|
|
491
585
|
</div>
|
|
492
|
-
|
|
493
|
-
|
|
586
|
+
}
|
|
587
|
+
>
|
|
588
|
+
{overlay()}
|
|
589
|
+
</Show>
|
|
494
590
|
</Portal>
|
|
495
591
|
</Show>
|
|
496
|
-
)
|
|
592
|
+
);
|
|
497
593
|
}
|
|
498
594
|
|
|
499
|
-
// ============================================
|
|
500
|
-
// OVERLAY ARROW COMPONENT
|
|
501
|
-
// ============================================
|
|
502
|
-
|
|
503
595
|
export interface OverlayArrowProps {
|
|
504
596
|
/** The children - should be an SVG or element for the arrow. */
|
|
505
|
-
children?: JSX.Element
|
|
597
|
+
children?: JSX.Element;
|
|
598
|
+
/** Render function used when Solid children accessors would be ambiguous. */
|
|
599
|
+
render?: () => JSX.Element;
|
|
506
600
|
/** The CSS className. */
|
|
507
|
-
class?: string
|
|
601
|
+
class?: string;
|
|
508
602
|
/** The inline style. */
|
|
509
|
-
style?: JSX.CSSProperties
|
|
603
|
+
style?: JSX.CSSProperties;
|
|
510
604
|
}
|
|
511
605
|
|
|
512
606
|
/**
|
|
513
607
|
* An arrow element that points towards the trigger.
|
|
514
608
|
*/
|
|
515
609
|
export function OverlayArrow(props: OverlayArrowProps): JSX.Element {
|
|
516
|
-
const popoverContext = useContext(PopoverContext)
|
|
517
|
-
const placement = () => popoverContext?.placement() ?? null
|
|
518
|
-
|
|
519
|
-
const
|
|
520
|
-
const
|
|
521
|
-
|
|
522
|
-
|
|
610
|
+
const popoverContext = useContext(PopoverContext);
|
|
611
|
+
const placement = () => popoverContext?.placement() ?? null;
|
|
612
|
+
|
|
613
|
+
const mergedStyle = () => {
|
|
614
|
+
const contextStyle = (popoverContext?.arrowProps() as Record<string, unknown> | undefined)
|
|
615
|
+
?.style as (JSX.CSSProperties & Record<string, unknown>) | undefined;
|
|
616
|
+
const style: JSX.CSSProperties = {};
|
|
617
|
+
if (typeof contextStyle?.left === "string") {
|
|
618
|
+
style.left = contextStyle.left;
|
|
619
|
+
}
|
|
620
|
+
if (typeof contextStyle?.top === "string") {
|
|
621
|
+
style.top = contextStyle.top;
|
|
523
622
|
}
|
|
524
|
-
|
|
525
|
-
|
|
623
|
+
|
|
624
|
+
const localStyle =
|
|
625
|
+
props.style &&
|
|
626
|
+
!(typeof CSSStyleDeclaration !== "undefined" && props.style instanceof CSSStyleDeclaration)
|
|
627
|
+
? props.style
|
|
628
|
+
: undefined;
|
|
629
|
+
|
|
630
|
+
return {
|
|
631
|
+
...style,
|
|
632
|
+
...localStyle,
|
|
633
|
+
};
|
|
634
|
+
};
|
|
526
635
|
|
|
527
636
|
return (
|
|
528
637
|
<div
|
|
529
638
|
class={props.class}
|
|
530
|
-
style={
|
|
639
|
+
style={mergedStyle()}
|
|
531
640
|
data-placement={placement()}
|
|
532
641
|
aria-hidden="true"
|
|
533
642
|
role="presentation"
|
|
534
643
|
>
|
|
535
|
-
{
|
|
644
|
+
{props.render ? props.render() : props.children}
|
|
536
645
|
</div>
|
|
537
|
-
)
|
|
646
|
+
);
|
|
538
647
|
}
|
|
539
648
|
|
|
540
|
-
export default Popover
|
|
649
|
+
export default Popover;
|