@dryui/ui 1.2.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/accordion/accordion-button-trigger.svelte +6 -0
  2. package/dist/alert/alert.svelte +2 -1
  3. package/dist/alert-dialog/alert-dialog-content.svelte +9 -69
  4. package/dist/alpha-slider/alpha-slider-input.svelte +4 -0
  5. package/dist/avatar/avatar.svelte +3 -4
  6. package/dist/backdrop/backdrop.svelte +12 -9
  7. package/dist/badge/badge.svelte +84 -72
  8. package/dist/button/button.svelte +3 -6
  9. package/dist/button-group/button-group.svelte +2 -2
  10. package/dist/calendar/calendar-button-grid.svelte +1 -1
  11. package/dist/card/card-root.svelte +9 -4
  12. package/dist/chip-group/chip-group-root.svelte +2 -2
  13. package/dist/code-block/code-block-button.svelte +6 -2
  14. package/dist/context-menu/context-menu-content.svelte +12 -79
  15. package/dist/diagram/diagram.svelte +3 -4
  16. package/dist/dialog/dialog-content.svelte +5 -103
  17. package/dist/drag-and-drop/drag-and-drop-item.svelte +1 -1
  18. package/dist/drawer/drawer-dialog-content.svelte +5 -155
  19. package/dist/dropdown-menu/dropdown-menu-content.svelte +14 -71
  20. package/dist/heading/heading.svelte +7 -6
  21. package/dist/hover-card/hover-card-content.svelte +3 -12
  22. package/dist/icon/icon.svelte +2 -2
  23. package/dist/index.d.ts +2 -2
  24. package/dist/index.js +1 -1
  25. package/dist/input/input.svelte +3 -3
  26. package/dist/internal/calendar-grid-button.svelte +29 -26
  27. package/dist/internal/calendar-grid-button.svelte.d.ts +1 -0
  28. package/dist/internal/close-button-base.svelte +15 -2
  29. package/dist/internal/close-button-base.svelte.d.ts +1 -0
  30. package/dist/internal/modal-content.svelte +331 -0
  31. package/dist/internal/modal-content.svelte.d.ts +26 -0
  32. package/dist/link-preview/link-preview-content.svelte +3 -11
  33. package/dist/logo-mark/logo-mark.svelte +2 -3
  34. package/dist/marquee/marquee.svelte +14 -3
  35. package/dist/mask-reveal/mask-reveal.svelte +17 -14
  36. package/dist/mega-menu/mega-menu-panel.svelte +14 -2
  37. package/dist/mega-menu/mega-menu-panel.svelte.d.ts +1 -0
  38. package/dist/menubar/menubar-content.svelte +21 -81
  39. package/dist/noise/noise.svelte +1 -0
  40. package/dist/option-picker/option-picker-preview.svelte +11 -10
  41. package/dist/popover/popover-content.svelte +10 -36
  42. package/dist/progress/progress.svelte +2 -2
  43. package/dist/prompt-input/index.d.ts +13 -1
  44. package/dist/prompt-input/prompt-input-button-textarea.svelte +13 -1
  45. package/dist/prompt-input/prompt-input-button-textarea.svelte.d.ts +3 -0
  46. package/dist/shimmer/shimmer.svelte +28 -3
  47. package/dist/slider/slider-input.svelte +3 -4
  48. package/dist/spacer/spacer.svelte +2 -2
  49. package/dist/spinner/spinner.svelte +2 -2
  50. package/dist/tabs/tabs-content.svelte +1 -1
  51. package/dist/tabs/tabs-list.svelte +13 -0
  52. package/dist/tag/tag-button.svelte +2 -3
  53. package/dist/text/text.svelte +4 -15
  54. package/dist/themes/dark.css +40 -24
  55. package/dist/themes/default.css +16 -12
  56. package/dist/themes/token-scope.d.ts +1 -0
  57. package/dist/themes/token-scope.js +1 -0
  58. package/dist/timeline/timeline-icon.svelte +11 -1
  59. package/dist/timeline/timeline-item.svelte +18 -5
  60. package/dist/timeline/timeline-root.svelte +3 -10
  61. package/dist/toast/toast-close-button.svelte +19 -7
  62. package/dist/toast/toast-provider.svelte +22 -1
  63. package/dist/toggle/toggle-button.svelte +7 -3
  64. package/dist/toggle-group/toggle-group-root.svelte +2 -2
  65. package/dist/tooltip/tooltip-content.svelte +8 -32
  66. package/dist/tour/tour-root.css +26 -26
  67. package/dist/tree/tree-item-label.svelte +8 -14
  68. package/dist/tree/tree-item-label.svelte.d.ts +2 -2
  69. package/dist/typography/heading.svelte +8 -6
  70. package/dist/typography/text.svelte +4 -3
  71. package/package.json +6 -2
  72. package/skills/dryui/SKILL.md +3 -3
  73. package/skills/dryui/rules/native-web-transitions.md +2 -2
  74. package/dist/themes/use-theme-override.svelte.d.ts +0 -1
  75. package/dist/themes/use-theme-override.svelte.js +0 -1
