@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/Tooltip.tsx
CHANGED
|
@@ -15,19 +15,19 @@ import {
|
|
|
15
15
|
createEffect,
|
|
16
16
|
onCleanup,
|
|
17
17
|
Show,
|
|
18
|
-
} from
|
|
19
|
-
import { isServer } from
|
|
18
|
+
} from "solid-js";
|
|
19
|
+
import { isServer } from "solid-js/web";
|
|
20
20
|
import {
|
|
21
21
|
createTooltipTriggerState,
|
|
22
22
|
type TooltipTriggerState,
|
|
23
23
|
type TooltipTriggerProps as StateProps,
|
|
24
|
-
} from
|
|
24
|
+
} from "@proyecto-viviana/solid-stately";
|
|
25
25
|
import {
|
|
26
26
|
createTooltip,
|
|
27
27
|
createTooltipTrigger,
|
|
28
28
|
type TooltipTriggerProps as AriaProps,
|
|
29
29
|
OverlayContainer,
|
|
30
|
-
} from
|
|
30
|
+
} from "@proyecto-viviana/solidaria";
|
|
31
31
|
import {
|
|
32
32
|
type RenderChildren,
|
|
33
33
|
type ClassNameOrFunction,
|
|
@@ -35,11 +35,7 @@ import {
|
|
|
35
35
|
type SlotProps,
|
|
36
36
|
useRenderProps,
|
|
37
37
|
filterDOMProps,
|
|
38
|
-
} from
|
|
39
|
-
|
|
40
|
-
// ============================================
|
|
41
|
-
// TYPES
|
|
42
|
-
// ============================================
|
|
38
|
+
} from "./utils";
|
|
43
39
|
|
|
44
40
|
export interface TooltipRenderProps {
|
|
45
41
|
/** Whether the tooltip is currently entering (for animations). */
|
|
@@ -47,15 +43,36 @@ export interface TooltipRenderProps {
|
|
|
47
43
|
/** Whether the tooltip is currently exiting (for animations). */
|
|
48
44
|
isExiting: boolean;
|
|
49
45
|
/** The placement of the tooltip relative to the trigger. */
|
|
50
|
-
placement:
|
|
46
|
+
placement: TooltipResolvedPlacement | null;
|
|
51
47
|
}
|
|
52
48
|
|
|
49
|
+
export type TooltipPlacement = "top" | "bottom" | "left" | "right" | "start" | "end";
|
|
50
|
+
export type TooltipResolvedPlacement = "top" | "bottom" | "left" | "right";
|
|
51
|
+
|
|
53
52
|
export interface TooltipTriggerComponentProps extends StateProps, AriaProps {
|
|
54
53
|
/** The children of the tooltip trigger (trigger element and tooltip). */
|
|
55
54
|
children: JSX.Element;
|
|
55
|
+
/** The placement of the tooltip relative to the trigger. */
|
|
56
|
+
placement?: TooltipPlacement;
|
|
57
|
+
/** The placement padding between the tooltip and viewport edge. */
|
|
58
|
+
containerPadding?: number;
|
|
59
|
+
/** The additional offset along the cross axis. */
|
|
60
|
+
crossOffset?: number;
|
|
61
|
+
/** Whether the tooltip should flip when there is insufficient room. */
|
|
62
|
+
shouldFlip?: boolean;
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
export interface TooltipProps extends SlotProps {
|
|
66
|
+
/** The element id. */
|
|
67
|
+
id?: string;
|
|
68
|
+
/** Custom aria-label for the tooltip. */
|
|
69
|
+
"aria-label"?: string;
|
|
70
|
+
/** ID of an element that labels the tooltip. */
|
|
71
|
+
"aria-labelledby"?: string;
|
|
72
|
+
/** ID of an element that describes the tooltip. */
|
|
73
|
+
"aria-describedby"?: string;
|
|
74
|
+
/** ID of an element that provides details for the tooltip. */
|
|
75
|
+
"aria-details"?: string;
|
|
59
76
|
/** The children of the tooltip. A function may be provided to receive render props. */
|
|
60
77
|
children?: RenderChildren<TooltipRenderProps>;
|
|
61
78
|
/** The CSS className for the element. */
|
|
@@ -67,26 +84,42 @@ export interface TooltipProps extends SlotProps {
|
|
|
67
84
|
/** Whether the tooltip is open by default (uncontrolled). */
|
|
68
85
|
defaultOpen?: boolean;
|
|
69
86
|
/** The placement of the tooltip relative to the trigger. */
|
|
70
|
-
placement?:
|
|
71
|
-
/**
|
|
87
|
+
placement?: TooltipPlacement;
|
|
88
|
+
/** The placement padding between the tooltip and viewport edge. */
|
|
89
|
+
containerPadding?: number;
|
|
90
|
+
/** The additional offset along the cross axis. */
|
|
91
|
+
crossOffset?: number;
|
|
92
|
+
/** Whether the tooltip should flip when there is insufficient room. */
|
|
72
93
|
shouldFlip?: boolean;
|
|
94
|
+
/** The offset between the tooltip and trigger. */
|
|
95
|
+
offset?: number;
|
|
96
|
+
/** The arrow size used to keep the arrow overlapping the trigger. */
|
|
97
|
+
arrowSize?: number;
|
|
98
|
+
/** The padding between the arrow and tooltip edge. */
|
|
99
|
+
arrowBoundaryOffset?: number;
|
|
100
|
+
/** Whether the tooltip should be disabled. */
|
|
101
|
+
isDisabled?: boolean;
|
|
102
|
+
/** The element language. */
|
|
103
|
+
lang?: string;
|
|
104
|
+
/** The text direction. */
|
|
105
|
+
dir?: "ltr" | "rtl";
|
|
73
106
|
}
|
|
74
107
|
|
|
75
|
-
// ============================================
|
|
76
|
-
// CONTEXT
|
|
77
|
-
// ============================================
|
|
78
|
-
|
|
79
108
|
interface TooltipTriggerContextValue {
|
|
80
109
|
state: TooltipTriggerState;
|
|
81
|
-
tooltipProps: { id: string };
|
|
110
|
+
tooltipProps: { readonly id: string };
|
|
111
|
+
setTooltipId: (id: string | undefined) => void;
|
|
82
112
|
triggerRef: () => HTMLElement | null | undefined;
|
|
113
|
+
placement: () => TooltipPlacement | undefined;
|
|
114
|
+
containerPadding: () => number | undefined;
|
|
115
|
+
crossOffset: () => number | undefined;
|
|
116
|
+
shouldFlip: () => boolean | undefined;
|
|
117
|
+
isDisabled: () => boolean | undefined;
|
|
83
118
|
}
|
|
84
119
|
|
|
85
120
|
const TooltipTriggerContext = createContext<TooltipTriggerContextValue | null>(null);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
// COMPONENTS
|
|
89
|
-
// ============================================
|
|
121
|
+
export const TooltipContext = TooltipTriggerContext;
|
|
122
|
+
export const TooltipTriggerStateContext = createContext<TooltipTriggerState | null>(null);
|
|
90
123
|
|
|
91
124
|
/**
|
|
92
125
|
* TooltipTrigger wraps around a trigger element and a Tooltip.
|
|
@@ -103,42 +136,68 @@ const TooltipTriggerContext = createContext<TooltipTriggerContextValue | null>(n
|
|
|
103
136
|
*/
|
|
104
137
|
export const TooltipTrigger: ParentComponent<TooltipTriggerComponentProps> = (props) => {
|
|
105
138
|
let triggerRef: HTMLElement | null = null;
|
|
139
|
+
const [tooltipId, setTooltipId] = createSignal<string | undefined>();
|
|
106
140
|
|
|
107
141
|
const state = createTooltipTriggerState({
|
|
108
|
-
get delay() {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
get
|
|
112
|
-
|
|
142
|
+
get delay() {
|
|
143
|
+
return props.delay;
|
|
144
|
+
},
|
|
145
|
+
get closeDelay() {
|
|
146
|
+
return props.closeDelay;
|
|
147
|
+
},
|
|
148
|
+
get isOpen() {
|
|
149
|
+
return props.isOpen;
|
|
150
|
+
},
|
|
151
|
+
get defaultOpen() {
|
|
152
|
+
return props.defaultOpen;
|
|
153
|
+
},
|
|
154
|
+
get onOpenChange() {
|
|
155
|
+
return props.onOpenChange;
|
|
156
|
+
},
|
|
113
157
|
});
|
|
114
158
|
|
|
115
159
|
const { triggerProps, tooltipProps } = createTooltipTrigger(
|
|
116
160
|
{
|
|
117
|
-
get isDisabled() {
|
|
118
|
-
|
|
119
|
-
|
|
161
|
+
get isDisabled() {
|
|
162
|
+
return props.isDisabled;
|
|
163
|
+
},
|
|
164
|
+
get trigger() {
|
|
165
|
+
return props.trigger;
|
|
166
|
+
},
|
|
167
|
+
get shouldCloseOnPress() {
|
|
168
|
+
return props.shouldCloseOnPress;
|
|
169
|
+
},
|
|
170
|
+
get tooltipId() {
|
|
171
|
+
return tooltipId();
|
|
172
|
+
},
|
|
120
173
|
},
|
|
121
174
|
state,
|
|
122
|
-
() => triggerRef
|
|
175
|
+
() => triggerRef,
|
|
123
176
|
);
|
|
124
177
|
|
|
125
178
|
const context: TooltipTriggerContextValue = {
|
|
126
179
|
state,
|
|
127
180
|
tooltipProps,
|
|
181
|
+
setTooltipId,
|
|
128
182
|
triggerRef: () => triggerRef,
|
|
183
|
+
placement: () => props.placement,
|
|
184
|
+
containerPadding: () => props.containerPadding,
|
|
185
|
+
crossOffset: () => props.crossOffset,
|
|
186
|
+
shouldFlip: () => props.shouldFlip,
|
|
187
|
+
isDisabled: () => props.isDisabled,
|
|
129
188
|
};
|
|
130
189
|
|
|
131
|
-
// Clone children and inject trigger props into the first child
|
|
132
190
|
const processChildren = () => {
|
|
133
191
|
const children = props.children;
|
|
134
192
|
if (Array.isArray(children)) {
|
|
135
|
-
// First child is the trigger, rest are tooltip(s)
|
|
136
193
|
const [trigger, ...rest] = children;
|
|
137
194
|
return (
|
|
138
195
|
<>
|
|
139
196
|
<TriggerWrapper
|
|
140
197
|
triggerProps={triggerProps}
|
|
141
|
-
ref={(el) => {
|
|
198
|
+
ref={(el) => {
|
|
199
|
+
triggerRef = el;
|
|
200
|
+
}}
|
|
142
201
|
>
|
|
143
202
|
{trigger}
|
|
144
203
|
</TriggerWrapper>
|
|
@@ -150,9 +209,11 @@ export const TooltipTrigger: ParentComponent<TooltipTriggerComponentProps> = (pr
|
|
|
150
209
|
};
|
|
151
210
|
|
|
152
211
|
return (
|
|
153
|
-
<
|
|
154
|
-
{
|
|
155
|
-
|
|
212
|
+
<TooltipTriggerStateContext.Provider value={state}>
|
|
213
|
+
<TooltipTriggerContext.Provider value={context}>
|
|
214
|
+
{processChildren()}
|
|
215
|
+
</TooltipTriggerContext.Provider>
|
|
216
|
+
</TooltipTriggerStateContext.Provider>
|
|
156
217
|
);
|
|
157
218
|
};
|
|
158
219
|
|
|
@@ -163,13 +224,72 @@ const TriggerWrapper: ParentComponent<{
|
|
|
163
224
|
triggerProps: JSX.HTMLAttributes<HTMLElement>;
|
|
164
225
|
ref: (el: HTMLElement) => void;
|
|
165
226
|
}> = (props) => {
|
|
166
|
-
// Get the child element and clone it with trigger props
|
|
167
227
|
const child = () => props.children as JSX.Element;
|
|
228
|
+
const [triggerElement, setTriggerElement] = createSignal<HTMLElement | null>(null);
|
|
229
|
+
|
|
230
|
+
createEffect(() => {
|
|
231
|
+
const element = triggerElement();
|
|
232
|
+
if (!element) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const triggerProps = props.triggerProps as Record<string, unknown>;
|
|
237
|
+
const describedBy = triggerProps["aria-describedby"] as string | undefined;
|
|
238
|
+
if (describedBy) {
|
|
239
|
+
element.setAttribute("aria-describedby", describedBy);
|
|
240
|
+
} else {
|
|
241
|
+
element.removeAttribute("aria-describedby");
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const listeners: Array<[string, EventListener]> = [];
|
|
245
|
+
const eventProps = [
|
|
246
|
+
["onFocus", "focus"],
|
|
247
|
+
["onBlur", "blur"],
|
|
248
|
+
["onPointerEnter", "pointerenter"],
|
|
249
|
+
["onPointerLeave", "pointerleave"],
|
|
250
|
+
["onPointerOver", "pointerover"],
|
|
251
|
+
["onPointerOut", "pointerout"],
|
|
252
|
+
["onMouseEnter", "mouseenter"],
|
|
253
|
+
["onMouseLeave", "mouseleave"],
|
|
254
|
+
["onTouchStart", "touchstart"],
|
|
255
|
+
["onPointerDown", "pointerdown"],
|
|
256
|
+
["onKeyDown", "keydown"],
|
|
257
|
+
] as const;
|
|
258
|
+
|
|
259
|
+
for (const [propName, eventName] of eventProps) {
|
|
260
|
+
const handler = triggerProps[propName];
|
|
261
|
+
if (typeof handler === "function") {
|
|
262
|
+
const listener = handler as EventListener;
|
|
263
|
+
element.addEventListener(eventName, listener);
|
|
264
|
+
listeners.push([eventName, listener]);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
onCleanup(() => {
|
|
269
|
+
for (const [eventName, listener] of listeners) {
|
|
270
|
+
element.removeEventListener(eventName, listener);
|
|
271
|
+
}
|
|
272
|
+
if (describedBy && element.getAttribute("aria-describedby") === describedBy) {
|
|
273
|
+
element.removeAttribute("aria-describedby");
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
});
|
|
168
277
|
|
|
169
278
|
// We wrap in a span with display:contents to not affect layout.
|
|
170
279
|
// However, display:contents makes getBoundingClientRect return zeros,
|
|
171
280
|
// so we pass a ref callback that finds the first actual element child.
|
|
172
281
|
const handleRef = (span: HTMLSpanElement) => {
|
|
282
|
+
const findElementChild = (el: Element): HTMLElement | null => {
|
|
283
|
+
for (const child of el.children) {
|
|
284
|
+
if (child instanceof HTMLElement) {
|
|
285
|
+
return child;
|
|
286
|
+
}
|
|
287
|
+
const found = findElementChild(child);
|
|
288
|
+
if (found) return found;
|
|
289
|
+
}
|
|
290
|
+
return null;
|
|
291
|
+
};
|
|
292
|
+
|
|
173
293
|
// Find the first element child that has dimensions (not display:contents)
|
|
174
294
|
const findVisibleChild = (el: Element): HTMLElement | null => {
|
|
175
295
|
if (el instanceof HTMLElement) {
|
|
@@ -177,7 +297,6 @@ const TriggerWrapper: ParentComponent<{
|
|
|
177
297
|
if (rect.width > 0 && rect.height > 0) {
|
|
178
298
|
return el;
|
|
179
299
|
}
|
|
180
|
-
// Check children
|
|
181
300
|
for (const child of el.children) {
|
|
182
301
|
const found = findVisibleChild(child);
|
|
183
302
|
if (found) return found;
|
|
@@ -189,31 +308,28 @@ const TriggerWrapper: ParentComponent<{
|
|
|
189
308
|
// Use requestAnimationFrame to ensure children are rendered and have dimensions
|
|
190
309
|
// This is necessary because SolidJS may not have computed child layout yet
|
|
191
310
|
const resolveRef = () => {
|
|
311
|
+
const elementChild = findElementChild(span);
|
|
192
312
|
const visibleChild = findVisibleChild(span);
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
} else {
|
|
196
|
-
// Fallback to span itself
|
|
197
|
-
props.ref(span);
|
|
198
|
-
}
|
|
313
|
+
setTriggerElement(elementChild ?? visibleChild ?? span);
|
|
314
|
+
props.ref(visibleChild ?? elementChild ?? span);
|
|
199
315
|
};
|
|
200
316
|
|
|
201
|
-
|
|
317
|
+
const elementChild = findElementChild(span);
|
|
318
|
+
if (elementChild) {
|
|
319
|
+
setTriggerElement(elementChild);
|
|
320
|
+
}
|
|
321
|
+
|
|
202
322
|
const immediateChild = findVisibleChild(span);
|
|
203
323
|
if (immediateChild) {
|
|
324
|
+
setTriggerElement(elementChild ?? immediateChild);
|
|
204
325
|
props.ref(immediateChild);
|
|
205
326
|
} else {
|
|
206
|
-
// Defer to next frame when layout should be computed
|
|
207
327
|
requestAnimationFrame(resolveRef);
|
|
208
328
|
}
|
|
209
329
|
};
|
|
210
330
|
|
|
211
331
|
return (
|
|
212
|
-
<span
|
|
213
|
-
{...props.triggerProps}
|
|
214
|
-
ref={handleRef}
|
|
215
|
-
style={{ display: 'contents' }}
|
|
216
|
-
>
|
|
332
|
+
<span ref={handleRef} style={{ display: "contents" }}>
|
|
217
333
|
{child()}
|
|
218
334
|
</span>
|
|
219
335
|
);
|
|
@@ -233,26 +349,98 @@ const TriggerWrapper: ParentComponent<{
|
|
|
233
349
|
export function Tooltip(props: TooltipProps): JSX.Element {
|
|
234
350
|
const context = useContext(TooltipTriggerContext);
|
|
235
351
|
|
|
236
|
-
|
|
352
|
+
createEffect(() => {
|
|
353
|
+
context?.setTooltipId(props.id);
|
|
354
|
+
onCleanup(() => {
|
|
355
|
+
context?.setTooltipId(undefined);
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
237
359
|
const localState = createTooltipTriggerState({
|
|
238
|
-
get isOpen() {
|
|
239
|
-
|
|
360
|
+
get isOpen() {
|
|
361
|
+
return props.isOpen;
|
|
362
|
+
},
|
|
363
|
+
get defaultOpen() {
|
|
364
|
+
return props.defaultOpen;
|
|
365
|
+
},
|
|
240
366
|
});
|
|
241
367
|
|
|
242
368
|
const state = () => context?.state ?? localState;
|
|
243
|
-
const placement = () => props.placement ??
|
|
369
|
+
const placement = () => props.placement ?? context?.placement() ?? "top";
|
|
370
|
+
const containerPadding = () => props.containerPadding ?? context?.containerPadding() ?? 12;
|
|
371
|
+
const crossOffset = () => props.crossOffset ?? context?.crossOffset() ?? 0;
|
|
372
|
+
const shouldFlip = () => props.shouldFlip ?? context?.shouldFlip() ?? true;
|
|
373
|
+
const offset = () => props.offset ?? 9;
|
|
374
|
+
const arrowSize = () => props.arrowSize ?? 0;
|
|
375
|
+
const arrowBoundaryOffset = () => props.arrowBoundaryOffset ?? 0;
|
|
376
|
+
const isDisabled = () => props.isDisabled ?? context?.isDisabled() ?? false;
|
|
377
|
+
|
|
378
|
+
const isOpen = () => !isDisabled() && state().isOpen();
|
|
379
|
+
|
|
380
|
+
// Exit animation state machine: 'closed' | 'open' | 'exiting'
|
|
381
|
+
// Keeps the tooltip mounted during exit animation so CSS transitions can play.
|
|
382
|
+
const [exitState, setExitState] = createSignal<"closed" | "open" | "exiting">(
|
|
383
|
+
isOpen() ? "open" : "closed",
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
createEffect(() => {
|
|
387
|
+
const open = isOpen();
|
|
388
|
+
const current = exitState();
|
|
389
|
+
if (current === "open" && !open) {
|
|
390
|
+
setExitState("exiting");
|
|
391
|
+
} else if ((current === "closed" || current === "exiting") && open) {
|
|
392
|
+
setExitState("open");
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// Signal for the tooltip ref so we can observe exit animations
|
|
397
|
+
const [tooltipEl, setTooltipEl] = createSignal<HTMLDivElement | null>(null);
|
|
398
|
+
|
|
399
|
+
// When exiting, wait for CSS animations to finish, then set state to closed
|
|
400
|
+
createEffect(() => {
|
|
401
|
+
if (exitState() !== "exiting") return;
|
|
402
|
+
const el = tooltipEl();
|
|
403
|
+
if (!el || !("getAnimations" in el)) {
|
|
404
|
+
setExitState("closed");
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
const animations = el.getAnimations();
|
|
408
|
+
if (animations.length === 0) {
|
|
409
|
+
setExitState("closed");
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
let canceled = false;
|
|
413
|
+
Promise.all(animations.map((a) => a.finished))
|
|
414
|
+
.then(() => {
|
|
415
|
+
if (!canceled) setExitState((s) => (s === "exiting" ? "closed" : s));
|
|
416
|
+
})
|
|
417
|
+
.catch(() => {
|
|
418
|
+
if (!canceled) setExitState((s) => (s === "exiting" ? "closed" : s));
|
|
419
|
+
});
|
|
420
|
+
onCleanup(() => {
|
|
421
|
+
canceled = true;
|
|
422
|
+
});
|
|
423
|
+
});
|
|
244
424
|
|
|
245
|
-
|
|
246
|
-
const
|
|
425
|
+
const shouldRender = () => isOpen() || exitState() === "exiting";
|
|
426
|
+
const isExiting = () => exitState() === "exiting";
|
|
247
427
|
|
|
248
428
|
return (
|
|
249
|
-
<Show when={
|
|
429
|
+
<Show when={shouldRender()}>
|
|
250
430
|
<TooltipContent
|
|
251
431
|
{...props}
|
|
252
432
|
state={state()}
|
|
253
433
|
contextTooltipProps={context?.tooltipProps ?? {}}
|
|
254
434
|
placement={placement()}
|
|
435
|
+
containerPadding={containerPadding()}
|
|
436
|
+
crossOffset={crossOffset()}
|
|
437
|
+
shouldFlip={shouldFlip()}
|
|
438
|
+
offset={offset()}
|
|
439
|
+
arrowSize={arrowSize()}
|
|
440
|
+
arrowBoundaryOffset={arrowBoundaryOffset()}
|
|
255
441
|
triggerRef={context?.triggerRef ?? (() => null)}
|
|
442
|
+
isExiting={isExiting()}
|
|
443
|
+
onTooltipRef={setTooltipEl}
|
|
256
444
|
/>
|
|
257
445
|
</Show>
|
|
258
446
|
);
|
|
@@ -264,10 +452,18 @@ export function Tooltip(props: TooltipProps): JSX.Element {
|
|
|
264
452
|
function TooltipContent(
|
|
265
453
|
props: TooltipProps & {
|
|
266
454
|
state: TooltipTriggerState;
|
|
267
|
-
contextTooltipProps: { id?: string };
|
|
268
|
-
placement:
|
|
455
|
+
contextTooltipProps: { readonly id?: string };
|
|
456
|
+
placement: TooltipPlacement;
|
|
457
|
+
containerPadding: number;
|
|
458
|
+
crossOffset: number;
|
|
459
|
+
shouldFlip: boolean;
|
|
460
|
+
offset: number;
|
|
461
|
+
arrowSize: number;
|
|
462
|
+
arrowBoundaryOffset: number;
|
|
269
463
|
triggerRef: () => HTMLElement | null | undefined;
|
|
270
|
-
|
|
464
|
+
isExiting: boolean;
|
|
465
|
+
onTooltipRef: (el: HTMLDivElement | null) => void;
|
|
466
|
+
},
|
|
271
467
|
): JSX.Element {
|
|
272
468
|
if (isServer) {
|
|
273
469
|
return null as unknown as JSX.Element;
|
|
@@ -276,20 +472,57 @@ function TooltipContent(
|
|
|
276
472
|
let tooltipRef!: HTMLDivElement;
|
|
277
473
|
const { tooltipProps: ariaTooltipProps } = createTooltip({}, props.state);
|
|
278
474
|
|
|
279
|
-
// Signal to track position styles
|
|
280
475
|
// Start visible at 0,0 and update position asynchronously
|
|
281
476
|
// This ensures the tooltip is immediately accessible (for screen readers and tests)
|
|
282
477
|
// while the visual position gets calculated
|
|
283
478
|
const [positionStyles, setPositionStyles] = createSignal({
|
|
284
|
-
top:
|
|
285
|
-
left:
|
|
286
|
-
visibility:
|
|
479
|
+
top: "0px",
|
|
480
|
+
left: "0px",
|
|
481
|
+
visibility: "visible" as "hidden" | "visible",
|
|
482
|
+
});
|
|
483
|
+
const [renderedPlacement, setRenderedPlacement] = createSignal<TooltipResolvedPlacement>(
|
|
484
|
+
resolvePlacement(props.placement),
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
// Enter animation state: starts true on mount, clears after first animation frame.
|
|
488
|
+
// Uses getAnimations() to detect CSS animations/transitions - if none exist (JSDOM,
|
|
489
|
+
// no CSS defined, reduced-motion), clears immediately.
|
|
490
|
+
const [isEntering, setIsEntering] = createSignal(true);
|
|
491
|
+
|
|
492
|
+
createEffect(() => {
|
|
493
|
+
if (!isEntering()) return;
|
|
494
|
+
if (!tooltipRef || !("getAnimations" in tooltipRef)) {
|
|
495
|
+
setIsEntering(false);
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
// Cancel any premature CSS transitions triggered before layout
|
|
499
|
+
for (const anim of tooltipRef.getAnimations()) {
|
|
500
|
+
if (anim instanceof CSSTransition) {
|
|
501
|
+
anim.cancel();
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
const animations = tooltipRef.getAnimations();
|
|
505
|
+
if (animations.length === 0) {
|
|
506
|
+
setIsEntering(false);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
let canceled = false;
|
|
510
|
+
Promise.all(animations.map((a) => a.finished))
|
|
511
|
+
.then(() => {
|
|
512
|
+
if (!canceled) setIsEntering(false);
|
|
513
|
+
})
|
|
514
|
+
.catch(() => {
|
|
515
|
+
if (!canceled) setIsEntering(false);
|
|
516
|
+
});
|
|
517
|
+
onCleanup(() => {
|
|
518
|
+
canceled = true;
|
|
519
|
+
});
|
|
287
520
|
});
|
|
288
521
|
|
|
289
522
|
const values = createMemo<TooltipRenderProps>(() => ({
|
|
290
|
-
isEntering:
|
|
291
|
-
isExiting:
|
|
292
|
-
placement:
|
|
523
|
+
isEntering: isEntering(),
|
|
524
|
+
isExiting: props.isExiting,
|
|
525
|
+
placement: renderedPlacement(),
|
|
293
526
|
}));
|
|
294
527
|
|
|
295
528
|
const renderProps = useRenderProps(
|
|
@@ -297,13 +530,13 @@ function TooltipContent(
|
|
|
297
530
|
class: props.class,
|
|
298
531
|
style: props.style,
|
|
299
532
|
children: props.children,
|
|
300
|
-
defaultClassName:
|
|
533
|
+
defaultClassName: "solidaria-Tooltip",
|
|
301
534
|
},
|
|
302
|
-
values
|
|
535
|
+
values,
|
|
303
536
|
);
|
|
304
537
|
|
|
305
|
-
//
|
|
306
|
-
//
|
|
538
|
+
// Position the overlay in document coordinates, matching React Aria's
|
|
539
|
+
// absolute overlay positioning when the portal container is the document.
|
|
307
540
|
// Returns true if position was successfully updated, false if we need to retry
|
|
308
541
|
const updatePosition = (): boolean => {
|
|
309
542
|
const triggerEl = props.triggerRef();
|
|
@@ -320,98 +553,166 @@ function TooltipContent(
|
|
|
320
553
|
// when the element might be positioned off-screen initially
|
|
321
554
|
const tooltipWidth = tooltipRef.offsetWidth;
|
|
322
555
|
const tooltipHeight = tooltipRef.offsetHeight;
|
|
323
|
-
const
|
|
556
|
+
const viewportWidth = window.innerWidth;
|
|
557
|
+
const viewportHeight = window.innerHeight;
|
|
558
|
+
const containerPadding = props.containerPadding;
|
|
559
|
+
const crossOffset = props.crossOffset;
|
|
560
|
+
const placement = maybeFlipPlacement(
|
|
561
|
+
resolvePlacement(props.placement),
|
|
562
|
+
triggerRect,
|
|
563
|
+
tooltipWidth,
|
|
564
|
+
tooltipHeight,
|
|
565
|
+
viewportWidth,
|
|
566
|
+
viewportHeight,
|
|
567
|
+
containerPadding,
|
|
568
|
+
props.offset,
|
|
569
|
+
props.shouldFlip,
|
|
570
|
+
);
|
|
324
571
|
|
|
325
572
|
let top = 0;
|
|
326
573
|
let left = 0;
|
|
327
574
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
left = triggerRect.left + (triggerRect.width - tooltipWidth) / 2;
|
|
575
|
+
switch (placement) {
|
|
576
|
+
case "top":
|
|
577
|
+
top = triggerRect.top - tooltipHeight - props.offset;
|
|
578
|
+
left = triggerRect.left + (triggerRect.width - tooltipWidth) / 2 + crossOffset;
|
|
333
579
|
break;
|
|
334
|
-
case
|
|
335
|
-
top = triggerRect.bottom + offset;
|
|
336
|
-
left = triggerRect.left + (triggerRect.width - tooltipWidth) / 2;
|
|
580
|
+
case "bottom":
|
|
581
|
+
top = triggerRect.bottom + props.offset;
|
|
582
|
+
left = triggerRect.left + (triggerRect.width - tooltipWidth) / 2 + crossOffset;
|
|
337
583
|
break;
|
|
338
|
-
case
|
|
339
|
-
top = triggerRect.top + (triggerRect.height - tooltipHeight) / 2;
|
|
340
|
-
left = triggerRect.left - tooltipWidth - offset;
|
|
584
|
+
case "left":
|
|
585
|
+
top = triggerRect.top + (triggerRect.height - tooltipHeight) / 2 + crossOffset;
|
|
586
|
+
left = triggerRect.left - tooltipWidth - props.offset;
|
|
341
587
|
break;
|
|
342
|
-
case
|
|
343
|
-
top = triggerRect.top + (triggerRect.height - tooltipHeight) / 2;
|
|
344
|
-
left = triggerRect.right + offset;
|
|
588
|
+
case "right":
|
|
589
|
+
top = triggerRect.top + (triggerRect.height - tooltipHeight) / 2 + crossOffset;
|
|
590
|
+
left = triggerRect.right + props.offset;
|
|
345
591
|
break;
|
|
346
592
|
}
|
|
347
593
|
|
|
594
|
+
if (placement === "top" || placement === "bottom") {
|
|
595
|
+
left = clamp(
|
|
596
|
+
left,
|
|
597
|
+
triggerRect.left - tooltipWidth + props.arrowSize + props.arrowBoundaryOffset,
|
|
598
|
+
triggerRect.left + triggerRect.width - props.arrowSize - props.arrowBoundaryOffset,
|
|
599
|
+
);
|
|
600
|
+
left = clamp(left, containerPadding, viewportWidth - tooltipWidth - containerPadding);
|
|
601
|
+
} else {
|
|
602
|
+
top = clamp(
|
|
603
|
+
top,
|
|
604
|
+
triggerRect.top - tooltipHeight + props.arrowSize + props.arrowBoundaryOffset,
|
|
605
|
+
triggerRect.top + triggerRect.height - props.arrowSize - props.arrowBoundaryOffset,
|
|
606
|
+
);
|
|
607
|
+
top = clamp(top, containerPadding, viewportHeight - tooltipHeight - containerPadding);
|
|
608
|
+
}
|
|
609
|
+
setRenderedPlacement(placement);
|
|
348
610
|
setPositionStyles({
|
|
349
|
-
top: `${top}px`,
|
|
350
|
-
left: `${left}px`,
|
|
351
|
-
visibility:
|
|
611
|
+
top: `${Math.floor(top + window.scrollY)}px`,
|
|
612
|
+
left: `${Math.floor(left + window.scrollX)}px`,
|
|
613
|
+
visibility: "visible",
|
|
352
614
|
});
|
|
353
615
|
|
|
354
616
|
return true;
|
|
355
617
|
};
|
|
356
618
|
|
|
357
|
-
// Set up positioning
|
|
619
|
+
// Set up positioning and scroll-close effects. Positioning retries while the
|
|
620
|
+
// trigger ref resolves, and pending rAF/setTimeout IDs are canceled on cleanup.
|
|
358
621
|
createEffect(() => {
|
|
359
|
-
|
|
360
|
-
|
|
622
|
+
// Track positioning inputs synchronously so updates from controlled route
|
|
623
|
+
// props reschedule measurement even though layout reads happen in rAF.
|
|
624
|
+
props.placement;
|
|
625
|
+
props.containerPadding;
|
|
626
|
+
props.crossOffset;
|
|
627
|
+
props.shouldFlip;
|
|
628
|
+
props.offset;
|
|
629
|
+
props.arrowSize;
|
|
630
|
+
props.arrowBoundaryOffset;
|
|
361
631
|
|
|
362
|
-
// Initial position calculation - use requestAnimationFrame to ensure
|
|
363
|
-
// the element is rendered and has proper dimensions
|
|
364
|
-
// We may need multiple frames if the trigger ref hasn't resolved yet
|
|
365
632
|
let retryCount = 0;
|
|
366
633
|
const maxRetries = 5;
|
|
634
|
+
let pendingRaf = 0;
|
|
635
|
+
let pendingTimeout = 0;
|
|
367
636
|
|
|
368
637
|
const tryUpdatePosition = () => {
|
|
638
|
+
pendingRaf = 0;
|
|
639
|
+
pendingTimeout = 0;
|
|
369
640
|
const success = updatePosition();
|
|
370
641
|
if (!success && retryCount < maxRetries) {
|
|
371
642
|
retryCount++;
|
|
372
|
-
|
|
373
|
-
// Use setTimeout for more reliable deferral across environments
|
|
374
|
-
setTimeout(tryUpdatePosition, 16); // ~60fps
|
|
643
|
+
pendingTimeout = window.setTimeout(tryUpdatePosition, 16);
|
|
375
644
|
}
|
|
376
|
-
// If all retries fail, tooltip stays at 0,0 (test environments)
|
|
377
|
-
// The tooltip is visible by default, so it remains accessible
|
|
378
645
|
};
|
|
379
646
|
|
|
380
|
-
|
|
381
|
-
|
|
647
|
+
pendingRaf = requestAnimationFrame(tryUpdatePosition);
|
|
648
|
+
|
|
649
|
+
const closeOnScroll = (event: Event) => {
|
|
650
|
+
const trigger = props.triggerRef();
|
|
651
|
+
const target = event.target;
|
|
652
|
+
if (!trigger || (target instanceof Node && !target.contains(trigger))) {
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
382
658
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
659
|
+
props.state.close(true);
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
window.addEventListener("scroll", closeOnScroll, true);
|
|
663
|
+
window.addEventListener("resize", updatePosition);
|
|
386
664
|
|
|
387
665
|
onCleanup(() => {
|
|
388
|
-
|
|
389
|
-
|
|
666
|
+
if (pendingRaf) cancelAnimationFrame(pendingRaf);
|
|
667
|
+
if (pendingTimeout) clearTimeout(pendingTimeout);
|
|
668
|
+
window.removeEventListener("scroll", closeOnScroll, true);
|
|
669
|
+
window.removeEventListener("resize", updatePosition);
|
|
390
670
|
});
|
|
391
671
|
});
|
|
392
672
|
|
|
393
|
-
|
|
394
|
-
const
|
|
673
|
+
const domProps = filterDOMProps(props, { global: true });
|
|
674
|
+
const tooltipId = () => props.contextTooltipProps.id ?? (domProps as { id?: string }).id;
|
|
395
675
|
|
|
396
676
|
// Extract ref from ariaTooltipProps to avoid type conflicts (SolidJS ref types are element-specific)
|
|
397
677
|
const { ref: _ariaRef, ...cleanAriaProps } = ariaTooltipProps as Record<string, unknown>;
|
|
398
678
|
|
|
679
|
+
const setRef = (el: HTMLDivElement) => {
|
|
680
|
+
tooltipRef = el;
|
|
681
|
+
if (!props.dir) {
|
|
682
|
+
el.dir = getDocumentDirection();
|
|
683
|
+
}
|
|
684
|
+
if (!props.lang) {
|
|
685
|
+
const documentLang = document.documentElement.lang;
|
|
686
|
+
const navigatorLang = typeof navigator !== "undefined" ? navigator.language : "";
|
|
687
|
+
el.lang = documentLang.includes("-")
|
|
688
|
+
? documentLang
|
|
689
|
+
: navigatorLang || documentLang || "en-US";
|
|
690
|
+
}
|
|
691
|
+
props.onTooltipRef(el);
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
onCleanup(() => {
|
|
695
|
+
props.onTooltipRef(null);
|
|
696
|
+
});
|
|
697
|
+
|
|
399
698
|
return (
|
|
400
699
|
<OverlayContainer>
|
|
401
700
|
<div
|
|
402
701
|
{...domProps}
|
|
403
|
-
{...props.contextTooltipProps}
|
|
404
702
|
{...cleanAriaProps}
|
|
703
|
+
id={tooltipId()}
|
|
405
704
|
role="tooltip"
|
|
406
|
-
ref={
|
|
705
|
+
ref={setRef}
|
|
407
706
|
class={renderProps.class()}
|
|
408
707
|
style={{
|
|
409
|
-
position:
|
|
410
|
-
|
|
708
|
+
position: "absolute",
|
|
709
|
+
"z-index": 100000,
|
|
411
710
|
...positionStyles(),
|
|
412
711
|
...renderProps.style(),
|
|
413
712
|
}}
|
|
414
|
-
data-placement={
|
|
713
|
+
data-placement={renderedPlacement()}
|
|
714
|
+
data-entering={isEntering() || undefined}
|
|
715
|
+
data-exiting={props.isExiting || undefined}
|
|
415
716
|
>
|
|
416
717
|
{renderProps.renderChildren()}
|
|
417
718
|
</div>
|
|
@@ -419,5 +720,61 @@ function TooltipContent(
|
|
|
419
720
|
);
|
|
420
721
|
}
|
|
421
722
|
|
|
422
|
-
|
|
723
|
+
function resolvePlacement(placement: TooltipPlacement): TooltipResolvedPlacement {
|
|
724
|
+
if (placement === "start") {
|
|
725
|
+
return getDocumentDirection() === "rtl" ? "right" : "left";
|
|
726
|
+
}
|
|
727
|
+
if (placement === "end") {
|
|
728
|
+
return getDocumentDirection() === "rtl" ? "left" : "right";
|
|
729
|
+
}
|
|
730
|
+
return placement;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function getDocumentDirection(): "ltr" | "rtl" {
|
|
734
|
+
if (typeof document === "undefined") {
|
|
735
|
+
return "ltr";
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
return document.documentElement.dir === "rtl" ? "rtl" : "ltr";
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function maybeFlipPlacement(
|
|
742
|
+
placement: TooltipResolvedPlacement,
|
|
743
|
+
triggerRect: DOMRect,
|
|
744
|
+
tooltipWidth: number,
|
|
745
|
+
tooltipHeight: number,
|
|
746
|
+
viewportWidth: number,
|
|
747
|
+
viewportHeight: number,
|
|
748
|
+
containerPadding: number,
|
|
749
|
+
offset: number,
|
|
750
|
+
shouldFlip: boolean,
|
|
751
|
+
): TooltipResolvedPlacement {
|
|
752
|
+
if (!shouldFlip) {
|
|
753
|
+
return placement;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
switch (placement) {
|
|
757
|
+
case "top":
|
|
758
|
+
return triggerRect.top - tooltipHeight - offset < containerPadding ? "bottom" : placement;
|
|
759
|
+
case "bottom":
|
|
760
|
+
return triggerRect.bottom + tooltipHeight + offset > viewportHeight - containerPadding
|
|
761
|
+
? "top"
|
|
762
|
+
: placement;
|
|
763
|
+
case "left":
|
|
764
|
+
return triggerRect.left - tooltipWidth - offset < containerPadding ? "right" : placement;
|
|
765
|
+
case "right":
|
|
766
|
+
return triggerRect.right + tooltipWidth + offset > viewportWidth - containerPadding
|
|
767
|
+
? "left"
|
|
768
|
+
: placement;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function clamp(value: number, min: number, max: number): number {
|
|
773
|
+
if (max < min) {
|
|
774
|
+
return min;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
return Math.min(Math.max(value, min), max);
|
|
778
|
+
}
|
|
779
|
+
|
|
423
780
|
export type { TooltipTriggerState };
|