@svelte-atoms/core 1.0.0-alpha.30 → 1.0.0-alpha.31
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/README.md +853 -852
- package/dist/components/accordion/accordion-root.svelte +7 -3
- package/dist/components/accordion/accordion.stories.svelte +7 -82
- package/dist/components/accordion/item/accordion-item-body.svelte +44 -42
- package/dist/components/accordion/item/accordion-item-header.svelte +51 -50
- package/dist/components/accordion/item/accordion-item-indicator.svelte +51 -50
- package/dist/components/accordion/item/accordion-item-root.svelte +66 -65
- package/dist/components/accordion/item/bond.svelte.d.ts +2 -0
- package/dist/components/accordion/item/index.d.ts +3 -0
- package/dist/components/accordion/item/index.js +3 -0
- package/dist/components/accordion/item/motion.svelte.d.ts +15 -0
- package/dist/components/accordion/item/motion.svelte.js +30 -0
- package/dist/components/accordion/item/types.d.ts +7 -24
- package/dist/components/alert/alert-close-button.svelte +66 -70
- package/dist/components/alert/alert-description.svelte +42 -42
- package/dist/components/alert/alert-description.svelte.d.ts +3 -6
- package/dist/components/alert/alert-root.svelte +68 -103
- package/dist/components/alert/alert-root.svelte.d.ts +2 -2
- package/dist/components/alert/alert.stories.svelte +400 -400
- package/dist/components/alert/bond.svelte.d.ts +0 -13
- package/dist/components/alert/bond.svelte.js +0 -32
- package/dist/components/alert/types.d.ts +8 -32
- package/dist/components/atom/html-atom.svelte +261 -261
- package/dist/components/avatar/avatar.stories.svelte +22 -22
- package/dist/components/badge/badge.stories.svelte +12 -12
- package/dist/components/badge/badge.svelte +19 -19
- package/dist/components/breadcrumb/breadcrumb.stories.svelte +5 -5
- package/dist/components/button/button.stories.svelte +27 -27
- package/dist/components/calendar/calendar-day.svelte +101 -96
- package/dist/components/calendar/calendar.stories.svelte +26 -26
- package/dist/components/card/card-body.svelte +39 -39
- package/dist/components/card/card-footer.svelte +41 -41
- package/dist/components/card/card-root.svelte +91 -91
- package/dist/components/card/card.stories.svelte +133 -133
- package/dist/components/checkbox/checkbox.stories.svelte +22 -22
- package/dist/components/checkbox/checkbox.svelte +6 -2
- package/dist/components/collapsible/collapsible.stories.svelte +172 -172
- package/dist/components/combobox/atoms.d.ts +1 -1
- package/dist/components/combobox/atoms.js +1 -1
- package/dist/components/combobox/combobox-root.svelte +65 -65
- package/dist/components/combobox/compobox.stories.svelte +51 -51
- package/dist/components/combobox/index.d.ts +1 -0
- package/dist/components/container/container.stories.svelte +20 -20
- package/dist/components/container/container.svelte.d.ts +1 -1
- package/dist/components/datagrid/datagrid.stories.svelte +72 -72
- package/dist/components/datagrid/tr/bond.svelte.d.ts +4 -2
- package/dist/components/datagrid/tr/bond.svelte.js +9 -7
- package/dist/components/datagrid/tr/datagrid-tr.svelte +90 -88
- package/dist/components/date-picker/date-picker-calendar.svelte +67 -67
- package/dist/components/date-picker/date-picker-root.svelte +95 -95
- package/dist/components/date-picker/date-picker.stories.svelte +35 -35
- package/dist/components/dialog/bond.svelte.d.ts +13 -3
- package/dist/components/dialog/bond.svelte.js +66 -5
- package/dist/components/dialog/dialog-content.svelte +44 -62
- package/dist/components/dialog/dialog-root.svelte +91 -110
- package/dist/components/dialog/dialog.stories.svelte +64 -64
- package/dist/components/dialog/motion.svelte.d.ts +13 -0
- package/dist/components/dialog/motion.svelte.js +44 -0
- package/dist/components/drawer/attachments.svelte.d.ts +1 -1
- package/dist/components/drawer/attachments.svelte.js +1 -3
- package/dist/components/drawer/bond.svelte.d.ts +24 -5
- package/dist/components/drawer/bond.svelte.js +77 -11
- package/dist/components/drawer/drawer-content.svelte +6 -14
- package/dist/components/drawer/drawer.stories.svelte +27 -95
- package/dist/components/drawer/index.d.ts +2 -0
- package/dist/components/drawer/index.js +2 -0
- package/dist/components/drawer/motion.d.ts +15 -0
- package/dist/components/drawer/motion.js +28 -0
- package/dist/components/dropdown/atoms.d.ts +1 -1
- package/dist/components/dropdown/atoms.js +1 -1
- package/dist/components/dropdown/bond.svelte.d.ts +5 -1
- package/dist/components/dropdown/dropdown-root.svelte +59 -59
- package/dist/components/dropdown/dropdown.stories.svelte +80 -80
- package/dist/components/dropdown/index.d.ts +1 -0
- package/dist/components/form/form.stories.svelte +96 -96
- package/dist/components/image/image.stories.svelte +20 -20
- package/dist/components/input/input.stories.svelte +35 -35
- package/dist/components/label/label.stories.svelte +15 -15
- package/dist/components/lazy/lazy.stories.svelte +28 -28
- package/dist/components/link/link.stories.svelte +15 -15
- package/dist/components/list/list-item.svelte +20 -20
- package/dist/components/menu/atoms.d.ts +1 -0
- package/dist/components/menu/atoms.js +1 -0
- package/dist/components/menu/index.d.ts +2 -1
- package/dist/components/menu/index.js +1 -1
- package/dist/components/menu/menu-item.svelte +69 -51
- package/dist/components/menu/menu-item.svelte.d.ts +1 -0
- package/dist/components/menu/menu.stories.svelte +33 -33
- package/dist/components/popover/bond.svelte.d.ts +20 -7
- package/dist/components/popover/bond.svelte.js +80 -27
- package/dist/components/popover/motion.d.ts +6 -0
- package/dist/components/popover/motion.js +56 -0
- package/dist/components/popover/popover-arrow.svelte +111 -111
- package/dist/components/popover/popover-content.svelte +34 -72
- package/dist/components/popover/popover-indicator.svelte +44 -44
- package/dist/components/popover/popover-root.svelte +48 -48
- package/dist/components/popover/popover.stories.svelte +3 -3
- package/dist/components/popover/types.d.ts +9 -7
- package/dist/components/portal/active-portal.svelte +29 -22
- package/dist/components/portal/active-portal.svelte.d.ts +2 -9
- package/dist/components/portal/portal-root.svelte +76 -83
- package/dist/components/portal/portal-root.svelte.d.ts +4 -6
- package/dist/components/portal/teleport.svelte +49 -50
- package/dist/components/portal/teleport.svelte.d.ts +3 -4
- package/dist/components/qr-code/qr-code.stories.svelte +18 -18
- package/dist/components/radio/radio-group.stories.svelte +41 -41
- package/dist/components/radio/radio.stories.svelte +17 -17
- package/dist/components/radio/radio.svelte +1 -1
- package/dist/components/radio/types.d.ts +98 -0
- package/dist/components/radio/types.js +2 -0
- package/dist/components/root/root.svelte +13 -30
- package/dist/components/root/root.svelte.d.ts +1 -1
- package/dist/components/scrollable/scrollable-root.svelte.d.ts +2 -2
- package/dist/components/scrollable/scrollable.stories.svelte +116 -116
- package/dist/components/sidebar/index.d.ts +2 -0
- package/dist/components/sidebar/index.js +2 -0
- package/dist/components/sidebar/motion.svelte.d.ts +11 -0
- package/dist/components/sidebar/motion.svelte.js +16 -0
- package/dist/components/sidebar/sidebar-content.svelte +3 -13
- package/dist/components/sidebar/sidebar-root.svelte +39 -39
- package/dist/components/sidebar/sidebar.stories.svelte +43 -43
- package/dist/components/sidebar/types.d.ts +2 -12
- package/dist/components/tabs/tabs.stories.svelte +56 -56
- package/dist/components/textarea/atoms.d.ts +1 -0
- package/dist/components/textarea/atoms.js +1 -0
- package/dist/components/textarea/textarea-input.svelte +9 -6
- package/dist/components/textarea/textarea-root.svelte +9 -9
- package/dist/components/textarea/textarea-root.svelte.d.ts +2 -0
- package/dist/components/tooltip/tooltip-trigger.svelte +2 -2
- package/dist/components/tooltip/tooltip-trigger.svelte.d.ts +1 -0
- package/dist/components/tooltip/tooltip.stories.svelte +32 -32
- package/dist/components/tree/tree.stories.svelte +142 -142
- package/dist/icons/icon-copy.svelte +6 -0
- package/dist/{components/radio/types.svelte.d.ts → icons/icon-copy.svelte.d.ts} +3 -3
- package/dist/utils/markdown-to-llm.d.ts +28 -0
- package/dist/utils/markdown-to-llm.js +76 -0
- package/package.json +1 -2
- package/dist/components/radio/types.svelte +0 -0
- package/llm/composition.md +0 -395
- package/llm/crafting.md +0 -838
- package/llm/motion.md +0 -970
- package/llm/philosophy.md +0 -23
- package/llm/preset-variant-integration.md +0 -516
- package/llm/preset.md +0 -383
- package/llm/styling.md +0 -216
- package/llm/usage.md +0 -46
- package/llm/variants.md +0 -1259
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
<script module>
|
|
2
|
-
import { defineMeta } from '@storybook/addon-svelte-csf';
|
|
3
|
-
import { Menu as AMenu } from '.';
|
|
4
|
-
import { Button } from '../button';
|
|
5
|
-
|
|
6
|
-
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories
|
|
7
|
-
const { Story } = defineMeta({
|
|
8
|
-
title: 'Atoms/Menu',
|
|
9
|
-
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
|
|
10
|
-
|
|
11
|
-
parameters: {
|
|
12
|
-
// More on how to position stories at: https://storybook.js.org/docs/configure/story-layout
|
|
13
|
-
layout: 'fullscreen'
|
|
14
|
-
},
|
|
15
|
-
args: {}
|
|
16
|
-
});
|
|
17
|
-
</script>
|
|
18
|
-
|
|
19
|
-
<script lang="ts">
|
|
20
|
-
let open = $state(false);
|
|
21
|
-
</script>
|
|
22
|
-
|
|
23
|
-
<Story name="Menu" args={{}}>
|
|
24
|
-
<AMenu.Root bind:open offset={4}>
|
|
25
|
-
<AMenu.Trigger base={Button}>Select a language</AMenu.Trigger>
|
|
26
|
-
<AMenu.List>
|
|
27
|
-
<AMenu.Item>Arabic</AMenu.Item>
|
|
28
|
-
<AMenu.Item>English</AMenu.Item>
|
|
29
|
-
<AMenu.Item>Spanish</AMenu.Item>
|
|
30
|
-
<AMenu.Item>Italian</AMenu.Item>
|
|
31
|
-
</AMenu.List>
|
|
32
|
-
</AMenu.Root>
|
|
33
|
-
</Story>
|
|
1
|
+
<script module>
|
|
2
|
+
import { defineMeta } from '@storybook/addon-svelte-csf';
|
|
3
|
+
import { Menu as AMenu } from '.';
|
|
4
|
+
import { Button } from '../button';
|
|
5
|
+
|
|
6
|
+
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories
|
|
7
|
+
const { Story } = defineMeta({
|
|
8
|
+
title: 'Atoms/Menu',
|
|
9
|
+
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
|
|
10
|
+
|
|
11
|
+
parameters: {
|
|
12
|
+
// More on how to position stories at: https://storybook.js.org/docs/configure/story-layout
|
|
13
|
+
layout: 'fullscreen'
|
|
14
|
+
},
|
|
15
|
+
args: {}
|
|
16
|
+
});
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<script lang="ts">
|
|
20
|
+
let open = $state(false);
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<Story name="Menu" args={{}}>
|
|
24
|
+
<AMenu.Root bind:open offset={4}>
|
|
25
|
+
<AMenu.Trigger base={Button}>Select a language</AMenu.Trigger>
|
|
26
|
+
<AMenu.List>
|
|
27
|
+
<AMenu.Item>Arabic</AMenu.Item>
|
|
28
|
+
<AMenu.Item>English</AMenu.Item>
|
|
29
|
+
<AMenu.Item>Spanish</AMenu.Item>
|
|
30
|
+
<AMenu.Item>Italian</AMenu.Item>
|
|
31
|
+
</AMenu.List>
|
|
32
|
+
</AMenu.Root>
|
|
33
|
+
</Story>
|
|
@@ -31,11 +31,15 @@ export type PopoverDomElements = {
|
|
|
31
31
|
arrow: HTMLElement;
|
|
32
32
|
};
|
|
33
33
|
export declare class PopoverBond<Props extends PopoverStateProps = PopoverStateProps, State extends PopoverState<Props> = PopoverState<Props>, Elements extends PopoverDomElements = PopoverDomElements> extends Bond<Props, State, Elements> {
|
|
34
|
-
#private;
|
|
35
34
|
static CONTEXT_KEY: string;
|
|
35
|
+
position: ComputePositionReturn | undefined;
|
|
36
36
|
constructor(state: State);
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
trigger(props?: Record<string, unknown> & {
|
|
38
|
+
onclick?: (ev: PointerEvent) => void;
|
|
39
|
+
onkeydown?: (ev: KeyboardEvent) => void;
|
|
40
|
+
}): {
|
|
41
|
+
onclick: (ev: PointerEvent) => void;
|
|
42
|
+
onkeydown: (ev: KeyboardEvent) => void;
|
|
39
43
|
id: string;
|
|
40
44
|
role: string;
|
|
41
45
|
disabled: boolean | undefined;
|
|
@@ -43,26 +47,35 @@ export declare class PopoverBond<Props extends PopoverStateProps = PopoverStateP
|
|
|
43
47
|
'aria-expanded': boolean;
|
|
44
48
|
'aria-disabled': boolean;
|
|
45
49
|
'aria-controls': string;
|
|
50
|
+
'aria-haspopup': string;
|
|
46
51
|
'data-kind': string;
|
|
47
|
-
onclick: (ev: PointerEvent) => void;
|
|
48
52
|
};
|
|
49
|
-
content(props?: Record<string, unknown>
|
|
53
|
+
content(props?: Record<string, unknown> & {
|
|
54
|
+
onchange?: (node: HTMLElement, position: ComputePositionReturn) => void;
|
|
55
|
+
}): {
|
|
56
|
+
onchange?: (node: HTMLElement, position: ComputePositionReturn) => void;
|
|
50
57
|
id: string;
|
|
51
58
|
role: string;
|
|
52
59
|
'aria-modal': boolean;
|
|
53
60
|
'aria-labelledby': string;
|
|
54
|
-
'aria-
|
|
61
|
+
'aria-hidden': boolean;
|
|
62
|
+
inert: string | undefined;
|
|
63
|
+
tabindex: number;
|
|
55
64
|
'data-atom': string;
|
|
56
65
|
'data-kind': string;
|
|
57
66
|
'data-active': boolean;
|
|
67
|
+
onkeydown: ((ev: KeyboardEvent) => void) | undefined;
|
|
58
68
|
};
|
|
59
69
|
indicator(props?: Record<string, unknown>): {
|
|
60
70
|
id: string;
|
|
61
|
-
'aria-
|
|
71
|
+
'aria-hidden': boolean;
|
|
72
|
+
'aria-live': string;
|
|
62
73
|
'data-kind': string;
|
|
63
74
|
};
|
|
64
75
|
arrow(props?: Record<string, unknown>): {
|
|
65
76
|
id: string;
|
|
77
|
+
role: string;
|
|
78
|
+
'aria-hidden': boolean;
|
|
66
79
|
'data-kind': string;
|
|
67
80
|
};
|
|
68
81
|
share(): this;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { getContext, setContext, untrack } from 'svelte';
|
|
2
2
|
import { createAttachmentKey } from 'svelte/attachments';
|
|
3
3
|
import { autoUpdate, computePosition, arrow, flip, offset } from '@floating-ui/dom';
|
|
4
|
-
import { debounce } from 'es-toolkit';
|
|
5
4
|
import { getElementId, isBrowser } from '../../utils/dom.svelte.js';
|
|
6
5
|
import { Bond, BondState } from '../../shared/bond.svelte.js';
|
|
7
6
|
const POPOVER_ELEMENTS_KIND = {
|
|
@@ -12,13 +11,10 @@ const POPOVER_ELEMENTS_KIND = {
|
|
|
12
11
|
};
|
|
13
12
|
export class PopoverBond extends Bond {
|
|
14
13
|
static CONTEXT_KEY = '@atomic-sv/bonds/popover';
|
|
15
|
-
|
|
14
|
+
position = $state();
|
|
16
15
|
constructor(state) {
|
|
17
16
|
super(state);
|
|
18
17
|
}
|
|
19
|
-
get position() {
|
|
20
|
-
return this.#position;
|
|
21
|
-
}
|
|
22
18
|
trigger(props = {}) {
|
|
23
19
|
const isButtonElement = isBrowser()
|
|
24
20
|
? this.elements.trigger instanceof HTMLButtonElement
|
|
@@ -30,12 +26,13 @@ export class PopoverBond extends Bond {
|
|
|
30
26
|
const overlayId = getElementId(this.id, POPOVER_ELEMENTS_KIND.content);
|
|
31
27
|
return {
|
|
32
28
|
id,
|
|
33
|
-
role: isButtonElement ? '' : 'button',
|
|
29
|
+
role: isButtonElement ? '' : 'button',
|
|
34
30
|
disabled: isButtonElement ? isDisabled : undefined,
|
|
35
|
-
tabindex: isDisabled ? -1 : 0,
|
|
31
|
+
tabindex: isDisabled ? -1 : 0,
|
|
36
32
|
'aria-expanded': isOpen,
|
|
37
33
|
'aria-disabled': isDisabled,
|
|
38
34
|
'aria-controls': overlayId,
|
|
35
|
+
'aria-haspopup': 'dialog',
|
|
39
36
|
'data-kind': kind,
|
|
40
37
|
onclick: (ev) => {
|
|
41
38
|
if (ev.button === 2) {
|
|
@@ -45,17 +42,34 @@ export class PopoverBond extends Bond {
|
|
|
45
42
|
return;
|
|
46
43
|
}
|
|
47
44
|
this.state.toggle();
|
|
45
|
+
props.onclick?.(ev);
|
|
46
|
+
},
|
|
47
|
+
onkeydown: (ev) => {
|
|
48
|
+
if (isDisabled)
|
|
49
|
+
return;
|
|
50
|
+
// Toggle on Enter or Space
|
|
51
|
+
if (ev.key === 'Enter' || ev.key === ' ') {
|
|
52
|
+
ev.preventDefault();
|
|
53
|
+
this.state.toggle();
|
|
54
|
+
props.onkeydown?.(ev);
|
|
55
|
+
}
|
|
56
|
+
// Close on Escape
|
|
57
|
+
else if (ev.key === 'Escape' && isOpen) {
|
|
58
|
+
ev.preventDefault();
|
|
59
|
+
this.state.close();
|
|
60
|
+
props.onkeydown?.(ev);
|
|
61
|
+
}
|
|
48
62
|
},
|
|
49
63
|
...props,
|
|
50
64
|
[createAttachmentKey()]: (node) => {
|
|
51
65
|
this.elements.trigger = node;
|
|
52
|
-
const position = untrack(() => this
|
|
66
|
+
const position = untrack(() => this.position);
|
|
53
67
|
if (!position) {
|
|
54
68
|
const init = async () => {
|
|
55
69
|
popover(this)({
|
|
56
70
|
...props,
|
|
57
|
-
onchange: (
|
|
58
|
-
this
|
|
71
|
+
onchange: (_node, position) => {
|
|
72
|
+
this.position = position;
|
|
59
73
|
}
|
|
60
74
|
});
|
|
61
75
|
const pointerLeaveHandler = () => {
|
|
@@ -79,40 +93,76 @@ export class PopoverBond extends Bond {
|
|
|
79
93
|
const isOpen = this.state?.props?.open ?? false;
|
|
80
94
|
const isDisabled = this.state?.props?.disabled ?? false;
|
|
81
95
|
const isActive = isOpen && !isDisabled;
|
|
96
|
+
// Focus management
|
|
97
|
+
const focusTrap = (ev) => {
|
|
98
|
+
const node = ev.currentTarget;
|
|
99
|
+
if (ev.key === 'Escape') {
|
|
100
|
+
ev.preventDefault();
|
|
101
|
+
this.state.close();
|
|
102
|
+
this.elements.trigger?.focus();
|
|
103
|
+
}
|
|
104
|
+
// Tab trap - keep focus within popover
|
|
105
|
+
else if (ev.key === 'Tab') {
|
|
106
|
+
const focusableElements = node.querySelectorAll('a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])');
|
|
107
|
+
const firstElement = focusableElements[0];
|
|
108
|
+
const lastElement = focusableElements[focusableElements.length - 1];
|
|
109
|
+
if (ev.shiftKey && document.activeElement === firstElement) {
|
|
110
|
+
ev.preventDefault();
|
|
111
|
+
lastElement?.focus();
|
|
112
|
+
}
|
|
113
|
+
else if (!ev.shiftKey && document.activeElement === lastElement) {
|
|
114
|
+
ev.preventDefault();
|
|
115
|
+
firstElement?.focus();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
82
119
|
return {
|
|
83
120
|
id,
|
|
84
|
-
role: 'dialog',
|
|
85
|
-
'aria-modal':
|
|
86
|
-
'aria-labelledby': triggerId,
|
|
87
|
-
'aria-
|
|
121
|
+
role: 'dialog',
|
|
122
|
+
'aria-modal': false,
|
|
123
|
+
'aria-labelledby': triggerId,
|
|
124
|
+
'aria-hidden': !isActive,
|
|
125
|
+
inert: !isActive ? '' : undefined,
|
|
126
|
+
tabindex: -1,
|
|
88
127
|
'data-atom': this.id,
|
|
89
|
-
'data-kind': '
|
|
128
|
+
'data-kind': 'content',
|
|
90
129
|
'data-active': isActive,
|
|
130
|
+
onkeydown: isOpen ? focusTrap : undefined,
|
|
91
131
|
...props,
|
|
92
132
|
[createAttachmentKey()]: (node) => {
|
|
93
133
|
this.elements.content = node;
|
|
94
134
|
if (!this.elements.trigger) {
|
|
95
135
|
return;
|
|
96
136
|
}
|
|
97
|
-
if (!this.state.isOpen)
|
|
137
|
+
if (!this.state.isOpen) {
|
|
98
138
|
return;
|
|
99
|
-
|
|
139
|
+
}
|
|
140
|
+
// Move focus to popover when opened
|
|
141
|
+
setTimeout(() => {
|
|
142
|
+
const firstFocusable = node.querySelector('a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])');
|
|
143
|
+
(firstFocusable || node).focus();
|
|
144
|
+
}, 0);
|
|
145
|
+
const cleanup = popover(this)({
|
|
100
146
|
...props,
|
|
101
147
|
onchange: (node, position) => {
|
|
102
|
-
this
|
|
148
|
+
this.position = position;
|
|
103
149
|
props.onchange?.(node, position);
|
|
104
150
|
}
|
|
105
151
|
}, autoUpdate);
|
|
152
|
+
return () => {
|
|
153
|
+
cleanup?.();
|
|
154
|
+
};
|
|
106
155
|
}
|
|
107
156
|
};
|
|
108
157
|
}
|
|
109
158
|
indicator(props = {}) {
|
|
110
159
|
const kind = POPOVER_ELEMENTS_KIND.indicator;
|
|
111
160
|
const id = getElementId(this.id, kind);
|
|
112
|
-
const
|
|
161
|
+
const isOpen = this.state?.props?.open ?? false;
|
|
113
162
|
return {
|
|
114
163
|
id,
|
|
115
|
-
'aria-
|
|
164
|
+
'aria-hidden': true,
|
|
165
|
+
'aria-live': isOpen ? 'polite' : 'off',
|
|
116
166
|
'data-kind': kind,
|
|
117
167
|
...props,
|
|
118
168
|
[createAttachmentKey()]: (node) => {
|
|
@@ -125,6 +175,8 @@ export class PopoverBond extends Bond {
|
|
|
125
175
|
const id = getElementId(this.id, kind);
|
|
126
176
|
return {
|
|
127
177
|
id: id,
|
|
178
|
+
role: 'presentation',
|
|
179
|
+
'aria-hidden': true,
|
|
128
180
|
'data-kind': kind,
|
|
129
181
|
...props,
|
|
130
182
|
[createAttachmentKey()]: (node) => {
|
|
@@ -167,8 +219,6 @@ function popover(bond) {
|
|
|
167
219
|
return;
|
|
168
220
|
}
|
|
169
221
|
const { content, trigger, arrow: arrowElement } = bond.elements;
|
|
170
|
-
// Set minimum width to match trigger
|
|
171
|
-
content.style.minWidth = `${trigger.clientWidth}px`;
|
|
172
222
|
// Build middleware stack
|
|
173
223
|
const middleware = [
|
|
174
224
|
offset(ofs),
|
|
@@ -183,17 +233,20 @@ function popover(bond) {
|
|
|
183
233
|
}
|
|
184
234
|
// Debounce position change callback
|
|
185
235
|
const onchangeCallback = props.onchange;
|
|
186
|
-
const onchangeDebounced = debounce((node, position) => {
|
|
187
|
-
onchangeCallback?.(node, position);
|
|
188
|
-
}, 1000 / 60 // ~16ms for 60fps
|
|
189
|
-
);
|
|
190
236
|
// Compute position and notify listeners
|
|
191
237
|
const compute = async () => {
|
|
238
|
+
// Wait for next frame to ensure DOM has settled and styles are applied
|
|
239
|
+
// Double requestAnimationFrame - This ensures the browser has completed both layout calculation AND painting, giving us accurate final dimensions
|
|
240
|
+
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
|
192
241
|
const position = await computePosition(trigger, content, {
|
|
193
242
|
placement: placement ?? 'bottom',
|
|
194
243
|
middleware
|
|
195
244
|
});
|
|
196
|
-
|
|
245
|
+
onchangeCallback?.(content, position);
|
|
246
|
+
// Set minimum width to match trigger
|
|
247
|
+
requestAnimationFrame(() => {
|
|
248
|
+
content.style.minWidth = `${trigger.clientWidth}px`;
|
|
249
|
+
});
|
|
197
250
|
};
|
|
198
251
|
// Use auto-update if provided, otherwise compute once
|
|
199
252
|
if (updater) {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { animate } from 'motion';
|
|
2
|
+
import { PopoverBond } from '.';
|
|
3
|
+
import { DURATION } from '../../shared';
|
|
4
|
+
export function animatePopoverContent(params = {}) {
|
|
5
|
+
return (node) => {
|
|
6
|
+
const bond = PopoverBond.get();
|
|
7
|
+
const { duration = DURATION.quick / 1000, delay = 0, ease = 'easeInOut' } = params;
|
|
8
|
+
const isOpen = bond?.state.props.open ?? false;
|
|
9
|
+
const position = bond.position;
|
|
10
|
+
const placement = position?.placement;
|
|
11
|
+
const x = position?.x ?? 0;
|
|
12
|
+
const y = position?.y ?? 0;
|
|
13
|
+
const dy = placement?.startsWith('top') ? -1 : placement?.startsWith('bottom') ? 1 : 0;
|
|
14
|
+
const dx = placement?.startsWith('left') ? -1 : placement?.startsWith('right') ? 1 : 0;
|
|
15
|
+
const offset = bond.state.props.offset;
|
|
16
|
+
const xOffset = dx * offset;
|
|
17
|
+
const yOffset = dy * offset;
|
|
18
|
+
const openAsNumber = +isOpen;
|
|
19
|
+
const deltaArrow = position?.middlewareData?.arrow ? 1 : 0;
|
|
20
|
+
const arrowClientWidth = bond?.elements.arrow?.clientWidth ?? 0;
|
|
21
|
+
const arrowClientHeight = bond?.elements.arrow?.clientHeight ?? 0;
|
|
22
|
+
const getTransformOrigin = () => {
|
|
23
|
+
switch (placement) {
|
|
24
|
+
case 'top':
|
|
25
|
+
case 'top-start':
|
|
26
|
+
case 'top-end':
|
|
27
|
+
return 'bottom';
|
|
28
|
+
case 'bottom':
|
|
29
|
+
case 'bottom-start':
|
|
30
|
+
case 'bottom-end':
|
|
31
|
+
return 'top';
|
|
32
|
+
case 'left':
|
|
33
|
+
case 'left-start':
|
|
34
|
+
case 'left-end':
|
|
35
|
+
return 'right';
|
|
36
|
+
case 'right':
|
|
37
|
+
case 'right-start':
|
|
38
|
+
case 'right-end':
|
|
39
|
+
return 'left';
|
|
40
|
+
default:
|
|
41
|
+
return 'center';
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
const transformOrigin = getTransformOrigin();
|
|
45
|
+
const from = isOpen ? 1 : 0.95;
|
|
46
|
+
animate(node, {
|
|
47
|
+
opacity: openAsNumber,
|
|
48
|
+
y: dy * (!isOpen ? -1 : 0) * (arrowClientHeight + yOffset),
|
|
49
|
+
x: dx * (!isOpen ? -1 : 0) * (arrowClientWidth + xOffset),
|
|
50
|
+
scaleY: dy ? (isOpen ? [from, 1] : [1, 0.8]) : undefined,
|
|
51
|
+
scaleX: dx ? (isOpen ? [from, 1] : [1, 0.8]) : undefined,
|
|
52
|
+
transformOrigin
|
|
53
|
+
}, { duration, delay, ease });
|
|
54
|
+
animate(node, { opacity: +isOpen }, { duration, ease, delay });
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -1,111 +1,111 @@
|
|
|
1
|
-
<script lang="ts" generics="E extends keyof HTMLElementTagNameMap = 'div', B extends Base = Base">
|
|
2
|
-
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
-
import { animate as motion } from 'motion';
|
|
4
|
-
import { HtmlAtom, type Base } from '../atom';
|
|
5
|
-
import { PopoverBond } from './bond.svelte';
|
|
6
|
-
import type { PopoverArrowProps } from './types';
|
|
7
|
-
|
|
8
|
-
type Element = HTMLElementTagNameMap[E];
|
|
9
|
-
|
|
10
|
-
const bond = PopoverBond.get();
|
|
11
|
-
|
|
12
|
-
if (!bond) {
|
|
13
|
-
throw new Error('');
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
let {
|
|
17
|
-
class: klass = '',
|
|
18
|
-
children = undefined,
|
|
19
|
-
onmount = undefined,
|
|
20
|
-
ondestroy = undefined,
|
|
21
|
-
animate = _animate,
|
|
22
|
-
enter = undefined,
|
|
23
|
-
exit = undefined,
|
|
24
|
-
initial = undefined,
|
|
25
|
-
...restProps
|
|
26
|
-
}: PopoverArrowProps<E, B> & HTMLAttributes<Element> = $props();
|
|
27
|
-
|
|
28
|
-
const position = $derived(bond.position);
|
|
29
|
-
const middlewareArrowData = $derived(position?.middlewareData?.arrow);
|
|
30
|
-
const isReady = $derived(!!middlewareArrowData);
|
|
31
|
-
const side = $derived(position?.placement?.split('-')[0] ?? 'top');
|
|
32
|
-
|
|
33
|
-
const arrowProps = $derived({
|
|
34
|
-
...bond.arrow(),
|
|
35
|
-
...restProps
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
// Rotation based on placement side
|
|
39
|
-
const rotation = $derived.by(() => {
|
|
40
|
-
switch (side) {
|
|
41
|
-
case 'top':
|
|
42
|
-
return 180;
|
|
43
|
-
case 'bottom':
|
|
44
|
-
return 0;
|
|
45
|
-
case 'left':
|
|
46
|
-
return 90;
|
|
47
|
-
case 'right':
|
|
48
|
-
return -90;
|
|
49
|
-
default:
|
|
50
|
-
return 0;
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
function _animate(node: HTMLElement) {
|
|
55
|
-
if (!middlewareArrowData) {
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const { x, y } = middlewareArrowData;
|
|
60
|
-
|
|
61
|
-
const isMainAxis = side === 'top' || side === 'bottom';
|
|
62
|
-
|
|
63
|
-
const crossAxisStyle = isMainAxis
|
|
64
|
-
? {
|
|
65
|
-
left: 0
|
|
66
|
-
}
|
|
67
|
-
: {
|
|
68
|
-
top: 0
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
motion(
|
|
72
|
-
node,
|
|
73
|
-
{
|
|
74
|
-
x: x ?? 0,
|
|
75
|
-
y: y ?? 0,
|
|
76
|
-
opacity: 1,
|
|
77
|
-
...crossAxisStyle
|
|
78
|
-
},
|
|
79
|
-
{ duration: 0 }
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
</script>
|
|
83
|
-
|
|
84
|
-
<HtmlAtom
|
|
85
|
-
{bond}
|
|
86
|
-
preset="popover.arrow"
|
|
87
|
-
class={['text-border border-border pointer-events-none absolute opacity-0', '$preset', klass]}
|
|
88
|
-
onmount={onmount?.bind(bond.state)}
|
|
89
|
-
ondestroy={ondestroy?.bind(bond.state)}
|
|
90
|
-
animate={animate?.bind(bond.state)}
|
|
91
|
-
enter={enter?.bind(bond.state)}
|
|
92
|
-
exit={exit?.bind(bond.state)}
|
|
93
|
-
initial={initial?.bind(bond.state)}
|
|
94
|
-
style="{side}: 100%;"
|
|
95
|
-
{...arrowProps}
|
|
96
|
-
>
|
|
97
|
-
{#if children}
|
|
98
|
-
{@render children({ popover: bond })}
|
|
99
|
-
{:else}
|
|
100
|
-
<svg
|
|
101
|
-
width="16"
|
|
102
|
-
height="8"
|
|
103
|
-
viewBox="0 0 16 8"
|
|
104
|
-
fill="none"
|
|
105
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
106
|
-
style="transform: rotate({rotation}deg);"
|
|
107
|
-
>
|
|
108
|
-
<path d="M0 8C2 8 6 4 8 0C10 4 14 8 16 8H0Z" fill="currentColor" />
|
|
109
|
-
</svg>
|
|
110
|
-
{/if}
|
|
111
|
-
</HtmlAtom>
|
|
1
|
+
<script lang="ts" generics="E extends keyof HTMLElementTagNameMap = 'div', B extends Base = Base">
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { animate as motion } from 'motion';
|
|
4
|
+
import { HtmlAtom, type Base } from '../atom';
|
|
5
|
+
import { PopoverBond } from './bond.svelte';
|
|
6
|
+
import type { PopoverArrowProps } from './types';
|
|
7
|
+
|
|
8
|
+
type Element = HTMLElementTagNameMap[E];
|
|
9
|
+
|
|
10
|
+
const bond = PopoverBond.get();
|
|
11
|
+
|
|
12
|
+
if (!bond) {
|
|
13
|
+
throw new Error('');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
class: klass = '',
|
|
18
|
+
children = undefined,
|
|
19
|
+
onmount = undefined,
|
|
20
|
+
ondestroy = undefined,
|
|
21
|
+
animate = _animate,
|
|
22
|
+
enter = undefined,
|
|
23
|
+
exit = undefined,
|
|
24
|
+
initial = undefined,
|
|
25
|
+
...restProps
|
|
26
|
+
}: PopoverArrowProps<E, B> & HTMLAttributes<Element> = $props();
|
|
27
|
+
|
|
28
|
+
const position = $derived(bond.position);
|
|
29
|
+
const middlewareArrowData = $derived(position?.middlewareData?.arrow);
|
|
30
|
+
const isReady = $derived(!!middlewareArrowData);
|
|
31
|
+
const side = $derived(position?.placement?.split('-')[0] ?? 'top');
|
|
32
|
+
|
|
33
|
+
const arrowProps = $derived({
|
|
34
|
+
...bond.arrow(),
|
|
35
|
+
...restProps
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Rotation based on placement side
|
|
39
|
+
const rotation = $derived.by(() => {
|
|
40
|
+
switch (side) {
|
|
41
|
+
case 'top':
|
|
42
|
+
return 180;
|
|
43
|
+
case 'bottom':
|
|
44
|
+
return 0;
|
|
45
|
+
case 'left':
|
|
46
|
+
return 90;
|
|
47
|
+
case 'right':
|
|
48
|
+
return -90;
|
|
49
|
+
default:
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
function _animate(node: HTMLElement) {
|
|
55
|
+
if (!middlewareArrowData) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const { x, y } = middlewareArrowData;
|
|
60
|
+
|
|
61
|
+
const isMainAxis = side === 'top' || side === 'bottom';
|
|
62
|
+
|
|
63
|
+
const crossAxisStyle = isMainAxis
|
|
64
|
+
? {
|
|
65
|
+
left: 0
|
|
66
|
+
}
|
|
67
|
+
: {
|
|
68
|
+
top: 0
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
motion(
|
|
72
|
+
node,
|
|
73
|
+
{
|
|
74
|
+
x: x ?? 0,
|
|
75
|
+
y: y ?? 0,
|
|
76
|
+
opacity: 1,
|
|
77
|
+
...crossAxisStyle
|
|
78
|
+
},
|
|
79
|
+
{ duration: 0 }
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
</script>
|
|
83
|
+
|
|
84
|
+
<HtmlAtom
|
|
85
|
+
{bond}
|
|
86
|
+
preset="popover.arrow"
|
|
87
|
+
class={['text-border border-border pointer-events-none absolute opacity-0', '$preset', klass]}
|
|
88
|
+
onmount={onmount?.bind(bond.state)}
|
|
89
|
+
ondestroy={ondestroy?.bind(bond.state)}
|
|
90
|
+
animate={animate?.bind(bond.state)}
|
|
91
|
+
enter={enter?.bind(bond.state)}
|
|
92
|
+
exit={exit?.bind(bond.state)}
|
|
93
|
+
initial={initial?.bind(bond.state)}
|
|
94
|
+
style="{side}: 100%;"
|
|
95
|
+
{...arrowProps}
|
|
96
|
+
>
|
|
97
|
+
{#if children}
|
|
98
|
+
{@render children({ popover: bond })}
|
|
99
|
+
{:else}
|
|
100
|
+
<svg
|
|
101
|
+
width="16"
|
|
102
|
+
height="8"
|
|
103
|
+
viewBox="0 0 16 8"
|
|
104
|
+
fill="none"
|
|
105
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
106
|
+
style="transform: rotate({rotation}deg);"
|
|
107
|
+
>
|
|
108
|
+
<path d="M0 8C2 8 6 4 8 0C10 4 14 8 16 8H0Z" fill="currentColor" />
|
|
109
|
+
</svg>
|
|
110
|
+
{/if}
|
|
111
|
+
</HtmlAtom>
|