package/dist/index.js CHANGED
@@ -150,5 +150,5 @@ export { ShaderCanvas, PRESETS as SHADER_PRESETS } from './shader-canvas/index.j
150
150
  export { Svg } from './svg/index.js';
151
151
  export { StarRating } from './star-rating/index.js';
152
152
  export { Tag } from './tag/index.js';
153
- export { useThemeOverride } from './themes/use-theme-override.svelte.js';
153
+ export { TokenScope } from './themes/token-scope.js';
154
154
  export { Diagram } from './diagram/index.js';
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { HTMLInputAttributes } from 'svelte/elements';
3
- import { getFormControlCtx } from '@dryui/primitives';
3
+ import { getFormControlCtx, variantAttrs } from '@dryui/primitives';
4
4
 
5
5
  interface Props extends Omit<HTMLInputAttributes, 'size'> {
6
6
  value?: string;
@@ -34,8 +34,7 @@
34
34
  aria-invalid={ctx?.hasError || undefined}
35
35
  aria-errormessage={ctx?.errorMessageId}
36
36
  data-disabled={isDisabled || undefined}
37
- data-size={size}
38
- data-variant={variant !== 'default' ? variant : undefined}
37
+ {...variantAttrs({ size, variant: variant !== 'default' ? variant : undefined })}
39
38
  class={className}
40
39
  {...rest}
41
40
  />
@@ -45,6 +44,7 @@
45
44
  .wrapper {
46
45
  container-type: inline-size;
47
46
  display: grid;
47
+ grid-template-columns: minmax(0, 1fr);
48
48
  }
49
49
 
50
50
  input {
@@ -41,9 +41,10 @@
41
41
 
42
42
  interface Props extends HTMLAttributes<HTMLDivElement> {
43
43
  adapter: CalendarGridAdapter;
44
+ hideHeader?: boolean;
44
45
  }
45
46
 
46
- let { adapter, class: className, ...rest }: Props = $props();
47
+ let { adapter, hideHeader = false, class: className, ...rest }: Props = $props();
47
48
 
48
49
  let containerEl = $state<HTMLDivElement>();
49
50
 
@@ -90,31 +91,33 @@
90
91
 
91
92
  <div {@attach bindContainer} class={className} {...rest} data-calendar-grid>
92
93
  <div role="group" aria-label={monthYearLabel} data-calendar-panel>
93
- <div data-calendar-header>
94
- <Button
95
- variant="trigger"
96
- size="icon-sm"
97
- type="button"
98
- aria-label="Previous month"
99
- disabled={adapter.disabled}
100
- onclick={() => adapter.prevMonth()}
101
- >
102
- &#8249;
103
- </Button>
104
- <span aria-live="polite" aria-atomic="true" data-calendar-heading>
105
- {monthYearLabel}
106
- </span>
107
- <Button
108
- variant="trigger"
109
- size="icon-sm"
110
- type="button"
111
- aria-label="Next month"
112
- disabled={adapter.disabled}
113
- onclick={() => adapter.nextMonth()}
114
- >
115
- &#8250;
116
- </Button>
117
- </div>
94
+ {#if !hideHeader}
95
+ <div data-calendar-header>
96
+ <Button
97
+ variant="trigger"
98
+ size="icon-sm"
99
+ type="button"
100
+ aria-label="Previous month"
101
+ disabled={adapter.disabled}
102
+ onclick={() => adapter.prevMonth()}
103
+ >
104
+ &#8249;
105
+ </Button>
106
+ <span aria-live="polite" aria-atomic="true" data-calendar-heading>
107
+ {monthYearLabel}
108
+ </span>
109
+ <Button
110
+ variant="trigger"
111
+ size="icon-sm"
112
+ type="button"
113
+ aria-label="Next month"
114
+ disabled={adapter.disabled}
115
+ onclick={() => adapter.nextMonth()}
116
+ >
117
+ &#8250;
118
+ </Button>
119
+ </div>
120
+ {/if}
118
121
 
119
122
  <div role="grid" aria-label={monthYearLabel}>
120
123
  <div role="row" data-calendar-row>
@@ -21,6 +21,7 @@ export interface CalendarGridAdapter {
21
21
  import type { HTMLAttributes } from 'svelte/elements';
22
22
  interface Props extends HTMLAttributes<HTMLDivElement> {
23
23
  adapter: CalendarGridAdapter;
24
+ hideHeader?: boolean;
24
25
  }
25
26
  declare const CalendarGridButton: import("svelte").Component<Props, {}, "">;
26
27
  type CalendarGridButton = ReturnType<typeof CalendarGridButton>;
@@ -7,13 +7,26 @@
7
7
  interface Props extends HTMLButtonAttributes {
8
8
  variant?: ButtonVariant;
9
9
  size?: ButtonSize;
10
+ label?: string;
10
11
  children?: Snippet;
11
12
  }
12
13
 
13
- let { variant = 'trigger', size = 'icon-sm', children, ...rest }: Props = $props();
14
+ let {
15
+ variant = 'trigger',
16
+ size = 'icon-sm',
17
+ label = 'Dismiss',
18
+ children,
19
+ ...rest
20
+ }: Props = $props();
14
21
  </script>
15
22
 
16
- <Button {variant} {size} type="button" {...rest}>
23
+ <Button
24
+ {variant}
25
+ {size}
26
+ type="button"
27
+ aria-label={children !== undefined ? undefined : label}
28
+ {...rest}
29
+ >
17
30
  {#if children}
18
31
  {@render children()}
19
32
  {:else}
@@ -4,6 +4,7 @@ import type { ButtonVariant, ButtonSize } from '../button/index.js';
4
4
  interface Props extends HTMLButtonAttributes {
5
5
  variant?: ButtonVariant;
6
6
  size?: ButtonSize;
7
+ label?: string;
7
8
  children?: Snippet;
8
9
  }
9
10
  declare const CloseButtonBase: import("svelte").Component<Props, {}, "">;
@@ -0,0 +1,331 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLDialogAttributes } from 'svelte/elements';
4
+
5
+ type ModalSide = 'center' | 'top' | 'right' | 'bottom' | 'left';
6
+ type ModalVariant = 'dialog' | 'drawer' | 'alert-dialog';
7
+
8
+ interface Props extends HTMLDialogAttributes {
9
+ ctx: { readonly open: boolean; readonly headerId: string; close: () => void };
10
+ /** Presentational variant; drives the scoped style block. */
11
+ variant: ModalVariant;
12
+ /**
13
+ * Positioning hint. `center` stacks the panel in the middle;
14
+ * `top`/`right`/`bottom`/`left` pin it to an edge (drawer-style).
15
+ */
16
+ side?: ModalSide;
17
+ /** When false, clicking the dialog backdrop does not dismiss. */
18
+ dismissOnBackdropClick?: boolean;
19
+ /** When false, pressing Escape does not dismiss. */
20
+ dismissOnEscape?: boolean;
21
+ children: Snippet;
22
+ }
23
+
24
+ let {
25
+ ctx,
26
+ variant,
27
+ side = 'center',
28
+ dismissOnBackdropClick = true,
29
+ dismissOnEscape = true,
30
+ class: className,
31
+ children,
32
+ ...rest
33
+ }: Props = $props();
34
+
35
+ let dialogEl = $state<HTMLDialogElement>();
36
+
37
+ $effect(() => {
38
+ if (!dialogEl) return;
39
+
40
+ if (side !== 'center') {
41
+ // Native <dialog> applies a max-width that leaves a strip beside edge drawers.
42
+ dialogEl.style.setProperty('max-width', 'none');
43
+ }
44
+
45
+ if (ctx.open && !dialogEl.open) {
46
+ dialogEl.showModal();
47
+ }
48
+ if (!ctx.open && dialogEl.open) {
49
+ dialogEl.close();
50
+ }
51
+ });
52
+ </script>
53
+
54
+ <dialog
55
+ bind:this={dialogEl}
56
+ data-modal-content
57
+ data-variant={variant}
58
+ data-side={side}
59
+ data-dialog-content={variant === 'dialog' ? '' : undefined}
60
+ data-drawer-content={variant === 'drawer' ? '' : undefined}
61
+ data-alert-dialog-content={variant === 'alert-dialog' ? '' : undefined}
62
+ aria-labelledby={ctx.headerId || undefined}
63
+ data-state={ctx.open ? 'open' : 'closed'}
64
+ onclose={() => ctx.close()}
65
+ oncancel={dismissOnEscape ? undefined : (e) => e.preventDefault()}
66
+ onclick={dismissOnBackdropClick
67
+ ? (e) => {
68
+ if (e.target === dialogEl) {
69
+ ctx.close();
70
+ }
71
+ }
72
+ : undefined}
73
+ class={className}
74
+ {...rest}
75
+ >
76
+ <div
77
+ data-modal-panel
78
+ data-variant={variant}
79
+ data-side={side}
80
+ data-dialog-panel={variant === 'dialog' ? '' : undefined}
81
+ data-drawer-panel={variant === 'drawer' ? '' : undefined}
82
+ data-alert-dialog-panel={variant === 'alert-dialog' ? '' : undefined}
83
+ >
84
+ {@render children()}
85
+ </div>
86
+ </dialog>
87
+
88
+ <style>
89
+ [data-modal-content] {
90
+ position: fixed;
91
+ inset: 0;
92
+ height: 100vh;
93
+ height: 100dvh;
94
+ max-height: none;
95
+ border: none;
96
+ background: transparent;
97
+ color: var(--dry-color-text-strong);
98
+ padding: 0;
99
+ }
100
+
101
+ [data-modal-content][data-variant]:not([open]) {
102
+ display: none;
103
+ }
104
+
105
+ /* ---------- Dialog (center) ---------- */
106
+
107
+ [data-modal-content][data-variant='dialog'] {
108
+ /* dryui-allow width */
109
+ width: 100vw;
110
+ /* dryui-allow width */
111
+ max-width: none;
112
+ display: grid;
113
+ grid-template-columns: min(90vw, var(--dry-dialog-max-width, 32rem));
114
+ place-content: center;
115
+ place-items: center;
116
+ overflow: visible;
117
+ }
118
+
119
+ [data-modal-content][data-variant='dialog']::backdrop {
120
+ background: var(--dry-overlay-bg, var(--dry-color-overlay-backdrop));
121
+ backdrop-filter: blur(var(--dry-overlay-blur, 12px));
122
+ -webkit-backdrop-filter: blur(var(--dry-overlay-blur, 12px));
123
+ }
124
+
125
+ [data-modal-panel][data-variant='dialog'] {
126
+ --dry-dialog-border: var(--dry-overlay-border, var(--dry-color-stroke-weak));
127
+ --dry-dialog-padding: var(--dry-space-8);
128
+ --dry-radius-nested: max(
129
+ 0px,
130
+ calc(
131
+ var(--dry-dialog-radius, var(--dry-overlay-radius, var(--dry-radius-2xl))) -
132
+ var(--dry-dialog-padding)
133
+ )
134
+ );
135
+
136
+ container-type: inline-size;
137
+ justify-self: stretch;
138
+ border: 1px solid var(--dry-dialog-border);
139
+ border-radius: var(--dry-dialog-radius, var(--dry-overlay-radius, var(--dry-radius-2xl)));
140
+ background: var(--dry-dialog-bg, var(--dry-overlay-bg, var(--dry-color-bg-overlay)));
141
+ color: var(--dry-color-text-strong);
142
+ box-shadow: var(--dry-dialog-shadow, var(--dry-overlay-shadow, var(--dry-shadow-overlay)));
143
+ padding: 0;
144
+ max-block-size: var(--dry-dialog-max-block-size, 85vh);
145
+ display: grid;
146
+ overflow: var(--dry-dialog-overflow, auto);
147
+
148
+ transition:
149
+ opacity var(--dry-duration-normal) var(--dry-ease-spring-snappy),
150
+ transform var(--dry-duration-normal) var(--dry-ease-spring-snappy);
151
+ }
152
+
153
+ [data-modal-content][data-variant='dialog'][data-state='open'] [data-modal-panel] {
154
+ opacity: 1;
155
+ transform: scale(1) translateY(0);
156
+ }
157
+
158
+ @starting-style {
159
+ [data-modal-content][data-variant='dialog'][open] [data-modal-panel] {
160
+ opacity: 0;
161
+ transform: scale(var(--dry-motion-scale-enter)) translateY(var(--dry-motion-distance-sm));
162
+ }
163
+ }
164
+
165
+ /* ---------- AlertDialog (center) ---------- */
166
+
167
+ [data-modal-content][data-variant='alert-dialog'] {
168
+ /* dryui-allow width */
169
+ width: 100vw;
170
+ /* dryui-allow width */
171
+ max-width: none;
172
+ display: grid;
173
+ grid-template-columns: min(90vw, var(--dry-dialog-max-width, 32rem));
174
+ place-content: center;
175
+ place-items: center;
176
+ overflow: visible;
177
+ }
178
+
179
+ [data-modal-content][data-variant='alert-dialog']::backdrop {
180
+ background: var(--dry-overlay-bg, var(--dry-color-overlay-backdrop-strong));
181
+ backdrop-filter: blur(var(--dry-overlay-blur, 4px));
182
+ -webkit-backdrop-filter: blur(var(--dry-overlay-blur, 4px));
183
+ }
184
+
185
+ [data-modal-panel][data-variant='alert-dialog'] {
186
+ --dry-dialog-bg: var(--dry-color-bg-overlay);
187
+ --dry-dialog-border: var(--dry-color-stroke-weak);
188
+ --dry-dialog-radius: var(--dry-radius-xl);
189
+ --dry-dialog-shadow: var(--dry-shadow-overlay);
190
+ --dry-dialog-padding: var(--dry-space-6);
191
+ --dry-dialog-max-width: 32rem;
192
+
193
+ container-type: inline-size;
194
+ justify-self: stretch;
195
+ border: 1px solid var(--dry-dialog-border);
196
+ border-radius: var(--dry-dialog-radius);
197
+ background: var(--dry-dialog-bg);
198
+ color: var(--dry-color-text-strong);
199
+ box-shadow: var(--dry-dialog-shadow);
200
+ padding: 0;
201
+ max-block-size: 85vh;
202
+ display: grid;
203
+ grid-template-rows: max-content minmax(0, 1fr) max-content;
204
+ overflow: hidden;
205
+
206
+ transition:
207
+ opacity var(--dry-duration-normal) var(--dry-ease-spring-snappy),
208
+ transform var(--dry-duration-normal) var(--dry-ease-spring-snappy);
209
+ }
210
+
211
+ [data-modal-content][data-variant='alert-dialog'][data-state='open'] [data-modal-panel] {
212
+ opacity: 1;
213
+ transform: scale(1) translateY(0);
214
+ }
215
+
216
+ @starting-style {
217
+ [data-modal-content][data-variant='alert-dialog'][open] [data-modal-panel] {
218
+ opacity: 0;
219
+ transform: scale(var(--dry-motion-scale-enter)) translateY(var(--dry-motion-distance-sm));
220
+ }
221
+ }
222
+
223
+ /* ---------- Drawer (edge-pinned) ---------- */
224
+
225
+ [data-modal-content][data-variant='drawer'] {
226
+ margin: 0;
227
+ display: grid;
228
+ grid-template-columns: minmax(0, 1fr);
229
+ grid-template-rows: minmax(0, 1fr);
230
+ }
231
+
232
+ [data-modal-content][data-variant='drawer']::backdrop {
233
+ background: var(--dry-overlay-bg, var(--dry-color-overlay-backdrop-strong));
234
+ backdrop-filter: blur(var(--dry-overlay-blur, 8px));
235
+ -webkit-backdrop-filter: blur(var(--dry-overlay-blur, 8px));
236
+ }
237
+
238
+ [data-modal-content][data-variant='drawer'][data-side='right'] {
239
+ grid-template-columns:
240
+ minmax(0, calc(100dvw - var(--dry-drawer-size, 25rem)))
241
+ var(--dry-drawer-size, 25rem);
242
+ }
243
+
244
+ [data-modal-content][data-variant='drawer'][data-side='left'] {
245
+ grid-template-columns:
246
+ var(--dry-drawer-size, 25rem)
247
+ minmax(0, calc(100dvw - var(--dry-drawer-size, 25rem)));
248
+ }
249
+
250
+ [data-modal-content][data-variant='drawer'][data-side='top'] {
251
+ grid-template-rows:
252
+ var(--dry-drawer-size, 25rem)
253
+ minmax(0, calc(100dvh - var(--dry-drawer-size, 25rem)));
254
+ }
255
+
256
+ [data-modal-content][data-variant='drawer'][data-side='bottom'] {
257
+ grid-template-rows:
258
+ minmax(0, calc(100dvh - var(--dry-drawer-size, 25rem)))
259
+ var(--dry-drawer-size, 25rem);
260
+ }
261
+
262
+ [data-modal-panel][data-variant='drawer'] {
263
+ --dry-drawer-bg: var(--dry-color-bg-overlay);
264
+ --dry-drawer-border: var(--dry-color-stroke-weak);
265
+ --dry-drawer-size: 25rem;
266
+ --_drawer-rest-transform: translateX(0);
267
+ --_drawer-enter-transform: translateX(100%);
268
+
269
+ background: var(--dry-drawer-bg);
270
+ color: var(--dry-color-text-strong);
271
+ box-shadow: var(--dry-drawer-shadow, var(--dry-shadow-overlay));
272
+ padding: 0;
273
+ display: grid;
274
+ grid-template-rows: max-content minmax(0, 1fr) max-content;
275
+ overflow: hidden;
276
+ opacity: 1;
277
+ transform: var(--_drawer-rest-transform);
278
+ will-change: transform, opacity;
279
+
280
+ transition:
281
+ transform var(--dry-duration-slow) var(--dry-ease-spring-snappy),
282
+ opacity var(--dry-duration-normal) var(--dry-ease-out);
283
+ }
284
+
285
+ [data-modal-content][data-variant='drawer'][data-side='right'] [data-modal-panel] {
286
+ grid-column: 2;
287
+ height: 100%;
288
+ border-left: 1px solid var(--dry-drawer-border);
289
+ }
290
+
291
+ [data-modal-content][data-variant='drawer'][data-side='left'] [data-modal-panel] {
292
+ --_drawer-enter-transform: translateX(-100%);
293
+ grid-column: 1;
294
+ height: 100%;
295
+ border-right: 1px solid var(--dry-drawer-border);
296
+ }
297
+
298
+ [data-modal-content][data-variant='drawer'][data-side='top'] [data-modal-panel] {
299
+ --_drawer-rest-transform: translateY(0);
300
+ --_drawer-enter-transform: translateY(-100%);
301
+ grid-row: 1;
302
+ height: var(--dry-drawer-size);
303
+ border-bottom: 1px solid var(--dry-drawer-border);
304
+ }
305
+
306
+ [data-modal-content][data-variant='drawer'][data-side='bottom'] [data-modal-panel] {
307
+ --_drawer-rest-transform: translateY(0);
308
+ --_drawer-enter-transform: translateY(100%);
309
+ grid-row: 2;
310
+ height: var(--dry-drawer-size);
311
+ border-top: 1px solid var(--dry-drawer-border);
312
+ }
313
+
314
+ @starting-style {
315
+ [data-modal-content][data-variant='drawer'][open] [data-modal-panel] {
316
+ opacity: 0;
317
+ transform: var(--_drawer-enter-transform);
318
+ }
319
+ }
320
+
321
+ [data-modal-content][data-variant='drawer'][data-state='open'] [data-modal-panel] {
322
+ opacity: 1;
323
+ transform: var(--_drawer-rest-transform);
324
+ }
325
+
326
+ @media (prefers-reduced-motion: reduce) {
327
+ [data-modal-panel] {
328
+ transition: none;
329
+ }
330
+ }
331
+ </style>
@@ -0,0 +1,26 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLDialogAttributes } from 'svelte/elements';
3
+ type ModalSide = 'center' | 'top' | 'right' | 'bottom' | 'left';
4
+ type ModalVariant = 'dialog' | 'drawer' | 'alert-dialog';
5
+ interface Props extends HTMLDialogAttributes {
6
+ ctx: {
7
+ readonly open: boolean;
8
+ readonly headerId: string;
9
+ close: () => void;
10
+ };
11
+ /** Presentational variant; drives the scoped style block. */
12
+ variant: ModalVariant;
13
+ /**
14
+ * Positioning hint. `center` stacks the panel in the middle;
15
+ * `top`/`right`/`bottom`/`left` pin it to an edge (drawer-style).
16
+ */
17
+ side?: ModalSide;
18
+ /** When false, clicking the dialog backdrop does not dismiss. */
19
+ dismissOnBackdropClick?: boolean;
20
+ /** When false, pressing Escape does not dismiss. */
21
+ dismissOnEscape?: boolean;
22
+ children: Snippet;
23
+ }
24
+ declare const ModalContent: import("svelte").Component<Props, {}, "">;
25
+ type ModalContent = ReturnType<typeof ModalContent>;
26
+ export default ModalContent;
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
- import { createPositionedPopover } from '@dryui/primitives';
4
+ import { createAnchoredPopover } from '@dryui/primitives';
5
5
  import type { Placement } from '@dryui/primitives';
6
6
  import { getLinkPreviewCtx } from './context.svelte.js';
7
7
 
@@ -24,21 +24,13 @@
24
24
 
25
25
  let contentEl = $state<HTMLDivElement>();
26
26
 
27
- const popover = createPositionedPopover({
27
+ const popover = createAnchoredPopover({
28
28
  triggerEl: () => ctx.triggerEl,
29
29
  contentEl: () => contentEl ?? null,
30
+ open: () => ctx.open,
30
31
  placement: () => placement,
31
32
  offset: () => offset
32
33
  });
33
-
34
- $effect(() => {
35
- if (!contentEl) return;
36
- if (ctx.open) {
37
- popover.showPopover(contentEl);
38
- } else {
39
- popover.hidePopover(contentEl);
40
- }
41
- });
42
34
  </script>
43
35
 
44
36
  <div
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
+ import { variantAttrs } from '@dryui/primitives';
3
4
 
4
5
  interface Props extends HTMLAttributes<HTMLSpanElement> {
5
6
  src?: string;
@@ -42,9 +43,7 @@
42
43
  role="img"
43
44
  aria-label={alt}
44
45
  data-logo-mark
45
- data-size={size}
46
- data-shape={shape}
47
- data-color={color}
46
+ {...variantAttrs({ size, shape, color })}
48
47
  class={className}
49
48
  {...rest}
50
49
  >
@@ -57,6 +57,7 @@
57
57
  $effect(() => {
58
58
  node.style.setProperty('--marquee-duration', `${duration}s`);
59
59
  node.style.setProperty('--marquee-gap', gap);
60
+ node.style.setProperty('--marquee-shift', `${contentSize}px`);
60
61
  });
61
62
  }
62
63
 
@@ -100,7 +101,6 @@
100
101
  [data-marquee-track] {
101
102
  display: grid;
102
103
  grid-auto-flow: var(--_flow, column);
103
- gap: var(--marquee-gap, 1rem);
104
104
  animation-duration: var(--dry-marquee-speed);
105
105
  animation-timing-function: linear;
106
106
  animation-iteration-count: infinite;
@@ -113,6 +113,17 @@
113
113
  gap: var(--marquee-gap, 1rem);
114
114
  }
115
115
 
116
+ /* Trailing padding on content (not gap on track) keeps the keyframe loop seamless. */
117
+ [data-marquee][data-direction='left'] [data-marquee-content],
118
+ [data-marquee][data-direction='right'] [data-marquee-content] {
119
+ padding-inline-end: var(--marquee-gap, 1rem);
120
+ }
121
+
122
+ [data-marquee][data-direction='up'] [data-marquee-content],
123
+ [data-marquee][data-direction='down'] [data-marquee-content] {
124
+ padding-block-end: var(--marquee-gap, 1rem);
125
+ }
126
+
116
127
  [data-marquee][data-direction='left'] [data-marquee-track],
117
128
  [data-marquee][data-direction='right'] [data-marquee-track] {
118
129
  animation-name: marquee-horizontal;
@@ -137,7 +148,7 @@
137
148
  transform: translateX(0);
138
149
  }
139
150
  to {
140
- transform: translateX(-50%);
151
+ transform: translateX(calc(-1 * var(--marquee-shift, 0px)));
141
152
  }
142
153
  }
143
154
 
@@ -146,7 +157,7 @@
146
157
  transform: translateY(0);
147
158
  }
148
159
  to {
149
- transform: translateY(-50%);
160
+ transform: translateY(calc(-1 * var(--marquee-shift, 0px)));
150
161
  }
151
162
  }
152
163
 
@@ -96,23 +96,26 @@
96
96
  });
