@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.
Files changed (147) hide show
  1. package/README.md +853 -852
  2. package/dist/components/accordion/accordion-root.svelte +7 -3
  3. package/dist/components/accordion/accordion.stories.svelte +7 -82
  4. package/dist/components/accordion/item/accordion-item-body.svelte +44 -42
  5. package/dist/components/accordion/item/accordion-item-header.svelte +51 -50
  6. package/dist/components/accordion/item/accordion-item-indicator.svelte +51 -50
  7. package/dist/components/accordion/item/accordion-item-root.svelte +66 -65
  8. package/dist/components/accordion/item/bond.svelte.d.ts +2 -0
  9. package/dist/components/accordion/item/index.d.ts +3 -0
  10. package/dist/components/accordion/item/index.js +3 -0
  11. package/dist/components/accordion/item/motion.svelte.d.ts +15 -0
  12. package/dist/components/accordion/item/motion.svelte.js +30 -0
  13. package/dist/components/accordion/item/types.d.ts +7 -24
  14. package/dist/components/alert/alert-close-button.svelte +66 -70
  15. package/dist/components/alert/alert-description.svelte +42 -42
  16. package/dist/components/alert/alert-description.svelte.d.ts +3 -6
  17. package/dist/components/alert/alert-root.svelte +68 -103
  18. package/dist/components/alert/alert-root.svelte.d.ts +2 -2
  19. package/dist/components/alert/alert.stories.svelte +400 -400
  20. package/dist/components/alert/bond.svelte.d.ts +0 -13
  21. package/dist/components/alert/bond.svelte.js +0 -32
  22. package/dist/components/alert/types.d.ts +8 -32
  23. package/dist/components/atom/html-atom.svelte +261 -261
  24. package/dist/components/avatar/avatar.stories.svelte +22 -22
  25. package/dist/components/badge/badge.stories.svelte +12 -12
  26. package/dist/components/badge/badge.svelte +19 -19
  27. package/dist/components/breadcrumb/breadcrumb.stories.svelte +5 -5
  28. package/dist/components/button/button.stories.svelte +27 -27
  29. package/dist/components/calendar/calendar-day.svelte +101 -96
  30. package/dist/components/calendar/calendar.stories.svelte +26 -26
  31. package/dist/components/card/card-body.svelte +39 -39
  32. package/dist/components/card/card-footer.svelte +41 -41
  33. package/dist/components/card/card-root.svelte +91 -91
  34. package/dist/components/card/card.stories.svelte +133 -133
  35. package/dist/components/checkbox/checkbox.stories.svelte +22 -22
  36. package/dist/components/checkbox/checkbox.svelte +6 -2
  37. package/dist/components/collapsible/collapsible.stories.svelte +172 -172
  38. package/dist/components/combobox/atoms.d.ts +1 -1
  39. package/dist/components/combobox/atoms.js +1 -1
  40. package/dist/components/combobox/combobox-root.svelte +65 -65
  41. package/dist/components/combobox/compobox.stories.svelte +51 -51
  42. package/dist/components/combobox/index.d.ts +1 -0
  43. package/dist/components/container/container.stories.svelte +20 -20
  44. package/dist/components/container/container.svelte.d.ts +1 -1
  45. package/dist/components/datagrid/datagrid.stories.svelte +72 -72
  46. package/dist/components/datagrid/tr/bond.svelte.d.ts +4 -2
  47. package/dist/components/datagrid/tr/bond.svelte.js +9 -7
  48. package/dist/components/datagrid/tr/datagrid-tr.svelte +90 -88
  49. package/dist/components/date-picker/date-picker-calendar.svelte +67 -67
  50. package/dist/components/date-picker/date-picker-root.svelte +95 -95
  51. package/dist/components/date-picker/date-picker.stories.svelte +35 -35
  52. package/dist/components/dialog/bond.svelte.d.ts +13 -3
  53. package/dist/components/dialog/bond.svelte.js +66 -5
  54. package/dist/components/dialog/dialog-content.svelte +44 -62
  55. package/dist/components/dialog/dialog-root.svelte +91 -110
  56. package/dist/components/dialog/dialog.stories.svelte +64 -64
  57. package/dist/components/dialog/motion.svelte.d.ts +13 -0
  58. package/dist/components/dialog/motion.svelte.js +44 -0
  59. package/dist/components/drawer/attachments.svelte.d.ts +1 -1
  60. package/dist/components/drawer/attachments.svelte.js +1 -3
  61. package/dist/components/drawer/bond.svelte.d.ts +24 -5
  62. package/dist/components/drawer/bond.svelte.js +77 -11
  63. package/dist/components/drawer/drawer-content.svelte +6 -14
  64. package/dist/components/drawer/drawer.stories.svelte +27 -95
  65. package/dist/components/drawer/index.d.ts +2 -0
  66. package/dist/components/drawer/index.js +2 -0
  67. package/dist/components/drawer/motion.d.ts +15 -0
  68. package/dist/components/drawer/motion.js +28 -0
  69. package/dist/components/dropdown/atoms.d.ts +1 -1
  70. package/dist/components/dropdown/atoms.js +1 -1
  71. package/dist/components/dropdown/bond.svelte.d.ts +5 -1
  72. package/dist/components/dropdown/dropdown-root.svelte +59 -59
  73. package/dist/components/dropdown/dropdown.stories.svelte +80 -80
  74. package/dist/components/dropdown/index.d.ts +1 -0
  75. package/dist/components/form/form.stories.svelte +96 -96
  76. package/dist/components/image/image.stories.svelte +20 -20
  77. package/dist/components/input/input.stories.svelte +35 -35
  78. package/dist/components/label/label.stories.svelte +15 -15
  79. package/dist/components/lazy/lazy.stories.svelte +28 -28
  80. package/dist/components/link/link.stories.svelte +15 -15
  81. package/dist/components/list/list-item.svelte +20 -20
  82. package/dist/components/menu/atoms.d.ts +1 -0
  83. package/dist/components/menu/atoms.js +1 -0
  84. package/dist/components/menu/index.d.ts +2 -1
  85. package/dist/components/menu/index.js +1 -1
  86. package/dist/components/menu/menu-item.svelte +69 -51
  87. package/dist/components/menu/menu-item.svelte.d.ts +1 -0
  88. package/dist/components/menu/menu.stories.svelte +33 -33
  89. package/dist/components/popover/bond.svelte.d.ts +20 -7
  90. package/dist/components/popover/bond.svelte.js +80 -27
  91. package/dist/components/popover/motion.d.ts +6 -0
  92. package/dist/components/popover/motion.js +56 -0
  93. package/dist/components/popover/popover-arrow.svelte +111 -111
  94. package/dist/components/popover/popover-content.svelte +34 -72
  95. package/dist/components/popover/popover-indicator.svelte +44 -44
  96. package/dist/components/popover/popover-root.svelte +48 -48
  97. package/dist/components/popover/popover.stories.svelte +3 -3
  98. package/dist/components/popover/types.d.ts +9 -7
  99. package/dist/components/portal/active-portal.svelte +29 -22
  100. package/dist/components/portal/active-portal.svelte.d.ts +2 -9
  101. package/dist/components/portal/portal-root.svelte +76 -83
  102. package/dist/components/portal/portal-root.svelte.d.ts +4 -6
  103. package/dist/components/portal/teleport.svelte +49 -50
  104. package/dist/components/portal/teleport.svelte.d.ts +3 -4
  105. package/dist/components/qr-code/qr-code.stories.svelte +18 -18
  106. package/dist/components/radio/radio-group.stories.svelte +41 -41
  107. package/dist/components/radio/radio.stories.svelte +17 -17
  108. package/dist/components/radio/radio.svelte +1 -1
  109. package/dist/components/radio/types.d.ts +98 -0
  110. package/dist/components/radio/types.js +2 -0
  111. package/dist/components/root/root.svelte +13 -30
  112. package/dist/components/root/root.svelte.d.ts +1 -1
  113. package/dist/components/scrollable/scrollable-root.svelte.d.ts +2 -2
  114. package/dist/components/scrollable/scrollable.stories.svelte +116 -116
  115. package/dist/components/sidebar/index.d.ts +2 -0
  116. package/dist/components/sidebar/index.js +2 -0
  117. package/dist/components/sidebar/motion.svelte.d.ts +11 -0
  118. package/dist/components/sidebar/motion.svelte.js +16 -0
  119. package/dist/components/sidebar/sidebar-content.svelte +3 -13
  120. package/dist/components/sidebar/sidebar-root.svelte +39 -39
  121. package/dist/components/sidebar/sidebar.stories.svelte +43 -43
  122. package/dist/components/sidebar/types.d.ts +2 -12
  123. package/dist/components/tabs/tabs.stories.svelte +56 -56
  124. package/dist/components/textarea/atoms.d.ts +1 -0
  125. package/dist/components/textarea/atoms.js +1 -0
  126. package/dist/components/textarea/textarea-input.svelte +9 -6
  127. package/dist/components/textarea/textarea-root.svelte +9 -9
  128. package/dist/components/textarea/textarea-root.svelte.d.ts +2 -0
  129. package/dist/components/tooltip/tooltip-trigger.svelte +2 -2
  130. package/dist/components/tooltip/tooltip-trigger.svelte.d.ts +1 -0
  131. package/dist/components/tooltip/tooltip.stories.svelte +32 -32
  132. package/dist/components/tree/tree.stories.svelte +142 -142
  133. package/dist/icons/icon-copy.svelte +6 -0
  134. package/dist/{components/radio/types.svelte.d.ts → icons/icon-copy.svelte.d.ts} +3 -3
  135. package/dist/utils/markdown-to-llm.d.ts +28 -0
  136. package/dist/utils/markdown-to-llm.js +76 -0
  137. package/package.json +1 -2
  138. package/dist/components/radio/types.svelte +0 -0
  139. package/llm/composition.md +0 -395
  140. package/llm/crafting.md +0 -838
  141. package/llm/motion.md +0 -970
  142. package/llm/philosophy.md +0 -23
  143. package/llm/preset-variant-integration.md +0 -516
  144. package/llm/preset.md +0 -383
  145. package/llm/styling.md +0 -216
  146. package/llm/usage.md +0 -46
  147. package/llm/variants.md +0 -1259
