@svelte-atoms/core 1.0.0-alpha.29 → 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 (176) hide show
  1. package/README.md +3 -2
  2. package/dist/attachments/clickout.svelte.d.ts +1 -1
  3. package/dist/attachments/clickout.svelte.js +2 -2
  4. package/dist/components/accordion/accordion-root.svelte +65 -61
  5. package/dist/components/accordion/accordion-root.svelte.d.ts +1 -1
  6. package/dist/components/accordion/accordion.stories.svelte +70 -134
  7. package/dist/components/accordion/item/accordion-item-body.svelte +44 -42
  8. package/dist/components/accordion/item/accordion-item-header.svelte +51 -50
  9. package/dist/components/accordion/item/accordion-item-indicator.svelte +51 -50
  10. package/dist/components/accordion/item/accordion-item-root.svelte +66 -65
  11. package/dist/components/accordion/item/bond.svelte.d.ts +2 -0
  12. package/dist/components/accordion/item/index.d.ts +3 -0
  13. package/dist/components/accordion/item/index.js +3 -0
  14. package/dist/components/accordion/item/motion.svelte.d.ts +15 -0
  15. package/dist/components/accordion/item/motion.svelte.js +30 -0
  16. package/dist/components/accordion/item/types.d.ts +7 -24
  17. package/dist/components/alert/alert-close-button.svelte +66 -70
  18. package/dist/components/alert/alert-description.svelte +42 -42
  19. package/dist/components/alert/alert-description.svelte.d.ts +3 -6
  20. package/dist/components/alert/alert-root.svelte +68 -103
  21. package/dist/components/alert/alert-root.svelte.d.ts +2 -2
  22. package/dist/components/alert/alert.stories.svelte +10 -11
  23. package/dist/components/alert/bond.svelte.d.ts +0 -13
  24. package/dist/components/alert/bond.svelte.js +0 -32
  25. package/dist/components/alert/types.d.ts +8 -32
  26. package/dist/components/atom/html-atom.svelte +261 -207
  27. package/dist/components/avatar/avatar.stories.svelte +8 -13
  28. package/dist/components/badge/badge.stories.svelte +1 -6
  29. package/dist/components/badge/badge.svelte +1 -1
  30. package/dist/components/breadcrumb/breadcrumb.stories.svelte +16 -21
  31. package/dist/components/button/button.stories.svelte +1 -34
  32. package/dist/components/calendar/calendar-day.svelte +9 -4
  33. package/dist/components/calendar/calendar-header.svelte +29 -29
  34. package/dist/components/calendar/calendar-root.svelte +206 -206
  35. package/dist/components/calendar/calendar.stories.svelte +26 -31
  36. package/dist/components/card/card-body.svelte +1 -1
  37. package/dist/components/card/card-footer.svelte +1 -1
  38. package/dist/components/card/card-root.svelte +1 -1
  39. package/dist/components/card/card.stories.svelte +92 -104
  40. package/dist/components/checkbox/checkbox.stories.svelte +4 -9
  41. package/dist/components/checkbox/checkbox.svelte +159 -157
  42. package/dist/components/collapsible/collapsible.stories.svelte +2 -3
  43. package/dist/components/combobox/atoms.d.ts +1 -1
  44. package/dist/components/combobox/atoms.js +1 -1
  45. package/dist/components/combobox/combobox-root.svelte +1 -1
  46. package/dist/components/combobox/compobox.stories.svelte +19 -22
  47. package/dist/components/combobox/index.d.ts +1 -0
  48. package/dist/components/container/container.stories.svelte +8 -11
  49. package/dist/components/container/container.svelte.d.ts +1 -1
  50. package/dist/components/datagrid/datagrid-root.svelte +59 -59
  51. package/dist/components/datagrid/datagrid.css +5 -5
  52. package/dist/components/datagrid/datagrid.stories.svelte +47 -50
  53. package/dist/components/datagrid/tr/bond.svelte.d.ts +4 -2
  54. package/dist/components/datagrid/tr/bond.svelte.js +9 -7
  55. package/dist/components/datagrid/tr/datagrid-tr.svelte +90 -88
  56. package/dist/components/date-picker/date-picker-calendar.svelte +2 -2
  57. package/dist/components/date-picker/date-picker-header.svelte +100 -100
  58. package/dist/components/date-picker/date-picker-months.svelte +142 -142
  59. package/dist/components/date-picker/date-picker-root.svelte +95 -95
  60. package/dist/components/date-picker/date-picker-years.svelte +205 -205
  61. package/dist/components/date-picker/date-picker.stories.svelte +35 -42
  62. package/dist/components/dialog/bond.svelte.d.ts +13 -3
  63. package/dist/components/dialog/bond.svelte.js +66 -5
  64. package/dist/components/dialog/dialog-content.svelte +2 -20
  65. package/dist/components/dialog/dialog-root.svelte +91 -110
  66. package/dist/components/dialog/dialog.stories.svelte +34 -37
  67. package/dist/components/dialog/motion.svelte.d.ts +13 -0
  68. package/dist/components/dialog/motion.svelte.js +44 -0
  69. package/dist/components/drawer/attachments.svelte.d.ts +1 -1
  70. package/dist/components/drawer/attachments.svelte.js +7 -10
  71. package/dist/components/drawer/bond.svelte.d.ts +24 -5
  72. package/dist/components/drawer/bond.svelte.js +77 -11
  73. package/dist/components/drawer/drawer-content.svelte +49 -42
  74. package/dist/components/drawer/drawer.stories.svelte +144 -224
  75. package/dist/components/drawer/index.d.ts +2 -0
  76. package/dist/components/drawer/index.js +2 -0
  77. package/dist/components/drawer/motion.d.ts +15 -0
  78. package/dist/components/drawer/motion.js +28 -0
  79. package/dist/components/dropdown/atoms.d.ts +1 -1
  80. package/dist/components/dropdown/atoms.js +1 -1
  81. package/dist/components/dropdown/bond.svelte.d.ts +5 -1
  82. package/dist/components/dropdown/dropdown-root.svelte +1 -1
  83. package/dist/components/dropdown/dropdown.stories.svelte +38 -41
  84. package/dist/components/dropdown/index.d.ts +1 -0
  85. package/dist/components/form/form.stories.svelte +58 -61
  86. package/dist/components/image/image.stories.svelte +9 -12
  87. package/dist/components/input/input.stories.svelte +11 -14
  88. package/dist/components/label/label.stories.svelte +1 -12
  89. package/dist/components/label/label.stories.svelte.d.ts +24 -4
  90. package/dist/components/lazy/lazy.stories.svelte +28 -35
  91. package/dist/components/lazy/lazy.svelte +28 -28
  92. package/dist/components/link/link.stories.svelte +1 -12
  93. package/dist/components/link/link.stories.svelte.d.ts +24 -4
  94. package/dist/components/list/list-item.svelte +20 -20
  95. package/dist/components/menu/atoms.d.ts +1 -0
  96. package/dist/components/menu/atoms.js +1 -0
  97. package/dist/components/menu/index.d.ts +2 -1
  98. package/dist/components/menu/index.js +1 -1
  99. package/dist/components/menu/menu-item.svelte +69 -51
  100. package/dist/components/menu/menu-item.svelte.d.ts +1 -0
  101. package/dist/components/menu/menu-list.svelte +40 -40
  102. package/dist/components/menu/menu.stories.svelte +9 -12
  103. package/dist/components/popover/bond.svelte.d.ts +20 -7
  104. package/dist/components/popover/bond.svelte.js +104 -45
  105. package/dist/components/popover/motion.d.ts +6 -0
  106. package/dist/components/popover/motion.js +56 -0
  107. package/dist/components/popover/popover-arrow.svelte +4 -4
  108. package/dist/components/popover/popover-content.svelte +137 -178
  109. package/dist/components/popover/popover-indicator.svelte +2 -1
  110. package/dist/components/popover/popover-root.svelte +1 -1
  111. package/dist/components/popover/popover.stories.svelte +49 -52
  112. package/dist/components/popover/types.d.ts +9 -7
  113. package/dist/components/portal/active-portal.svelte +29 -22
  114. package/dist/components/portal/active-portal.svelte.d.ts +2 -9
  115. package/dist/components/portal/portal-root.svelte +76 -83
  116. package/dist/components/portal/portal-root.svelte.d.ts +4 -6
  117. package/dist/components/portal/teleport.svelte +49 -50
  118. package/dist/components/portal/teleport.svelte.d.ts +3 -4
  119. package/dist/components/qr-code/qr-code.stories.svelte +18 -27
  120. package/dist/components/qr-code/qr-code.svelte +75 -75
  121. package/dist/components/radio/radio-group.stories.svelte +21 -30
  122. package/dist/components/radio/radio.stories.svelte +1 -10
  123. package/dist/components/radio/radio.svelte +109 -109
  124. package/dist/components/radio/types.d.ts +98 -0
  125. package/dist/components/radio/types.js +2 -0
  126. package/dist/components/root/root.svelte +104 -121
  127. package/dist/components/scrollable/scrollable-root.svelte.d.ts +2 -2
  128. package/dist/components/scrollable/scrollable.stories.svelte +95 -105
  129. package/dist/components/sidebar/index.d.ts +2 -0
  130. package/dist/components/sidebar/index.js +2 -0
  131. package/dist/components/sidebar/motion.svelte.d.ts +11 -0
  132. package/dist/components/sidebar/motion.svelte.js +16 -0
  133. package/dist/components/sidebar/sidebar-content.svelte +3 -2
  134. package/dist/components/sidebar/sidebar-root.svelte +39 -41
  135. package/dist/components/sidebar/sidebar.stories.svelte +43 -54
  136. package/dist/components/sidebar/types.d.ts +3 -12
  137. package/dist/components/tabs/tab/bond.svelte.d.ts +4 -1
  138. package/dist/components/tabs/tab/bond.svelte.js +4 -1
  139. package/dist/components/tabs/tabs.stories.svelte +31 -34
  140. package/dist/components/textarea/atoms.d.ts +1 -0
  141. package/dist/components/textarea/atoms.js +1 -0
  142. package/dist/components/textarea/textarea-input.svelte +9 -6
  143. package/dist/components/textarea/textarea-root.svelte +9 -9
  144. package/dist/components/textarea/textarea-root.svelte.d.ts +2 -0
  145. package/dist/components/tooltip/tooltip-trigger.svelte +39 -37
  146. package/dist/components/tooltip/tooltip-trigger.svelte.d.ts +1 -0
  147. package/dist/components/tooltip/tooltip.stories.svelte +7 -10
  148. package/dist/components/tree/tree.stories.svelte +102 -94
  149. package/dist/context/preset.svelte.d.ts +3 -3
  150. package/dist/icons/icon-copy.svelte +6 -0
  151. package/dist/{components/radio/types.svelte.d.ts → icons/icon-copy.svelte.d.ts} +3 -3
  152. package/dist/utils/function.d.ts +2 -0
  153. package/dist/utils/function.js +6 -0
  154. package/dist/utils/markdown-to-llm.d.ts +28 -0
  155. package/dist/utils/markdown-to-llm.js +76 -0
  156. package/package.json +6 -10
  157. package/dist/actions/animation.svelte.d.ts +0 -6
  158. package/dist/actions/animation.svelte.js +0 -14
  159. package/dist/actions/clickout.svelte.d.ts +0 -2
  160. package/dist/actions/clickout.svelte.js +0 -15
  161. package/dist/actions/popover.svelte.d.ts +0 -19
  162. package/dist/actions/popover.svelte.js +0 -81
  163. package/dist/actions/portal.svelte.d.ts +0 -8
  164. package/dist/actions/portal.svelte.js +0 -32
  165. package/dist/attachments/gsap.svelte.d.ts +0 -2
  166. package/dist/attachments/gsap.svelte.js +0 -26
  167. package/dist/components/radio/types.svelte +0 -0
  168. package/llm/composition.md +0 -395
  169. package/llm/crafting.md +0 -838
  170. package/llm/motion.md +0 -970
  171. package/llm/philosophy.md +0 -23
  172. package/llm/preset-variant-integration.md +0 -516
  173. package/llm/preset.md +0 -383
  174. package/llm/styling.md +0 -216
  175. package/llm/usage.md +0 -46
  176. package/llm/variants.md +0 -1259
