@dryui/ui 1.2.0 → 1.3.1

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 (75) hide show
  1. package/dist/accordion/accordion-button-trigger.svelte +6 -0
  2. package/dist/alert/alert.svelte +2 -1
  3. package/dist/alert-dialog/alert-dialog-content.svelte +9 -69
  4. package/dist/alpha-slider/alpha-slider-input.svelte +4 -0
  5. package/dist/avatar/avatar.svelte +3 -4
  6. package/dist/backdrop/backdrop.svelte +12 -9
  7. package/dist/badge/badge.svelte +84 -72
  8. package/dist/button/button.svelte +3 -6
  9. package/dist/button-group/button-group.svelte +2 -2
  10. package/dist/calendar/calendar-button-grid.svelte +1 -1
  11. package/dist/card/card-root.svelte +9 -4
  12. package/dist/chip-group/chip-group-root.svelte +2 -2
  13. package/dist/code-block/code-block-button.svelte +6 -2
  14. package/dist/context-menu/context-menu-content.svelte +12 -79
  15. package/dist/diagram/diagram.svelte +3 -4
  16. package/dist/dialog/dialog-content.svelte +5 -103
  17. package/dist/drag-and-drop/drag-and-drop-item.svelte +1 -1
  18. package/dist/drawer/drawer-dialog-content.svelte +5 -155
  19. package/dist/dropdown-menu/dropdown-menu-content.svelte +14 -71
  20. package/dist/heading/heading.svelte +7 -6
  21. package/dist/hover-card/hover-card-content.svelte +3 -12
  22. package/dist/icon/icon.svelte +2 -2
  23. package/dist/index.d.ts +2 -2
  24. package/dist/index.js +1 -1
  25. package/dist/input/input.svelte +3 -3
  26. package/dist/internal/calendar-grid-button.svelte +29 -26
  27. package/dist/internal/calendar-grid-button.svelte.d.ts +1 -0
  28. package/dist/internal/close-button-base.svelte +15 -2
  29. package/dist/internal/close-button-base.svelte.d.ts +1 -0
  30. package/dist/internal/modal-content.svelte +331 -0
  31. package/dist/internal/modal-content.svelte.d.ts +26 -0
  32. package/dist/link-preview/link-preview-content.svelte +3 -11
  33. package/dist/logo-mark/logo-mark.svelte +2 -3
  34. package/dist/marquee/marquee.svelte +14 -3
  35. package/dist/mask-reveal/mask-reveal.svelte +17 -14
  36. package/dist/mega-menu/mega-menu-panel.svelte +14 -2
  37. package/dist/mega-menu/mega-menu-panel.svelte.d.ts +1 -0
  38. package/dist/menubar/menubar-content.svelte +21 -81
  39. package/dist/noise/noise.svelte +1 -0
  40. package/dist/option-picker/option-picker-preview.svelte +11 -10
  41. package/dist/popover/popover-content.svelte +10 -36
  42. package/dist/progress/progress.svelte +2 -2
  43. package/dist/prompt-input/index.d.ts +13 -1
  44. package/dist/prompt-input/prompt-input-button-textarea.svelte +13 -1
  45. package/dist/prompt-input/prompt-input-button-textarea.svelte.d.ts +3 -0
  46. package/dist/shimmer/shimmer.svelte +28 -3
  47. package/dist/slider/slider-input.svelte +3 -4
  48. package/dist/spacer/spacer.svelte +2 -2
  49. package/dist/spinner/spinner.svelte +2 -2
  50. package/dist/tabs/tabs-content.svelte +1 -1
  51. package/dist/tabs/tabs-list.svelte +13 -0
  52. package/dist/tag/tag-button.svelte +2 -3
  53. package/dist/text/text.svelte +4 -15
  54. package/dist/themes/dark.css +40 -24
  55. package/dist/themes/default.css +16 -12
  56. package/dist/themes/token-scope.d.ts +1 -0
  57. package/dist/themes/token-scope.js +1 -0
  58. package/dist/timeline/timeline-icon.svelte +11 -1
  59. package/dist/timeline/timeline-item.svelte +18 -5
  60. package/dist/timeline/timeline-root.svelte +3 -10
  61. package/dist/toast/toast-close-button.svelte +19 -7
  62. package/dist/toast/toast-provider.svelte +22 -1
  63. package/dist/toggle/toggle-button.svelte +7 -3
  64. package/dist/toggle-group/toggle-group-root.svelte +2 -2
  65. package/dist/tooltip/tooltip-content.svelte +8 -32
  66. package/dist/tour/tour-root.css +26 -26
  67. package/dist/tree/tree-item-label.svelte +8 -14
  68. package/dist/tree/tree-item-label.svelte.d.ts +2 -2
  69. package/dist/typography/heading.svelte +8 -6
  70. package/dist/typography/text.svelte +4 -3
  71. package/package.json +6 -2
  72. package/skills/dryui/SKILL.md +3 -3
  73. package/skills/dryui/rules/native-web-transitions.md +2 -2
  74. package/dist/themes/use-theme-override.svelte.d.ts +0 -1
  75. package/dist/themes/use-theme-override.svelte.js +0 -1
