@dryui/ui 1.3.0 → 1.4.0

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 (93) hide show
  1. package/dist/accordion/accordion-content.svelte +1 -1
  2. package/dist/alert/alert.svelte +1 -1
  3. package/dist/app-frame/app-frame.svelte +125 -0
  4. package/dist/app-frame/app-frame.svelte.d.ts +10 -0
  5. package/dist/app-frame/index.d.ts +8 -0
  6. package/dist/app-frame/index.js +1 -0
  7. package/dist/aurora/aurora.svelte +22 -59
  8. package/dist/beam/beam.svelte +28 -9
  9. package/dist/carousel/carousel-button-dots.svelte +25 -8
  10. package/dist/carousel/carousel-button-thumbnails.svelte +25 -8
  11. package/dist/carousel/carousel-root.svelte +115 -4
  12. package/dist/carousel/carousel-slide.svelte +5 -1
  13. package/dist/carousel/carousel-viewport.svelte +2 -0
  14. package/dist/carousel/context.svelte.d.ts +5 -0
  15. package/dist/chart/chart-bars.svelte +25 -16
  16. package/dist/chart/chart-donut.svelte +25 -16
  17. package/dist/chart/chart-root.svelte +134 -30
  18. package/dist/chart/chart-root.svelte.d.ts +1 -0
  19. package/dist/chart/context.svelte.d.ts +3 -1
  20. package/dist/chart/context.svelte.js +1 -0
  21. package/dist/chart/index.d.ts +1 -0
  22. package/dist/chromatic-shift/chromatic-shift.svelte +36 -9
  23. package/dist/collapsible/collapsible-content.svelte +2 -1
  24. package/dist/combobox/combobox-content.svelte +26 -44
  25. package/dist/combobox/combobox-content.svelte.d.ts +1 -1
  26. package/dist/combobox/combobox-input-root.svelte +7 -1
  27. package/dist/combobox/combobox-input.svelte +21 -8
  28. package/dist/country-select/country-select-button-input.svelte +124 -260
  29. package/dist/date-picker/datepicker-content.svelte +18 -26
  30. package/dist/date-picker/datepicker-content.svelte.d.ts +2 -1
  31. package/dist/date-picker/datepicker-input-root.svelte +7 -1
  32. package/dist/date-range-picker/date-range-picker-content.svelte +18 -14
  33. package/dist/date-range-picker/date-range-picker-content.svelte.d.ts +2 -1
  34. package/dist/date-range-picker/date-range-picker-root.svelte +7 -1
  35. package/dist/displacement/displacement.svelte +16 -22
  36. package/dist/drag-and-drop/context.svelte.d.ts +2 -0
  37. package/dist/drag-and-drop/drag-and-drop-handle.svelte +34 -5
  38. package/dist/drag-and-drop/drag-and-drop-item.svelte +23 -14
  39. package/dist/drag-and-drop/drag-and-drop-root.svelte +60 -16
  40. package/dist/god-rays/god-rays.svelte +11 -0
  41. package/dist/gradient-mesh/gradient-mesh.svelte +27 -5
  42. package/dist/hover-card/context.svelte.d.ts +1 -10
  43. package/dist/hover-card/context.svelte.js +1 -2
  44. package/dist/hover-card/hover-card-content.svelte +41 -3
  45. package/dist/hover-card/hover-card-root.svelte +7 -55
  46. package/dist/hover-card/hover-card-trigger.svelte +79 -40
  47. package/dist/hover-card/hover-card-trigger.svelte.d.ts +1 -1
  48. package/dist/index.d.ts +2 -0
  49. package/dist/index.js +1 -0
  50. package/dist/internal/motion.d.ts +1 -1
  51. package/dist/internal/motion.js +1 -1
  52. package/dist/marquee/marquee.svelte +56 -8
  53. package/dist/mega-menu/context.svelte.d.ts +2 -1
  54. package/dist/mega-menu/mega-menu-button-trigger.svelte +2 -14
  55. package/dist/mega-menu/mega-menu-item.svelte +3 -1
  56. package/dist/mega-menu/mega-menu-panel.svelte +35 -3
  57. package/dist/mega-menu/mega-menu-root.svelte +28 -13
  58. package/dist/menubar/context.svelte.d.ts +2 -2
  59. package/dist/menubar/menubar-button-trigger.svelte +5 -3
  60. package/dist/menubar/menubar-content.svelte +20 -12
  61. package/dist/menubar/menubar-root.svelte +4 -4
  62. package/dist/multi-select-combobox/multi-select-combobox-content.svelte +18 -55
  63. package/dist/multi-select-combobox/multi-select-combobox-content.svelte.d.ts +1 -1
  64. package/dist/noise/noise.svelte +38 -6
  65. package/dist/notification-center/context.svelte.d.ts +0 -1
  66. package/dist/notification-center/notification-center-panel.svelte +54 -35
  67. package/dist/notification-center/notification-center-root.svelte +0 -1
  68. package/dist/notification-center/notification-center-trigger-button.svelte +1 -8
  69. package/dist/option-picker/option-picker-description.svelte +2 -2
  70. package/dist/option-picker/option-picker-item.svelte +10 -3
  71. package/dist/option-picker/option-picker-label.svelte +2 -2
  72. package/dist/option-picker/option-picker-preview.svelte +18 -13
  73. package/dist/phone-input/phone-input-select.svelte +2 -152
  74. package/dist/phone-input/phone-input-select.svelte.d.ts +1 -7
  75. package/dist/rich-text-editor/rich-text-editor-toolbar-button-input.svelte +84 -29
  76. package/dist/scroll-area/scroll-area.svelte +16 -4
  77. package/dist/select/select-content.svelte +21 -31
  78. package/dist/select/select-content.svelte.d.ts +1 -1
  79. package/dist/select/select-root-input.svelte +7 -1
  80. package/dist/shimmer/shimmer.svelte +22 -12
  81. package/dist/tabs/tabs-list.svelte +12 -0
  82. package/dist/transfer/transfer-item.svelte +0 -3
  83. package/dist/transfer/transfer-list-input.svelte +1 -6
  84. package/dist/tree/context.svelte.d.ts +7 -1
  85. package/dist/tree/tree-item-children.svelte +12 -10
  86. package/dist/tree/tree-item-label.svelte +6 -17
  87. package/dist/tree/tree-item-label.svelte.d.ts +2 -2
  88. package/dist/tree/tree-item.svelte +28 -1
  89. package/dist/tree/tree-root.svelte +135 -59
  90. package/dist/typography/heading.svelte +1 -0
  91. package/package.json +8 -2
  92. package/skills/dryui/SKILL.md +1 -0
  93. package/dist/hover-card/hover-card-root.svelte.d.ts +0 -9
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
+ import { fromAction } from 'svelte/attachments';
3
4
  import type { HTMLAttributes } from 'svelte/elements';