@@ -1,51 +1,69 @@
1
- <script lang="ts" generics="T extends keyof HTMLElementTagNameMap = 'div', B extends Base = Base">
2
- import { PopoverBond } from '../popover/bond.svelte';
3
- import type { Base } from '../atom';
4
- import { List } from '../list';
5
-
6
- const bond = PopoverBond.get();
7
-
8
- let {
9
- class: klass = '',
10
- preset: presetKey = 'menu.item',
11
- children = undefined,
12
- onclick = undefined,
13
- onmount = undefined,
14
- ondestroy = undefined,
15
- animate = undefined,
16
- enter = undefined,
17
- exit = undefined,
18
- initial = undefined,
19
- ...restProps
20
- } = $props();
21
-
22
- function _onclick(ev: MouseEvent) {
23
- onclick?.(ev);
24
-
25
- if (ev.defaultPrevented) {
26
- return;
27
- }
28
-
29
- bond?.state.close();
30
- }
31
- </script>
32
-
33
- <List.Item
34
- {bond}
35
- preset={presetKey}
36
- class={[
37
- 'border-border last:border-b-none hover:bg-foreground/5 active:bg-foreground/10 cursor-pointer border-b',
38
- '$preset',
39
- klass
40
- ]}
41
- onmount={onmount?.bind(bond.state)}
42
- ondestroy={ondestroy?.bind(bond.state)}
43
- enter={enter?.bind(bond.state)}
44
- exit={exit?.bind(bond.state)}
45
- initial={initial?.bind(bond.state)}
46
- animate={animate?.bind(bond.state)}
47
- onclick={_onclick}
48
- {...restProps}
49
- >
50
- {@render children?.({ menu: bond })}
51
- </List.Item>
1
+ <script lang="ts" generics="T extends keyof HTMLElementTagNameMap = 'div', B extends Base = Base">
2
+ import { PopoverBond } from '../popover/bond.svelte';
3
+ import type { Base } from '../atom';
4
+ import { List } from '../list';
5
+
6
+ const bond = PopoverBond.get();
7
+
8
+ let {
9
+ class: klass = '',
10
+ preset: presetKey = 'menu.item',
11
+ children = undefined,
12
+ onclick = undefined,
13
+ disabled = undefined,
14
+ onmount = undefined,
15
+ ondestroy = undefined,
16
+ animate = undefined,
17
+ enter = undefined,
18
+ exit = undefined,
19
+ initial = undefined,
20
+ ...restProps
21
+ } = $props();
22
+
23
+ function _onclick(ev: MouseEvent) {
24
+ onclick?.(ev);
25
+
26
+ if (ev.defaultPrevented) {
27
+ return;
28
+ }
29
+
30
+ bond?.state.close();
31
+ }
32
+
33
+ function _onkeydown(ev: KeyboardEvent) {
34
+ if (disabled) return;
35
+
36
+ // Activate on Enter or Space
37
+ if (ev.key === 'Enter' || ev.key === ' ') {
38
+ ev.preventDefault();
39
+ // Call the click handler if provided
40
+ // cast to any to avoid strict event-type mismatch when forwarding
41
+ onclick?.(ev as unknown as MouseEvent);
42
+ bond?.state.close();
43
+ }
44
+ }
45
+ </script>
46
+
47
+ <List.Item
48
+ {bond}
49
+ preset={presetKey}
50
+ class={[
51
+ 'border-border last:border-b-none hover:bg-foreground/5 active:bg-foreground/10 cursor-pointer border-b',
52
+ '$preset',
53
+ klass
54
+ ]}
55
+ onmount={onmount?.bind(bond.state)}
56
+ ondestroy={ondestroy?.bind(bond.state)}
57
+ enter={enter?.bind(bond.state)}
58
+ exit={exit?.bind(bond.state)}
59
+ initial={initial?.bind(bond.state)}
60
+ animate={animate?.bind(bond.state)}
61
+ role="menuitem"
62
+ aria-disabled={disabled ? true : undefined}
63
+ tabIndex={disabled ? -1 : 0}
64
+ onkeydown={_onkeydown}
65
+ onclick={_onclick}
66
+ {...restProps}
67
+ >
68
+ {@render children?.({ menu: bond })}
69
+ </List.Item>
@@ -5,6 +5,7 @@ declare function $$render<T extends keyof HTMLElementTagNameMap = 'div', B exten
5
5
  preset?: string;