@@ -1,10 +1,9 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
+ import { createDismiss, createMenuNavigation } from '@dryui/primitives';
4
5
  import { getContextMenuCtx } from './context.svelte.js';
5
6
 
6
- const MENU_ITEM_SELECTOR = '[role="menuitem"]:not([data-disabled])';
7
-
8
7
  interface Props extends HTMLAttributes<HTMLDivElement> {
9
8
  children: Snippet;
10
9
  }
@@ -15,15 +14,10 @@
15
14
 
16
15
  let el = $state<HTMLDivElement>();
17
16
 
18
- function getMenuItems(container: HTMLElement): HTMLElement[] {
19
- return Array.from(container.querySelectorAll<HTMLElement>(MENU_ITEM_SELECTOR));
20
- }
21
-
22
- function focusItem(items: HTMLElement[], index: number): void {
23
- if (items.length === 0) return;
24
- const clamped = ((index % items.length) + items.length) % items.length;
25
- items[clamped]?.focus();
26
- }
17
+ const menu = createMenuNavigation({
18
+ container: () => el ?? null,
19
+ orientation: 'vertical'
20
+ });
27
21
 
28
22
  $effect(() => {
29
23
  if (ctx.open && el) {
@@ -32,80 +26,19 @@
32
26
  el.style.top = `${ctx.position.y}px`;
33
27
  if (!el.matches(':popover-open')) {
34
28
  el.showPopover();
35
- const items = getMenuItems(el);
36
- const first = items[0];
37
- if (first) {
38
- first.focus();
39
- } else {
40
- el.focus();
41
- }
29
+ menu.focusFirst();
42
30
  }
43
31
  } else if (!ctx.open && el?.matches(':popover-open')) {
44
32
  el.hidePopover();
45
33
  }
46
34
  });
47
35
 