4
5
  import { createAnchoredPopover, createMenuNavigation } from '@dryui/primitives';
5
6
  import { getMenubarCtx, getMenubarMenuCtx } from './context.svelte.js';
@@ -22,14 +23,21 @@
22
23
  const ctx = getMenubarCtx();
23
24
  const menuCtx = getMenubarMenuCtx();
24
25
 
25
- let el = $state<HTMLDivElement>();
26
- let triggerEl = $state<HTMLElement | null>(null);
26
+ let el = $state<HTMLDivElement | null>(null);
27
+ const triggerEl = $derived(
28
+ ctx.rootElement?.querySelector<HTMLElement>(`[data-menubar-trigger="${menuCtx.menuId}"]`) ??
29
+ null
30
+ );
27
31
 
28
- $effect(() => {
29
- const root = ctx.rootElement;
30
- triggerEl =
31
- root?.querySelector<HTMLElement>(`[data-menubar-trigger="${menuCtx.menuId}"]`) ?? null;
32
- });
32
+ function attachContent(node: HTMLDivElement) {
33
+ el = node;
34
+
35
+ return () => {
36
+ if (el === node) {
37
+ el = null;
38
+ }
39
+ };
40
+ }
33
41
 
34
42
  const popover = createAnchoredPopover({
35
43
  triggerEl: () => triggerEl,
@@ -61,12 +69,12 @@
61
69
  switch (e.key) {
62
70
  case 'ArrowRight': {
63
71
  e.preventDefault();
64
- ctx.focusNextMenu(menuCtx.menuId);
72
+ ctx.focusNextMenu(menuCtx.menuId, true);
65
73
  return;
66
74
  }
67
75
  case 'ArrowLeft': {
68
76
  e.preventDefault();
69
- ctx.focusPrevMenu(menuCtx.menuId);
77
+ ctx.focusPrevMenu(menuCtx.menuId, true);
70
78
  return;
71
79
  }
72
80
  case 'Escape': {
@@ -84,15 +92,15 @@
84
92
  </script>
85
93
 
86
94
  <div
87
- bind:this={el}
95
+ {@attach attachContent}
96
+ {@attach fromAction(popover.applyPosition, () => style)}
88
97
  popover="auto"
89
98
  role="menu"
90
99
  tabindex="-1"
91
- aria-labelledby={`menubar-trigger-${menuCtx.menuId}`}
100
+ aria-labelledby={triggerEl?.id}
92
101
  data-menubar-content
93
102
  data-state={menuCtx.open ? 'open' : 'closed'}
94
103
  class={className}
95
- use:popover.applyPosition={style}
96
104
  ontoggle={(e) => {
97
105
  const newState = (e as ToggleEvent).newState === 'open';
98
106
  if (!newState && menuCtx.open) {
@@ -48,20 +48,20 @@
48
48
  getMenuIds() {
49
49
  return menuIds;
50
50
  },
51
- focusNextMenu(currentId) {
51
+ focusNextMenu(currentId, open = false) {
52
52
  const idx = menuIds.indexOf(currentId);
53
53
  const nextIdx = (idx + 1) % menuIds.length;
54
54
  const nextId = menuIds[nextIdx] ?? null;
55
- activeMenu = nextId;
55
+ activeMenu = open ? nextId : null;
56
56
  requestAnimationFrame(() => {
57
57
  rootEl?.querySelector<HTMLButtonElement>(`[data-menubar-trigger="${nextId}"]`)?.focus();
58
58
  });
59
59
  },
60
- focusPrevMenu(currentId) {
60
+ focusPrevMenu(currentId, open = false) {
61
61
  const idx = menuIds.indexOf(currentId);
62
62
  const prevIdx = (idx - 1 + menuIds.length) % menuIds.length;
63
63
  const prevId = menuIds[prevIdx] ?? null;
64
- activeMenu = prevId;
64
+ activeMenu = open ? prevId : null;
65
65
  requestAnimationFrame(() => {
66
66
  rootEl?.querySelector<HTMLButtonElement>(`[data-menubar-trigger="${prevId}"]`)?.focus();
67
67
  });
@@ -1,8 +1,8 @@
1
1
  <script lang="ts">
2
+ import { fromAction } from 'svelte/attachments';
2
3
  import type { Snippet } from 'svelte';
3
4
  import type { HTMLAttributes } from 'svelte/elements';
4
- import { createAnchorPosition } from '@dryui/primitives';
5
- import type { Placement } from '@dryui/primitives';
5
+ import { createAnchoredPopover, createDismiss, type Placement } from '@dryui/primitives';
6
6
  import { getMultiSelectComboboxCtx } from './context.svelte.js';
7
7
 
8
8
  interface Props extends HTMLAttributes<HTMLDivElement> {
@@ -28,29 +28,6 @@
28
28
 
29
29
  let el = $state<HTMLDivElement | null>(null);
30
30
 
31
- const anchor = createAnchorPosition(
32
- () => ctx.anchorEl,
33
- () => el ?? null,
34
- {
35
- get placement() {
36
- return placement;
37
- },
38
- get offset() {
39
- return offset;
40
- }
41
- }
42
- );
43
-
44
- $effect(() => {
45
- if (!el) return;
46
-
47
- el.style.cssText = typeof style === 'string' ? style : '';
48
- const positionStyles = anchor.styles;
49
- for (const [key, value] of Object.entries(positionStyles)) {
50
- el.style.setProperty(key, value);
51
- }
52
- });
53
-
54
31
  function attachContent(node: HTMLDivElement) {
55
32
  el = node;
56
33
 
@@ -61,41 +38,26 @@
61
38
  };
62
39
  }
63
40
 
64
- function syncPopover(isOpen: boolean) {
65
- return (node: HTMLDivElement) => {
66
- if (isOpen && !node.matches(':popover-open')) {
67
- node.showPopover();
68
- } else if (!isOpen && node.matches(':popover-open')) {
69
- node.hidePopover();
70
- }
71
- };
72
- }
73
-
74
- function dismissOnOutsidePointerDown(node: HTMLDivElement) {
75
- function handlePointerDown(event: PointerEvent) {
76
- const target = event.target;
77
- if (!(target instanceof Node)) {
78
- return;
79
- }
80
-
81
- if (ctx.anchorEl?.contains(target) || node.contains(target)) {
82
- return;
83
- }
84
-
85
- ctx.close();
86
- }
41
+ const popover = createAnchoredPopover({
42
+ triggerEl: () => ctx.anchorEl,
43
+ contentEl: () => el ?? null,
44
+ open: () => ctx.open,
45
+ placement: () => placement,
46
+ offset: () => offset
47
+ });
87
48
 
88
- document.addEventListener('pointerdown', handlePointerDown);
89
- return () => {
90
- document.removeEventListener('pointerdown', handlePointerDown);
91
- };
92
- }
49
+ createDismiss({
50
+ enabled: () => ctx.open,
51
+ escapeKey: false,
52
+ onDismiss: () => ctx.close(),
53
+ contentEl: () => el ?? null,
54
+ triggerEl: () => ctx.anchorEl
55
+ });
93
56
  </script>
94
57
 
95
58
  <div
96
59
  {@attach attachContent}
97
- {@attach syncPopover(ctx.open)}
98
- {@attach ctx.open && dismissOnOutsidePointerDown}
60
+ {@attach fromAction(popover.applyPosition, () => style)}
99
61
  popover="manual"
100
62
  role="listbox"
101
63
  id={ctx.contentId}
@@ -123,6 +85,7 @@
123
85
  [data-multi-select-content] {
124
86
  inset: unset;
125
87
  margin: 0;
88
+ grid-template-columns: minmax(anchor-size(inline), max-content);
126
89
  background: var(--dry-color-bg-overlay);
127
90
  border: 1px solid var(--dry-color-stroke-weak);
128
91
  border-radius: var(--dry-radius-md);
@@ -1,6 +1,6 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
- import type { Placement } from '@dryui/primitives';
3
+ import { type Placement } from '@dryui/primitives';
4
4
  interface Props extends HTMLAttributes<HTMLDivElement> {
5
5
  placement?: Placement;
6
6
  offset?: number;
@@ -2,7 +2,11 @@
2
2
  import { onMount } from 'svelte';
3
3
  import type { Snippet } from 'svelte';
4
4
  import type { HTMLAttributes } from 'svelte/elements';
5
- import { observeReducedMotionPreference } from '@dryui/primitives/internal/motion';
5
+ import {
6
+ observeInViewport,
7
+ observePageVisibility,
8
+ observeReducedMotionPreference
9
+ } from '@dryui/primitives/internal/motion';
6
10
 
7
11
  interface Props extends HTMLAttributes<HTMLDivElement> {
8
12
  opacity?: number;
@@ -24,15 +28,25 @@
24
28
  }: Props = $props();
25
29
 
26
30
  let prefersReducedMotion = $state(false);
31
+ let onScreen = $state(true);
32
+ let tabVisible = $state(true);
27
33
 
28
- onMount(() =>
29
- observeReducedMotionPreference((matches) => {
34
+ onMount(() => {
35
+ const stopMotion = observeReducedMotionPreference((matches) => {
30
36
  prefersReducedMotion = matches;
31
- })
32
- );
37
+ });
38
+ const stopVisibility = observePageVisibility((visible) => {
39
+ tabVisible = visible;
40
+ });
41
+ return () => {
42
+ stopMotion();
43
+ stopVisibility();
44
+ };
45
+ });
33
46
 
34
47
  const normalizedOpacity = $derived(`${Math.max(0, Math.min(1, opacity))}`);
35
48
  const shouldAnimate = $derived(animated && !prefersReducedMotion);
49
+ const paused = $derived(!onScreen || !tabVisible);
36
50
 
37
51
  function applyStyles(node: HTMLElement) {
38
52
  $effect(() => {
@@ -40,6 +54,16 @@
40
54
  node.style.setProperty('--dry-noise-opacity', normalizedOpacity);
41
55
  node.style.setProperty('--dry-noise-blend', blend);
42
56
  });
57
+
58
+ $effect(() =>
59
+ observeInViewport(
60
+ node,
61
+ (inView) => {
62
+ onScreen = inView;
63
+ },
64
+ { rootMargin: '200px' }
65
+ )
66
+ );
43
67
  }
44
68
  </script>
45
69
 
@@ -48,6 +72,7 @@
48
72
  data-noise
49
73
  data-animated={shouldAnimate || undefined}
50
74
  data-reduced-motion={prefersReducedMotion || undefined}
75
+ data-paused={paused || undefined}
51
76
  data-grain={grain}
52
77
  {...rest}
53
78
  {@attach applyStyles}
@@ -95,10 +120,17 @@
95
120
  }
96
121
 
97
122
  [data-noise][data-animated] [data-noise-texture] {
98
- will-change: transform;
99
123
  animation: noise-drift 1.6s steps(6) infinite;
100
124
  }
101
125
 
126
+ [data-noise][data-animated]:not([data-paused]) [data-noise-texture] {
127
+ will-change: transform;
128
+ }
129
+
130
+ [data-noise][data-animated][data-paused] [data-noise-texture] {
131
+ animation-play-state: paused;
132
+ }
133
+
102
134
  [data-noise][data-reduced-motion] [data-noise-texture] {
103
135
  animation: none;
104
136
  }
@@ -10,7 +10,6 @@ export interface NotificationCenterContext {
10
10
  readonly open: boolean;
11
11
  readonly triggerId: string;
12
12
  readonly panelId: string;
13
- triggerEl: HTMLElement | null;
14
13
  markAllRead: () => void;
15
14
  markRead: (id: string) => void;
16
15
  remove: (id: string) => void;
@@ -1,7 +1,8 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
+ import { fromAction } from 'svelte/attachments';
3
4
  import type { HTMLAttributes } from 'svelte/elements';
4
- import { useAnchorStyles } from '../utils/use-anchor-styles.svelte.js';
5
+ import { createAnchoredPopover } from '@dryui/primitives';
5
6
  import { getNotificationCenterCtx } from './context.svelte.js';
6
7
 
7
8
  interface Props extends HTMLAttributes<HTMLDivElement> {
@@ -33,54 +34,68 @@
33
34
 
34
35
  const ctx = getNotificationCenterCtx();
35
36
 
36
- let panelEl = $state<HTMLDivElement>();
37
+ let panelEl = $state<HTMLDivElement | null>(null);
38
+ const triggerEl = $derived(document.getElementById(ctx.triggerId) as HTMLElement | null);
37
39
 
38
- const anchor = useAnchorStyles({
39
- triggerEl: () => ctx.triggerEl,
40
- contentEl: () => panelEl ?? null,
41
- placement: () => placement,
42
- offset: () => offset
43
- });
40
+ function attachPanel(node: HTMLDivElement) {
41
+ panelEl = node;
44
42
 
45
- $effect(() => {
46
- if (!panelEl) return;
43
+ return () => {
44
+ if (panelEl === node) {
45
+ panelEl = null;
46
+ }
47
+ };
48
+ }
47
49
 
50
+ function focusTrigger() {
51
+ if (triggerEl instanceof HTMLElement) {
52
+ triggerEl.focus();
53
+ }
54
+ }
55
+
56
+ function handleClose(restoreFocus: boolean) {
48
57
  if (ctx.open) {
49
- try {
50
- if (!panelEl.matches(':popover-open')) {
51
- panelEl.showPopover();
52
- }
53
- } catch {
54
- // Already shown
55
- }
58
+ ctx.close();
59
+ }
60
+ if (restoreFocus) {
61
+ requestAnimationFrame(() => focusTrigger());
62
+ }
63
+ }
56
64
 
57
- // Nudge panel into viewport if anchor positioning overflows
65
+ function handleKeydown(event: KeyboardEvent) {
66
+ if (event.key !== 'Escape') return;
67
+ event.preventDefault();
68
+ event.stopPropagation();
69
+ handleClose(true);
70
+ }
71
+
72
+ const popover = createAnchoredPopover({
73
+ triggerEl: () => triggerEl,
74
+ contentEl: () => panelEl ?? null,
75
+ open: () => ctx.open,
76
+ placement: () => placement,
77
+ offset: () => offset,
78
+ onAfterShow: (node) => {
58
79
  requestAnimationFrame(() => {
59
- if (!panelEl) return;
60
- panelEl.style.translate = '';
61
- const rect = panelEl.getBoundingClientRect();
80
+ node.style.translate = '';
81
+ const rect = node.getBoundingClientRect();
62
82
  const pad = 8;
63
83
  if (rect.left < pad) {
64
- panelEl.style.translate = `${pad - rect.left}px 0`;
84
+ node.style.translate = `${pad - rect.left}px 0`;
65
85
  } else if (rect.right > window.innerWidth - pad) {
66
- panelEl.style.translate = `${window.innerWidth - pad - rect.right}px 0`;
86
+ node.style.translate = `${window.innerWidth - pad - rect.right}px 0`;
67
87
  }
68
88
  });
69
- } else {
70
- try {
71
- if (panelEl.matches(':popover-open')) {
72
- panelEl.hidePopover();
73
- }
74
- } catch {
75
- // Already hidden
76
- }
77
- if (panelEl) panelEl.style.translate = '';
89
+ },
90
+ onAfterHide: (node) => {
91
+ node.style.translate = '';
78
92
  }
79
93
  });
80
94
  </script>
81
95
 
82
96
  <div
83
- bind:this={panelEl}
97
+ {@attach attachPanel}
98
+ {@attach fromAction(popover.applyPosition, () => style)}
84
99
  id={ctx.panelId}
85
100
  popover="auto"
86
101
  role="region"
@@ -88,15 +103,19 @@
88
103
  data-notification-center-panel
89
104
  data-state={ctx.open ? 'open' : 'closed'}
90
105
  class={className}
91
- use:anchor.applyPosition={style}
92
106
  ontoggle={(e) => {
93
107
  const event = /** @type {ToggleEvent} */ (e);
94
108
  if (event.newState === 'open') {
95
109
  ctx.show();
96
110
  } else {
97
- ctx.close();
111
+ const restoreFocus =
112
+ panelEl instanceof HTMLElement &&
113
+ document.activeElement instanceof HTMLElement &&
114
+ panelEl.contains(document.activeElement);
115
+ handleClose(restoreFocus);
98
116
  }
99
117
  }}
118
+ onkeydown={handleKeydown}
100
119
  {...rest}
101
120
  >
102
121
  {@render children()}
@@ -36,7 +36,6 @@
36
36
  get panelId() {
37
37
  return panelId;
38
38
  },
39
- triggerEl: null,
40
39
  markAllRead() {
41
40
  items = items.map((item) => ({ ...item, read: true }));
42
41
  },
@@ -11,13 +11,6 @@
11
11
  let { children, onclick, ...rest }: Props = $props();
12
12
 
13
13
  const ctx = getNotificationCenterCtx();
14
-
15
- $effect(() => {
16
- const el = document.getElementById(ctx.triggerId);
17
- if (el) {
18
- ctx.triggerEl = el as HTMLButtonElement;
19
- }
20
- });
21
14
  </script>
22
15
 
23
16
  <Button
@@ -25,8 +18,8 @@
25
18
  type="button"
26
19
  id={ctx.triggerId}
27
20
  popovertarget={ctx.panelId}
21
+ aria-controls={ctx.panelId}
28
22
  aria-expanded={ctx.open}
29
- aria-haspopup="dialog"
30
23
  data-notification-center-trigger
31
24
  {onclick}
32
25
  {...rest}
@@ -17,8 +17,8 @@
17
17
  [data-option-picker-description] {
18
18
  grid-column: var(--dry-option-picker-description-column, 2);
19
19
  grid-row: var(--dry-option-picker-description-row, 2);
20
- font-size: var(--dry-type-small-size);
21
- line-height: 1.35;
20
+ font-size: var(--dry-option-picker-description-size, var(--dry-type-small-size));
21
+ line-height: var(--dry-option-picker-description-line-height, 1.35);
22
22
  color: var(--dry-color-text-weak);
23
23
  text-wrap: pretty;
24
24
  }
@@ -193,9 +193,16 @@
193
193
  .content {
194
194
  display: grid;
195
195
  grid-template-columns: auto minmax(0, 1fr);
196
- align-items: center;
196
+ align-items: var(--dry-option-picker-content-align, center);
197
197
  justify-items: start;
198
- gap: var(--dry-option-picker-item-gap, var(--dry-space-3));
198
+ column-gap: var(
199
+ --dry-option-picker-item-column-gap,
200
+ var(--dry-option-picker-item-gap, var(--dry-space-3))
201
+ );
202
+ row-gap: var(
203
+ --dry-option-picker-item-row-gap,
204
+ var(--dry-option-picker-item-gap, var(--dry-space-3))
205
+ );
199
206
  padding: var(--dry-option-picker-padding-y, var(--dry-space-3))
200
207
  var(--dry-option-picker-padding-x, var(--dry-space-3));
201
208
  min-block-size: var(--dry-option-picker-min-block-size, 3.5rem);
@@ -227,7 +234,7 @@
227
234
  }
228
235
 
229
236
  .root[data-size='compact'] .content {
230
- min-block-size: 2.75rem;
237
+ min-block-size: var(--dry-option-picker-compact-min-block-size, 2.75rem);
231
238
  }
232
239
 
233
240
  .root[data-size='visual'] .content {
@@ -17,8 +17,8 @@
17
17
  [data-option-picker-label] {
18
18
  grid-column: var(--dry-option-picker-label-column, 2);
19
19
  grid-row: var(--dry-option-picker-label-row, 1);
20
- font-size: var(--dry-type-small-size);
20
+ font-size: var(--dry-option-picker-label-size, var(--dry-type-small-size));
21
21
  font-weight: 600;
22
- line-height: 1.2;
22
+ line-height: var(--dry-option-picker-label-line-height, 1.2);
23
23
  }
24
24
  </style>
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
+ import type { Attachment } from 'svelte/attachments';
3
4
  import type { HTMLAttributes } from 'svelte/elements';
4
5
 
5
6
  interface Props extends HTMLAttributes<HTMLSpanElement> {
@@ -11,20 +12,23 @@
11
12
 
12
13
  let { color, shape = 'rounded', variant = 'default', children, ...rest }: Props = $props();
13
14
 
14
- let el: HTMLSpanElement | undefined = $state();
15
-
16
- $effect(() => {
17
- if (!el) return;
18
- if (color) {
19
- el.style.setProperty('--dry-option-picker-preview-bg', color);
20
- } else {
21
- el.style.removeProperty('--dry-option-picker-preview-bg');
22
- }
23
- });
15
+ function applyColor(value?: string): Attachment<HTMLSpanElement> {
16
+ return (node) => {
17
+ if (value) {
18
+ node.style.setProperty('--dry-option-picker-preview-bg', value);
19
+ } else {
20
+ node.style.removeProperty('--dry-option-picker-preview-bg');
21
+ }
22
+
23
+ return () => {
24
+ node.style.removeProperty('--dry-option-picker-preview-bg');
25
+ };
26
+ };
27
+ }
24
28
  </script>
25
29
 
26
30
  <span
27
- bind:this={el}
31
+ {@attach applyColor(color)}
28
32
  data-option-picker-preview
29
33
  data-option-picker-preview-shape={shape}
30
34
  data-variant={variant !== 'default' ? variant : undefined}
@@ -39,9 +43,10 @@
39
43
  grid-column: var(--dry-option-picker-preview-column, 1);
40
44
  grid-row: var(--dry-option-picker-preview-row, 1 / span 3);
41
45
  place-items: center;
42
- align-self: center;
46
+ align-self: var(--dry-option-picker-preview-align-self, center);
43
47
  block-size: var(--dry-option-picker-preview-size, 2.25rem);
44
48
  aspect-ratio: 1;
49
+ margin-block-start: var(--dry-option-picker-preview-offset-block-start, 0);
45
50
  padding: var(--dry-option-picker-preview-padding, 0);
46
51
  border: 1px solid var(--dry-option-picker-preview-border, var(--dry-color-stroke-weak));
47
52
  border-radius: var(--dry-option-picker-preview-radius, var(--dry-radius-md));
@@ -75,7 +80,7 @@
75
80
  var(--_preset-color) 46%,
76
81
  var(--dry-color-stroke-weak) 54%
77
82
  );
78
- --dry-option-picker-preview-size: 2.75rem;
83
+ --dry-option-picker-preview-size: var(--dry-option-picker-preview-preset-size, 2.75rem);
79
84
  background: linear-gradient(
80
85
  155deg,
81
86
  color-mix(in srgb, white 18%, var(--_preset-color) 82%) 0%,