@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,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
- 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
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)({
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) => {
@@ -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
- onchangeDebounced(content, position);
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,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
+ }
@@ -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>