48
- // Manual dismiss: close on click outside or Escape
49
- $effect(() => {
50
- if (!ctx.open) return;
51
-
52
- function handlePointerDown(e: PointerEvent) {
53
- if (el?.contains(e.target as Node)) return;
54
- ctx.close();
55
- }
56
-
57
- function handleKeydown(e: KeyboardEvent) {
58
- if (e.key === 'Escape') {
59
- e.preventDefault();
60
- ctx.close();
61
- }
62
- }
63
-
64
- document.addEventListener('pointerdown', handlePointerDown);
65
- document.addEventListener('keydown', handleKeydown, true);
66
- return () => {
67
- document.removeEventListener('pointerdown', handlePointerDown);
68
- document.removeEventListener('keydown', handleKeydown, true);
69
- };
36
+ createDismiss({
37
+ enabled: () => ctx.open,
38
+ onDismiss: () => ctx.close(),
39
+ contentEl: () => el ?? null,
40
+ preventDefaultOnEscape: true
70
41
  });
71
-
72
- function handleKeydown(e: KeyboardEvent) {
73
- if (!el) return;
74
- const items = getMenuItems(el);
75
- const currentIndex = items.indexOf(document.activeElement as HTMLElement);
76
-
77
- switch (e.key) {
78
- case 'ArrowDown': {
79
- e.preventDefault();
80
- focusItem(items, currentIndex + 1);
81
- return;
82
- }
83
- case 'ArrowUp': {
84
- e.preventDefault();
85
- focusItem(items, currentIndex - 1);
86
- return;
87
- }
88
- case 'Home': {
89
- e.preventDefault();
90
- focusItem(items, 0);
91
- return;
92
- }
93
- case 'End': {
94
- e.preventDefault();
95
- focusItem(items, items.length - 1);
96
- return;
97
- }
98
- default: {
99
- if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
100
- const char = e.key.toLowerCase();
101
- const match = items.find((item) =>
102
- item.textContent?.trim().toLowerCase().startsWith(char)
103
- );
104
- if (match) match.focus();
105
- }
106
- }
107
- }
108
- }
109
42
  </script>
110
43
 
111
44
  <div
@@ -119,7 +52,7 @@
119
52
  data-state={ctx.open ? 'open' : 'closed'}
120
53
  class={className}
121
54
  {style}
122
- onkeydown={handleKeydown}
55
+ onkeydown={(e) => menu.handleKeydown(e)}
123
56
  {...rest}
124
57
  >
125
58
  {@render children()}
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { SVGAttributes } from 'svelte/elements';
3
+ import { variantAttrs } from '@dryui/primitives';
3
4
  import type { DiagramConfig } from './types.js';
4
5
  import { computeLayout } from './layout.js';
5
6
 
@@ -300,8 +301,7 @@
300
301
  {@const WpIcon = wp.iconComponent}
301
302
  <g
302
303
  data-part="waypoint"
303
- data-variant={wp.variant}
304
- data-color={wp.color}
304
+ {...variantAttrs({ variant: wp.variant, color: wp.color })}
305
305
  transform="translate({wp.x},{wp.y})"
306
306
  >
307
307
  <rect
@@ -349,8 +349,7 @@
349
349
  {@const NodeIcon = node.iconComponent}
350
350
  <g
351
351
  data-part="node"
352
- data-variant={node.variant}
353
- data-color={node.color}
352
+ {...variantAttrs({ variant: node.variant, color: node.color })}
354
353
  data-state={node.state}
355
354
  transform="translate({node.x},{node.y})"
356
355
  >
@@ -2,115 +2,17 @@
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
4
  import { getDialogCtx } from './context.svelte.js';
5
+ import ModalContent from '../internal/modal-content.svelte';
5
6
 
6
7
  interface Props extends HTMLAttributes<HTMLDialogElement> {
7
8
  children: Snippet;
8
9
  }
9
10
 
10
- let { class: className, children, ...rest }: Props = $props();
11
+ let { children, ...rest }: Props = $props();
11
12
 
12
13
  const ctx = getDialogCtx();
13
-
14
- let dialogEl = $state<HTMLDialogElement>();
15
-
16
- $effect(() => {
17
- if (ctx.open && dialogEl && !dialogEl.open) {
18
- dialogEl.showModal();
19
- }
20
- if (!ctx.open && dialogEl?.open) {
21
- dialogEl.close();
22
- }
23
- });
24
14
  </script>
25
15
 
26
- <dialog
27
- bind:this={dialogEl}
28
- data-dialog-content
29
- aria-labelledby={ctx.headerId}
30
- data-state={ctx.open ? 'open' : 'closed'}
31
- onclose={() => ctx.close()}
32
- onclick={(e) => {
33
- if (e.target === dialogEl) {
34
- ctx.close();
35
- }
36
- }}
37
- class={className}
38
- {...rest}
39
- >
40
- <div data-dialog-panel>
41
- {@render children()}
42
- </div>
43
- </dialog>
44
-
45
- <style>
46
- [data-dialog-content] {
47
- position: fixed;
48
- inset: 0;
49
- /* dryui-allow width */
50
- width: 100vw;
51
- /* dryui-allow width */
52
- max-width: none;
53
- height: 100vh;
54
- height: 100dvh;
55
- max-height: none;
56
- border: none;
57
- background: transparent;
58
- color: var(--dry-color-text-strong);
59
- padding: 0;
60
- display: grid;
61
- grid-template-columns: min(90vw, var(--dry-dialog-max-width, 32rem));
62
- place-content: center;
63
- place-items: center;
64
- overflow: visible;
65
- }
66
-
67
- [data-dialog-content]:not([open]) {
68
- display: none;
69
- }
70
-
71
- [data-dialog-content]::backdrop {
72
- background: var(--dry-overlay-bg, var(--dry-color-overlay-backdrop));
73
- backdrop-filter: blur(var(--dry-overlay-blur, 12px));
74
- -webkit-backdrop-filter: blur(var(--dry-overlay-blur, 12px));
75
- }
76
-
77
- [data-dialog-panel] {
78
- --dry-dialog-border: var(--dry-overlay-border, var(--dry-color-stroke-weak));
79
- --dry-dialog-padding: var(--dry-space-8);
80
- --dry-radius-nested: max(
81
- 0px,
82
- calc(
83
- var(--dry-dialog-radius, var(--dry-overlay-radius, var(--dry-radius-2xl))) -
84
- var(--dry-dialog-padding)
85
- )
86
- );
87
-
88
- container-type: inline-size;
89
- justify-self: stretch;
90
- border: 1px solid var(--dry-dialog-border);
91
- border-radius: var(--dry-dialog-radius, var(--dry-overlay-radius, var(--dry-radius-2xl)));
92
- background: var(--dry-dialog-bg, var(--dry-overlay-bg, var(--dry-color-bg-overlay)));
93
- color: var(--dry-color-text-strong);
94
- box-shadow: var(--dry-dialog-shadow, var(--dry-overlay-shadow, var(--dry-shadow-overlay)));
95
- padding: 0;
96
- max-block-size: var(--dry-dialog-max-block-size, 85vh);
97
- display: grid;
98
- overflow: var(--dry-dialog-overflow, auto);
99
-
100
- transition:
101
- opacity var(--dry-duration-normal) var(--dry-ease-spring-snappy),
102
- transform var(--dry-duration-normal) var(--dry-ease-spring-snappy);
103
- }
104
-
105
- [data-dialog-content][data-state='open'] [data-dialog-panel] {
106
- opacity: 1;
107
- transform: scale(1) translateY(0);
108
- }
109
-
110
- @starting-style {
111
- [data-dialog-content][open] [data-dialog-panel] {
112
- opacity: 0;
113
- transform: scale(var(--dry-motion-scale-enter)) translateY(var(--dry-motion-distance-sm));
114
- }
115
- }
116
- </style>
16
+ <ModalContent {ctx} variant="dialog" side="center" {...rest}>
17
+ {@render children()}
18
+ </ModalContent>
@@ -12,7 +12,7 @@
12
12
 
13
13
  const ctx = getDragAndDropCtx();
14
14
 
15
- let itemIsDragging = $derived(ctx.draggedIndex === index);
15
+ let itemIsDragging = $derived(ctx.draggedIndex === index && ctx.isDragging);
16
16
  let isOver = $derived(
17
17
  (ctx.overIndex === index && ctx.isDragging && ctx.draggedIndex !== index) ||
18
18
  ctx.foreignOverIndex === index
@@ -2,167 +2,17 @@
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
4
  import { getDrawerCtx } from './context.svelte.js';
5
+ import ModalContent from '../internal/modal-content.svelte';
5
6
 
6
7
  interface Props extends HTMLAttributes<HTMLDialogElement> {
7
8
  children: Snippet;
8
9
  }
9
10
 
10
- let { class: className, children, ...rest }: Props = $props();
11
+ let { children, ...rest }: Props = $props();
11
12
 
12
13
  const ctx = getDrawerCtx();
13
-
14
- let dialogEl = $state<HTMLDialogElement>();
15
-
16
- $effect(() => {
17
- if (!dialogEl) return;
18
-
19
- // Native <dialog> applies a max-width that leaves a strip beside edge drawers.
20
- dialogEl.style.setProperty('max-width', 'none');
21
-
22
- if (ctx.open && !dialogEl.open) {
23
- dialogEl.showModal();
24
- }
25
- if (!ctx.open && dialogEl.open) {
26
- dialogEl.close();
27
- }
28
- });
29
14
  </script>
30
15
 
31
- <dialog
32
- bind:this={dialogEl}
33
- data-drawer-content
34
- data-side={ctx.side}
35
- aria-labelledby={ctx.headerId}
36
- data-state={ctx.open ? 'open' : 'closed'}
37
- onclose={() => ctx.close()}
38
- onclick={(e) => {
39
- if (e.target === dialogEl) {
40
- ctx.close();
41
- }
42
- }}
43
- class={className}
44
- {...rest}
45
- >
46
- <div data-drawer-panel>
47
- {@render children()}
48
- </div>
49
- </dialog>
50
-
51
- <style>
52
- [data-drawer-content] {
53
- margin: 0;
54
- position: fixed;
55
- inset: 0;
56
- height: 100vh;
57
- height: 100dvh;
58
- max-height: none;
59
- border: none;
60
- background: transparent;
61
- color: var(--dry-color-text-strong);
62
- padding: 0;
63
- box-sizing: border-box;
64
- display: grid;
65
- grid-template-columns: minmax(0, 1fr);
66
- grid-template-rows: minmax(0, 1fr);
67
- }
68
-
69
- [data-drawer-content]:not([open]) {
70
- display: none;
71
- }
72
-
73
- [data-drawer-content]::backdrop {
74
- background: var(--dry-overlay-bg, var(--dry-color-overlay-backdrop-strong));
75
- backdrop-filter: blur(var(--dry-overlay-blur, 8px));
76
- -webkit-backdrop-filter: blur(var(--dry-overlay-blur, 8px));
77
- }
78
-
79
- /* Side positioning via grid alignment */
80
- [data-drawer-content][data-side='right'] {
81
- grid-template-columns:
82
- minmax(0, calc(100dvw - var(--dry-drawer-size, 25rem)))
83
- var(--dry-drawer-size, 25rem);
84
- }
85
-
86
- [data-drawer-content][data-side='left'] {
87
- grid-template-columns:
88
- var(--dry-drawer-size, 25rem)
89
- minmax(0, calc(100dvw - var(--dry-drawer-size, 25rem)));
90
- }
91
-
92
- [data-drawer-content][data-side='top'] {
93
- grid-template-rows:
94
- var(--dry-drawer-size, 25rem)
95
- minmax(0, calc(100dvh - var(--dry-drawer-size, 25rem)));
96
- }
97
-
98
- [data-drawer-content][data-side='bottom'] {
99
- grid-template-rows:
100
- minmax(0, calc(100dvh - var(--dry-drawer-size, 25rem)))
101
- var(--dry-drawer-size, 25rem);
102
- }
103
-
104
- /* Panel */
105
- [data-drawer-panel] {
106
- --dry-drawer-bg: var(--dry-color-bg-overlay);
107
- --dry-drawer-border: var(--dry-color-stroke-weak);
108
- --dry-drawer-size: 25rem;
109
- --_drawer-rest-transform: translateX(0);
110
- --_drawer-enter-transform: translateX(100%);
111
-
112
- background: var(--dry-drawer-bg);
113
- color: var(--dry-color-text-strong);
114
- box-shadow: var(--dry-drawer-shadow, var(--dry-shadow-overlay));
115
- padding: 0;
116
- display: grid;
117
- grid-template-rows: max-content minmax(0, 1fr) max-content;
118
- overflow: hidden;
119
- opacity: 1;
120
- transform: var(--_drawer-rest-transform);
121
- will-change: transform, opacity;
122
-
123
- transition:
124
- transform var(--dry-duration-slow) var(--dry-ease-spring-snappy),
125
- opacity var(--dry-duration-normal) var(--dry-ease-out);
126
- }
127
-
128
- [data-drawer-content][data-side='right'] [data-drawer-panel] {
129
- grid-column: 2;
130
- height: 100%;
131
- border-left: 1px solid var(--dry-drawer-border);
132
- }
133
-
134
- [data-drawer-content][data-side='left'] [data-drawer-panel] {
135
- --_drawer-enter-transform: translateX(-100%);
136
- grid-column: 1;
137
- height: 100%;
138
- border-right: 1px solid var(--dry-drawer-border);
139
- }
140
-
141
- [data-drawer-content][data-side='top'] [data-drawer-panel] {
142
- --_drawer-rest-transform: translateY(0);
143
- --_drawer-enter-transform: translateY(-100%);
144
- grid-row: 1;
145
- height: var(--dry-drawer-size);
146
- border-bottom: 1px solid var(--dry-drawer-border);
147
- }
148
-
149
- [data-drawer-content][data-side='bottom'] [data-drawer-panel] {
150
- --_drawer-rest-transform: translateY(0);
151
- --_drawer-enter-transform: translateY(100%);
152
- grid-row: 2;
153
- height: var(--dry-drawer-size);
154
- border-top: 1px solid var(--dry-drawer-border);
155
- }
156
-
157
- @starting-style {
158
- [data-drawer-content][open] [data-drawer-panel] {
159
- opacity: 0;
160
- transform: var(--_drawer-enter-transform);
161
- }
162
- }
163
-
164
- [data-drawer-content][data-state='open'] [data-drawer-panel] {
165
- opacity: 1;
166
- transform: var(--_drawer-rest-transform);
167
- }
168
- </style>
16
+ <ModalContent {ctx} variant="drawer" side={ctx.side} {...rest}>
17
+ {@render children()}
18
+ </ModalContent>
@@ -1,12 +1,10 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
- import { createPositionedPopover } from '@dryui/primitives';
4
+ import { createAnchoredPopover, createMenuNavigation } from '@dryui/primitives';
5
5
  import type { Placement } from '@dryui/primitives';
6
6
  import { getDropdownMenuCtx } from './context.svelte.js';
7
7
 
8
- const MENU_ITEM_SELECTOR = '[role="menuitem"]:not([data-disabled])';
9
-
10
8
  interface Props extends HTMLAttributes<HTMLDivElement> {
11
9
  placement?: Placement;
12
10
  offset?: number;
@@ -26,79 +24,18 @@
26
24
 
27
25
  let el = $state<HTMLDivElement>();
28
26
 
29
- const popover = createPositionedPopover({
27
+ const popover = createAnchoredPopover({
30
28
  triggerEl: () => ctx.triggerEl,
31
29
  contentEl: () => el ?? null,
30
+ open: () => ctx.open,
32
31
  placement: () => placement,
33
32
  offset: () => offset
34
33
  });
35
34
 
36
- function getMenuItems(container: HTMLElement): HTMLElement[] {
37
- return Array.from(container.querySelectorAll<HTMLElement>(MENU_ITEM_SELECTOR));
38
- }
39
-
40
- function focusItem(items: HTMLElement[], index: number): void {
41
- if (items.length === 0) return;
42
- const clamped = ((index % items.length) + items.length) % items.length;
43
- items[clamped]?.focus();
44
- }
45
-
46
- function focusFirstItem(): void {
47
- if (!el) return;
48
- const items = getMenuItems(el);
49
- const first = items[0];
50
- if (first) {
51
- first.focus();
52
- } else {
53
- el.focus();
54
- }
55
- }
56
-
57
- $effect(() => {
58
- if (ctx.open && el && !el.matches(':popover-open')) {
59
- popover.showPopover(el);
60
- } else if (!ctx.open && el?.matches(':popover-open')) {
61
- popover.hidePopover(el);
62
- }
35
+ const menu = createMenuNavigation({
36
+ container: () => el ?? null,
37
+ orientation: 'vertical'
63
38
  });
64
-
65
- function handleKeydown(e: KeyboardEvent) {
66
- if (!el) return;
67
- const items = getMenuItems(el);
68
- const currentIndex = items.indexOf(document.activeElement as HTMLElement);
69
-
70
- switch (e.key) {
71
- case 'ArrowDown': {
72
- e.preventDefault();
73
- focusItem(items, currentIndex + 1);
74
- return;
75
- }
76
- case 'ArrowUp': {
77
- e.preventDefault();
78
- focusItem(items, currentIndex - 1);
79
- return;
80
- }
81
- case 'Home': {
82
- e.preventDefault();
83
- focusItem(items, 0);
84
- return;
85
- }
86
- case 'End': {
87
- e.preventDefault();
88
- focusItem(items, items.length - 1);
89
- return;
90
- }
91
- default: {
92
- if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
93
- const char = e.key.toLowerCase();
94
- const match = items.find((item) =>
95
- item.textContent?.trim().toLowerCase().startsWith(char)
96
- );
97
- if (match) match.focus();
98
- }
99
- }
100
- }
101
- }
102
39
  </script>
103
40
 
104
41
  <div
@@ -116,12 +53,12 @@
116
53
  const newState = (e as ToggleEvent).newState === 'open';
117
54
  if (newState && !ctx.open) {
118
55
  ctx.show();
119
- queueMicrotask(focusFirstItem);
56
+ queueMicrotask(() => menu.focusFirst());
120
57
  } else if (!newState && ctx.open) {
121
58
  ctx.close();
122
59
  }
123
60
  }}
124
- onkeydown={handleKeydown}
61
+ onkeydown={(e) => menu.handleKeydown(e)}
125
62
  {...rest}
126
63
  >
127
64
  {@render children()}
@@ -171,4 +108,10 @@
171
108
  translateY(calc(var(--dry-motion-distance-xs) * -1));
172
109
  }
173
110
  }
111
+
112
+ @media (prefers-reduced-motion: reduce) {
113
+ [data-dropdown-menu-content] {
114
+ transition: none;
115
+ }
116
+ }
174
117
  </style>
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
+ import { variantAttrs } from '@dryui/primitives';
4
5
 
5
6
  interface Props extends HTMLAttributes<HTMLHeadingElement> {
6
7
  level?: 1 | 2 | 3 | 4 | 5 | 6;
@@ -12,27 +13,27 @@
12
13
  </script>
13
14
 
14
15
  {#if level === 1}
15
- <h1 class={className} data-level={level} data-variant={variant} {...rest}>
16
+ <h1 class={className} {...variantAttrs({ level, variant })} {...rest}>
16
17
  {@render children()}
17
18
  </h1>
18
19
  {:else if level === 2}
19
- <h2 class={className} data-level={level} data-variant={variant} {...rest}>
20
+ <h2 class={className} {...variantAttrs({ level, variant })} {...rest}>
20
21
  {@render children()}
21
22
  </h2>
22
23
  {:else if level === 3}
23
- <h3 class={className} data-level={level} data-variant={variant} {...rest}>
24
+ <h3 class={className} {...variantAttrs({ level, variant })} {...rest}>
24
25
  {@render children()}
25
26
  </h3>
26
27
  {:else if level === 4}
27
- <h4 class={className} data-level={level} data-variant={variant} {...rest}>
28
+ <h4 class={className} {...variantAttrs({ level, variant })} {...rest}>
28
29
  {@render children()}
29
30
  </h4>
30
31
  {:else if level === 5}
31
- <h5 class={className} data-level={level} data-variant={variant} {...rest}>
32
+ <h5 class={className} {...variantAttrs({ level, variant })} {...rest}>
32
33
  {@render children()}
33
34
  </h5>
34
35
  {:else}
35
- <h6 class={className} data-level={level} data-variant={variant} {...rest}>
36
+ <h6 class={className} {...variantAttrs({ level, variant })} {...rest}>
36
37
  {@render children()}
37
38
  </h6>
38
39
  {/if}
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
- import { createPositionedPopover } from '@dryui/primitives';
4
+ import { createAnchoredPopover } from '@dryui/primitives';
5
5
  import type { Placement } from '@dryui/primitives';
6
6
  import { getHoverCardCtx } from './context.svelte.js';
7
7
 
@@ -24,22 +24,14 @@
24
24
 
25
25
  let contentEl = $state<HTMLDivElement>();
26
26
 
27
- const popover = createPositionedPopover({
27
+ const popover = createAnchoredPopover({
28
28
  triggerEl: () => ctx.triggerEl,
29
29
  contentEl: () => contentEl ?? null,
30
+ open: () => ctx.open,
30
31
  placement: () => placement,
31
32
  offset: () => offset
32
33
  });
33
34
 
34
- $effect(() => {
35
- if (!contentEl) return;
36
- if (ctx.open) {
37
- popover.showPopover(contentEl);
38
- } else {
39
- popover.hidePopover(contentEl);
40
- }
41
- });
42
-
43
35
  function handleKeydown(e: KeyboardEvent) {
44
36
  if (e.key === 'Escape') {
45
37
  e.preventDefault();
@@ -51,7 +43,6 @@
51
43
  <div
52
44
  bind:this={contentEl}
53
45
  id={ctx.contentId}
54
- role="dialog"
55
46
  popover="manual"
56
47
  data-hover-card-content
57
48
  data-state={ctx.open ? 'open' : 'closed'}
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
+ import { variantAttrs } from '@dryui/primitives';
3
4
 
4
5
  interface Props {
5
6
  size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
@@ -24,8 +25,7 @@
24
25
  role={label ? 'img' : 'presentation'}
25
26
  aria-label={label}
26
27
  aria-hidden={label ? undefined : true}
27
- data-size={size}
28
- data-color={color}
28
+ {...variantAttrs({ size, color })}
29
29
  class={className}
30
30
  use:sizeSvg
31
31
  >
package/dist/index.d.ts CHANGED
@@ -292,7 +292,7 @@ export { StarRating } from './star-rating/index.js';
292
292
  export type { StarRatingRootProps } from './star-rating/index.js';
293
293
  export { Tag } from './tag/index.js';
294
294
  export type { TagProps, TagColor } from './tag/index.js';
295
- export { useThemeOverride } from './themes/use-theme-override.svelte.js';
296
- export type { ThemeTokenMap } from './themes/use-theme-override.svelte.js';
295
+ export { TokenScope } from './themes/token-scope.js';
296
+ export type { TokenScopeProps } from './themes/token-scope.js';
297
297
  export { Diagram } from './diagram/index.js';
298
298
  export type { DiagramConfig, DiagramNode, DiagramEdge, DiagramCluster, DiagramAnnotation, DiagramSwimlane, DiagramRegion, DiagramColor, DiagramDirection } from './diagram/index.js';