@dryui/ui 1.6.0 → 1.7.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 (55) 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/card/card-root.svelte +1 -0
  7. package/dist/chart/chart-y-axis.svelte +5 -0
  8. package/dist/checkbox/checkbox-input.svelte +30 -31
  9. package/dist/combobox/combobox-content.svelte +1 -0
  10. package/dist/combobox/combobox-item.svelte +9 -0
  11. package/dist/command-palette/command-palette-item.svelte +9 -0
  12. package/dist/command-palette/command-palette-list.svelte +1 -0
  13. package/dist/context-menu/context-menu-content.svelte +1 -0
  14. package/dist/data-grid/data-grid-cell.svelte +5 -0
  15. package/dist/diagram/edge-routing.d.ts +6 -12
  16. package/dist/diagram/edge-routing.js +2 -2
  17. package/dist/diagram/layout.js +12 -4
  18. package/dist/dropdown-menu/dropdown-menu-content.svelte +7 -0
  19. package/dist/hover-card/context.svelte.d.ts +1 -1
  20. package/dist/hover-card/context.svelte.js +1 -1
  21. package/dist/image/image.svelte +5 -0
  22. package/dist/internal/date-family-controller.svelte.d.ts +1 -1
  23. package/dist/internal/menu-item.svelte +10 -1
  24. package/dist/internal/modal-content.svelte +18 -0
  25. package/dist/internal/motion.d.ts +1 -1
  26. package/dist/internal/motion.js +1 -1
  27. package/dist/internal/nav-arrow-button.svelte +21 -5
  28. package/dist/menubar/menubar-content.svelte +1 -0
  29. package/dist/menubar/menubar-item.svelte +10 -1
  30. package/dist/number-input/number-input-button.svelte +1 -0
  31. package/dist/pin-input/pin-input-cell.svelte +1 -0
  32. package/dist/popover/popover-content.svelte +6 -0
  33. package/dist/progress/progress.svelte +1 -0
  34. package/dist/radio-group/radio-group-item-input.svelte +17 -2
  35. package/dist/reveal/reveal.svelte +1 -1
  36. package/dist/select/select-content.svelte +1 -0
  37. package/dist/select/select-item.svelte +9 -0
  38. package/dist/select/select-trigger-button.svelte +18 -1
  39. package/dist/shader-canvas/presets.d.ts +0 -10
  40. package/dist/shader-canvas/presets.js +5 -5
  41. package/dist/skeleton/skeleton.svelte +2 -0
  42. package/dist/slider/slider-input.svelte +1 -0
  43. package/dist/text/text.svelte +1 -0
  44. package/dist/theme-toggle/theme-controller.svelte.d.ts +0 -1
  45. package/dist/theme-toggle/theme-controller.svelte.js +1 -1
  46. package/dist/theme-toggle/theme-toggle.svelte +12 -2
  47. package/dist/themes/dark.css +6 -0
  48. package/dist/themes/default.css +92 -0
  49. package/dist/toast/toast-provider.svelte +1 -0
  50. package/dist/toast/toast-root.svelte +1 -0
  51. package/dist/tooltip/tooltip-content.svelte +5 -0
  52. package/dist/video-embed/video-embed-button.svelte +2 -1
  53. package/package.json +2 -2
  54. package/dist/utils/use-anchor-styles.svelte.d.ts +0 -14
  55. package/dist/utils/use-anchor-styles.svelte.js +0 -32
@@ -46,6 +46,15 @@
46
46
  display: grid;
47
47
  border-bottom: 1px solid var(--dry-color-stroke-weak);
48
48
 
49
+ transition:
50
+ opacity var(--dry-duration-fast) var(--dry-ease-out),
51
+ transform var(--dry-duration-fast) var(--dry-ease-out);
52
+
53
+ @starting-style {
54
+ opacity: 0;
55
+ transform: translateY(4px);
56
+ }
57
+
49
58
  &:last-child {
50
59
  border-bottom: none;
51
60
  }
@@ -47,7 +47,7 @@
47
47
  });
48
48
  </script>
49
49
 
50
- <div data-accordion data-orientation={orientation} class={className} {...rest}>
50
+ <div data-accordion data-dry-stagger data-orientation={orientation} class={className} {...rest}>
51
51
  {@render children()}
52
52
  </div>
53
53
 
@@ -83,6 +83,7 @@
83
83
  var(--dry-alert-padding, var(--dry-space-6))
84
84
  )
85
85
  );
86
+ --dry-btn-radius: var(--dry-radius-nested);
86
87
 
87
88
  container-type: inline-size;
88
89
  display: grid;
@@ -121,7 +121,7 @@
121
121
  font-size: var(--dry-avatar-font-size);
122
122
  font-weight: 600;
123
123
  line-height: 1;
124
- border: 1px solid var(--dry-color-stroke-weak);
124
+ box-shadow: inset 0 0 0 1px var(--dry-image-edge);
125
125
  overflow: hidden;
126
126
  user-select: none;
127
127
  }
@@ -177,6 +177,7 @@
177
177
  text-decoration: none;
178
178
  white-space: nowrap;
179
179
  user-select: none;
180
+ transform-origin: center;
180
181
  transition:
181
182
  background var(--dry-duration-fast) var(--dry-ease-default),
182
183
  border-color var(--dry-duration-fast) var(--dry-ease-default),
@@ -191,7 +192,7 @@
191
192
  }
192
193
 
193
194
  &:active:not([data-disabled]) {
194
- transform: translateY(1px);
195
+ transform: scale(0.98);
195
196
  }
196
197
 