@@ -1,110 +1,91 @@
1
- <script lang="ts" generics="E extends keyof HTMLElementTagNameMap = 'div', B extends Base = Base">
2
- import { animate as motion } from 'motion';
3
- import { Teleport, ActivePortal } from '../portal';
4
- import { defineProperty, defineState } from '../../utils';
5
- import type { Base } from '../atom';
6
- import { DURATION } from '../../shared';
7
- import { DialogBond, DialogBondState, type DialogBondProps } from './bond.svelte';
8
- import type { DialogProps } from './types';
9
-
10
- let {
11
- class: klass = '',
12
- open = $bindable(false),
13
- disabled = false,
14
- as = 'dialog' as E,
15
- portal = undefined,
16
- factory = _factory,
17
- onmount = undefined,
18
- ondestroy = undefined,
19
- animate = _animate,
20
- enter = undefined,
21
- exit = undefined,
22
- initial = undefined,
23
- children = undefined,
24
- ...restProps
25
- }: DialogProps<E, B> = $props();
26
-
27
- const bondProps = defineState<DialogBondProps>(
28
- [
29
- defineProperty(
30
- 'open',
31
- () => open,
32
- (v) => {
33
- open = v;
34
- }
35
- )
36
- ],
37
- () => ({ disabled })
38
- );
39
- const bond = _factory(bondProps).share();
40
-
41
- const rootProps = $derived({
42
- ...bond?.root({}),
43
- ...restProps
44
- });
45
-
46
- function _factory(props: typeof bondProps) {
47
- const bondState = new DialogBondState(() => props);
48
- return new DialogBond(bondState);
49
- }
50
-
51
- function _animate(node: HTMLDialogElement) {
52
- motion(
53
- node,
54
- {
55
- opacity: +open
56
- },
57
- {
58
- duration: DURATION.normal / 1000,
59
- ease: 'anticipate',
60
- onComplete: () => {
61
- if (!open) {
62
- node?.close?.();
63
- }
64
- }
65
- }
66
- );
67
- }
68
-
69
- function onclickDialogElement(ev: MouseEvent) {
70
- if (bond?.elements?.content?.contains(ev.target)) {
71
- return;
72
- }
73
-
74
- // Clicked the backdrop
75
- bond.state.close();
76
- }
77
-
78
- export function getBond() {
79
- return bond;
80
- }
81
- </script>
82
-
83
- <Teleport
84
- {as}
85
- {bond}
86
- preset="dialog"
87
- portal={portal ?? 'root.l1'}
88
- class={[
89
- 'border-border pointer-events-auto fixed top-0 left-0 flex h-full w-full items-center justify-center bg-neutral-900/10 opacity-0',
90
- !open && 'pointer-events-none',
91
- '$preset',
92
- klass
93
- ]}
94
- enter={enter?.bind(bond.state)}
95
- exit={exit?.bind(bond.state)}
96
- initial={initial?.bind(bond.state)}
97
- animate={animate?.bind(bond.state)}
98
- onmount={onmount?.bind(bond.state)}
99
- ondestroy={ondestroy?.bind(bond.state)}
100
- onclick={onclickDialogElement}
101
- oncancel={(ev) => {
102
- ev.preventDefault();
103
- open = false;
104
- }}
105
- {...rootProps}
106
- >
107
- <ActivePortal portal={portal ?? 'root.l1'}>
108
- {@render children?.({ dialog: bond })}
109
- </ActivePortal>
110
- </Teleport>
1
+ <script lang="ts" generics="E extends keyof HTMLElementTagNameMap = 'div', B extends Base = Base">
2
+ import { Teleport, ActivePortal } from '../portal';
3
+ import { defineProperty, defineState } from '../../utils';
4
+ import type { Base } from '../atom';
5
+ import { DialogBond, DialogBondState, type DialogBondProps } from './bond.svelte';
6
+ import type { DialogProps } from './types';
7
+ import { animateDialogRoot } from './motion.svelte';
8
+
9
+ let {
10
+ class: klass = '',
11
+ open = $bindable(false),
12
+ disabled = false,
13
+ as = 'dialog' as E,
14
+ portal = undefined,
15
+ factory = _factory,
16
+ onmount = undefined,
17
+ ondestroy = undefined,
18
+ animate = animateDialogRoot(),
19
+ enter = undefined,
20
+ exit = undefined,
21
+ initial = undefined,
22
+ children = undefined,
23
+ ...restProps
24
+ }: DialogProps<E, B> = $props();
25
+
26
+ const bondProps = defineState<DialogBondProps>(
27
+ [
28
+ defineProperty(
29
+ 'open',
30
+ () => open,
31
+ (v) => {
32
+ open = v;
33
+ }
34
+ )
35
+ ],
36
+ () => ({ disabled })
37
+ );
38
+ const bond = _factory(bondProps).share();
39
+
40
+ const rootProps = $derived({
41
+ ...bond?.root(),
42
+ ...restProps
43
+ });
44
+
45
+ function _factory(props: typeof bondProps) {
46
+ const bondState = new DialogBondState(() => props);
47
+ return new DialogBond(bondState);
48
+ }
49
+
50
+ function onclickDialogElement(ev: MouseEvent) {
51
+ if (bond?.elements?.content?.contains(ev.target)) {
52
+ return;
53
+ }
54
+
55
+ // Clicked the backdrop
56
+ bond.state.close();
57
+ }
58
+
59
+ export function getBond() {
60
+ return bond;
61
+ }
62
+ </script>
63
+
64
+ <Teleport
65
+ {as}
66
+ {bond}
67
+ preset="dialog"
68
+ portal={portal ?? 'root.l1'}
69
+ class={[
70
+ 'border-border pointer-events-auto fixed top-0 left-0 flex h-full w-full items-center justify-center bg-neutral-900/10 opacity-0',
71
+ !open && 'pointer-events-none',
72
+ '$preset',
73
+ klass
74
+ ]}
75
+ enter={enter?.bind(bond.state)}
76
+ exit={exit?.bind(bond.state)}
77
+ initial={initial?.bind(bond.state)}
78
+ animate={animate?.bind(bond.state)}
79
+ onmount={onmount?.bind(bond.state)}
80
+ ondestroy={ondestroy?.bind(bond.state)}
81
+ onclick={onclickDialogElement}
82
+ oncancel={(ev) => {
83
+ ev.preventDefault();
84
+ open = false;
85
+ }}
86
+ {...rootProps}
87
+ >
88
+ <ActivePortal portal={portal ?? 'root.l1'}>
89
+ {@render children?.({ dialog: bond })}
90
+ </ActivePortal>
91
+ </Teleport>
@@ -1,64 +1,64 @@
1
- <script module>
2
- import { defineMeta } from '@storybook/addon-svelte-csf';
3
- import { Dialog as ADialog } from '.';
4
- import { Dropdown } from '../dropdown';
5
- import { dialog } from './attachements.svelte';
6
-
7
- // More on how to set up stories at: https://storybook.js.org/docs/writing-stories
8
- const { Story } = defineMeta({
9
- title: 'Atoms/Dialog',
10
- // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
11
-
12
- parameters: {
13
- // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout
14
- layout: 'fullscreen'
15
- },
16
- args: {}
17
- });
18
- </script>
19
-
20
- <script lang="ts">
21
- let isDialogOpen = $state(false);
22
- let isDropdownOpen = $state(false);
23
- </script>
24
-
25
- <Story name="Dialog" args={{}}>
26
- <div class="size-10 bg-red-500"></div>
27
- <button onclick={() => (isDialogOpen = !isDialogOpen)}>Open Dialog</button>
28
-
29
- <ADialog.Root class="bg-neutral-900/20" bind:open={isDialogOpen}>
30
- <ADialog.Content>
31
- <ADialog.Header>
32
- <div>Open Popover</div>
33
- <ADialog.CloseButton class="ml-auto"></ADialog.CloseButton>
34
- </ADialog.Header>
35
-
36
- <ADialog.Body>
37
- <p
38
- {@attach dialog((node, atom) => {
39
- console.log(atom);
40
- })}
41
- >
42
- Mauris et habitasse cubilia potenti at condimentum iaculis nam. Ante fusce litora
43
- tristique letius libero. Curabitur vitae cursus consectetur feugiat aenean viverra vel
44
- dolor diam nascetur.
45
- </p>
46
-
47
- <Dropdown.Root open={isDialogOpen && isDropdownOpen} class="w-full">
48
- <Dropdown.Trigger>Hello World</Dropdown.Trigger>
49
- <Dropdown.List>
50
- <Dropdown.Item id="ar">Arabic</Dropdown.Item>
51
- <Dropdown.Item id="en">English</Dropdown.Item>
52
- <Dropdown.Item id="sp">Spanish</Dropdown.Item>
53
- <Dropdown.Item id="it">Italian</Dropdown.Item>
54
- </Dropdown.List>
55
- </Dropdown.Root>
56
- </ADialog.Body>
57
-
58
- <ADialog.Footer class="gap-4">
59
- <button onclick={() => (isDialogOpen = false)}>Cancel</button>
60
- <button>Save</button>
61
- </ADialog.Footer>
62
- </ADialog.Content>
63
- </ADialog.Root>
64
- </Story>
1
+ <script module>
2
+ import { defineMeta } from '@storybook/addon-svelte-csf';
3
+ import { Dialog as ADialog } from '.';
4
+ import { Dropdown } from '../dropdown';
5
+ import { dialog } from './attachements.svelte';
6
+
7
+ // More on how to set up stories at: https://storybook.js.org/docs/writing-stories
8
+ const { Story } = defineMeta({
9
+ title: 'Atoms/Dialog',
10
+ // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
11
+
12
+ parameters: {
13
+ // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout
14
+ layout: 'fullscreen'
15
+ },
16
+ args: {}
17
+ });
18
+ </script>
19
+
20
+ <script lang="ts">
21
+ let isDialogOpen = $state(false);
22
+ let isDropdownOpen = $state(false);
23
+ </script>
24
+
25
+ <Story name="Dialog" args={{}}>
26
+ <div class="size-10 bg-red-500"></div>
27
+ <button onclick={() => (isDialogOpen = !isDialogOpen)}>Open Dialog</button>
28
+
29
+ <ADialog.Root class="bg-neutral-900/20" bind:open={isDialogOpen}>
30
+ <ADialog.Content>
31
+ <ADialog.Header>
32
+ <div>Open Popover</div>
33
+ <ADialog.CloseButton class="ml-auto"></ADialog.CloseButton>
34
+ </ADialog.Header>
35
+
36
+ <ADialog.Body>
37
+ <p
38
+ {@attach dialog((node, atom) => {
39
+ console.log(atom);
40
+ })}
41
+ >
42
+ Mauris et habitasse cubilia potenti at condimentum iaculis nam. Ante fusce litora
43
+ tristique letius libero. Curabitur vitae cursus consectetur feugiat aenean viverra vel
44
+ dolor diam nascetur.
45
+ </p>
46
+
47
+ <Dropdown.Root open={isDialogOpen && isDropdownOpen} class="w-full">
48
+ <Dropdown.Trigger>Hello World</Dropdown.Trigger>
49
+ <Dropdown.List>
50
+ <Dropdown.Item id="ar">Arabic</Dropdown.Item>
51
+ <Dropdown.Item id="en">English</Dropdown.Item>
52
+ <Dropdown.Item id="sp">Spanish</Dropdown.Item>
53
+ <Dropdown.Item id="it">Italian</Dropdown.Item>
54
+ </Dropdown.List>
55
+ </Dropdown.Root>
56
+ </ADialog.Body>
57
+
58
+ <ADialog.Footer class="gap-4">
59
+ <button onclick={() => (isDialogOpen = false)}>Cancel</button>
60
+ <button>Save</button>
61
+ </ADialog.Footer>
62
+ </ADialog.Content>
63
+ </ADialog.Root>
64
+ </Story>
@@ -0,0 +1,13 @@
1
+ type AnimateDialogRootParams = {
2
+ duration?: number;
3
+ delay?: number;
4
+ ease?: string;
5
+ };
6
+ export declare function animateDialogRoot(params?: AnimateDialogRootParams): (node: HTMLElement) => void;
7
+ type AnimateDialogContentParams = {
8
+ duration?: number;
9
+ delay?: number;
10
+ ease?: string;
11
+ };
12
+ export declare function animateDialogContent(params?: AnimateDialogContentParams): (node: HTMLElement) => void;
13
+ export {};
@@ -0,0 +1,44 @@
1
+ import { DURATION } from '../../shared';
2
+ import { animate } from 'motion';
3
+ import { DialogBond } from './bond.svelte';
4
+ export function animateDialogRoot(params = {}) {
5
+ const { duration = DURATION.fast / 1000, delay = 0, ease = 'anticipate' } = params;
6
+ return (node) => {
7
+ const bond = DialogBond.get();
8
+ const isOpen = bond?.state.props.open ?? false;
9
+ if (node instanceof HTMLDialogElement) {
10
+ node.show();
11
+ }
12
+ animate(node, {
13
+ opacity: +isOpen
14
+ }, {
15
+ duration,
16
+ delay,
17
+ ease
18
+ });
19
+ };
20
+ }
21
+ export function animateDialogContent(params = {}) {
22
+ const { duration = DURATION.fast / 1000, delay = 0, ease = 'anticipate' } = params;
23
+ const bond = DialogBond.get();
24
+ return (node) => {
25
+ const isOpen = bond?.state.props.open ?? false;
26
+ if (!bond?.elements.root.open) {
27
+ if (node instanceof HTMLDialogElement) {
28
+ node.show();
29
+ }
30
+ }
31
+ animate(node, { scale: 0.9 + 0.1 * +isOpen, opacity: +isOpen }, {
32
+ duration,
33
+ easing: ease,
34
+ delay,
35
+ onComplete: () => {
36
+ if (!isOpen) {
37
+ if (node instanceof HTMLDialogElement) {
38
+ node.close();
39
+ }
40
+ }
41
+ }
42
+ });
43
+ };
44
+ }
@@ -3,4 +3,4 @@ export declare function drawer(callback: (node: HTMLElement, bond?: DrawerBond)
3
3
  export declare function toggleDrawer(onclick?: (ev: MouseEvent) => void): (node: HTMLElement) => () => void;
4
4
  export declare function openDrawer(onclick?: (ev: MouseEvent) => void): (node: HTMLElement) => () => void;
5
5
  export declare function closeDrawer(onclick?: (ev: MouseEvent) => void): (node: HTMLElement) => (() => void) | undefined;
6
- export declare function clickoutDrawer(onclickout?: (ev: PointerEvent) => void): (node: Element) => () => void;
6
+ export declare function clickoutDrawer(onclickout?: (ev: PointerEvent, bond?: DrawerBond) => void): (node: Element) => () => void;
@@ -1,4 +1,3 @@
1
- import { on } from '../../attachments/event.svelte';
2
1
  import { clickout } from '../../attachments/clickout.svelte';
3
2
  import { DrawerBond } from './bond.svelte';
4
3
  export function drawer(callback) {
@@ -68,8 +67,7 @@ export function clickoutDrawer(onclickout) {
68
67
  if (bond.elements.content?.contains(target)) {
69
68
  return;
70
69
  }
71
- console.log('clickoutDrawer invoked', bond.state.props.open);
72
- onclickout?.(ev);
70
+ onclickout?.(ev, bond);
73
71
  if (ev.defaultPrevented) {
74
72
  return;
75
73
  }
@@ -18,16 +18,27 @@ export type DrawerBondElements = {
18
18
  export declare class DrawerBond<Props extends DrawerBondProps = DrawerBondProps, State extends DrawerBondState<Props> = DrawerBondState<Props>> extends Bond<Props, State, DrawerBondElements> {
19
19
  static CONTEXT_KEY: string;
20
20
  constructor(state: State);
21
- root(props?: Record<string, unknown>): {
21
+ root(props?: Record<string, unknown> & {
22
+ onclick?: (ev: MouseEvent) => void;
23
+ onkeydown?: (ev: KeyboardEvent) => void;
24
+ }): {
25
+ onclick?: (ev: MouseEvent) => void;
26
+ onkeydown: (ev: KeyboardEvent) => void;
22
27
  id: string;
23
- 'aria-expanded': boolean;
24
- 'aria-disabled': boolean;
28
+ role: string;
29
+ 'aria-modal': boolean;
25
30
  'aria-labelledby': string | undefined;
31
+ 'aria-describedby': string | undefined;
32
+ 'aria-hidden': boolean;
33
+ inert: string | undefined;
34
+ tabindex: number;
26
35
  'data-active': boolean;
36
+ 'data-open': boolean;
27
37
  'data-kind': string;
28
38
  };
29
39
  content(props?: Record<string, unknown>): {
30
40
  id: string;
41
+ role: string;
31
42
  'data-kind': string;
32
43
  };
33
44
  body(props?: Record<string, unknown>): {
@@ -42,6 +53,8 @@ export declare class DrawerBond<Props extends DrawerBondProps = DrawerBondProps,
42
53
  };
43
54
  title(props?: Record<string, unknown>): {
44
55
  id: string;
56
+ role: string;
57
+ 'aria-level': number;
45
58
  'data-kind': string;
46
59
  };
47
60
  description(props?: Record<string, unknown>): {
@@ -50,10 +63,16 @@ export declare class DrawerBond<Props extends DrawerBondProps = DrawerBondProps,
50
63
  };
51
64
  footer(props?: Record<string, unknown>): {
52
65
  id: string;
66
+ role: string;
53
67
  'data-kind': string;
54
68
  };
55
- backdrop(): {
56
- [x: symbol]: (node: HTMLElement) => void;
69
+ backdrop(props?: Record<string, unknown> & {
70
+ onclick?: (ev: MouseEvent) => void;
71
+ }): {
72
+ onclick: (ev: MouseEvent) => void;
73
+ role: string;
74
+ 'aria-hidden': boolean;
75
+ 'data-kind': string;
57
76
  };
58
77
  share(): this;
59
78
  static get(): DrawerBond | undefined;
@@ -19,27 +19,77 @@ export class DrawerBond extends Bond {
19
19
  }
20
20
  root(props = {}) {
21
21
  const id = getElementId(this.id, DRAWER_ELEMENTS_KIND.root);
22
- const drawerHeaderId = getElementId(this.id, DRAWER_ELEMENTS_KIND.header);
23
22
  const drawerTitleId = getElementId(this.id, DRAWER_ELEMENTS_KIND.title);
24
- const haveHeaderElement = !!this.elements.header;
23
+ const drawerDescriptionId = getElementId(this.id, DRAWER_ELEMENTS_KIND.description);
24
+ const haveDescriptionElement = !!this.elements.description;
25
25
  const haveTitleElement = !!this.elements.title;
26
26
  const isOpen = this.state?.props?.open ?? false;
27
27
  const isDisabled = this.state?.props?.disabled ?? false;
28
28
  const isActive = isOpen && !isDisabled;
29
+ // Focus trap handler
30
+ const focusTrap = (ev) => {
31
+ const node = ev.currentTarget;
32
+ if (ev.key === 'Tab') {
33
+ const focusableElements = node.querySelectorAll('a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])');
34
+ const firstElement = focusableElements[0];
35
+ const lastElement = focusableElements[focusableElements.length - 1];
36
+ if (focusableElements.length === 0)
37
+ return;
38
+ if (ev.shiftKey && document.activeElement === firstElement) {
39
+ ev.preventDefault();
40
+ lastElement?.focus();
41
+ }
42
+ else if (!ev.shiftKey && document.activeElement === lastElement) {
43
+ ev.preventDefault();
44
+ firstElement?.focus();
45
+ }
46
+ }
47
+ };
48
+ let previousActiveElement = null;
29
49
  return {
30
50
  id: id,
31
- 'aria-expanded': isOpen,
32
- 'aria-disabled': isDisabled,
33
- 'aria-labelledby': haveTitleElement
34
- ? drawerTitleId
35
- : haveHeaderElement
36
- ? drawerHeaderId
37
- : undefined,
51
+ role: 'dialog',
52
+ 'aria-modal': true,
53
+ 'aria-labelledby': haveTitleElement ? drawerTitleId : undefined,
54
+ 'aria-describedby': haveDescriptionElement ? drawerDescriptionId : undefined,
55
+ 'aria-hidden': !isActive,
56
+ inert: !isActive ? '' : undefined,
57
+ tabindex: -1,
38
58
  'data-active': isActive,
59
+ 'data-open': isOpen,
39
60
  'data-kind': DRAWER_ELEMENTS_KIND.root,
61
+ onkeydown: (ev) => {
62
+ focusTrap(ev);
63
+ // Close on Escape key
64
+ if (ev.key === 'Escape' && !isDisabled) {
65
+ ev.preventDefault();
66
+ this.state.close();
67
+ }
68
+ props.onkeydown?.(ev);
69
+ },
40
70
  ...props,
41
71
  [createAttachmentKey()]: (node) => {
42
72
  this.elements.root = node;
73
+ if (this.state.props.open) {
74
+ // Store current focus
75
+ previousActiveElement = document.activeElement;
76
+ // Focus first focusable element or drawer itself
77
+ setTimeout(() => {
78
+ const firstFocusable = node.querySelector('a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])');
79
+ if (firstFocusable) {
80
+ firstFocusable.focus();
81
+ }
82
+ else {
83
+ node.focus();
84
+ }
85
+ }, 0);
86
+ }
87
+ else {
88
+ // Restore focus to previous element
89
+ if (previousActiveElement instanceof HTMLElement) {
90
+ previousActiveElement.focus();
91
+ }
92
+ }
43
93
  }
44
94
  };
45
95
  }
@@ -47,6 +97,7 @@ export class DrawerBond extends Bond {
47
97
  const id = getElementId(this.id, DRAWER_ELEMENTS_KIND.content);
48
98
  return {
49
99
  id: id,
100
+ role: 'document',
50
101
  'data-kind': DRAWER_ELEMENTS_KIND.content,
51
102
  ...props,
52
103
  [createAttachmentKey()]: (node) => {
@@ -70,7 +121,7 @@ export class DrawerBond extends Bond {
70
121
  const id = getElementId(this.id, DRAWER_ELEMENTS_KIND.header);
71
122
  return {
72
123
  id: id,
73
- role: 'heading',
124
+ role: 'banner',
74
125
  'data-kind': DRAWER_ELEMENTS_KIND.header,
75
126
  ...props,
76
127
  [createAttachmentKey()]: (node) => {
@@ -82,6 +133,8 @@ export class DrawerBond extends Bond {
82
133
  const id = getElementId(this.id, DRAWER_ELEMENTS_KIND.title);
83
134
  return {
84
135
  id: id,
136
+ role: 'heading',
137
+ 'aria-level': 2,
85
138
  'data-kind': DRAWER_ELEMENTS_KIND.title,
86
139
  ...props,
87
140
  [createAttachmentKey()]: (node) => {
@@ -104,6 +157,7 @@ export class DrawerBond extends Bond {
104
157
  const id = getElementId(this.id, DRAWER_ELEMENTS_KIND.footer);
105
158
  return {
106
159
  id: id,
160
+ role: 'contentinfo',
107
161
  'data-kind': DRAWER_ELEMENTS_KIND.footer,
108
162
  ...props,
109
163
  [createAttachmentKey()]: (node) => {
@@ -111,8 +165,20 @@ export class DrawerBond extends Bond {
111
165
  }
112
166
  };
113
167
  }
114
- backdrop() {
168
+ backdrop(props = {}) {
169
+ const isDisabled = this.state?.props?.disabled ?? false;
115
170
  return {
171
+ role: 'presentation',
172
+ 'aria-hidden': true,
173
+ 'data-kind': DRAWER_ELEMENTS_KIND.backdrop,
174
+ onclick: (ev) => {
175
+ // Close drawer on backdrop click
176
+ if (!isDisabled) {
177
+ this.state.close();
178
+ }
179
+ props.onclick?.(ev);
180
+ },
181
+ ...props,
116
182
  [createAttachmentKey()]: (node) => {
117
183
  this.elements.backdrop = node;
118
184
  }
@@ -1,9 +1,9 @@
1
1
  <script lang="ts" generics="E extends keyof HTMLElementTagNameMap = 'div', B extends Base = Base">
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
- import { HtmlAtom, type Base } from '../atom';
3
+ import { HtmlAtom as Atom, type Base } from '../atom';
4
4
  import type { SlideoverContentProps } from './types';
5
5
  import { DrawerBond } from './bond.svelte';
6
- import { animate as motion } from 'motion';
6
+ import { animateDrawerContent } from './motion';
7
7
 
8
8
  type Element = HTMLElementTagNameMap[E];
9
9
 
@@ -15,10 +15,10 @@
15
15
  children = undefined,
16
16
  onmount = undefined,
17
17
  ondestroy = undefined,
18
- animate = _animate,
18
+ animate = animateDrawerContent({ ease: 'easeOut', side: 'left' }),
19
19
  enter = undefined,
20
20
  exit = undefined,
21
- initial = _initial,
21
+ initial = animateDrawerContent({ ease: 'easeOut', side: 'left', duration: 0 }),
22
22
  ...restProps
23
23
  }: SlideoverContentProps<E, B> & HTMLAttributes<Element> = $props();
24
24
 
@@ -26,17 +26,9 @@
26
26
  ...bond?.content(),
27
27
  ...restProps
28
28
  });
29
-
30
- function _initial(node: HTMLElement) {
31
- motion(node, { x: isOpen ? 0 : -100 + '%', left: 0 }, { duration: 0.3, ease: 'anticipate' });
32
- }
33
-
34
- function _animate(node: HTMLElement) {
35
- motion(node, { x: isOpen ? 0 : -100 + '%', left: 0 }, { duration: 0.3, ease: 'anticipate' });
36
- }
37
29
  </script>
38
30
 
39
- <HtmlAtom
31
+ <Atom
40
32
  preset="drawer.content"
41
33
  class={[
42
34
  'bg-card text-foreground border-border pointer-events-none absolute',
@@ -54,4 +46,4 @@
54
46
  {...contentProps}
55
47
  >
56
48
  {@render children?.({ drawer: bond })}
57
- </HtmlAtom>
49
+ </Atom>