@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
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared SelectionIndicator primitive for selected collection items.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { type JSX, createContext, createMemo, splitProps, useContext, Show } from "solid-js";
|
|
6
|
+
import {
|
|
7
|
+
type RenderChildren,
|
|
8
|
+
type ClassNameOrFunction,
|
|
9
|
+
type StyleOrFunction,
|
|
10
|
+
type SlotProps,
|
|
11
|
+
useRenderProps,
|
|
12
|
+
} from "./utils";
|
|
13
|
+
import { SharedElement, useHasSharedElementTransitionScope } from "./SharedElementTransition";
|
|
14
|
+
|
|
15
|
+
export interface SelectionIndicatorContextValue {
|
|
16
|
+
isSelected: () => boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const SelectionIndicatorContext = createContext<SelectionIndicatorContextValue | null>(null);
|
|
20
|
+
|
|
21
|
+
export interface SelectionIndicatorRenderProps {
|
|
22
|
+
/** Whether the parent item is selected. */
|
|
23
|
+
isSelected: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface SelectionIndicatorProps
|
|
27
|
+
extends SlotProps, Omit<JSX.HTMLAttributes<HTMLSpanElement>, "class" | "style" | "children"> {
|
|
28
|
+
/** Optional controlled selected state override. */
|
|
29
|
+
isSelected?: boolean;
|
|
30
|
+
/** Whether to keep mounted when not selected. */
|
|
31
|
+
shouldForceMount?: boolean;
|
|
32
|
+
/** The children content. */
|
|
33
|
+
children?: RenderChildren<SelectionIndicatorRenderProps>;
|
|
34
|
+
/** The CSS className for the element. */
|
|
35
|
+
class?: ClassNameOrFunction<SelectionIndicatorRenderProps>;
|
|
36
|
+
/** The inline style for the element. */
|
|
37
|
+
style?: StyleOrFunction<SelectionIndicatorRenderProps>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* SelectionIndicator renders when its parent item is selected.
|
|
42
|
+
*/
|
|
43
|
+
export function SelectionIndicator(props: SelectionIndicatorProps): JSX.Element {
|
|
44
|
+
const [local, domProps] = splitProps(props, [
|
|
45
|
+
"isSelected",
|
|
46
|
+
"shouldForceMount",
|
|
47
|
+
"children",
|
|
48
|
+
"class",
|
|
49
|
+
"style",
|
|
50
|
+
"slot",
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
const context = useContext(SelectionIndicatorContext);
|
|
54
|
+
const hasSharedElementScope = useHasSharedElementTransitionScope();
|
|
55
|
+
const isSelected = () => local.isSelected ?? context?.isSelected() ?? false;
|
|
56
|
+
const isVisible = () => local.shouldForceMount || isSelected();
|
|
57
|
+
|
|
58
|
+
const renderValues = createMemo<SelectionIndicatorRenderProps>(() => ({
|
|
59
|
+
isSelected: isSelected(),
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
const renderProps = useRenderProps(
|
|
63
|
+
{
|
|
64
|
+
children: local.children,
|
|
65
|
+
class: local.class,
|
|
66
|
+
style: local.style,
|
|
67
|
+
defaultClassName: "solidaria-SelectionIndicator",
|
|
68
|
+
},
|
|
69
|
+
renderValues,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const sharedElementProps = createMemo(() => {
|
|
73
|
+
const { ref: _ref, ...rest } = domProps as JSX.HTMLAttributes<HTMLSpanElement> & {
|
|
74
|
+
ref?: unknown;
|
|
75
|
+
};
|
|
76
|
+
return rest;
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
if (hasSharedElementScope) {
|
|
80
|
+
return (
|
|
81
|
+
<SharedElement
|
|
82
|
+
{...sharedElementProps()}
|
|
83
|
+
name="SelectionIndicator"
|
|
84
|
+
isVisible={isVisible()}
|
|
85
|
+
aria-hidden="true"
|
|
86
|
+
class={renderProps.class()}
|
|
87
|
+
style={renderProps.style()}
|
|
88
|
+
data-selected={isSelected() || undefined}
|
|
89
|
+
>
|
|
90
|
+
{renderProps.renderChildren()}
|
|
91
|
+
</SharedElement>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<Show when={isVisible()}>
|
|
97
|
+
<span
|
|
98
|
+
{...domProps}
|
|
99
|
+
aria-hidden="true"
|
|
100
|
+
class={renderProps.class()}
|
|
101
|
+
style={renderProps.style()}
|
|
102
|
+
data-selected={isSelected() || undefined}
|
|
103
|
+
>
|
|
104
|
+
{renderProps.renderChildren()}
|
|
105
|
+
</span>
|
|
106
|
+
</Show>
|
|
107
|
+
);
|
|
108
|
+
}
|
package/src/Separator.tsx
CHANGED
|
@@ -5,51 +5,42 @@
|
|
|
5
5
|
* Port of react-aria-components/src/Separator.tsx
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
createContext,
|
|
11
|
-
createMemo,
|
|
12
|
-
splitProps,
|
|
13
|
-
} from 'solid-js';
|
|
14
|
-
import { Dynamic } from 'solid-js/web';
|
|
8
|
+
import { type JSX, createContext, createMemo, splitProps } from "solid-js";
|
|
9
|
+
import { Dynamic } from "solid-js/web";
|
|
15
10
|
import {
|
|
16
11
|
createSeparator,
|
|
17
12
|
type AriaSeparatorProps,
|
|
18
13
|
type Orientation,
|
|
19
|
-
} from
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
} from './utils';
|
|
14
|
+
} from "@proyecto-viviana/solidaria";
|
|
15
|
+
import { type SlotProps, filterDOMProps } from "./utils";
|
|
16
|
+
|
|
17
|
+
type RefLike<T> = ((el: T) => void) | { current?: T | null } | undefined;
|
|
24
18
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
19
|
+
function assignRef<T>(ref: RefLike<T>, el: T): void {
|
|
20
|
+
if (!ref) return;
|
|
21
|
+
if (typeof ref === "function") {
|
|
22
|
+
ref(el);
|
|
23
|
+
} else {
|
|
24
|
+
ref.current = el;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
28
27
|
|
|
29
28
|
export interface SeparatorRenderProps {
|
|
30
29
|
/** The orientation of the separator. */
|
|
31
30
|
orientation: Orientation;
|
|
32
31
|
}
|
|
33
32
|
|
|
34
|
-
export interface SeparatorProps
|
|
35
|
-
extends AriaSeparatorProps,
|
|
36
|
-
SlotProps {
|
|
33
|
+
export interface SeparatorProps extends AriaSeparatorProps, SlotProps {
|
|
37
34
|
/** The CSS className for the element. A function may be provided to receive render props. */
|
|
38
35
|
class?: string | ((renderProps: SeparatorRenderProps) => string);
|
|
39
36
|
/** The inline style for the element. A function may be provided to receive render props. */
|
|
40
37
|
style?: JSX.CSSProperties | ((renderProps: SeparatorRenderProps) => JSX.CSSProperties);
|
|
38
|
+
/** Ref for the underlying separator element. */
|
|
39
|
+
ref?: RefLike<HTMLElement>;
|
|
41
40
|
}
|
|
42
41
|
|
|
43
|
-
// ============================================
|
|
44
|
-
// CONTEXT
|
|
45
|
-
// ============================================
|
|
46
|
-
|
|
47
42
|
export const SeparatorContext = createContext<SeparatorProps | null>(null);
|
|
48
43
|
|
|
49
|
-
// ============================================
|
|
50
|
-
// SEPARATOR COMPONENT
|
|
51
|
-
// ============================================
|
|
52
|
-
|
|
53
44
|
/**
|
|
54
45
|
* A separator is a visual divider between two groups of content,
|
|
55
46
|
* e.g. groups of menu items or sections of a page.
|
|
@@ -66,55 +57,61 @@ export const SeparatorContext = createContext<SeparatorProps | null>(null);
|
|
|
66
57
|
* ```
|
|
67
58
|
*/
|
|
68
59
|
export function Separator(props: SeparatorProps): JSX.Element {
|
|
69
|
-
const [local, ariaProps] = splitProps(props, [
|
|
70
|
-
'class',
|
|
71
|
-
'style',
|
|
72
|
-
'slot',
|
|
73
|
-
]);
|
|
60
|
+
const [local, ariaProps] = splitProps(props, ["class", "style", "ref", "slot"]);
|
|
74
61
|
|
|
75
|
-
// Determine the element type
|
|
76
62
|
const elementType = createMemo(() => {
|
|
77
|
-
let element = ariaProps.elementType ||
|
|
63
|
+
let element = ariaProps.elementType || "hr";
|
|
78
64
|
// If vertical and using hr, switch to div since hr is inherently horizontal
|
|
79
|
-
if (element ===
|
|
80
|
-
element =
|
|
65
|
+
if (element === "hr" && ariaProps.orientation === "vertical") {
|
|
66
|
+
element = "div";
|
|
81
67
|
}
|
|
82
68
|
return element;
|
|
83
69
|
});
|
|
84
70
|
|
|
85
|
-
// Create separator aria props
|
|
86
71
|
const separatorAria = createSeparator({
|
|
87
|
-
get orientation() {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
get
|
|
91
|
-
|
|
72
|
+
get orientation() {
|
|
73
|
+
return ariaProps.orientation;
|
|
74
|
+
},
|
|
75
|
+
get elementType() {
|
|
76
|
+
return ariaProps.elementType;
|
|
77
|
+
},
|
|
78
|
+
get "aria-label"() {
|
|
79
|
+
return ariaProps["aria-label"];
|
|
80
|
+
},
|
|
81
|
+
get "aria-labelledby"() {
|
|
82
|
+
return ariaProps["aria-labelledby"];
|
|
83
|
+
},
|
|
84
|
+
get "aria-describedby"() {
|
|
85
|
+
return ariaProps["aria-describedby"];
|
|
86
|
+
},
|
|
87
|
+
get "aria-details"() {
|
|
88
|
+
return ariaProps["aria-details"];
|
|
89
|
+
},
|
|
90
|
+
get id() {
|
|
91
|
+
return ariaProps.id;
|
|
92
|
+
},
|
|
92
93
|
});
|
|
93
94
|
|
|
94
|
-
// Render props values
|
|
95
95
|
const renderValues = createMemo<SeparatorRenderProps>(() => ({
|
|
96
|
-
orientation: ariaProps.orientation ??
|
|
96
|
+
orientation: ariaProps.orientation ?? "horizontal",
|
|
97
97
|
}));
|
|
98
98
|
|
|
99
|
-
// Resolve class
|
|
100
99
|
const resolvedClass = createMemo(() => {
|
|
101
100
|
const cls = local.class;
|
|
102
|
-
if (typeof cls ===
|
|
101
|
+
if (typeof cls === "function") {
|
|
103
102
|
return cls(renderValues());
|
|
104
103
|
}
|
|
105
|
-
return cls ??
|
|
104
|
+
return cls ?? "solidaria-Separator";
|
|
106
105
|
});
|
|
107
106
|
|
|
108
|
-
// Resolve style
|
|
109
107
|
const resolvedStyle = createMemo(() => {
|
|
110
108
|
const style = local.style;
|
|
111
|
-
if (typeof style ===
|
|
109
|
+
if (typeof style === "function") {
|
|
112
110
|
return style(renderValues());
|
|
113
111
|
}
|
|
114
112
|
return style;
|
|
115
113
|
});
|
|
116
114
|
|
|
117
|
-
// Filter DOM props
|
|
118
115
|
const domProps = createMemo(() => filterDOMProps(ariaProps, { global: true }));
|
|
119
116
|
|
|
120
117
|
return (
|
|
@@ -122,6 +119,7 @@ export function Separator(props: SeparatorProps): JSX.Element {
|
|
|
122
119
|
component={elementType()}
|
|
123
120
|
{...domProps()}
|
|
124
121
|
{...separatorAria.separatorProps}
|
|
122
|
+
ref={(el: HTMLElement) => assignRef(local.ref, el)}
|
|
125
123
|
class={resolvedClass()}
|
|
126
124
|
style={resolvedStyle()}
|
|
127
125
|
slot={local.slot}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SharedElementTransition primitives for solidaria-components.
|
|
3
|
+
*
|
|
4
|
+
* Provides FLIP-based shared element animations when elements move between
|
|
5
|
+
* parents within a scope. Captures geometry snapshots on unmount and applies
|
|
6
|
+
* transition animations on mount.
|
|
7
|
+
*
|
|
8
|
+
* Parity target: react-aria-components/src/SharedElementTransition.tsx
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
type JSX,
|
|
13
|
+
createContext,
|
|
14
|
+
createEffect,
|
|
15
|
+
createMemo,
|
|
16
|
+
createSignal,
|
|
17
|
+
onCleanup,
|
|
18
|
+
splitProps,
|
|
19
|
+
useContext,
|
|
20
|
+
Show,
|
|
21
|
+
on,
|
|
22
|
+
} from "solid-js";
|
|
23
|
+
import {
|
|
24
|
+
type ClassNameOrFunction,
|
|
25
|
+
type StyleOrFunction,
|
|
26
|
+
type RenderChildren,
|
|
27
|
+
useRenderProps,
|
|
28
|
+
filterDOMProps,
|
|
29
|
+
} from "./utils";
|
|
30
|
+
|
|
31
|
+
type SharedElementLifecycle = "hidden" | "entering" | "visible" | "exiting";
|
|
32
|
+
|
|
33
|
+
/** Safe wrapper — jsdom doesn't implement the Web Animations API. */
|
|
34
|
+
function getAnimations(el: HTMLElement): Animation[] {
|
|
35
|
+
return typeof el.getAnimations === "function" ? el.getAnimations() : [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface Snapshot {
|
|
39
|
+
rect: DOMRect;
|
|
40
|
+
style: [string, string][];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface SharedElementScope {
|
|
44
|
+
snapshots: { [name: string]: Snapshot };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const SharedElementContext = createContext<SharedElementScope | null>(null);
|
|
48
|
+
|
|
49
|
+
export function useHasSharedElementTransitionScope(): boolean {
|
|
50
|
+
return useContext(SharedElementContext) != null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface SharedElementTransitionProps {
|
|
54
|
+
children?: JSX.Element;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A scope for SharedElements, which animate between parents.
|
|
59
|
+
*/
|
|
60
|
+
export function SharedElementTransition(props: SharedElementTransitionProps): JSX.Element {
|
|
61
|
+
const scope: SharedElementScope = {
|
|
62
|
+
snapshots: {},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<SharedElementContext.Provider value={scope}>{props.children}</SharedElementContext.Provider>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface SharedElementRenderProps {
|
|
71
|
+
isEntering: boolean;
|
|
72
|
+
isExiting: boolean;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface SharedElementPropsBase extends Omit<
|
|
76
|
+
JSX.HTMLAttributes<HTMLDivElement>,
|
|
77
|
+
"children" | "class" | "style" | "ref"
|
|
78
|
+
> {
|
|
79
|
+
children?: RenderChildren<SharedElementRenderProps>;
|
|
80
|
+
class?: ClassNameOrFunction<SharedElementRenderProps>;
|
|
81
|
+
style?: StyleOrFunction<SharedElementRenderProps>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface SharedElementProps extends SharedElementPropsBase {
|
|
85
|
+
name: string;
|
|
86
|
+
isVisible?: boolean;
|
|
87
|
+
ref?: ((el: HTMLDivElement) => void) | { current?: HTMLDivElement };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* An element that animates between its old and new position when moving
|
|
92
|
+
* between parents within a SharedElementTransition scope.
|
|
93
|
+
*/
|
|
94
|
+
export function SharedElement(props: SharedElementProps): JSX.Element | null {
|
|
95
|
+
const scope = useContext(SharedElementContext);
|
|
96
|
+
if (!scope) {
|
|
97
|
+
throw new Error("<SharedElement> must be rendered inside a <SharedElementTransition>");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const [local, domProps] = splitProps(props, [
|
|
101
|
+
"name",
|
|
102
|
+
"isVisible",
|
|
103
|
+
"children",
|
|
104
|
+
"class",
|
|
105
|
+
"style",
|
|
106
|
+
"ref",
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
const [lifecycle, setLifecycle] = createSignal<SharedElementLifecycle>(
|
|
110
|
+
local.isVisible === false ? "hidden" : "visible",
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
let elementRef: HTMLDivElement | undefined;
|
|
114
|
+
let frame: number | undefined;
|
|
115
|
+
|
|
116
|
+
const setRef = (el: HTMLDivElement) => {
|
|
117
|
+
elementRef = el;
|
|
118
|
+
// Forward ref to consumer
|
|
119
|
+
const userRef = local.ref;
|
|
120
|
+
if (typeof userRef === "function") {
|
|
121
|
+
userRef(el);
|
|
122
|
+
} else if (userRef !== undefined) {
|
|
123
|
+
userRef.current = el;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Handle visibility transitions with FLIP animation
|
|
128
|
+
createEffect(
|
|
129
|
+
on(
|
|
130
|
+
() => local.isVisible !== false,
|
|
131
|
+
(isVisible) => {
|
|
132
|
+
const name = local.name;
|
|
133
|
+
const element = elementRef;
|
|
134
|
+
|
|
135
|
+
if (frame != null) {
|
|
136
|
+
cancelAnimationFrame(frame);
|
|
137
|
+
frame = undefined;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (isVisible && element) {
|
|
141
|
+
const prevSnapshot = scope.snapshots[name];
|
|
142
|
+
|
|
143
|
+
if (prevSnapshot) {
|
|
144
|
+
// FLIP: Element is transitioning from a previous instance.
|
|
145
|
+
setLifecycle("visible");
|
|
146
|
+
const animations = getAnimations(element);
|
|
147
|
+
|
|
148
|
+
// Set properties to animate from.
|
|
149
|
+
const values = prevSnapshot.style.map(([property, prevValue]) => {
|
|
150
|
+
const value = element.style.getPropertyValue(property);
|
|
151
|
+
if (property === "translate") {
|
|
152
|
+
const prevRect = prevSnapshot.rect;
|
|
153
|
+
const currentRect = element.getBoundingClientRect();
|
|
154
|
+
const deltaX = prevRect.left - currentRect.left;
|
|
155
|
+
const deltaY = prevRect.top - currentRect.top;
|
|
156
|
+
element.style.setProperty("translate", `${deltaX}px ${deltaY}px`);
|
|
157
|
+
} else {
|
|
158
|
+
element.style.setProperty(property, prevValue);
|
|
159
|
+
}
|
|
160
|
+
return [property, value] as [string, string];
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Cancel any new animations triggered by these properties.
|
|
164
|
+
for (const a of getAnimations(element)) {
|
|
165
|
+
if (!animations.includes(a)) {
|
|
166
|
+
a.cancel();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Remove overrides after one frame to animate to the current values.
|
|
171
|
+
frame = requestAnimationFrame(() => {
|
|
172
|
+
frame = undefined;
|
|
173
|
+
for (const [property, value] of values) {
|
|
174
|
+
element.style.setProperty(property, value);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
delete scope.snapshots[name];
|
|
179
|
+
} else {
|
|
180
|
+
// No previous instance exists, apply the entering state.
|
|
181
|
+
queueMicrotask(() => setLifecycle("entering"));
|
|
182
|
+
frame = requestAnimationFrame(() => {
|
|
183
|
+
frame = undefined;
|
|
184
|
+
setLifecycle("visible");
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
} else if (!isVisible && element) {
|
|
188
|
+
// Wait a microtask to check if a snapshot still exists (meaning no new
|
|
189
|
+
// SharedElement consumed it), then enter exiting state.
|
|
190
|
+
queueMicrotask(() => {
|
|
191
|
+
if (scope.snapshots[name]) {
|
|
192
|
+
delete scope.snapshots[name];
|
|
193
|
+
setLifecycle("exiting");
|
|
194
|
+
// Wait for animations to finish before hiding.
|
|
195
|
+
Promise.all(getAnimations(element).map((a) => a.finished))
|
|
196
|
+
.then(() => setLifecycle("hidden"))
|
|
197
|
+
.catch(() => {});
|
|
198
|
+
} else {
|
|
199
|
+
// Snapshot was consumed by another instance, unmount immediately.
|
|
200
|
+
setLifecycle("hidden");
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
} else if (isVisible) {
|
|
204
|
+
// Element not yet in DOM, entering fresh
|
|
205
|
+
setLifecycle("entering");
|
|
206
|
+
frame = requestAnimationFrame(() => {
|
|
207
|
+
frame = undefined;
|
|
208
|
+
setLifecycle("visible");
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
),
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
// Capture snapshot on cleanup (unmount)
|
|
216
|
+
onCleanup(() => {
|
|
217
|
+
if (frame != null) {
|
|
218
|
+
cancelAnimationFrame(frame);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const element = elementRef;
|
|
222
|
+
if (element && element.isConnected && !element.hasAttribute("data-exiting")) {
|
|
223
|
+
// Store a snapshot of the rectangle and computed style for transitioning properties.
|
|
224
|
+
const style = window.getComputedStyle(element);
|
|
225
|
+
if (style.transitionProperty !== "none") {
|
|
226
|
+
const transitionProperty = style.transitionProperty.split(/\s*,\s*/);
|
|
227
|
+
scope.snapshots[local.name] = {
|
|
228
|
+
rect: element.getBoundingClientRect(),
|
|
229
|
+
style: transitionProperty.map((property) => [property, style.getPropertyValue(property)]),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const renderProps = useRenderProps(
|
|
236
|
+
{
|
|
237
|
+
children: local.children,
|
|
238
|
+
class: local.class,
|
|
239
|
+
style: local.style,
|
|
240
|
+
defaultClassName: "solidaria-SharedElement",
|
|
241
|
+
},
|
|
242
|
+
() => ({
|
|
243
|
+
isEntering: lifecycle() === "entering",
|
|
244
|
+
isExiting: lifecycle() === "exiting",
|
|
245
|
+
}),
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
const filteredDomProps = createMemo(() => filterDOMProps(domProps, { global: true }));
|
|
249
|
+
|
|
250
|
+
return (
|
|
251
|
+
<Show when={lifecycle() !== "hidden"}>
|
|
252
|
+
<div
|
|
253
|
+
ref={setRef}
|
|
254
|
+
{...filteredDomProps()}
|
|
255
|
+
class={renderProps.class()}
|
|
256
|
+
style={renderProps.style()}
|
|
257
|
+
data-entering={lifecycle() === "entering" || undefined}
|
|
258
|
+
data-exiting={lifecycle() === "exiting" || undefined}
|
|
259
|
+
>
|
|
260
|
+
{renderProps.renderChildren()}
|
|
261
|
+
</div>
|
|
262
|
+
</Show>
|
|
263
|
+
);
|
|
264
|
+
}
|