97
97
  </script>
98
98
 
99
- <div
100
- {@attach captureElement}
101
- {@attach applyDuration}
102
- class={className}
103
- data-mask-reveal
104
- data-shape={shape}
105
- data-direction={direction}
106
- data-revealed={revealed || undefined}
107
- data-reduced-motion={prefersReducedMotion || undefined}
108
- {...rest}
109
- >
110
- {#if childSnippet}
111
- {@render childSnippet()}
112
- {/if}
99
+ <div {@attach captureElement} class={className} data-mask-reveal-root {...rest}>
100
+ <div
101
+ {@attach applyDuration}
102
+ data-mask-reveal
103
+ data-shape={shape}
104
+ data-direction={direction}
105
+ data-revealed={revealed || undefined}
106
+ data-reduced-motion={prefersReducedMotion || undefined}
107
+ >
108
+ {#if childSnippet}
109
+ {@render childSnippet()}
110
+ {/if}
111
+ </div>
113
112
  </div>
114
113
 
115
114
  <style>
115
+ [data-mask-reveal-root] {
116
+ display: block;
117
+ }
118
+
116
119
  [data-mask-reveal] {
117
120
  --dry-mask-reveal-duration: var(--dry-duration-entrance, 480ms);
118
121
  --dry-mask-reveal-ease: var(--dry-ease-spring-snappy, cubic-bezier(0.16, 1, 0.3, 1));
@@ -7,10 +7,18 @@
7
7
 
8
8
  interface Props extends HTMLAttributes<HTMLDivElement> {
9
9
  fullWidth?: boolean;
10
+ align?: 'start' | 'center' | 'end';
10
11
  children: Snippet;
11
12
  }
12
13
 
13
- let { fullWidth = false, class: className, children, style, ...rest }: Props = $props();
14
+ let {
15
+ fullWidth = false,
16
+ align = 'start',
17
+ class: className,
18
+ children,
19
+ style,
20
+ ...rest
21
+ }: Props = $props();
14
22
 
15
23
  const ctx = getMegaMenuCtx();
16
24
  const itemCtx = getMegaMenuItemCtx();
@@ -20,7 +28,11 @@
20
28
  const popover = createPositionedPopover({
21
29
  triggerEl: () => document.getElementById(itemCtx.triggerId),
22
30
  contentEl: () => panelEl ?? null,
23
- placement: () => 'bottom-start',
31
+ placement: () => {
32
+ if (align === 'center') return 'bottom';
33
+ if (align === 'end') return 'bottom-end';
34
+ return 'bottom-start';
35
+ },
24
36
  offset: () => 4
25
37
  });
26
38