197
198
  &[data-disabled] {
@@ -499,7 +500,7 @@
499
500
  --_dry-btn-padding-x: var(--dry-btn-padding-x, 0);
500
501
  --_dry-btn-padding-y: var(--dry-btn-padding-y, 0);
501
502
  aspect-ratio: 1;
502
- height: var(--dry-space-8);
503
+ height: var(--dry-space-10);
503
504
  --_dry-btn-radius: var(--dry-btn-radius, var(--dry-radius-sm));
504
505
  --_dry-btn-font-size: var(
505
506
  --dry-btn-font-size,
@@ -69,6 +69,7 @@
69
69
  0px,
70
70
  calc(var(--dry-card-radius) - var(--dry-card-padding, var(--dry-space-8)))
71
71
  );
72
+ --dry-btn-radius: var(--dry-radius-nested);
72
73
 
73
74
  container-type: inline-size;
74
75
  display: grid;
@@ -45,6 +45,7 @@
45
45
  font-size="11"
46
46
  fill="currentColor"
47
47
  opacity="0.6"
48
+ data-chart-tick-label
48
49
  >
49
50
  {Math.round(tick.value)}
50
51
  </text>
@@ -63,4 +64,8 @@
63
64
  [data-chart-axis] {
64
65
  color: var(--dry-chart-axis-color);
65
66
  }
67
+
68
+ [data-chart-tick-label] {
69
+ font-variant-numeric: tabular-nums;
70
+ }
66
71
  </style>
@@ -133,8 +133,21 @@
133
133
 
134
134
  &::after {
135
135
  content: '';
136
- display: none;
137
- transition: transform var(--dry-duration-fast) var(--dry-ease-spring);
136
+ display: block;
137
+ height: 60%;
138
+ aspect-ratio: 35 / 60;
139
+ border: solid transparent;
140
+ border-width: 0 2px 2px 0;
141
+ margin-left: calc(var(--dry-checkbox-size) * 0.52);
142
+ margin-top: calc(var(--dry-checkbox-size) * -0.18);
143
+ transform-origin: center;
144
+ opacity: 0;
145
+ transform: rotate(45deg) scale(0.25);
146
+ filter: blur(4px);
147
+ transition:
148
+ opacity var(--dry-duration-fast) var(--dry-ease-spring-snappy),
149
+ transform var(--dry-duration-fast) var(--dry-ease-spring-snappy),
150
+ filter var(--dry-duration-fast) var(--dry-ease-spring-snappy);
138
151
  }
139
152
 
140
153
  &[data-state='checked'] {
@@ -143,16 +156,10 @@
143
156
  box-shadow: inset 0 0 0 1px var(--dry-color-stroke-selected);
144
157
 
145
158
  &::after {
146
- display: block;
147
- height: 60%;
148
- aspect-ratio: 35 / 60;
149
- border: solid var(--dry-checkbox-check-color);
150
- border-width: 0 2px 2px 0;
151
- margin-left: calc(var(--dry-checkbox-size) * 0.52);
152
- margin-top: calc(var(--dry-checkbox-size) * -0.18);
159
+ border-color: var(--dry-checkbox-check-color);
160
+ opacity: 1;
153
161
  transform: rotate(45deg) scale(1);
154
- transform-origin: center;
155
- animation: checkScale var(--dry-duration-fast) var(--dry-ease-spring);
162
+ filter: blur(0);
156
163
  }
157
164
  }
158
165
 
@@ -163,11 +170,15 @@
163
170
  box-shadow: inset 0 0 0 1px var(--dry-color-stroke-selected);
164
171
 
165
172
  &::after {
166
- display: block;
167
173
  height: 2px;
174
+ aspect-ratio: auto;
175
+ border: none;
168
176
  background: var(--dry-checkbox-check-color);
177
+ margin-left: 0;
178
+ margin-top: 0;
179
+ opacity: 1;
169
180
  transform: scale(1);
170
- animation: dashScale var(--dry-duration-fast) var(--dry-ease-spring);
181
+ filter: blur(0);
171
182
  }
172
183
  }
173
184
 
@@ -226,28 +237,16 @@
226
237
  --dry-checkbox-radius: var(--dry-radius-md);
227
238
  }
228
239
 
