@dryui/ui 1.5.1 → 1.7.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 (118) hide show
  1. package/dist/accordion/accordion-item.svelte +9 -0
  2. package/dist/accordion/accordion-root.svelte +1 -1
  3. package/dist/alert/alert.svelte +1 -0
  4. package/dist/avatar/avatar.svelte +1 -1
  5. package/dist/button/button.svelte +3 -2
  6. package/dist/button-group/context.svelte.js +4 -7
  7. package/dist/calendar/calendar-root.svelte +15 -32
  8. package/dist/card/card-root.svelte +1 -0
  9. package/dist/chart/chart-y-axis.svelte +5 -0
  10. package/dist/checkbox/checkbox-input.svelte +30 -31
  11. package/dist/chip-group/context.svelte.d.ts +2 -4
  12. package/dist/chip-group/context.svelte.js +2 -9
  13. package/dist/combobox/combobox-content.svelte +1 -0
  14. package/dist/combobox/combobox-item.svelte +9 -0
  15. package/dist/command-palette/command-palette-item.svelte +9 -0
  16. package/dist/command-palette/command-palette-list.svelte +1 -0
  17. package/dist/context-menu/context-menu-content.svelte +25 -12
  18. package/dist/context-menu/context-menu-group.svelte +3 -2
  19. package/dist/context-menu/context-menu-item.svelte +8 -61
  20. package/dist/context-menu/context-menu-label.svelte +3 -11
  21. package/dist/context-menu/context-menu-root.svelte +10 -29
  22. package/dist/context-menu/context-menu-separator.svelte +2 -9
  23. package/dist/context-menu/context.svelte.d.ts +2 -12
  24. package/dist/data-grid/data-grid-cell.svelte +5 -0
  25. package/dist/date-picker/datepicker-content.svelte +11 -81
  26. package/dist/date-picker/datepicker-content.svelte.d.ts +1 -1
  27. package/dist/date-picker/datepicker-input-root.svelte +39 -47
  28. package/dist/date-range-picker/date-range-picker-content.svelte +11 -75
  29. package/dist/date-range-picker/date-range-picker-content.svelte.d.ts +1 -1
  30. package/dist/date-range-picker/date-range-picker-root.svelte +44 -49
  31. package/dist/drag-and-drop/group-context.svelte.d.ts +1 -1
  32. package/dist/drag-and-drop/group-context.svelte.js +4 -4
  33. package/dist/dropdown-menu/context.svelte.d.ts +2 -8
  34. package/dist/dropdown-menu/dropdown-menu-content.svelte +22 -3
  35. package/dist/dropdown-menu/dropdown-menu-group.svelte +3 -2
  36. package/dist/dropdown-menu/dropdown-menu-item.svelte +8 -61
  37. package/dist/dropdown-menu/dropdown-menu-label.svelte +3 -11
  38. package/dist/dropdown-menu/dropdown-menu-root.svelte +10 -21
  39. package/dist/dropdown-menu/dropdown-menu-separator.svelte +2 -9
  40. package/dist/flip-card/context.svelte.d.ts +5 -0
  41. package/dist/flip-card/context.svelte.js +2 -0
  42. package/dist/flip-card/flip-card-back.svelte +2 -2
  43. package/dist/flip-card/flip-card-root.svelte +42 -15
  44. package/dist/heading/heading.svelte +10 -1
  45. package/dist/heading/heading.svelte.d.ts +1 -0
  46. package/dist/heading/index.d.ts +1 -0
  47. package/dist/hover-card/hover-card-content.svelte +9 -21
  48. package/dist/hover-card/hover-card-root.svelte +2 -2
  49. package/dist/hover-card/hover-card-root.svelte.d.ts +4 -0
  50. package/dist/image/image.svelte +5 -0
  51. package/dist/index.d.ts +2 -0
  52. package/dist/index.js +1 -0
  53. package/dist/internal/anchored-overlay-content.svelte.d.ts +20 -0
  54. package/dist/internal/anchored-overlay-content.svelte.js +28 -0
  55. package/dist/internal/date-family-controller.svelte.d.ts +45 -0
  56. package/dist/internal/date-family-controller.svelte.js +99 -0
  57. package/dist/internal/menu-group.svelte +15 -0
  58. package/dist/internal/menu-group.svelte.d.ts +9 -0
  59. package/dist/internal/menu-item.svelte +91 -0
  60. package/dist/internal/menu-item.svelte.d.ts +11 -0
  61. package/dist/internal/menu-label.svelte +24 -0
  62. package/dist/internal/menu-label.svelte.d.ts +9 -0
  63. package/dist/internal/menu-root-state.svelte.d.ts +24 -0
  64. package/dist/internal/menu-root-state.svelte.js +42 -0
  65. package/dist/internal/menu-separator.svelte +19 -0
  66. package/dist/internal/menu-separator.svelte.d.ts +7 -0
  67. package/dist/internal/modal-content.svelte +18 -0
  68. package/dist/internal/motion.js +12 -1
  69. package/dist/internal/nav-arrow-button.svelte +21 -5
  70. package/dist/internal/picker-popover-content.svelte +112 -0
  71. package/dist/internal/picker-popover-content.svelte.d.ts +16 -0
  72. package/dist/link-preview/link-preview-content.svelte +7 -10
  73. package/dist/list/list-item-icon.svelte +3 -3
  74. package/dist/list/list-item-icon.svelte.d.ts +1 -1
  75. package/dist/list/list-item-text.svelte +3 -3
  76. package/dist/list/list-item-text.svelte.d.ts +1 -1
  77. package/dist/list/list-item.svelte +58 -35
  78. package/dist/list/list-item.svelte.d.ts +8 -2
  79. package/dist/menubar/menubar-content.svelte +1 -0
  80. package/dist/menubar/menubar-item.svelte +10 -1
  81. package/dist/number-input/number-input-button.svelte +1 -0
  82. package/dist/pin-input/pin-input-cell.svelte +1 -0
  83. package/dist/popover/popover-content.svelte +15 -11
  84. package/dist/progress/progress.svelte +1 -0
  85. package/dist/radio-group/radio-group-item-input.svelte +17 -2
  86. package/dist/range-calendar/range-calendar-root.svelte +13 -19
  87. package/dist/reveal/reveal.svelte +1 -1
  88. package/dist/select/select-content.svelte +1 -0
  89. package/dist/select/select-item.svelte +9 -0
  90. package/dist/select/select-trigger-button.svelte +18 -1
  91. package/dist/skeleton/skeleton.svelte +2 -0
  92. package/dist/slider/slider-input.svelte +1 -0
  93. package/dist/text/index.d.ts +1 -0
  94. package/dist/text/text.svelte +4 -1
  95. package/dist/text/text.svelte.d.ts +1 -0
  96. package/dist/theme-toggle/index.d.ts +18 -0
  97. package/dist/theme-toggle/index.js +3 -0
  98. package/dist/theme-toggle/theme-controller.svelte.d.ts +54 -0
  99. package/dist/theme-toggle/theme-controller.svelte.js +121 -0
  100. package/dist/theme-toggle/theme-flash.d.ts +16 -0
  101. package/dist/theme-toggle/theme-flash.js +38 -0
  102. package/dist/theme-toggle/theme-toggle.svelte +199 -0
  103. package/dist/theme-toggle/theme-toggle.svelte.d.ts +40 -0
  104. package/dist/themes/dark.css +6 -0
  105. package/dist/themes/default.css +92 -0
  106. package/dist/toast/toast-provider.svelte +1 -0
  107. package/dist/toast/toast-root.svelte +1 -0
  108. package/dist/tooltip/tooltip-content.svelte +13 -10
  109. package/dist/typography/heading.svelte +13 -89
  110. package/dist/typography/heading.svelte.d.ts +3 -8
  111. package/dist/typography/index.d.ts +8 -7
  112. package/dist/typography/text.svelte +12 -84
  113. package/dist/typography/text.svelte.d.ts +3 -10
  114. package/dist/video-embed/video-embed-button.svelte +2 -1
  115. package/package.json +7 -2
  116. package/skills/dryui/SKILL.md +18 -5
  117. package/skills/dryui/rules/composition.md +1 -1
  118. package/skills/dryui/rules/theming.md +1 -2