6
6
  children?: any;
7
7
  onclick?: any;
8
+ disabled?: any;
8
9
  onmount?: any;
9
10
  ondestroy?: any;
10
11
  animate?: any;
@@ -1,40 +1,40 @@
1
- <script lang="ts" generics="T extends keyof HTMLElementTagNameMap = 'div', B extends Base = Base">
2
- import { List } from '../list';
3
- import { Content } from '../popover/atoms';
4
- import type { Base } from '../atom';
5
- import { PopoverBond } from '../popover';
6
-
7
- const bond = PopoverBond.get();
8
-
9
- let {
10
- class: klass = '',
11
- as = 'ul' as T,
12
- base = List.Root as B,
13
- preset = 'menu.list',
14
- children = undefined,
15
- onmount = undefined,
16
- ondestroy = undefined,
17
- animate = undefined,
18
- enter = undefined,
19
- exit = undefined,
20
- initial = undefined,
21
- ...restProps
22
- } = $props();
23
- </script>
24
-
25
- <Content
26
- {as}
27
- {base}
28
- {bond}
29
- {preset}
30
- class={['bg-background border-border overflow-hidden p-0', '$preset', klass]}
31
- onmount={onmount?.bind(bond.state)}
32
- ondestroy={ondestroy?.bind(bond.state)}
33
- enter={enter?.bind(bond.state)}
34
- exit={exit?.bind(bond.state)}
35
- initial={initial?.bind(bond.state)}
36
- animate={animate?.bind(bond.state)}
37
- {...restProps}
38
- >
39
- {@render children?.()}
40
- </Content>
1
+ <script lang="ts" generics="T extends keyof HTMLElementTagNameMap = 'div', B extends Base = Base">
2
+ import { List } from '../list';
3
+ import { Content } from '../popover/atoms';
4
+ import type { Base } from '../atom';
5
+ import { PopoverBond } from '../popover';
6
+
7
+ const bond = PopoverBond.get();
8
+
9
+ let {
10
+ class: klass = '',
11
+ as = 'ul' as T,
12
+ base = List.Root as B,
13
+ preset = 'menu.list',
14
+ children = undefined,
15
+ onmount = undefined,
16
+ ondestroy = undefined,
17
+ animate = undefined,
18
+ enter = undefined,
19
+ exit = undefined,
20
+ initial = undefined,
21
+ ...restProps
22
+ } = $props();
23
+ </script>
24
+
25
+ <Content
26
+ {as}
27
+ {base}
28
+ {bond}
29
+ {preset}
30
+ class={['bg-background border-border overflow-hidden p-0', '$preset', klass]}
31
+ onmount={onmount?.bind(bond.state)}
32
+ ondestroy={ondestroy?.bind(bond.state)}
33
+ enter={enter?.bind(bond.state)}
34
+ exit={exit?.bind(bond.state)}
35
+ initial={initial?.bind(bond.state)}
36
+ animate={animate?.bind(bond.state)}
37
+ {...restProps}
38
+ >
39
+ {@render children?.()}
40
+ </Content>
@@ -1,7 +1,6 @@
1
1
  <script module>