229
- @keyframes checkScale {
230
- from {
231
- transform: rotate(45deg) scale(0);
240
+ @media (prefers-reduced-motion: reduce) {
241
+ input::after {
242
+ transition: none;
243
+ filter: none;
232
244
  }
233
- to {
245
+ input[data-state='checked']::after {
234
246
  transform: rotate(45deg) scale(1);
235
247
  }
236
- }
237
-
238
- @keyframes dashScale {
239
- from {
240
- transform: scale(0);
241
- }
242
- to {
243
- transform: scale(1);
244
- }
245
- }
246
-
247
- @media (prefers-reduced-motion: reduce) {
248
- input[data-state='checked']::after,
249
248
  input[data-state='indeterminate']::after {
250
- animation: none;
249
+ transform: scale(1);
251
250
  }
252
251
  }
253
252
  </style>
@@ -64,6 +64,7 @@
64
64
  id={ctx.contentId}
65
65
  aria-labelledby={ctx.inputId}
66
66
  data-combobox-content
67
+ data-dry-stagger
67
68
  data-state={ctx.open ? 'open' : 'closed'}
68
69
  class={className}
69
70
  {...rest}
@@ -97,6 +97,15 @@
97
97
  outline: none;
98
98
  color: var(--dry-color-text-strong);
99
99
  min-height: var(--dry-space-10);
100
+
101
+ transition:
102
+ opacity var(--dry-duration-fast) var(--dry-ease-out),
103
+ transform var(--dry-duration-fast) var(--dry-ease-out);
104
+
105
+ @starting-style {
106
+ opacity: 0;
107
+ transform: translateY(4px);
108
+ }
100
109
  }
101
110
 
102
111
  [data-combobox-item][data-has-icon] {
@@ -71,6 +71,15 @@
71
71
  cursor: pointer;
72
72
  color: var(--dry-color-text-strong);
73
73
  min-height: var(--dry-space-11);
74
+
75
+ transition:
76
+ opacity var(--dry-duration-fast) var(--dry-ease-out),
77
+ transform var(--dry-duration-fast) var(--dry-ease-out);
78
+
79
+ @starting-style {
80
+ opacity: 0;
81
+ transform: translateY(4px);
82
+ }
74
83
  }
75
84
 
76
85
  [data-command-palette-item]:hover:not([data-disabled]) {
@@ -15,6 +15,7 @@
15
15
  <div
16
16
  role="listbox"
17
17
  data-command-palette-list
18
+ data-dry-stagger
18
19
  id={ctx.listId}
19
20
  aria-label="Commands"
20
21
  class={className}
@@ -60,6 +60,7 @@
60
60
  id={ctx.contentId}
61
61
  aria-labelledby={ctx.triggerId}
62
62
  data-context-menu-content
63
+ data-dry-stagger
63
64
  data-state={ctx.open ? 'open' : 'closed'}
64
65
  class={className}
65
66
  {style}
@@ -29,4 +29,9 @@
29
29
  background: inherit;
30
30
  box-shadow: 2px 0 4px -2px rgb(15 23 42 / 0.1);
31
31
  }
32
+
33
+ [data-dg-cell][data-type='number'],
34
+ [data-dg-cell][data-align='end'] {
35
+ font-variant-numeric: tabular-nums;
36
+ }
32
37
  </style>
@@ -1,24 +1,19 @@
1
1
  import type { DiagramEdge, DiagramDirection, PositionedEdge } from './types.js';
2
- /** Minimum distance (in path units, measured along the polyline) between a
3
- * forward edge label and a directed-cluster boundary. Cross-boundary edges
4
- * whose natural midpoint sits closer than this to the cluster are slid along
5
- * the polyline toward the outside endpoint. */
6
- export declare const LABEL_BORDER_AVOID_PX = 28;
7
2
  interface Point {
8
3
  x: number;
9
4
  y: number;
10
5
  }
11
- export interface EdgeRouteBounds {
6
+ interface EdgeRouteBounds {
12
7
  minX: number;
13
8
  minY: number;
14
9
  maxX: number;
15
10
  maxY: number;
16
11
  }
17
- export interface ComputedEdges {
12
+ interface ComputedEdges {
18
13
  edges: PositionedEdge[];
19
14
  collapsed: (Point[] | null)[];
20
15
  }
21
- export interface ComputeEdgePathsOptions {
16
+ interface ComputeEdgePathsOptions {
22
17
  cornerRadius?: number;
23
18
  reversedEdges?: Set<string>;
24
19
  bounds?: EdgeRouteBounds;
@@ -46,14 +41,13 @@ export declare function computeEdgePaths(edges: DiagramEdge[], positions: Map<st
46
41
  h: number;
47
42
  }>, direction: DiagramDirection, opts?: ComputeEdgePathsOptions): ComputedEdges;
48
43
  declare function buildPathFromCollapsed(collapsed: Point[], cornerRadius?: number): string;
49
- declare function collapsePoints(points: Point[]): Point[];
50
- export interface PointAtFraction {
44
+ interface PointAtFraction {
51
45
  point: Point;
52
46
  segmentIndex: number;
53
47
  axis: 'h' | 'v';
54
48
  }
55
49
  export declare function getPointAtFraction(collapsed: Point[], t: number): PointAtFraction;
56
- export interface WaypointBox {
50
+ interface WaypointBox {
57
51
  x: number;
58
52
  y: number;
59
53
  width: number;
@@ -67,5 +61,5 @@ export declare function splitCollapsedAtBox(collapsed: Point[], segmentIndex: nu
67
61
  entry: Point[];
68
62
  exit: Point[];
69
63
  } | null;
70
- export { collapsePoints, buildPathFromCollapsed };
64
+ export { buildPathFromCollapsed };
71
65
  export declare function emptyEdge(edge: DiagramEdge): PositionedEdge;
@@ -8,7 +8,7 @@ const BACK_EDGE_LANE_STEP = 22;
8
8
  * forward edge label and a directed-cluster boundary. Cross-boundary edges
9
9
  * whose natural midpoint sits closer than this to the cluster are slid along
10
10
  * the polyline toward the outside endpoint. */
11
- export const LABEL_BORDER_AVOID_PX = 28;
11
+ const LABEL_BORDER_AVOID_PX = 28;
12
12
  function edgeKey(edge) {
13
13
  return `${edge.from}->${edge.to}`;
14
14
  }
@@ -557,7 +557,7 @@ export function splitCollapsedAtBox(collapsed, segmentIndex, box, axis) {
557
557
  return null;
558
558
  return { entry, exit };
559
559
  }
560
- export { collapsePoints, buildPathFromCollapsed };
560
+ export { buildPathFromCollapsed };
561
561
  export function emptyEdge(edge) {
562
562
  return {
563
563
  from: edge.from,
@@ -103,7 +103,9 @@ function orderWithinLayers(layers, adjacencyOut, adjacencyIn, clusterMap) {
103
103
  const posInLayer = new Map();
104
104
  // Initialize positions
105
105
  for (const layer of layers) {
106
- layer.forEach((id, i) => posInLayer.set(id, i));
106
+ for (const [i, id] of layer.entries()) {
107
+ posInLayer.set(id, i);
108
+ }
107
109
  }
108
110
  function resortLayer(layer, neighbors) {
109
111
  const sorted = layer.map((nodeId) => {
@@ -123,13 +125,17 @@ function orderWithinLayers(layers, adjacencyOut, adjacencyIn, clusterMap) {
123
125
  for (let i = 1; i < layers.length; i++) {
124
126
  const next = resortLayer(layers[i], adjacencyIn);
125
127
  layers[i] = next;
126
- next.forEach((id, j) => posInLayer.set(id, j));
128
+ for (const [j, id] of next.entries()) {
129
+ posInLayer.set(id, j);
130
+ }
127
131
  }
128
132
  // Up sweep
129
133
  for (let i = layers.length - 2; i >= 0; i--) {
130
134
  const next = resortLayer(layers[i], adjacencyOut);
131
135
  layers[i] = next;
132
- next.forEach((id, j) => posInLayer.set(id, j));
136
+ for (const [j, id] of next.entries()) {
137
+ posInLayer.set(id, j);
138
+ }
133
139
  }
134
140
  // Cluster-aware grouping: after barycenter ordering, group cluster members together
135
141
  // and push non-clustered nodes to the edges
@@ -137,7 +143,9 @@ function orderWithinLayers(layers, adjacencyOut, adjacencyIn, clusterMap) {
137
143
  for (let i = 0; i < layers.length; i++) {
138
144
  const regrouped = groupByCluster(layers[i], clusterMap, adjacencyIn, adjacencyOut, posInLayer);
139
145
  layers[i] = regrouped;
140
- regrouped.forEach((id, j) => posInLayer.set(id, j));
146
+ for (const [j, id] of regrouped.entries()) {
147
+ posInLayer.set(id, j);
148
+ }
141
149
  }
142
150
  }
143
151
  return layers;
@@ -58,6 +58,7 @@
58
58
  id={ctx.contentId}
59
59
  aria-labelledby={ctx.triggerId}
60
60
  data-dropdown-menu-content
61
+ data-dry-stagger
61
62
  data-state={ctx.open ? 'open' : 'closed'}
62
63
  class={className}
63
64
  ontoggle={(e) => {
@@ -97,12 +98,18 @@
97
98
  var(--dry-menu-radius, var(--dry-radius-lg)) - var(--dry-menu-padding, var(--dry-space-2))
98
99
  )
99
100
  );
101
+ --dry-btn-radius: var(--dry-radius-nested);
100
102
 
101
103
  transition:
102
104
  opacity var(--dry-duration-fast) var(--dry-ease-emphasized),
103
105
  transform var(--dry-duration-fast) var(--dry-ease-emphasized);
104
106
  }
105
107
 
108
+ [data-dropdown-menu-content][data-state='closed'] {
109
+ transition-duration: calc(var(--dry-duration-fast) / 2);
110
+ transition-timing-function: var(--dry-ease-out);
111
+ }
112
+
106
113
  [data-dropdown-menu-content]:not(:popover-open) {
107
114
  display: none;
108
115
  }
@@ -1 +1 @@
1
- export { setHoverCardCtx, getHoverCardCtx, type HoverCardContext } from '@dryui/primitives';
1
+ export { getHoverCardCtx } from '@dryui/primitives';
@@ -1 +1 @@
1
- export { setHoverCardCtx, getHoverCardCtx } from '@dryui/primitives';
1
+ export { getHoverCardCtx } from '@dryui/primitives';
@@ -67,6 +67,11 @@
67
67
  object-fit: var(--dry-image-object-fit);
68
68
  }
69
69
 
70
+ img[data-state='loaded'],
71
+ img[data-state='fallback'] {
72
+ box-shadow: inset 0 0 0 1px var(--dry-image-edge);
73
+ }
74
+
70
75
  img[data-state='fallback'] {
71
76
  opacity: 0.95;
72
77
  }
@@ -2,7 +2,7 @@ interface DateViewControllerConfig {
2
2
  initialDate?: Date | null;
3
3
  locale: () => string;
4
4
  }
5
- export interface DateViewController {
5
+ interface DateViewController {
6
6
  readonly focusedDate: Date;
7
7
  readonly viewMonth: number;
8
8
  readonly viewYear: number;
@@ -62,7 +62,16 @@
62
62
  outline: none;
63
63
  color: var(--dry-color-text-strong);
64
64
  min-height: var(--dry-space-11);
65
- transition: background var(--dry-duration-fast) var(--dry-ease-default);
65
+
66
+ transition:
67
+ background var(--dry-duration-fast) var(--dry-ease-default),
68
+ opacity var(--dry-duration-fast) var(--dry-ease-out),
69
+ transform var(--dry-duration-fast) var(--dry-ease-out);
70
+
71
+ @starting-style {
72
+ opacity: 0;
73
+ transform: translateY(4px);
74
+ }
66
75
  }
67
76
 
68
77
  div:hover:not([data-disabled]),
@@ -132,6 +132,7 @@
132
132
  var(--dry-dialog-padding)
133
133
  )
134
134
  );
135
+ --dry-btn-radius: var(--dry-radius-nested);
135
136
 
136
137
  container-type: inline-size;
137
138
  justify-self: stretch;
@@ -150,6 +151,11 @@
150
151
  transform var(--dry-duration-normal) var(--dry-ease-spring-snappy);
151
152
  }
152
153
 
154
+ [data-modal-content][data-variant='dialog'][data-state='closed'] [data-modal-panel] {
155
+ transition-duration: var(--dry-duration-fast);
156
+ transition-timing-function: var(--dry-ease-out);
157
+ }
158
+
153
159
  [data-modal-content][data-variant='dialog'][data-state='open'] [data-modal-panel] {
154
160
  opacity: 1;
155
161
  transform: scale(1) translateY(0);
@@ -189,6 +195,8 @@
189
195
  --dry-dialog-shadow: var(--dry-shadow-overlay);
190
196
  --dry-dialog-padding: var(--dry-space-6);
191
197
  --dry-dialog-max-width: 32rem;
198
+ --dry-radius-nested: max(0px, calc(var(--dry-dialog-radius) - var(--dry-dialog-padding)));
199
+ --dry-btn-radius: var(--dry-radius-nested);
192
200
 
193
201
  container-type: inline-size;
194
202
  justify-self: stretch;
@@ -208,6 +216,11 @@
208
216
  transform var(--dry-duration-normal) var(--dry-ease-spring-snappy);
209
217
  }
210
218
 
219
+ [data-modal-content][data-variant='alert-dialog'][data-state='closed'] [data-modal-panel] {
220
+ transition-duration: var(--dry-duration-fast);
221
+ transition-timing-function: var(--dry-ease-out);
222
+ }
223
+
211
224
  [data-modal-content][data-variant='alert-dialog'][data-state='open'] [data-modal-panel] {
212
225
  opacity: 1;
213
226
  transform: scale(1) translateY(0);
@@ -282,6 +295,11 @@
282
295
  opacity var(--dry-duration-normal) var(--dry-ease-out);
283
296
  }
284
297
 
298
+ [data-modal-content][data-variant='drawer'][data-state='closed'] [data-modal-panel] {
299
+ transition-duration: var(--dry-duration-fast);
300
+ transition-timing-function: var(--dry-ease-out);
301
+ }
302
+
285
303
  [data-modal-content][data-variant='drawer'][data-side='right'] [data-modal-panel] {
286
304
  grid-column: 2;
287
305
  height: 100%;
@@ -1,4 +1,4 @@
1
- export { getReducedMotionPreference, observeReducedMotionPreference, supportsIntersectionObservers, supportsPointerTracking, supportsPropertyRegistration, registerPropertyOnce, supportsWebGL2, observeInViewport, observePageVisibility, observeOffscreenState } from '@dryui/primitives/internal/motion';
1
+ export { getReducedMotionPreference, observeReducedMotionPreference, supportsIntersectionObservers, supportsPointerTracking, supportsPropertyRegistration, registerPropertyOnce, supportsWebGL2 } from '@dryui/primitives/internal/motion';
2
2
  export declare function supportsViewTransitions(): boolean;
3
3
  export declare function supportsScrollTimelines(): boolean;
4
4
  export declare function extractThemeColor(property: string, element?: HTMLElement): [number, number, number];
@@ -1,5 +1,5 @@
1
1
  // Re-export shared motion utilities from primitives (single source of truth)
2
- export { getReducedMotionPreference, observeReducedMotionPreference, supportsIntersectionObservers, supportsPointerTracking, supportsPropertyRegistration, registerPropertyOnce, supportsWebGL2, observeInViewport, observePageVisibility, observeOffscreenState } from '@dryui/primitives/internal/motion';
2
+ export { getReducedMotionPreference, observeReducedMotionPreference, supportsIntersectionObservers, supportsPointerTracking, supportsPropertyRegistration, registerPropertyOnce, supportsWebGL2 } from '@dryui/primitives/internal/motion';
3
3
  // UI-only motion utilities
4
4
  export function supportsViewTransitions() {
5
5
  return typeof document !== 'undefined' && 'startViewTransition' in document;
@@ -18,9 +18,25 @@
18
18
  </script>
19
19
 
20
20
  <Button {variant} {size} type="button" aria-label={label} {...rest}>
21
- {#if children}
22
- {@render children()}
23
- {:else}
24
- {glyph}
25
- {/if}
21
+ <span data-part="nav-arrow-icon" data-direction={direction}>
22
+ {#if children}
23
+ {@render children()}
24
+ {:else}
25
+ {glyph}
26
+ {/if}
27
+ </span>
26
28
  </Button>
29
+
30
+ <style>
31
+ [data-part='nav-arrow-icon'] {
32
+ display: inline-flex;
33
+ align-items: center;
34
+ justify-content: center;
35
+ }
36
+ [data-part='nav-arrow-icon'][data-direction='prev'] {
37
+ transform: translateX(-0.5px);
38
+ }
39
+ [data-part='nav-arrow-icon'][data-direction='next'] {
40
+ transform: translateX(0.5px);
41
+ }
42
+ </style>
@@ -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);
@@ -61,6 +61,7 @@
61
61
  --dry-popover-shadow: var(--dry-overlay-shadow, var(--dry-shadow-lg));
62
62
  --dry-popover-padding: var(--dry-space-4);
63
63
  --dry-radius-nested: max(0px, calc(var(--dry-popover-radius) - var(--dry-popover-padding)));
64
+ --dry-btn-radius: var(--dry-radius-nested);
64
65
 
65
66
  inset: unset;
66
67
  margin: 0;
@@ -79,6 +80,11 @@
79
80
  transform var(--dry-duration-fast) var(--dry-ease-emphasized);
80
81
  }
81
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
+
82
88
  [data-popover-content]:not(:popover-open) {
83
89
  display: none;
84
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) {
@@ -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>
@@ -1,11 +1 @@
1
- /** sin/cos color rotation using u_time, u_resolution, optional u_color_* uniforms */
2
- export declare const gradientFlow = "#version 300 es\nprecision highp float;\n\nin vec2 v_uv;\nout vec4 fragColor;\n\nuniform float u_time;\nuniform vec2 u_resolution;\nuniform vec3 u_color_primary;\nuniform vec3 u_color_secondary;\n\nvoid main() {\n vec2 uv = v_uv;\n float t = u_time * 0.4;\n\n vec3 c1 = u_color_primary.r > 0.0 ? u_color_primary : vec3(0.3, 0.4, 0.9);\n vec3 c2 = u_color_secondary.r > 0.0 ? u_color_secondary : vec3(0.9, 0.3, 0.5);\n\n float angle = t + uv.x * 3.14159 + uv.y * 2.0;\n float s = sin(angle) * 0.5 + 0.5;\n float c = cos(angle * 0.7 + 1.0) * 0.5 + 0.5;\n\n vec3 color = mix(c1, c2, s);\n color = mix(color, vec3(1.0) - color * 0.3, c * 0.3);\n\n fragColor = vec4(color, 1.0);\n}\n";
3
- /** Procedural dots via hash/noise functions */
4
- export declare const particleField = "#version 300 es\nprecision highp float;\n\nin vec2 v_uv;\nout vec4 fragColor;\n\nuniform float u_time;\nuniform vec2 u_resolution;\n\nfloat hash(vec2 p) {\n return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);\n}\n\nvoid main() {\n vec2 uv = v_uv;\n float aspect = u_resolution.x / u_resolution.y;\n uv.x *= aspect;\n\n float t = u_time * 0.3;\n vec3 color = vec3(0.02, 0.02, 0.06);\n\n for (int i = 0; i < 36; i++) {\n float fi = float(i);\n vec2 pos = vec2(\n hash(vec2(fi, 0.0)),\n hash(vec2(0.0, fi))\n );\n pos.x *= aspect;\n pos += vec2(sin(t + fi * 0.5) * 0.05, cos(t * 0.7 + fi * 0.3) * 0.05);\n\n float d = length(uv - pos);\n float size = 0.002 + hash(vec2(fi, fi)) * 0.003;\n float brightness = smoothstep(size * 2.0, size * 0.5, d);\n\n vec3 particleColor = vec3(\n 0.4 + hash(vec2(fi, 1.0)) * 0.6,\n 0.3 + hash(vec2(fi, 2.0)) * 0.4,\n 0.6 + hash(vec2(fi, 3.0)) * 0.4\n );\n\n color += particleColor * brightness * 0.8;\n }\n\n fragColor = vec4(color, 1.0);\n}\n";
5
- /** Sinusoidal UV warp, responds to u_mouse */
6
- export declare const waveDistortion = "#version 300 es\nprecision highp float;\n\nin vec2 v_uv;\nout vec4 fragColor;\n\nuniform float u_time;\nuniform vec2 u_resolution;\nuniform vec2 u_mouse;\n\nvoid main() {\n vec2 uv = v_uv;\n float t = u_time * 0.5;\n vec2 mouse = u_mouse;\n\n float wave1 = sin(uv.x * 8.0 + t * 2.0 + mouse.x * 3.0) * 0.02;\n float wave2 = sin(uv.y * 6.0 + t * 1.5 + mouse.y * 2.0) * 0.03;\n float wave3 = cos(uv.x * 4.0 - t + uv.y * 5.0) * 0.015;\n\n uv.x += wave1 + wave3;\n uv.y += wave2;\n\n vec3 c1 = vec3(0.1, 0.2, 0.4);\n vec3 c2 = vec3(0.3, 0.1, 0.5);\n vec3 c3 = vec3(0.05, 0.3, 0.4);\n\n float blend = sin(uv.x * 3.14159 + uv.y * 2.0 + t) * 0.5 + 0.5;\n vec3 color = mix(mix(c1, c2, uv.x), c3, blend);\n\n fragColor = vec4(color, 1.0);\n}\n";
7
- /** Multi-point gradient interpolation */
8
- export declare const meshGradient = "#version 300 es\nprecision highp float;\n\nin vec2 v_uv;\nout vec4 fragColor;\n\nuniform float u_time;\nuniform vec2 u_resolution;\nuniform vec3 u_color_primary;\nuniform vec3 u_color_secondary;\n\nvoid main() {\n vec2 uv = v_uv;\n float t = u_time * 0.2;\n\n vec3 c1 = u_color_primary.r > 0.0 ? u_color_primary : vec3(0.5, 0.2, 0.8);\n vec3 c2 = u_color_secondary.r > 0.0 ? u_color_secondary : vec3(0.2, 0.6, 0.9);\n vec3 c3 = vec3(0.9, 0.3, 0.4);\n vec3 c4 = vec3(0.2, 0.8, 0.5);\n\n vec2 p1 = vec2(0.2 + sin(t) * 0.1, 0.3 + cos(t * 0.7) * 0.1);\n vec2 p2 = vec2(0.8 + sin(t * 0.8 + 1.0) * 0.1, 0.2 + cos(t * 0.6) * 0.1);\n vec2 p3 = vec2(0.7 + sin(t * 0.5 + 2.0) * 0.1, 0.8 + cos(t * 0.9) * 0.1);\n vec2 p4 = vec2(0.3 + sin(t * 0.6 + 3.0) * 0.1, 0.7 + cos(t * 0.4) * 0.1);\n\n float d1 = 1.0 / (length(uv - p1) + 0.01);\n float d2 = 1.0 / (length(uv - p2) + 0.01);\n float d3 = 1.0 / (length(uv - p3) + 0.01);\n float d4 = 1.0 / (length(uv - p4) + 0.01);\n float total = d1 + d2 + d3 + d4;\n\n vec3 color = (c1 * d1 + c2 * d2 + c3 * d3 + c4 * d4) / total;\n\n fragColor = vec4(color, 1.0);\n}\n";
9
- /** Fresnel-like metallic reflection via distance fields */
10
- export declare const liquidMetal = "#version 300 es\nprecision highp float;\n\nin vec2 v_uv;\nout vec4 fragColor;\n\nuniform float u_time;\nuniform vec2 u_resolution;\n\nfloat smin(float a, float b, float k) {\n float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);\n return mix(b, a, h) - k * h * (1.0 - h);\n}\n\nvoid main() {\n vec2 uv = v_uv * 2.0 - 1.0;\n float aspect = u_resolution.x / u_resolution.y;\n uv.x *= aspect;\n float t = u_time * 0.3;\n\n // Animated blob positions\n vec2 p1 = vec2(sin(t * 0.7) * 0.6, cos(t * 0.5) * 0.4);\n vec2 p2 = vec2(cos(t * 0.8 + 1.0) * 0.5, sin(t * 0.6 + 2.0) * 0.5);\n vec2 p3 = vec2(sin(t * 0.4 + 3.0) * 0.4, cos(t * 0.9 + 1.5) * 0.3);\n\n float d1 = length(uv - p1) - 0.3;\n float d2 = length(uv - p2) - 0.25;\n float d3 = length(uv - p3) - 0.2;\n\n float d = smin(smin(d1, d2, 0.3), d3, 0.3);\n\n // Fresnel-like effect\n float edge = smoothstep(0.02, -0.02, d);\n float fresnel = pow(1.0 - abs(d) * 3.0, 3.0);\n fresnel = clamp(fresnel, 0.0, 1.0);\n\n // Metal color\n vec3 baseColor = vec3(0.7, 0.72, 0.75);\n vec3 highlight = vec3(0.95, 0.95, 1.0);\n vec3 dark = vec3(0.15, 0.16, 0.2);\n\n float n = sin(uv.x * 8.0 + t) * cos(uv.y * 8.0 - t * 0.7) * 0.5 + 0.5;\n vec3 color = mix(dark, baseColor, edge);\n color = mix(color, highlight, fresnel * 0.8);\n color += n * 0.05 * edge;\n\n // Environment reflection hint\n float ref = sin(uv.x * 4.0 + uv.y * 3.0 + t * 2.0) * 0.5 + 0.5;\n color += vec3(0.1, 0.12, 0.15) * ref * edge * 0.4;\n\n fragColor = vec4(color, 1.0);\n}\n";
11
1
  export declare const PRESETS: Record<string, string>;
@@ -1,5 +1,5 @@
1
1
  /** sin/cos color rotation using u_time, u_resolution, optional u_color_* uniforms */
2
- export const gradientFlow = `#version 300 es
2
+ const gradientFlow = `#version 300 es
3
3
  precision highp float;
4
4
 
5
5
  in vec2 v_uv;
@@ -28,7 +28,7 @@ void main() {
28
28
  }
29
29
  `;
30
30
  /** Procedural dots via hash/noise functions */
31
- export const particleField = `#version 300 es
31
+ const particleField = `#version 300 es
32
32
  precision highp float;
33
33
 
34
34
  in vec2 v_uv;
@@ -75,7 +75,7 @@ void main() {
75
75
  }
76
76
  `;
77
77
  /** Sinusoidal UV warp, responds to u_mouse */
78
- export const waveDistortion = `#version 300 es
78
+ const waveDistortion = `#version 300 es
79
79
  precision highp float;
80
80
 
81
81
  in vec2 v_uv;
@@ -108,7 +108,7 @@ void main() {
108
108
  }
109
109
  `;
110
110
  /** Multi-point gradient interpolation */
111
- export const meshGradient = `#version 300 es
111
+ const meshGradient = `#version 300 es
112
112
  precision highp float;
113
113
 
114
114
  in vec2 v_uv;
@@ -145,7 +145,7 @@ void main() {
145
145
  }
146
146
  `;
147
147
  /** Fresnel-like metallic reflection via distance fields */
148
- export const liquidMetal = `#version 300 es
148
+ const liquidMetal = `#version 300 es
149
149
  precision highp float;
150
150
 
151
151
  in vec2 v_uv;
@@ -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)
@@ -60,6 +60,7 @@
60
60
  color: var(--dry-typography-text-color, var(--dry-color-text-strong));
61
61
  font-family: var(--dry-font-sans);
62
62
  line-height: 1.7;
63
+ text-wrap: pretty;
63
64
  }
64
65
 
65
66
  [data-color='muted'] {
@@ -26,7 +26,6 @@ export interface ThemeController {
26
26
  /** Stop watching `matchMedia` changes. Called automatically on HMR disposal. */
27
27
  destroy(): void;
28
28
  }
29
- export declare const DARK_MEDIA_QUERY = "(prefers-color-scheme: dark)";
30
29
  export declare const DEFAULT_STORAGE_KEY = "dryui-theme";
31
30
  /**
32
31
  * Read the stored theme mode. Exported for testing; production callers
@@ -1,4 +1,4 @@
1
- export const DARK_MEDIA_QUERY = '(prefers-color-scheme: dark)';
1
+ const DARK_MEDIA_QUERY = '(prefers-color-scheme: dark)';
2
2
  export const DEFAULT_STORAGE_KEY = 'dryui-theme';
3
3
  function isBrowser() {
4
4
  return typeof window !== 'undefined' && typeof document !== 'undefined';
@@ -113,7 +113,12 @@
113
113
  >
114
114
  {#snippet icon()}
115
115
  <span class="icons" data-mode={iconState}>
116
- <span class="icon sun" aria-hidden="true">
116
+ <span
117
+ class="icon sun"
118
+ data-dry-icon-reveal
119
+ data-hidden={iconState === 'dark' || undefined}
120
+ aria-hidden="true"
121
+ >
117
122
  {#if sunIcon}
118
123
  {@render sunIcon()}
119
124
  {:else}
@@ -138,7 +143,12 @@
138
143
  </svg>
139
144
  {/if}
140
145
  </span>
141
- <span class="icon moon" aria-hidden="true">
146
+ <span
147
+ class="icon moon"
148
+ data-dry-icon-reveal
149
+ data-hidden={iconState === 'light' || undefined}
150
+ aria-hidden="true"
151
+ >
142
152
  {#if moonIcon}
143
153
  {@render moonIcon()}
144
154
  {:else}
@@ -65,6 +65,9 @@
65
65
  --dry-color-bg-brand: var(--dry-color-fill-brand);
66
66
  --dry-color-bg-inverse: #ffffff;
67
67
 
68
+ /* ─── Image edge ring (dark) ─────────────────────────────────── */
69
+ --dry-image-edge: rgba(255, 255, 255, 0.1);
70
+
68
71
  /* ─── Component: Toggle ─────────────────────────────────────────
69
72
  Derived from semantic tokens so brand overrides (e.g. a consumer
70
73
  retheming --dry-color-fill-brand) flow through to toggles too.
@@ -283,6 +286,9 @@
283
286
  --dry-color-bg-brand: var(--dry-color-fill-brand);
284
287
  --dry-color-bg-inverse: #ffffff;
285
288
 
289
+ /* ─── Image edge ring (dark) ─────────────────────────────────── */
290
+ --dry-image-edge: rgba(255, 255, 255, 0.1);
291
+
286
292
  /* ─── Component: Toggle ─────────────────────────────────────────
287
293
  Derived from semantic tokens so brand overrides (e.g. a consumer
288
294
  retheming --dry-color-fill-brand) flow through to toggles too.
@@ -91,6 +91,12 @@ samp {
91
91
  --dry-color-bg-brand: var(--dry-color-fill-brand);
92
92
  --dry-color-bg-inverse: hsl(233, 18%, 9%);
93
93
 
94
+ /* ─── Image edge ring ────────────────────────────────────────────
95
+ Subtle 1px outline for image surfaces (avatars, images, image
96
+ skeletons). Prevents image edges from reading as surface
97
+ imperfections. Hardcoded rgba — not brand-derivable. */
98
+ --dry-image-edge: rgba(0, 0, 0, 0.1);
99
+
94
100
  /* ─── Component: Toggle ─────────────────────────────────────────
95
101
  Derived from semantic tokens so brand overrides (e.g. a consumer
96
102
  retheming --dry-color-fill-brand) flow through to toggles too.
@@ -365,6 +371,10 @@ samp {
365
371
  --dry-motion-blur-enter: 16px;
366
372
  --dry-motion-scale-enter: 0.96;
367
373
 
374
+ /* ─── Stagger ─────────────────────────────────────────────────── */
375
+ --dry-stagger-step: 60ms;
376
+ --dry-stagger-max: 8; /* cap delay so very long lists don't take forever */
377
+
368
378
  /* ─── Glass surface ───────────────────────────────────────────── */
369
379
  --dry-glass-blur: 12px;
370
380
  --dry-glass-tint: var(--dry-color-bg-raised);
@@ -501,3 +511,85 @@ samp {
501
511
  --dry-motion-scale-enter: 1;
502
512
  }
503
513
  }
514
+
515
+ /* Stagger — per-child transition-delay driven by `--dry-index`. The first
516
+ 12 children get `--dry-index` automatically via `:nth-child()`; for longer
517
+ lists set `--dry-index` on each child. The child must declare a
518
+ `transition` on its enter properties for the delay to have effect. */
519
+ [data-dry-stagger] > * {
520
+ --dry-stagger-delay: calc(
521
+ min(var(--dry-index, 0), var(--dry-stagger-max)) * var(--dry-stagger-step)
522
+ );
523
+ transition-delay: var(--dry-stagger-delay);
524
+ animation-delay: var(--dry-stagger-delay);
525
+ }
526
+
527
+ [data-dry-stagger] > *:nth-child(1) {
528
+ --dry-index: 0;
529
+ }
530
+ [data-dry-stagger] > *:nth-child(2) {
531
+ --dry-index: 1;
532
+ }
533
+ [data-dry-stagger] > *:nth-child(3) {
534
+ --dry-index: 2;
535
+ }
536
+ [data-dry-stagger] > *:nth-child(4) {
537
+ --dry-index: 3;
538
+ }
539
+ [data-dry-stagger] > *:nth-child(5) {
540
+ --dry-index: 4;
541
+ }
542
+ [data-dry-stagger] > *:nth-child(6) {
543
+ --dry-index: 5;
544
+ }
545
+ [data-dry-stagger] > *:nth-child(7) {
546
+ --dry-index: 6;
547
+ }
548
+ [data-dry-stagger] > *:nth-child(8) {
549
+ --dry-index: 7;
550
+ }
551
+ [data-dry-stagger] > *:nth-child(9) {
552
+ --dry-index: 8;
553
+ }
554
+ [data-dry-stagger] > *:nth-child(10) {
555
+ --dry-index: 9;
556
+ }
557
+ [data-dry-stagger] > *:nth-child(11) {
558
+ --dry-index: 10;
559
+ }
560
+ [data-dry-stagger] > *:nth-child(12) {
561
+ --dry-index: 11;
562
+ }
563
+
564
+ @media (prefers-reduced-motion: reduce) {
565
+ [data-dry-stagger] > * {
566
+ transition: none;
567
+ animation: none;
568
+ }
569
+ }
570
+
571
+ .dry-tabular-nums {
572
+ font-variant-numeric: tabular-nums;
573
+ }
574
+
575
+ [data-dry-icon-reveal] {
576
+ transition:
577
+ opacity var(--dry-duration-fast) var(--dry-ease-spring-snappy),
578
+ transform var(--dry-duration-fast) var(--dry-ease-spring-snappy),
579
+ filter var(--dry-duration-fast) var(--dry-ease-spring-snappy);
580
+ transform-origin: center;
581
+ }
582
+ [data-dry-icon-reveal][data-state='hidden'],
583
+ [data-dry-icon-reveal][data-hidden],
584
+ [data-dry-icon-reveal][hidden] {
585
+ opacity: 0;
586
+ transform: scale(0.25);
587
+ filter: blur(4px);
588
+ }
589
+ @media (prefers-reduced-motion: reduce) {
590
+ [data-dry-icon-reveal] {
591
+ transition: none;
592
+ filter: none;
593
+ transform: none;
594
+ }
595
+ }
@@ -47,6 +47,7 @@
47
47
  role="region"
48
48
  aria-label="Notifications"
49
49
  data-part="provider"
50
+ data-dry-stagger
50
51
  data-position={position}
51
52
  class={className}
52
53
  {...rest}
@@ -60,6 +60,7 @@
60
60
  [data-part='root'] {
61
61
  --dry-toast-accent: var(--dry-color-fill-info);
62
62
  --dry-radius-nested: max(0px, calc(var(--dry-radius-lg) - var(--dry-space-4)));
63
+ --dry-btn-radius: var(--dry-radius-nested);
63
64
 
64
65
  position: relative;
65
66
  background: var(--dry-toast-bg, var(--dry-color-bg-overlay));
@@ -70,6 +70,11 @@
70
70
  transform var(--dry-duration-fast) var(--dry-ease-emphasized);
71
71
  }
72
72
 
73
+ [data-tooltip-content][data-state='closed'] {
74
+ transition-duration: calc(var(--dry-duration-fast) / 2);
75
+ transition-timing-function: var(--dry-ease-out);
76
+ }
77
+
73
78
  [data-tooltip-content]:not(:popover-open) {
74
79
  display: none;
75
80
  }
@@ -166,10 +166,11 @@
166
166
  height: var(--dry-video-embed-play-size);
167
167
  aspect-ratio: 1;
168
168
  filter: drop-shadow(0 2px 8px rgb(15 23 42 / 0.3));
169
+ transform: translateX(1px);
169
170
  transition: transform var(--dry-duration-normal) var(--dry-ease-default);
170
171
  }
171
172
 
172
173
  .play-btn-slot:hover [data-part='play-icon'] {
173
- transform: scale(1.1);
174
+ transform: translateX(1px) scale(1.1);
174
175
  }
175
176
  </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dryui/ui",
3
- "version": "1.6.0",
3
+ "version": "1.7.1",
4
4
  "description": "Zero-dependency styled Svelte 5 components with scoped styles and --dry-* CSS variable theming.",
5
5
  "author": "Rob Balfre",
6
6
  "license": "MIT",
@@ -784,7 +784,7 @@
784
784
  "postpack": "bun ../../scripts/postpack-exports.ts"
785
785
  },
786
786
  "dependencies": {
787
- "@dryui/primitives": "^1.6.0"
787
+ "@dryui/primitives": "^1.7.1"
788
788
  },
789
789
  "peerDependencies": {
790
790
  "svelte": "^5.55.1"
@@ -1,14 +0,0 @@
1
- import type { Placement } from '@dryui/primitives';
2
- export type { Placement };
3
- interface AnchorStylesOptions {
4
- triggerEl: () => HTMLElement | null;
5
- contentEl: () => HTMLElement | null;
6
- placement: () => Placement;
7
- offset: () => number;
8
- }
9
- export declare function useAnchorStyles(options: AnchorStylesOptions): {
10
- readonly styles: Record<string, string>;
11
- applyPosition: (node: HTMLElement, initialUserStyle?: string | null) => {
12
- update(newUserStyle?: string | null): void;
13
- };
14
- };
@@ -1,32 +0,0 @@
1
- import { createAnchorPosition } from '@dryui/primitives';
2
- export function useAnchorStyles(options) {
3
- const position = createAnchorPosition(options.triggerEl, options.contentEl, {
4
- get placement() {
5
- return options.placement();
6
- },
7
- get offset() {
8
- return options.offset();
9
- }
10
- });
11
- function applyPosition(node, initialUserStyle) {
12
- let userStyle = $state(initialUserStyle);
13
- $effect(() => {
14
- node.style.cssText = typeof userStyle === 'string' ? userStyle : '';
15
- const styles = position.styles;
16
- for (const [key, value] of Object.entries(styles)) {
17
- node.style.setProperty(key, value);
18
- }
19
- });
20
- return {
21
- update(newUserStyle) {
22
- userStyle = newUserStyle;
23
- }
24
- };
25
- }
26
- return {
27
- get styles() {
28
- return position.styles;
29
- },
30
- applyPosition
31
- };
32
- }