@@ -1,12 +1,22 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
- import type { HTMLAttributes } from 'svelte/elements';
3
+ import type { ClassValue, HTMLAttributes, HTMLButtonAttributes } from 'svelte/elements';
4
+ import Button from '../button/button.svelte';
4
5
  import { getListCtx } from './context.svelte.js';
5
6
 
6
- interface Props extends HTMLAttributes<HTMLLIElement> {
7
+ interface Props extends Omit<
8
+ HTMLAttributes<HTMLLIElement>,
9
+ 'children' | 'class' | 'onclick' | 'onkeydown' | 'aria-label' | 'aria-labelledby' | 'title'
10
+ > {
7
11
  interactive?: boolean;
8
12
  disabled?: boolean;
13
+ class?: ClassValue;
9
14
  children: Snippet;
15
+ onclick?: HTMLButtonAttributes['onclick'];
16
+ onkeydown?: HTMLButtonAttributes['onkeydown'];
17
+ 'aria-label'?: HTMLButtonAttributes['aria-label'];
18
+ 'aria-labelledby'?: HTMLButtonAttributes['aria-labelledby'];
19
+ title?: string;
10
20
  }
11
21
 
12
22
  let {
@@ -15,74 +25,87 @@
15
25
  class: className,
16
26
  children,
17
27
  onclick,
28
+ onkeydown,
29
+ 'aria-label': ariaLabel,
30
+ 'aria-labelledby': ariaLabelledBy,
31
+ title,
18
32
  ...rest
19
33
  }: Props = $props();
20
34
 
21
35
  const ctx = getListCtx();
22
-
23
- function handleKeydown(e: KeyboardEvent) {
24
- if (!interactive || disabled) return;
25
- if (e.key === 'Enter' || e.key === ' ') {
26
- e.preventDefault();
27
- if (onclick) {
28
- (onclick as (e: Event) => void)(e);
29
- }
30
- }
31
- }
32
36
  </script>
33
37
 
34
- <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
35
38
  <li
36
39
  data-list-item
37
40
  data-interactive={interactive || undefined}
38
41
  data-disabled={disabled || undefined}
39
42
  data-dense={ctx.dense || undefined}
40
- role={interactive ? 'button' : undefined}
41
- tabindex={interactive && !disabled ? 0 : undefined}
42
- aria-disabled={disabled || undefined}
43
43
  class={className}
44
- {onclick}
45
- onkeydown={interactive ? handleKeydown : undefined}
46
44
  {...rest}
47
45
  >
48
- {@render children()}
46
+ {#if interactive}
47
+ <Button
48
+ variant="secondary"
49
+ {disabled}
50
+ aria-label={ariaLabel}
51
+ aria-labelledby={ariaLabelledBy}
52
+ {title}
53
+ {onclick}
54
+ {onkeydown}
55
+ >
56
+ <span data-list-item-surface>
57
+ {@render children()}
58
+ </span>
59
+ </Button>
60
+ {:else}
61
+ <div data-list-item-surface>
62
+ {@render children()}
63
+ </div>
64
+ {/if}
49
65
  </li>
50
66
 
51
67
  <style>
52
68
  [data-list-item] {
69
+ display: grid;
70
+ list-style: none;
71
+ }
72
+
73
+ [data-list-item-surface] {
53
74
  display: grid;
54
75
  grid-template-columns: auto minmax(0, 1fr);
55
76
  align-items: start;
56
77
  gap: var(--dry-list-item-gap);
57
78
  padding: var(--dry-list-item-padding);
79
+ border: 0;
58
80
  border-radius: var(--dry-list-item-radius);
81
+ background: transparent;
82
+ color: inherit;
83
+ font: inherit;
84
+ text-align: left;
59
85
  transition:
60
86
  background var(--dry-duration-fast) var(--dry-ease-default),
61
87
  color var(--dry-duration-fast) var(--dry-ease-default);
62
88
  }
63
89
 
64
90
  [data-list-item][data-interactive='true'] {
91
+ --dry-btn-bg: transparent;
92
+ --dry-btn-border: transparent;
93
+ --dry-btn-color: inherit;
94
+ --dry-btn-padding-x: 0;
95
+ --dry-btn-padding-y: 0;
96
+ --dry-btn-min-height: 0;
97
+ --dry-btn-accent: var(--dry-list-item-active-bg);
98
+ --dry-btn-accent-fg: inherit;
99
+ --dry-btn-accent-stroke: transparent;
100
+ --dry-btn-accent-weak: var(--dry-list-item-hover-bg);
101
+ --dry-btn-on-accent: inherit;
102
+ --dry-btn-radius: var(--dry-list-item-radius);
103
+ box-shadow: none;
65
104
  cursor: pointer;
66
105
  }
67
106
 
68
- [data-list-item][data-interactive='true']:hover,
69
- [data-list-item][data-interactive='true']:focus-visible {
70
- background: var(--dry-list-item-hover-bg);
71
- }
72
-
73
- [data-list-item][data-interactive='true']:active {
74
- background: var(--dry-list-item-active-bg);
75
- }
76
-
77
- [data-list-item][data-interactive='true']:focus-visible {
78
- outline: var(--dry-focus-ring);
79
- outline-offset: -2px;
80
- }
81
-
82
107
  [data-list-item][data-disabled='true'] {
83
108
  opacity: var(--dry-state-disabled-opacity);
84
- cursor: not-allowed;
85
- pointer-events: none;
86
109
  }
87
110
 
88
111
  [data-list-item][data-dense='true'] {
@@ -1,9 +1,15 @@
1
1
  import type { Snippet } from 'svelte';
2
- import type { HTMLAttributes } from 'svelte/elements';
3
- interface Props extends HTMLAttributes<HTMLLIElement> {
2
+ import type { ClassValue, HTMLAttributes, HTMLButtonAttributes } from 'svelte/elements';
3
+ interface Props extends Omit<HTMLAttributes<HTMLLIElement>, 'children' | 'class' | 'onclick' | 'onkeydown' | 'aria-label' | 'aria-labelledby' | 'title'> {
4
4
  interactive?: boolean;
5
5
  disabled?: boolean;
6
+ class?: ClassValue;
6
7
  children: Snippet;
8
+ onclick?: HTMLButtonAttributes['onclick'];
9
+ onkeydown?: HTMLButtonAttributes['onkeydown'];
10
+ 'aria-label'?: HTMLButtonAttributes['aria-label'];
11
+ 'aria-labelledby'?: HTMLButtonAttributes['aria-labelledby'];
12
+ title?: string;
7
13
  }
8
14
  declare const ListItem: import("svelte").Component<Props, {}, "">;
9
15
  type ListItem = ReturnType<typeof ListItem>;
@@ -99,6 +99,7 @@
99
99
  tabindex="-1"
100
100
  aria-labelledby={triggerEl?.id}
101
101
  data-menubar-content
102
+ data-dry-stagger
102
103
  data-state={menuCtx.open ? 'open' : 'closed'}
103
104
  class={className}
104
105
  ontoggle={(e) => {
@@ -69,7 +69,16 @@
69
69
  outline: none;
70
70
  color: var(--dry-color-text-strong);
71
71
  min-height: var(--dry-space-11);
72
- transition: background var(--dry-duration-fast) var(--dry-ease-default);
72
+
73
+ transition:
74
+ background var(--dry-duration-fast) var(--dry-ease-default),
75
+ opacity var(--dry-duration-fast) var(--dry-ease-out),
76
+ transform var(--dry-duration-fast) var(--dry-ease-out);
77
+
78
+ @starting-style {
79
+ opacity: 0;
80
+ transform: translateY(4px);
81
+ }
73
82
  }
74
83
 
75
84
  [data-menubar-item]:hover:not([data-disabled]),
@@ -82,6 +82,7 @@
82
82
  font-size: var(--dry-input-font-size);
83
83
  line-height: var(--dry-type-small-leading);
84
84
  font-family: var(--dry-font-sans);
85
+ font-variant-numeric: tabular-nums;
85
86
  color: var(--dry-input-color);
86
87
  background: var(--dry-input-bg);
87
88
  border: 1px solid var(--dry-input-border);
@@ -53,6 +53,7 @@
53
53
  height: var(--dry-pin-size);
54
54
  font-size: var(--dry-pin-font-size);
55
55
  font-family: var(--dry-font-mono);
56
+ font-variant-numeric: tabular-nums;
56
57
  font-weight: 500;
57
58
  color: var(--dry-color-text-strong);
58
59
  background: var(--dry-pin-bg);
@@ -1,8 +1,9 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
- import { createAnchoredPopover, createDismiss } from '@dryui/primitives';
4
+ import { createDismiss } from '@dryui/primitives';
5
5
  import type { Placement } from '@dryui/primitives';
6
+ import { createAnchoredOverlayContent } from '../internal/anchored-overlay-content.svelte.js';
6
7
  import { getPopoverCtx } from './context.svelte.js';
7
8
 
8
9
  interface Props extends HTMLAttributes<HTMLDivElement> {
@@ -22,20 +23,17 @@
22
23
 
23
24
  const ctx = getPopoverCtx();
24
25
 
25
- let contentEl = $state<HTMLDivElement>();
26
-
27
- const popover = createAnchoredPopover({
28
- triggerEl: () => ctx.triggerEl,
29
- contentEl: () => contentEl ?? null,
30
- open: () => ctx.open,
26
+ const overlay = createAnchoredOverlayContent({
27
+ ctx,
31
28
  placement: () => placement,
32
- offset: () => offset
29
+ offset: () => offset,
30
+ style: () => style
33
31
  });
34
32
 
35
33
  createDismiss({
36
34
  enabled: () => ctx.open,
37
35
  onDismiss: () => ctx.close(),
38
- contentEl: () => contentEl ?? null,
36
+ contentEl: overlay.contentEl,
39
37
  triggerEl: () => ctx.triggerEl,
40
38
  preventDefaultOnEscape: true,
41
39
  returnFocusTo: () => ctx.triggerEl
@@ -43,12 +41,12 @@
43
41
  </script>
44
42
 
45
43
  <div
46
- bind:this={contentEl}
44
+ {@attach overlay.bindContent}
45
+ {@attach overlay.position}
47
46
  id={ctx.contentId}
48
47
  popover="manual"
49
48
  data-popover-content
50
49
  data-state={ctx.open ? 'open' : 'closed'}
51
- use:popover.applyPosition={style}
52
50
  class={className}
53
51
  {...rest}
54
52
  >
@@ -63,6 +61,7 @@
63
61
  --dry-popover-shadow: var(--dry-overlay-shadow, var(--dry-shadow-lg));
64
62
  --dry-popover-padding: var(--dry-space-4);
65
63
  --dry-radius-nested: max(0px, calc(var(--dry-popover-radius) - var(--dry-popover-padding)));
64
+ --dry-btn-radius: var(--dry-radius-nested);
66
65
 
67
66
  inset: unset;
68
67
  margin: 0;
@@ -81,6 +80,11 @@
81
80
  transform var(--dry-duration-fast) var(--dry-ease-emphasized);
82
81
  }
83
82
 
83
+ [data-popover-content][data-state='closed'] {
84
+ transition-duration: calc(var(--dry-duration-fast) / 2);
85
+ transition-timing-function: var(--dry-ease-out);
86
+ }
87
+
84
88
  [data-popover-content]:not(:popover-open) {
85
89
  display: none;
86
90
  }
@@ -275,6 +275,7 @@
275
275
  font-size: var(--dry-type-ui-caption-size, var(--dry-text-xs-size, 0.75rem));
276
276
  color: var(--dry-color-text-weak, #64748b);
277
277
  white-space: nowrap;
278
+ font-variant-numeric: tabular-nums;
278
279
  }
279
280
 
280
281
  [data-part='label'][data-position='inside'] {
@@ -91,12 +91,18 @@
91
91
  position: absolute;
92
92
  top: 50%;
93
93
  left: 50%;
94
- transform: translate(-50%, -50%) scale(0);
94
+ transform: translate(-50%, -50%) scale(0.25);
95
+ opacity: 0;
96
+ filter: blur(4px);
95
97
  height: 8px;
96
98
  aspect-ratio: 1;
97
99
  border-radius: var(--dry-radius-full);
98
100
  background: var(--dry-color-on-brand);
99
- transition: transform var(--dry-duration-fast) var(--dry-ease-default);
101
+ transform-origin: center;
102
+ transition:
103
+ opacity var(--dry-duration-fast) var(--dry-ease-spring-snappy),
104
+ transform var(--dry-duration-fast) var(--dry-ease-spring-snappy),
105
+ filter var(--dry-duration-fast) var(--dry-ease-spring-snappy);
100
106
  }
101
107
 
102
108
  [data-radio-group-item] input[type='radio']:hover:not(:disabled) {
@@ -120,6 +126,15 @@
120
126
 
121
127
  [data-radio-group-item] input[type='radio']:checked::after {
122
128
  transform: translate(-50%, -50%) scale(1);
129
+ opacity: 1;
130
+ filter: blur(0);
131
+ }
132
+
133
+ @media (prefers-reduced-motion: reduce) {
134
+ [data-radio-group-item] input[type='radio']::after {
135
+ transition: none;
136
+ filter: none;
137
+ }
123
138
  }
124
139
 
125
140
  [data-radio-group-item] input[type='radio']:checked:hover:not(:disabled) {
@@ -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 { getWeekStartDay, addMonths } from '@dryui/primitives';
4
+ import { createDateViewController } from '../internal/date-family-controller.svelte.js';
5
5
  import { setRangeCalendarCtx } from './context.svelte.js';
6
6
 
7
7
  interface Props extends HTMLAttributes<HTMLDivElement> {
@@ -26,11 +26,11 @@
26
26
  ...rest
27
27
  }: Props = $props();
28
28
 
29
- const weekStartDay = $derived(getWeekStartDay(locale));
29
+ const view = createDateViewController({
30
+ initialDate: startDate,
31
+ locale: () => locale
32
+ });
30
33
 
31
- let viewMonth = $state(startDate ? startDate.getMonth() : new Date().getMonth());
32
- let viewYear = $state(startDate ? startDate.getFullYear() : new Date().getFullYear());
33
- let focusedDate = $state<Date>(startDate ?? new Date());
34
34
  let hoveredDate = $state<Date | null>(null);
35
35
 
36
36
  let selecting = $state(false);
@@ -46,13 +46,13 @@
46
46
  return hoveredDate;
47
47
  },
48
48
  get focusedDate() {
49
- return focusedDate;
49
+ return view.focusedDate;
50
50
  },
51
51
  get viewMonth() {
52
- return viewMonth;
52
+ return view.viewMonth;
53
53
  },
54
54
  get viewYear() {
55
- return viewYear;
55
+ return view.viewYear;
56
56
  },
57
57
  get locale() {
58
58
  return locale;
@@ -67,7 +67,7 @@
67
67
  return disabled;
68
68
  },
69
69
  get weekStartDay() {
70
- return weekStartDay;
70
+ return view.weekStartDay;
71
71
  },
72
72
  selectDate(date: Date) {
73
73
  if (!selecting) {
@@ -83,25 +83,19 @@
83
83
  }
84
84
  selecting = false;
85
85
  }
86
- focusedDate = date;
86
+ view.focusDate(date);
87
87
  },
88
88
  setHoveredDate(date: Date | null) {
89
89
  hoveredDate = date;
90
90
  },
91
91
  nextMonth() {
92
- const next = addMonths(new Date(viewYear, viewMonth, 1), 1);
93
- viewMonth = next.getMonth();
94
- viewYear = next.getFullYear();
92
+ view.nextMonth();
95
93
  },
96
94
  prevMonth() {
97
- const prev = addMonths(new Date(viewYear, viewMonth, 1), -1);
98
- viewMonth = prev.getMonth();
99
- viewYear = prev.getFullYear();
95
+ view.prevMonth();
100
96
  },
101
97
  setFocusedDate(date: Date) {
102
- focusedDate = date;
103
- viewMonth = date.getMonth();
104
- viewYear = date.getFullYear();
98
+ view.setFocusedDate(date);
105
99
  }
106
100
  });
107
101
  </script>
@@ -128,7 +128,7 @@
128
128
  <style>
129
129
  [data-reveal] {
130
130
  --dry-reveal-distance: var(--dry-motion-distance-sm, 0.75rem);
131
- --dry-reveal-delay: 0ms;
131
+ --dry-reveal-delay: var(--dry-stagger-delay, 0ms);
132
132
  --dry-reveal-duration: var(--dry-duration-entrance, 480ms);
133
133
  --dry-reveal-ease: cubic-bezier(0.16, 1, 0.3, 1);
134
134
  --dry-reveal-hidden-opacity: var(--dry-motion-opacity-enter, 0);
@@ -123,6 +123,7 @@
123
123
  id={ctx.contentId}
124
124
  aria-labelledby={ctx.triggerId}
125
125
  data-select-content
126
+ data-dry-stagger
126
127
  data-state={ctx.open ? 'open' : 'closed'}
127
128
  class={className}
128
129
  ontoggle={(e) => {
@@ -81,6 +81,15 @@
81
81
  outline: none;
82
82
  color: var(--dry-color-text-strong);
83
83
  min-height: var(--dry-space-10);
84
+
85
+ transition:
86
+ opacity var(--dry-duration-fast) var(--dry-ease-out),
87
+ transform var(--dry-duration-fast) var(--dry-ease-out);
88
+
89
+ @starting-style {
90
+ opacity: 0;
91
+ transform: translateY(4px);
92
+ }
84
93
  }
85
94
 
86
95
  [data-select-item]:hover:not([data-disabled]),
@@ -33,6 +33,7 @@
33
33
  {@render children()}
34
34
  <svg
35
35
  data-indicator
36
+ data-state={ctx.open ? 'open' : 'closed'}
36
37
  xmlns="http://www.w3.org/2000/svg"
37
38
  viewBox="0 0 24 24"
38
39
  fill="none"
@@ -55,6 +56,22 @@
55
56
  aspect-ratio: 1;
56
57
  place-self: center;
57
58
  opacity: 0.5;
58
- transition: transform var(--dry-duration-fast) var(--dry-ease-default);
59
+ transform-origin: center;
60
+ transition:
61
+ opacity var(--dry-duration-fast) var(--dry-ease-spring-snappy),
62
+ transform var(--dry-duration-fast) var(--dry-ease-spring-snappy),
63
+ filter var(--dry-duration-fast) var(--dry-ease-spring-snappy);
64
+ }
65
+
66
+ svg[data-indicator][data-state='open'] {
67
+ opacity: 1;
68
+ transform: scale(1.05);
69
+ }
70
+
71
+ @media (prefers-reduced-motion: reduce) {
72
+ svg[data-indicator] {
73
+ transition: none;
74
+ filter: none;
75
+ }
59
76
  }
60
77
  </style>
@@ -91,10 +91,12 @@
91
91
  --dry-skeleton-radius: var(--dry-radius-full);
92
92
  aspect-ratio: 1;
93
93
  height: var(--_h, var(--dry-space-10));
94
+ box-shadow: inset 0 0 0 1px var(--dry-image-edge);
94
95
  }
95
96
 
96
97
  div[data-variant='rectangular'] {
97
98
  --dry-skeleton-radius: var(--dry-radius-sm);
98
99
  height: var(--_h, var(--dry-space-16));
100
+ box-shadow: inset 0 0 0 1px var(--dry-image-edge);
99
101
  }
100
102
  </style>
@@ -249,6 +249,7 @@
249
249
  padding-inline: var(--dry-space-4);
250
250
  font-size: var(--dry-text-sm-size, 0.875rem);
251
251
  font-weight: 600;
252
+ font-variant-numeric: tabular-nums;
252
253
  color: var(--dry-color-text);
253
254
  clip-path: inset(
254
255
  0 calc(100% - var(--dry-slider-progress, 50%)) 0 0 round var(--dry-radius-full)
@@ -7,6 +7,7 @@ export interface TextProps extends HTMLAttributes<HTMLElement> {
7
7
  font?: 'sans' | 'mono';
8
8
  weight?: 'normal' | 'medium' | 'semibold' | 'bold';
9
9
  variant?: 'default' | 'label';
10
+ className?: HTMLAttributes<HTMLElement>['class'];
10
11
  children: Snippet;
11
12
  }
12
13
  export { default as Text } from './text.svelte';
@@ -10,6 +10,7 @@
10
10
  font?: 'sans' | 'mono';
11
11
  weight?: 'normal' | 'medium' | 'semibold' | 'bold';
12
12
  variant?: 'default' | 'label';
13
+ className?: HTMLAttributes<HTMLElement>['class'];
13
14
  children: Snippet;
14
15
  }
15
16
 
@@ -20,7 +21,8 @@
20
21
  font = 'sans',
21
22
  weight,
22
23
  variant = 'default',
23
- class: className,
24
+ class: classAttr,
25
+ className = classAttr,
24
26
  children,
25
27
  ...rest
26
28
  }: Props = $props();
@@ -58,6 +60,7 @@
58
60
  color: var(--dry-typography-text-color, var(--dry-color-text-strong));
59
61
  font-family: var(--dry-font-sans);
60
62
  line-height: 1.7;
63
+ text-wrap: pretty;
61
64
  }
62
65
 
63
66
  [data-color='muted'] {
@@ -7,6 +7,7 @@ interface Props extends HTMLAttributes<HTMLElement> {
7
7
  font?: 'sans' | 'mono';
8
8
  weight?: 'normal' | 'medium' | 'semibold' | 'bold';
9
9
  variant?: 'default' | 'label';
10
+ className?: HTMLAttributes<HTMLElement>['class'];
10
11
  children: Snippet;
11
12
  }
12
13
  declare const Text: import("svelte").Component<Props, {}, "">;
@@ -0,0 +1,18 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { ClassValue, HTMLButtonAttributes } from 'svelte/elements';
3
+ export { createThemeController } from './theme-controller.svelte.js';
4
+ export type { ThemeMode, ThemeController, ThemeControllerOptions } from './theme-controller.svelte.js';
5
+ export { themeFlashScript } from './theme-flash.js';
6
+ import type { ThemeController, ThemeMode } from './theme-controller.svelte.js';
7
+ export interface ThemeToggleProps extends Omit<HTMLButtonAttributes, 'onclick' | 'onkeydown' | 'disabled' | 'class'> {
8
+ storageKey?: string;
9
+ size?: 'sm' | 'md' | 'lg';
10
+ controller?: ThemeController;
11
+ 'aria-label'?: string;
12
+ sunIcon?: Snippet;
13
+ moonIcon?: Snippet;
14
+ onModeChange?: (mode: ThemeMode) => void;
15
+ disabled?: boolean;
16
+ class?: ClassValue;
17
+ }
18
+ export { default as ThemeToggle } from './theme-toggle.svelte';
@@ -0,0 +1,3 @@
1
+ export { createThemeController } from './theme-controller.svelte.js';
2
+ export { themeFlashScript } from './theme-flash.js';
3
+ export { default as ThemeToggle } from './theme-toggle.svelte';
@@ -0,0 +1,54 @@
1
+ export type ThemeMode = 'system' | 'light' | 'dark';
2
+ export interface ThemeControllerOptions {
3
+ /**
4
+ * Storage key used to persist the explicit theme preference.
5
+ * When the user selects system mode the key is removed.
6
+ * Defaults to `'dryui-theme'`.
7
+ */
8
+ storageKey?: string;
9
+ }
10
+ export interface ThemeController {
11
+ /** Current stored preference: `'system'`, `'light'`, or `'dark'`. */
12
+ readonly mode: ThemeMode;
13
+ /**
14
+ * Whether the active rendered theme is dark. Tracks `prefers-color-scheme`
15
+ * when the mode is `'system'`.
16
+ */
17
+ readonly isDark: boolean;
18
+ /** Whether the system color-scheme preference is currently dark. */
19
+ readonly systemPrefersDark: boolean;
20
+ /** Apply an explicit mode and persist it (or clear persistence for `'system'`). */
21
+ setMode(mode: ThemeMode): void;
22
+ /** Toggle between the two rendered themes, writing an explicit preference. */
23
+ cycle(): void;
24
+ /** Return to system mode and clear any persisted preference. */
25
+ reset(): void;
26
+ /** Stop watching `matchMedia` changes. Called automatically on HMR disposal. */
27
+ destroy(): void;
28
+ }
29
+ export declare const DARK_MEDIA_QUERY = "(prefers-color-scheme: dark)";
30
+ export declare const DEFAULT_STORAGE_KEY = "dryui-theme";
31
+ /**
32
+ * Read the stored theme mode. Exported for testing; production callers
33
+ * should go through `createThemeController`.
34
+ */
35
+ export declare function readStoredMode(storageKey: string): ThemeMode;
36
+ /**
37
+ * Persist the theme mode. When `mode === 'system'`, removes the key.
38
+ * Exported for testing.
39
+ */
40
+ export declare function writeStoredMode(storageKey: string, mode: ThemeMode): void;
41
+ /**
42
+ * Apply the mode to `<html>` via `classList.theme-auto` and `dataset.theme`.
43
+ * Exported for testing.
44
+ */
45
+ export declare function applyModeToDom(mode: ThemeMode): void;
46
+ /**
47
+ * Create a theme controller that reads the current preference from storage,
48
+ * applies it to `<html>`, and watches the system color-scheme for changes.
49
+ *
50
+ * The returned object exposes reactive `mode`, `isDark`, and `systemPrefersDark`
51
+ * properties backed by Svelte 5 `$state`, plus imperative methods for changing
52
+ * or resetting the mode.
53
+ */
54
+ export declare function createThemeController(options?: ThemeControllerOptions): ThemeController;