@proyecto-viviana/solidaria-components 0.2.5 → 0.2.9
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/dist/ActionBar.d.ts +71 -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/Breadcrumbs.d.ts +10 -2
- package/dist/Breadcrumbs.d.ts.map +1 -1
- package/dist/Button.d.ts +4 -0
- package/dist/Button.d.ts.map +1 -1
- package/dist/Calendar.d.ts +13 -0
- package/dist/Calendar.d.ts.map +1 -1
- package/dist/Checkbox.d.ts +2 -2
- package/dist/Checkbox.d.ts.map +1 -1
- package/dist/Collection.d.ts +125 -0
- package/dist/Collection.d.ts.map +1 -0
- package/dist/Color.d.ts +114 -2
- 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 +64 -0
- 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 +27 -2
- package/dist/DateField.d.ts.map +1 -1
- package/dist/DatePicker.d.ts +67 -2
- package/dist/DatePicker.d.ts.map +1 -1
- package/dist/Dialog.d.ts.map +1 -1
- package/dist/Disclosure.d.ts +2 -0
- 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 +23 -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 +27 -0
- package/dist/Form.d.ts.map +1 -0
- package/dist/GridList.d.ts +40 -1
- package/dist/GridList.d.ts.map +1 -1
- 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/Link.d.ts.map +1 -1
- package/dist/ListBox.d.ts +43 -1
- 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 +20 -2
- package/dist/Menu.d.ts.map +1 -1
- package/dist/Meter.d.ts +2 -2
- package/dist/Meter.d.ts.map +1 -1
- package/dist/Modal.d.ts +2 -0
- package/dist/Modal.d.ts.map +1 -1
- package/dist/NumberField.d.ts +2 -0
- package/dist/NumberField.d.ts.map +1 -1
- package/dist/Popover.d.ts +4 -2
- 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 +2 -2
- package/dist/ProgressBar.d.ts.map +1 -1
- package/dist/RadioGroup.d.ts.map +1 -1
- package/dist/RangeCalendar.d.ts +5 -0
- 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 +2 -3
- package/dist/SearchField.d.ts.map +1 -1
- package/dist/Select.d.ts +11 -0
- 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/SharedElementTransition.d.ts +39 -0
- package/dist/SharedElementTransition.d.ts.map +1 -0
- package/dist/Slider.d.ts +6 -3
- package/dist/Slider.d.ts.map +1 -1
- package/dist/Table.d.ts +39 -0
- package/dist/Table.d.ts.map +1 -1
- package/dist/Tabs.d.ts +4 -3
- package/dist/Tabs.d.ts.map +1 -1
- package/dist/TagGroup.d.ts +12 -2
- 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 +4 -0
- package/dist/TextField.d.ts.map +1 -1
- package/dist/TimeField.d.ts +26 -1
- package/dist/TimeField.d.ts.map +1 -1
- package/dist/Toast.d.ts.map +1 -1
- package/dist/ToggleButton.d.ts +30 -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.map +1 -1
- package/dist/Tooltip.d.ts +9 -0
- package/dist/Tooltip.d.ts.map +1 -1
- package/dist/Tree.d.ts +44 -2
- 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 +3 -1
- package/dist/VisuallyHidden.d.ts.map +1 -1
- package/dist/contexts.d.ts +1 -0
- package/dist/contexts.d.ts.map +1 -1
- package/dist/index.d.ts +57 -25
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13961 -5946
- package/dist/index.js.map +1 -7
- package/dist/index.ssr.js +9612 -2401
- package/dist/index.ssr.js.map +1 -7
- package/dist/useDragAndDrop.d.ts +93 -0
- package/dist/useDragAndDrop.d.ts.map +1 -0
- package/dist/utils.d.ts +7 -1
- 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 +8 -6
- package/src/ActionBar.tsx +248 -0
- package/src/ActionGroup.tsx +285 -0
- package/src/Alert.tsx +177 -0
- package/src/Autocomplete.tsx +1 -1
- package/src/Breadcrumbs.tsx +103 -17
- package/src/Button.tsx +65 -21
- package/src/Calendar.tsx +179 -53
- package/src/Checkbox.tsx +1 -2
- package/src/Collection.tsx +341 -0
- package/src/Color.tsx +652 -34
- package/src/ColorEditor.tsx +231 -0
- package/src/ComboBox.tsx +315 -81
- package/src/ContextualHelpTrigger.tsx +183 -0
- package/src/DateField.tsx +93 -19
- package/src/DatePicker.tsx +495 -25
- package/src/Dialog.tsx +40 -9
- package/src/Disclosure.tsx +33 -27
- package/src/DragAndDrop.tsx +334 -0
- package/src/DragPreview.tsx +45 -0
- package/src/DropZone.tsx +213 -0
- package/src/FieldError.tsx +67 -0
- package/src/FileTrigger.tsx +83 -0
- package/src/Focusable.tsx +106 -0
- package/src/Form.tsx +85 -0
- package/src/GridList.tsx +379 -41
- package/src/Icon.tsx +154 -0
- package/src/Keyboard.tsx +26 -0
- package/src/Link.tsx +14 -1
- package/src/ListBox.tsx +484 -33
- package/src/ListDropTargetDelegate.ts +282 -0
- package/src/Menu.tsx +388 -35
- package/src/Meter.tsx +7 -3
- package/src/Modal.tsx +32 -4
- package/src/NumberField.tsx +163 -43
- package/src/Popover.tsx +136 -180
- package/src/Pressable.tsx +108 -0
- package/src/ProgressBar.tsx +7 -3
- package/src/RadioGroup.tsx +35 -25
- package/src/RangeCalendar.tsx +100 -68
- package/src/RouterProvider.tsx +240 -0
- package/src/SearchField.tsx +142 -34
- package/src/Select.tsx +221 -73
- package/src/SelectionIndicator.tsx +105 -0
- package/src/SharedElementTransition.tsx +258 -0
- package/src/Slider.tsx +16 -6
- package/src/Table.tsx +417 -57
- package/src/Tabs.tsx +68 -35
- package/src/TagGroup.tsx +121 -36
- package/src/Text.tsx +18 -0
- package/src/TextField.tsx +25 -8
- package/src/TimeField.tsx +101 -151
- package/src/Toast.tsx +108 -14
- package/src/ToggleButton.tsx +159 -0
- package/src/ToggleButtonGroup.tsx +136 -0
- package/src/Toolbar.tsx +14 -8
- package/src/Tooltip.tsx +108 -19
- package/src/Tree.tsx +1143 -87
- package/src/Virtualizer.tsx +702 -0
- package/src/VirtualizerLayouts.ts +265 -0
- package/src/VisuallyHidden.tsx +15 -21
- package/src/contexts.ts +1 -0
- package/src/index.ts +1057 -620
- package/src/useDragAndDrop.ts +351 -0
- package/src/utils.tsx +37 -3
- package/src/virtualizer/Layout.ts +200 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToggleButtonGroup component for solidaria-components.
|
|
3
|
+
*
|
|
4
|
+
* Groups toggle buttons with single/multiple selection state.
|
|
5
|
+
* Parity target: react-aria-components/src/ToggleButtonGroup.tsx
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { type JSX, createContext, createMemo, splitProps, useContext } from 'solid-js';
|
|
9
|
+
import {
|
|
10
|
+
createToggleButtonGroup,
|
|
11
|
+
mergeProps,
|
|
12
|
+
} from '@proyecto-viviana/solidaria';
|
|
13
|
+
import {
|
|
14
|
+
createToggleGroupState,
|
|
15
|
+
type Key,
|
|
16
|
+
type ToggleGroupState,
|
|
17
|
+
} from '@proyecto-viviana/solid-stately';
|
|
18
|
+
import {
|
|
19
|
+
type ClassNameOrFunction,
|
|
20
|
+
type StyleOrFunction,
|
|
21
|
+
type RenderChildren,
|
|
22
|
+
type SlotProps,
|
|
23
|
+
useRenderProps,
|
|
24
|
+
filterDOMProps,
|
|
25
|
+
} from './utils';
|
|
26
|
+
|
|
27
|
+
export interface ToggleButtonGroupRenderProps {
|
|
28
|
+
orientation: 'horizontal' | 'vertical';
|
|
29
|
+
isDisabled: boolean;
|
|
30
|
+
state: ToggleGroupState;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ToggleButtonGroupProps
|
|
34
|
+
extends Omit<JSX.HTMLAttributes<HTMLDivElement>, 'children' | 'class' | 'style' | 'onSelectionChange'>,
|
|
35
|
+
SlotProps {
|
|
36
|
+
selectionMode?: 'single' | 'multiple';
|
|
37
|
+
disallowEmptySelection?: boolean;
|
|
38
|
+
selectedKeys?: Iterable<Key>;
|
|
39
|
+
defaultSelectedKeys?: Iterable<Key>;
|
|
40
|
+
onSelectionChange?: (keys: Set<Key>) => void;
|
|
41
|
+
orientation?: 'horizontal' | 'vertical';
|
|
42
|
+
isDisabled?: boolean;
|
|
43
|
+
children?: RenderChildren<ToggleButtonGroupRenderProps>;
|
|
44
|
+
class?: ClassNameOrFunction<ToggleButtonGroupRenderProps>;
|
|
45
|
+
style?: StyleOrFunction<ToggleButtonGroupRenderProps>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const ToggleButtonGroupContext = createContext<ToggleButtonGroupProps | null>(null);
|
|
49
|
+
export const ToggleButtonGroupStateContext = createContext<ToggleGroupState | null>(null);
|
|
50
|
+
export const ToggleGroupStateContext = ToggleButtonGroupStateContext;
|
|
51
|
+
export type ToggleButtonGroupStateContextValue = ToggleGroupState;
|
|
52
|
+
|
|
53
|
+
export function ToggleButtonGroup(props: ToggleButtonGroupProps): JSX.Element {
|
|
54
|
+
const [local, domProps] = splitProps(props, [
|
|
55
|
+
'selectionMode',
|
|
56
|
+
'disallowEmptySelection',
|
|
57
|
+
'selectedKeys',
|
|
58
|
+
'defaultSelectedKeys',
|
|
59
|
+
'onSelectionChange',
|
|
60
|
+
'orientation',
|
|
61
|
+
'isDisabled',
|
|
62
|
+
'children',
|
|
63
|
+
'class',
|
|
64
|
+
'style',
|
|
65
|
+
'slot',
|
|
66
|
+
'aria-label',
|
|
67
|
+
'aria-labelledby',
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
const state = createToggleGroupState(() => ({
|
|
71
|
+
selectionMode: local.selectionMode,
|
|
72
|
+
disallowEmptySelection: local.disallowEmptySelection,
|
|
73
|
+
selectedKeys: local.selectedKeys,
|
|
74
|
+
defaultSelectedKeys: local.defaultSelectedKeys,
|
|
75
|
+
onSelectionChange: local.onSelectionChange,
|
|
76
|
+
isDisabled: !!local.isDisabled,
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
const { groupProps } = createToggleButtonGroup(
|
|
80
|
+
{
|
|
81
|
+
get orientation() {
|
|
82
|
+
return local.orientation;
|
|
83
|
+
},
|
|
84
|
+
get isDisabled() {
|
|
85
|
+
return !!local.isDisabled;
|
|
86
|
+
},
|
|
87
|
+
get 'aria-label'() {
|
|
88
|
+
return local['aria-label'];
|
|
89
|
+
},
|
|
90
|
+
get 'aria-labelledby'() {
|
|
91
|
+
return local['aria-labelledby'];
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
state
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const renderProps = useRenderProps(
|
|
98
|
+
{
|
|
99
|
+
children: local.children,
|
|
100
|
+
class: local.class,
|
|
101
|
+
style: local.style,
|
|
102
|
+
defaultClassName: 'solidaria-ToggleButtonGroup',
|
|
103
|
+
},
|
|
104
|
+
() => ({
|
|
105
|
+
orientation: local.orientation ?? 'horizontal',
|
|
106
|
+
isDisabled: !!local.isDisabled,
|
|
107
|
+
state,
|
|
108
|
+
})
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const filteredDomProps = createMemo(() => filterDOMProps(domProps, { global: true }));
|
|
112
|
+
const mergedGroupProps = createMemo(() =>
|
|
113
|
+
mergeProps(filteredDomProps(), groupProps as Record<string, unknown>)
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<div
|
|
118
|
+
{...(mergedGroupProps() as JSX.HTMLAttributes<HTMLDivElement>)}
|
|
119
|
+
class={renderProps.class()}
|
|
120
|
+
style={renderProps.style()}
|
|
121
|
+
slot={local.slot}
|
|
122
|
+
data-orientation={local.orientation ?? 'horizontal'}
|
|
123
|
+
data-disabled={local.isDisabled || undefined}
|
|
124
|
+
>
|
|
125
|
+
<ToggleButtonGroupContext.Provider value={props}>
|
|
126
|
+
<ToggleButtonGroupStateContext.Provider value={state}>
|
|
127
|
+
{renderProps.renderChildren()}
|
|
128
|
+
</ToggleButtonGroupStateContext.Provider>
|
|
129
|
+
</ToggleButtonGroupContext.Provider>
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function useToggleButtonGroupStateContext(): ToggleGroupState | null {
|
|
135
|
+
return useContext(ToggleButtonGroupStateContext);
|
|
136
|
+
}
|
package/src/Toolbar.tsx
CHANGED
|
@@ -100,15 +100,21 @@ export function Toolbar(props: ToolbarProps): JSX.Element {
|
|
|
100
100
|
return {}
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
// Merge slot props with explicit props
|
|
104
|
-
const mergedAriaProps = createMemo(() => ({
|
|
105
|
-
orientation: ariaProps.orientation,
|
|
106
|
-
'aria-label': ariaProps['aria-label'] ?? slotProps()['aria-label'] as string | undefined,
|
|
107
|
-
'aria-labelledby': ariaProps['aria-labelledby'],
|
|
108
|
-
}))
|
|
109
|
-
|
|
110
103
|
// Create toolbar aria props
|
|
111
|
-
const { toolbarProps, orientation } = createToolbar(
|
|
104
|
+
const { toolbarProps, orientation } = createToolbar({
|
|
105
|
+
get orientation() {
|
|
106
|
+
return ariaProps.orientation
|
|
107
|
+
},
|
|
108
|
+
get 'aria-label'() {
|
|
109
|
+
return (
|
|
110
|
+
(ariaProps['aria-label'] as string | undefined) ??
|
|
111
|
+
(slotProps()['aria-label'] as string | undefined)
|
|
112
|
+
)
|
|
113
|
+
},
|
|
114
|
+
get 'aria-labelledby'() {
|
|
115
|
+
return ariaProps['aria-labelledby'] as string | undefined
|
|
116
|
+
},
|
|
117
|
+
})
|
|
112
118
|
|
|
113
119
|
// Render props values
|
|
114
120
|
const renderValues = createMemo<ToolbarRenderProps>(() => ({
|
package/src/Tooltip.tsx
CHANGED
|
@@ -83,6 +83,8 @@ interface TooltipTriggerContextValue {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
const TooltipTriggerContext = createContext<TooltipTriggerContextValue | null>(null);
|
|
86
|
+
export const TooltipContext = TooltipTriggerContext;
|
|
87
|
+
export const TooltipTriggerStateContext = createContext<TooltipTriggerState | null>(null);
|
|
86
88
|
|
|
87
89
|
// ============================================
|
|
88
90
|
// COMPONENTS
|
|
@@ -150,9 +152,11 @@ export const TooltipTrigger: ParentComponent<TooltipTriggerComponentProps> = (pr
|
|
|
150
152
|
};
|
|
151
153
|
|
|
152
154
|
return (
|
|
153
|
-
<
|
|
154
|
-
{
|
|
155
|
-
|
|
155
|
+
<TooltipTriggerStateContext.Provider value={state}>
|
|
156
|
+
<TooltipTriggerContext.Provider value={context}>
|
|
157
|
+
{processChildren()}
|
|
158
|
+
</TooltipTriggerContext.Provider>
|
|
159
|
+
</TooltipTriggerStateContext.Provider>
|
|
156
160
|
);
|
|
157
161
|
};
|
|
158
162
|
|
|
@@ -242,17 +246,60 @@ export function Tooltip(props: TooltipProps): JSX.Element {
|
|
|
242
246
|
const state = () => context?.state ?? localState;
|
|
243
247
|
const placement = () => props.placement ?? 'top';
|
|
244
248
|
|
|
245
|
-
// Only render when open
|
|
246
249
|
const isOpen = () => state().isOpen();
|
|
247
250
|
|
|
251
|
+
// Exit animation state machine: 'closed' | 'open' | 'exiting'
|
|
252
|
+
// Keeps the tooltip mounted during exit animation so CSS transitions can play.
|
|
253
|
+
const [exitState, setExitState] = createSignal<'closed' | 'open' | 'exiting'>(
|
|
254
|
+
isOpen() ? 'open' : 'closed'
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
createEffect(() => {
|
|
258
|
+
const open = isOpen();
|
|
259
|
+
const current = exitState();
|
|
260
|
+
if (current === 'open' && !open) {
|
|
261
|
+
setExitState('exiting');
|
|
262
|
+
} else if ((current === 'closed' || current === 'exiting') && open) {
|
|
263
|
+
setExitState('open');
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// Signal for the tooltip ref so we can observe exit animations
|
|
268
|
+
const [tooltipEl, setTooltipEl] = createSignal<HTMLDivElement | null>(null);
|
|
269
|
+
|
|
270
|
+
// When exiting, wait for CSS animations to finish, then set state to closed
|
|
271
|
+
createEffect(() => {
|
|
272
|
+
if (exitState() !== 'exiting') return;
|
|
273
|
+
const el = tooltipEl();
|
|
274
|
+
if (!el || !('getAnimations' in el)) {
|
|
275
|
+
setExitState('closed');
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const animations = el.getAnimations();
|
|
279
|
+
if (animations.length === 0) {
|
|
280
|
+
setExitState('closed');
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
let canceled = false;
|
|
284
|
+
Promise.all(animations.map((a) => a.finished))
|
|
285
|
+
.then(() => { if (!canceled) setExitState((s) => s === 'exiting' ? 'closed' : s); })
|
|
286
|
+
.catch(() => { if (!canceled) setExitState((s) => s === 'exiting' ? 'closed' : s); });
|
|
287
|
+
onCleanup(() => { canceled = true; });
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const shouldRender = () => isOpen() || exitState() === 'exiting';
|
|
291
|
+
const isExiting = () => exitState() === 'exiting';
|
|
292
|
+
|
|
248
293
|
return (
|
|
249
|
-
<Show when={
|
|
294
|
+
<Show when={shouldRender()}>
|
|
250
295
|
<TooltipContent
|
|
251
296
|
{...props}
|
|
252
297
|
state={state()}
|
|
253
298
|
contextTooltipProps={context?.tooltipProps ?? {}}
|
|
254
299
|
placement={placement()}
|
|
255
300
|
triggerRef={context?.triggerRef ?? (() => null)}
|
|
301
|
+
isExiting={isExiting()}
|
|
302
|
+
onTooltipRef={setTooltipEl}
|
|
256
303
|
/>
|
|
257
304
|
</Show>
|
|
258
305
|
);
|
|
@@ -267,6 +314,8 @@ function TooltipContent(
|
|
|
267
314
|
contextTooltipProps: { id?: string };
|
|
268
315
|
placement: 'top' | 'bottom' | 'left' | 'right';
|
|
269
316
|
triggerRef: () => HTMLElement | null | undefined;
|
|
317
|
+
isExiting: boolean;
|
|
318
|
+
onTooltipRef: (el: HTMLDivElement | null) => void;
|
|
270
319
|
}
|
|
271
320
|
): JSX.Element {
|
|
272
321
|
if (isServer) {
|
|
@@ -286,9 +335,38 @@ function TooltipContent(
|
|
|
286
335
|
visibility: 'visible' as 'hidden' | 'visible',
|
|
287
336
|
});
|
|
288
337
|
|
|
338
|
+
// Enter animation state: starts true on mount, clears after first animation frame.
|
|
339
|
+
// Uses getAnimations() to detect CSS animations/transitions - if none exist (JSDOM,
|
|
340
|
+
// no CSS defined, reduced-motion), clears immediately.
|
|
341
|
+
const [isEntering, setIsEntering] = createSignal(true);
|
|
342
|
+
|
|
343
|
+
createEffect(() => {
|
|
344
|
+
if (!isEntering()) return;
|
|
345
|
+
if (!tooltipRef || !('getAnimations' in tooltipRef)) {
|
|
346
|
+
setIsEntering(false);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
// Cancel any premature CSS transitions triggered before layout
|
|
350
|
+
for (const anim of tooltipRef.getAnimations()) {
|
|
351
|
+
if (anim instanceof CSSTransition) {
|
|
352
|
+
anim.cancel();
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
const animations = tooltipRef.getAnimations();
|
|
356
|
+
if (animations.length === 0) {
|
|
357
|
+
setIsEntering(false);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
let canceled = false;
|
|
361
|
+
Promise.all(animations.map((a) => a.finished))
|
|
362
|
+
.then(() => { if (!canceled) setIsEntering(false); })
|
|
363
|
+
.catch(() => { if (!canceled) setIsEntering(false); });
|
|
364
|
+
onCleanup(() => { canceled = true; });
|
|
365
|
+
});
|
|
366
|
+
|
|
289
367
|
const values = createMemo<TooltipRenderProps>(() => ({
|
|
290
|
-
isEntering:
|
|
291
|
-
isExiting:
|
|
368
|
+
isEntering: isEntering(),
|
|
369
|
+
isExiting: props.isExiting,
|
|
292
370
|
placement: props.placement,
|
|
293
371
|
}));
|
|
294
372
|
|
|
@@ -354,37 +432,36 @@ function TooltipContent(
|
|
|
354
432
|
return true;
|
|
355
433
|
};
|
|
356
434
|
|
|
357
|
-
// Set up positioning effect - runs when trigger ref is available
|
|
435
|
+
// Set up positioning effect - runs when trigger ref is available.
|
|
436
|
+
// Tracks pending rAF/setTimeout IDs so they can be canceled on cleanup.
|
|
358
437
|
createEffect(() => {
|
|
359
438
|
const trigger = props.triggerRef();
|
|
360
439
|
if (!trigger) return;
|
|
361
440
|
|
|
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
441
|
let retryCount = 0;
|
|
366
442
|
const maxRetries = 5;
|
|
443
|
+
let pendingRaf = 0;
|
|
444
|
+
let pendingTimeout = 0;
|
|
367
445
|
|
|
368
446
|
const tryUpdatePosition = () => {
|
|
447
|
+
pendingRaf = 0;
|
|
448
|
+
pendingTimeout = 0;
|
|
369
449
|
const success = updatePosition();
|
|
370
450
|
if (!success && retryCount < maxRetries) {
|
|
371
451
|
retryCount++;
|
|
372
|
-
|
|
373
|
-
// Use setTimeout for more reliable deferral across environments
|
|
374
|
-
setTimeout(tryUpdatePosition, 16); // ~60fps
|
|
452
|
+
pendingTimeout = window.setTimeout(tryUpdatePosition, 16);
|
|
375
453
|
}
|
|
376
|
-
// If all retries fail, tooltip stays at 0,0 (test environments)
|
|
377
|
-
// The tooltip is visible by default, so it remains accessible
|
|
378
454
|
};
|
|
379
455
|
|
|
380
|
-
|
|
381
|
-
requestAnimationFrame(tryUpdatePosition);
|
|
456
|
+
pendingRaf = requestAnimationFrame(tryUpdatePosition);
|
|
382
457
|
|
|
383
458
|
// Update on scroll/resize
|
|
384
459
|
window.addEventListener('scroll', updatePosition, true);
|
|
385
460
|
window.addEventListener('resize', updatePosition);
|
|
386
461
|
|
|
387
462
|
onCleanup(() => {
|
|
463
|
+
if (pendingRaf) cancelAnimationFrame(pendingRaf);
|
|
464
|
+
if (pendingTimeout) clearTimeout(pendingTimeout);
|
|
388
465
|
window.removeEventListener('scroll', updatePosition, true);
|
|
389
466
|
window.removeEventListener('resize', updatePosition);
|
|
390
467
|
});
|
|
@@ -396,6 +473,16 @@ function TooltipContent(
|
|
|
396
473
|
// Extract ref from ariaTooltipProps to avoid type conflicts (SolidJS ref types are element-specific)
|
|
397
474
|
const { ref: _ariaRef, ...cleanAriaProps } = ariaTooltipProps as Record<string, unknown>;
|
|
398
475
|
|
|
476
|
+
const setRef = (el: HTMLDivElement) => {
|
|
477
|
+
tooltipRef = el;
|
|
478
|
+
props.onTooltipRef(el);
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
// Clean up ref on unmount
|
|
482
|
+
onCleanup(() => {
|
|
483
|
+
props.onTooltipRef(null);
|
|
484
|
+
});
|
|
485
|
+
|
|
399
486
|
return (
|
|
400
487
|
<OverlayContainer>
|
|
401
488
|
<div
|
|
@@ -403,7 +490,7 @@ function TooltipContent(
|
|
|
403
490
|
{...props.contextTooltipProps}
|
|
404
491
|
{...cleanAriaProps}
|
|
405
492
|
role="tooltip"
|
|
406
|
-
ref={
|
|
493
|
+
ref={setRef}
|
|
407
494
|
class={renderProps.class()}
|
|
408
495
|
style={{
|
|
409
496
|
position: 'fixed',
|
|
@@ -412,6 +499,8 @@ function TooltipContent(
|
|
|
412
499
|
...renderProps.style(),
|
|
413
500
|
}}
|
|
414
501
|
data-placement={props.placement}
|
|
502
|
+
data-entering={isEntering() || undefined}
|
|
503
|
+
data-exiting={props.isExiting || undefined}
|
|
415
504
|
>
|
|
416
505
|
{renderProps.renderChildren()}
|
|
417
506
|
</div>
|