2
2
  import { defineMeta } from '@storybook/addon-svelte-csf';
3
3
  import { Menu as AMenu } from '.';
4
- import Root from '../root/root.svelte';
5
4
  import { Button } from '../button';
6
5
 
7
6
  // More on how to set up stories at: https://storybook.js.org/docs/writing-stories
@@ -22,15 +21,13 @@
22
21
  </script>
23
22
 
24
23
  <Story name="Menu" args={{}}>
25
- <Root class="p-4">
26
- <AMenu.Root bind:open offset={4}>
27
- <AMenu.Trigger base={Button}>Select a language</AMenu.Trigger>
28
- <AMenu.List>
29
- <AMenu.Item>Arabic</AMenu.Item>
30
- <AMenu.Item>English</AMenu.Item>
31
- <AMenu.Item>Spanish</AMenu.Item>
32
- <AMenu.Item>Italian</AMenu.Item>
33
- </AMenu.List>
34
- </AMenu.Root>
35
- </Root>
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>
36
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
- get position(): ComputePositionReturn | undefined;
38
- trigger(props?: Record<string, unknown>): {
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-controlledby': string;
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-controlledby': string;
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
- #position = $state();
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', // Ensure trigger is announced as a button
29
+ role: isButtonElement ? '' : 'button',
34
30
  disabled: isButtonElement ? isDisabled : undefined,
35
- tabindex: isDisabled ? -1 : 0, // Make focusable unless disabled
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.#position);
66
+ const position = untrack(() => this.position);
53
67
  if (!position) {
54
68
  const init = async () => {
55
- popover(this)(node, {
69
+ popover(this)({
56
70
  ...props,
57
- onchange: (node, position) => {
58
- this.#position = position;
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', // Announce as dialog
85
- 'aria-modal': true, // Modal dialog
86
- 'aria-labelledby': triggerId, // Link overlay to trigger
87
- 'aria-controlledby': triggerId, // Link overlay to trigger
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': 'overlay',
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
- return popover(this)(node, {
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.#position = position;
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 triggerId = getElementId(this.id, POPOVER_ELEMENTS_KIND.trigger);
161
+ const isOpen = this.state?.props?.open ?? false;
113
162
  return {
114
163
  id,
115
- 'aria-controlledby': triggerId,
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) => {
@@ -160,38 +212,45 @@ export class PopoverState extends BondState {
160
212
  }
161
213
  }
162
214
  function popover(bond) {
163
- return (node, props, updater) => {
215
+ return (props, updater) => {
164
216
  const { offset: ofs, placements, placement } = bond.state.props;
165
- if (!bond.elements.content) {
166
- return;
167
- }
168
- if (!bond.elements.trigger) {
217
+ // Guard: ensure required elements exist
218
+ if (!bond.elements.content || !bond.elements.trigger) {
169
219
  return;
170
220
  }
171
- bond.elements.content.style.minWidth = bond.elements.trigger.clientWidth + 'px';
172
- const middleware = [];
173
- if (bond.elements.arrow) {
174
- middleware.push(arrow({ element: bond.elements.arrow }));
221
+ const { content, trigger, arrow: arrowElement } = bond.elements;
222
+ // Build middleware stack
223
+ const middleware = [
224
+ offset(ofs),
225
+ flip({
226
+ fallbackPlacements: placements,
227
+ padding: 4
228
+ })
229
+ ];
230
+ // Add arrow middleware if element exists
231
+ if (arrowElement) {
232
+ middleware.push(arrow({ element: arrowElement }));
175
233
  }
176
- const onchangeDebounced = debounce((node, position) => {
177
- props.onchange?.(node, position);
178
- }, 1000 / 60);
234
+ // Debounce position change callback
235
+ const onchangeCallback = props.onchange;
236
+ // Compute position and notify listeners
179
237
  const compute = async () => {
180
- const position = await computePosition(bond.elements.trigger, node, {
181
- placement,
182
- middleware: [
183
- offset(ofs),
184
- flip({
185
- fallbackPlacements: placements,
186
- padding: 4
187
- }),
188
- ...middleware
189
- ]
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)));
241
+ const position = await computePosition(trigger, content, {
242
+ placement: placement ?? 'bottom',
243
+ middleware
244
+ });
245
+ onchangeCallback?.(content, position);
246
+ // Set minimum width to match trigger
247
+ requestAnimationFrame(() => {
248
+ content.style.minWidth = `${trigger.clientWidth}px`;
190
249
  });
191
- onchangeDebounced?.(node, position);
192
250
  };
251
+ // Use auto-update if provided, otherwise compute once
193
252
  if (updater) {
194
- return updater(bond.elements.trigger, node, compute, {});
253
+ return updater(trigger, content, compute, {});
195
254
  }
196
255
  compute();
197
256
  };
@@ -0,0 +1,6 @@
1
+ export type AnimatePopoverContentParams = {
2
+ duration?: number;
3
+ delay?: number;
4
+ ease?: string;
5
+ };
6
+ export declare function animatePopoverContent(params?: AnimatePopoverContentParams): (node: HTMLElement) => void;
@@ -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
+ }
@@ -98,14 +98,14 @@
98
98
  {@render children({ popover: bond })}
99
99
  {:else}
100
100
  <svg
101
- width="12"
102
- height="12"
103
- viewBox="0 0 12 12"
101
+ width="16"
102
+ height="8"
103
+ viewBox="0 0 16 8"
104
104
  fill="none"
105
105
  xmlns="http://www.w3.org/2000/svg"
106
106
  style="transform: rotate({rotation}deg);"
107
107
  >
108
- <path d="M0 12L6 6L12 12H0Z" fill="currentColor" stroke="none" />
108
+ <path d="M0 8C2 8 6 4 8 0C10 4 14 8 16 8H0Z" fill="currentColor" />
109
109
  </svg>
110
110
  {/if}
111
111
  </HtmlAtom>