@dryui/ui 1.9.0 → 2.0.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 (64) hide show
  1. package/dist/alert-dialog/alert-dialog-root.svelte +2 -2
  2. package/dist/avatar/avatar.svelte +2 -0
  3. package/dist/badge/badge.svelte +34 -32
  4. package/dist/button/button.svelte +67 -38
  5. package/dist/chromatic-aberration/chromatic-aberration.svelte +2 -2
  6. package/dist/collapsible/collapsible-root.svelte +3 -2
  7. package/dist/collapsible/context.svelte.d.ts +1 -2
  8. package/dist/collapsible/context.svelte.js +1 -2
  9. package/dist/color-picker/color-picker-channel-input.svelte +1 -0
  10. package/dist/combobox/combobox-content.svelte +1 -0
  11. package/dist/combobox/combobox-input-root.svelte +3 -3
  12. package/dist/command-palette/command-palette-dialog-root.svelte +3 -3
  13. package/dist/command-palette/command-palette-item.svelte +3 -3
  14. package/dist/context-menu/context-menu-root.svelte +2 -0
  15. package/dist/date-field/date-field-segment.svelte +26 -4
  16. package/dist/date-picker/datepicker-input-root.svelte +2 -0
  17. package/dist/date-range-picker/date-range-picker-root.svelte +2 -0
  18. package/dist/dialog/dialog-root.svelte +2 -2
  19. package/dist/drag-and-drop/drag-and-drop-root.svelte +34 -3
  20. package/dist/drawer/drawer-root.svelte +2 -2
  21. package/dist/dropdown-menu/dropdown-menu-root.svelte +2 -0
  22. package/dist/field/field-root.svelte +3 -2
  23. package/dist/file-select/file-select-root.svelte +1 -0
  24. package/dist/file-upload/file-upload-item.svelte +1 -0
  25. package/dist/heading/heading.svelte +6 -2
  26. package/dist/image/image.svelte +2 -0
  27. package/dist/internal/date-family-controller.svelte.d.ts +2 -1
  28. package/dist/internal/date-family-controller.svelte.js +4 -4
  29. package/dist/internal/menu-root-state.svelte.d.ts +1 -0
  30. package/dist/internal/menu-root-state.svelte.js +2 -3
  31. package/dist/internal/modal-content.svelte +2 -0
  32. package/dist/internal/picker-popover-content.svelte +1 -0
  33. package/dist/link-preview/link-preview-root.svelte +3 -3
  34. package/dist/logo-mark/logo-mark.svelte +1 -0
  35. package/dist/markdown-renderer/markdown-renderer.svelte +9 -3
  36. package/dist/markdown-renderer/markdown-renderer.svelte.d.ts +1 -1
  37. package/dist/mega-menu/mega-menu-item.svelte +4 -4
  38. package/dist/menubar/menubar-content.svelte +1 -0
  39. package/dist/menubar/menubar-menu.svelte +2 -2
  40. package/dist/menubar/menubar-root.svelte +1 -0
  41. package/dist/multi-select-combobox/multi-select-combobox-content.svelte +1 -0
  42. package/dist/multi-select-combobox/multi-select-combobox-item.svelte +2 -2
  43. package/dist/multi-select-combobox/multi-select-combobox-root-input.svelte +4 -3
  44. package/dist/navigation-menu/context.svelte.d.ts +0 -2
  45. package/dist/navigation-menu/context.svelte.js +1 -2
  46. package/dist/navigation-menu/navigation-menu-item.svelte +5 -8
  47. package/dist/notification-center/notification-center-root.svelte +3 -3
  48. package/dist/popover/popover-root.svelte +3 -3
  49. package/dist/radio-group/radio-group.svelte +2 -2
  50. package/dist/rich-text-editor/rich-text-editor-content.svelte +14 -6
  51. package/dist/rich-text-editor/rich-text-editor-root.svelte +19 -9
  52. package/dist/rich-text-editor/rich-text-editor-root.svelte.d.ts +1 -0
  53. package/dist/rich-text-editor/rich-text-editor-toolbar-button-input.svelte +1 -0
  54. package/dist/select/select-root-input.svelte +3 -3
  55. package/dist/tags-input/tags-input-root.svelte +1 -0
  56. package/dist/timeline/timeline-title.svelte +6 -0
  57. package/dist/toast/toast-root.svelte +1 -0
  58. package/dist/toolbar/toolbar-root.svelte +1 -0
  59. package/dist/tooltip/tooltip-root.svelte +3 -3
  60. package/dist/video-embed/video-embed-button.svelte +1 -0
  61. package/package.json +3 -3
  62. package/skills/dryui/SKILL.md +4 -1
  63. package/skills/dryui/rules/composition.md +1 -1
  64. package/skills/dryui/rules/theming.md +2 -2
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { onDestroy } from 'svelte';
2
3
  import type { HTMLAttributes } from 'svelte/elements';
3
4
  import { getDateFieldCtx, type DateSegmentType } from './context.svelte.js';
4
5
 
@@ -25,12 +26,31 @@
25
26
  );
26
27
 
27
28
  let inputBuffer = '';
28
- let bufferTimeout: ReturnType<typeof setTimeout>;
29
+ let segmentEl: HTMLSpanElement | undefined;
30
+ let bufferTimeout: ReturnType<typeof setTimeout> | undefined;
31
+
32
+ function clearBufferTimeout() {
33
+ if (bufferTimeout === undefined) return;
34
+ clearTimeout(bufferTimeout);
35
+ bufferTimeout = undefined;
36
+ }
37
+
38
+ function resetInputBuffer() {
39
+ clearBufferTimeout();
40
+ inputBuffer = '';
41
+ }
42
+
43
+ onDestroy(resetInputBuffer);
29
44
 
30
45
  function registerSegment(node: HTMLSpanElement) {
46
+ segmentEl = node;
31
47
  ctx.registerSegment(type, node);
48
+ node.addEventListener('blur', resetInputBuffer);
32
49
  return {
33
50
  destroy() {
51
+ node.removeEventListener('blur', resetInputBuffer);
52
+ resetInputBuffer();
53
+ if (segmentEl === node) segmentEl = undefined;
34
54
  ctx.unregisterSegment(type);
35
55
  }
36
56
  };
@@ -77,21 +97,21 @@
77
97
  break;
78
98
  case 'Backspace': {
79
99
  e.preventDefault();
80
- inputBuffer = '';
100
+ resetInputBuffer();
81
101
  ctx.updateSegment(type, minValue);
82
102
  break;
83
103
  }
84
104
  default: {
85
105
  if (/^\d$/.test(e.key)) {
86
106
  e.preventDefault();
87
- clearTimeout(bufferTimeout);
107
+ clearBufferTimeout();
88
108
  inputBuffer += e.key;
89
109
  const num = parseInt(inputBuffer, 10);
90
110
  const maxDigits = type === 'year' ? 4 : 2;
91
111
 
92
112
  if (inputBuffer.length >= maxDigits || num > maxValue) {
93
113
  ctx.updateSegment(type, Math.min(Math.max(num, minValue), maxValue));
94
- inputBuffer = '';
114
+ resetInputBuffer();
95
115
  // Auto-advance to next segment
96
116
  if (type !== 'year') {
97
117
  ctx.focusSegment(type, 'next');
@@ -100,6 +120,8 @@
100
120
  ctx.updateSegment(type, num);
101
121
  bufferTimeout = setTimeout(() => {
102
122
  inputBuffer = '';
123
+ bufferTimeout = undefined;
124
+ if (document.activeElement !== segmentEl) return;
103
125
  if (type !== 'year') {
104
126
  ctx.focusSegment(type, 'next');
105
127
  }
@@ -27,6 +27,7 @@
27
27
  disabled = false,
28
28
  children
29
29
  }: Props = $props();
30
+ const uid = $props.id();
30
31
 
31
32
  const view = createDateViewController({
32
33
  initialDate: value,
@@ -36,6 +37,7 @@
36
37
  const popover = createPickerPopoverController({
37
38
  triggerIdPrefix: 'datepicker-trigger',
38
39
  contentIdPrefix: 'datepicker-content',
40
+ uid,
39
41
  open: () => open,
40
42
  setOpen: (nextOpen) => {
41
43
  open = nextOpen;
@@ -27,6 +27,7 @@
27
27
  disabled = false,
28
28
  children
29
29
  }: Props = $props();
30
+ const uid = $props.id();
30
31
 
31
32
  const view = createDateViewController({
32
33
  initialDate: startDate,
@@ -42,6 +43,7 @@
42
43
  const popover = createPickerPopoverController({
43
44
  triggerIdPrefix: 'date-range-picker-trigger',
44
45
  contentIdPrefix: 'date-range-picker-content',
46
+ uid,
45
47
  open: () => open,
46
48
  setOpen: (nextOpen) => {
47
49
  open = nextOpen;
@@ -1,6 +1,5 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
- import { generateFormId } from '@dryui/primitives';
4
3
  import { setDialogCtx } from './context.svelte.js';
5
4
 
6
5
  interface Props {
@@ -10,7 +9,8 @@
10
9
 
11
10
  let { open = $bindable(false), children }: Props = $props();
12
11
 
13
- const headerId = generateFormId('dialog');
12
+ const uid = $props.id();
13
+ const headerId = `dialog-${uid}`;
14
14
 
15
15
  setDialogCtx({
16
16
  get open() {
@@ -1,8 +1,8 @@
1
1
  <script lang="ts" generics="T">
2
- import { flushSync } from 'svelte';
2
+ import { flushSync, onDestroy } from 'svelte';
3
3
  import type { Snippet } from 'svelte';
4
4
  import type { HTMLAttributes } from 'svelte/elements';
5
- import { createId, mergeIds } from '@dryui/primitives';
5
+ import { mergeIds } from '@dryui/primitives';
6
6
  import { setDragAndDropCtx } from './context.svelte.js';
7
7
  import { getGroupCtx } from './group-context.svelte.js';
8
8
 
@@ -48,7 +48,8 @@
48
48
  let crossListIndex: number | null = null;
49
49
 
50
50
  const groupCtx = getGroupCtx();
51
- const instructionsId = createId('dry-dnd-instructions');
51
+ const uid = $props.id();
52
+ const instructionsId = `dry-dnd-instructions-${uid}`;
52
53
 
53
54
  let keyboardInstructions = $derived.by(() => {
54
55
  const arrowKeys =
@@ -172,6 +173,7 @@
172
173
 
173
174
  const clone = draggedEl.cloneNode(true) as HTMLElement;
174
175
  clone.setAttribute('data-dnd-preview', '');
176
+ scrubPreviewClone(clone);
175
177
  clone.removeAttribute('data-index');
176
178
  clone.removeAttribute('data-dragging');
177
179
  clone.removeAttribute('data-drag-active');
@@ -204,6 +206,33 @@
204
206
  updatePreviewPosition();
205
207
  }
206
208
 
209
+ function scrubPreviewClone(clone: HTMLElement) {
210
+ clone.setAttribute('aria-hidden', 'true');
211
+ clone.setAttribute('inert', '');
212
+ clone.removeAttribute('id');
213
+
214
+ for (const el of clone.querySelectorAll<HTMLElement>('[id]')) {
215
+ el.removeAttribute('id');
216
+ }
217
+
218
+ const relationshipAttrs = [
219
+ 'for',
220
+ 'aria-activedescendant',
221
+ 'aria-controls',
222
+ 'aria-describedby',
223
+ 'aria-details',
224
+ 'aria-labelledby',
225
+ 'aria-owns'
226
+ ];
227
+ const selector = relationshipAttrs.map((attr) => `[${attr}]`).join(',');
228
+
229
+ for (const el of clone.querySelectorAll<HTMLElement>(selector)) {
230
+ for (const attr of relationshipAttrs) {
231
+ el.removeAttribute(attr);
232
+ }
233
+ }
234
+ }
235
+
207
236
  function updatePreviewPosition() {
208
237
  if (!previewEl) return;
209
238
  const dx = pointerX - startX;
@@ -358,6 +387,8 @@
358
387
  }
359
388
  }
360
389
 
390
+ onDestroy(removePreview);
391
+
361
392
  function handlePointerMove(e: PointerEvent) {
362
393
  if (isAnimating) return;
363
394
  if (!isPending && !isDragging) return;
@@ -1,6 +1,5 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
- import { generateFormId } from '@dryui/primitives';
4
3
  import { setDrawerCtx } from './context.svelte.js';
5
4
 
6
5
  interface Props {
@@ -11,7 +10,8 @@
11
10
 
12
11
  let { open = $bindable(false), side = 'right', children }: Props = $props();
13
12
 
14
- const headerId = generateFormId('drawer');
13
+ const uid = $props.id();
14
+ const headerId = `drawer-${uid}`;
15
15
 
16
16
  setDrawerCtx({
17
17
  get open() {
@@ -9,10 +9,12 @@
9
9
  }
10
10
 
11
11
  let { open = $bindable(false), children }: Props = $props();
12
+ const uid = $props.id();
12
13
 
13
14
  setDropdownMenuCtx(
14
15
  createMenuRootState({
15
16
  idBase: 'dropdown',
17
+ uid,
16
18
  getOpen: () => open,
17
19
  setOpen: (value) => {
18
20
  open = value;
@@ -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 { setFormControlCtx, generateFormId } from '@dryui/primitives';
4
+ import { setFormControlCtx } from '@dryui/primitives';
5
5
 
6
6
  interface Props extends HTMLAttributes<HTMLDivElement> {
7
7
  children: Snippet;
@@ -21,7 +21,8 @@
21
21
  ...rest
22
22
  }: Props = $props();
23
23
 
24
- const id = generateFormId('field');
24
+ const uid = $props.id();
25
+ const id = `field-${uid}`;
25
26
  let hasDescription = $state(false);
26
27
  let hasErrorMessage = $state(false);
27
28
 
@@ -85,6 +85,7 @@
85
85
  padding: var(--dry-space-2) var(--dry-space-3);
86
86
  font-family: var(--dry-font-sans);
87
87
  background: var(--dry-color-bg-raised);
88
+ /* dryui-allow solid-border-on-raised: file select is a form control and needs a persistent field edge. */
88
89
  border: 1px solid var(--dry-color-stroke-strong);
89
90
  border-radius: var(--dry-radius-md);
90
91
  transition:
@@ -27,6 +27,7 @@
27
27
  font-family: var(--dry-font-sans);
28
28
  color: var(--dry-color-text-strong);
29
29
  background: var(--dry-color-bg-raised);
30
+ /* dryui-allow solid-border-on-raised: uploaded file rows need a visible item boundary in dense lists. */
30
31
  border: 1px solid var(--dry-color-stroke-weak);
31
32
  border-radius: var(--dry-radius-md);
32
33
  }
@@ -27,26 +27,32 @@
27
27
  </script>
28
28
 
29
29
  {#if level === 1}
30
+ <!-- dryui-allow raw-heading: Heading is the canonical component that renders semantic h1-h6 output. -->
30
31
  <h1 class={className} {...variantAttrs({ level, variant, measure })} {...rest}>
31
32
  {@render children()}
32
33
  </h1>
33
34
  {:else if level === 2}
35
+ <!-- dryui-allow raw-heading: Heading is the canonical component that renders semantic h1-h6 output. -->
34
36
  <h2 class={className} {...variantAttrs({ level, variant, measure })} {...rest}>
35
37
  {@render children()}
36
38
  </h2>
37
39
  {:else if level === 3}
40
+ <!-- dryui-allow raw-heading: Heading is the canonical component that renders semantic h1-h6 output. -->
38
41
  <h3 class={className} {...variantAttrs({ level, variant, measure })} {...rest}>
39
42
  {@render children()}
40
43
  </h3>
41
44
  {:else if level === 4}
45
+ <!-- dryui-allow raw-heading: Heading is the canonical component that renders semantic h1-h6 output. -->
42
46
  <h4 class={className} {...variantAttrs({ level, variant, measure })} {...rest}>
43
47
  {@render children()}
44
48
  </h4>
45
49
  {:else if level === 5}
50
+ <!-- dryui-allow raw-heading: Heading is the canonical component that renders semantic h1-h6 output. -->
46
51
  <h5 class={className} {...variantAttrs({ level, variant, measure })} {...rest}>
47
52
  {@render children()}
48
53
  </h5>
49
54
  {:else}
55
+ <!-- dryui-allow raw-heading: Heading is the canonical component that renders semantic h1-h6 output. -->
50
56
  <h6 class={className} {...variantAttrs({ level, variant, measure })} {...rest}>
51
57
  {@render children()}
52
58
  </h6>
@@ -64,7 +70,6 @@
64
70
  font-family: var(--dry-font-sans);
65
71
  font-weight: 700;
66
72
  line-height: var(--dry-type-heading-2-leading, 2.5rem);
67
- letter-spacing: -0.03em;
68
73
  text-wrap: balance;
69
74
  }
70
75
 
@@ -102,7 +107,6 @@
102
107
  font-family: var(--dry-font-display, var(--dry-font-sans));
103
108
  font-size: var(--dry-type-display-size, var(--dry-text-4xl-size, 2.25rem));
104
109
  line-height: var(--dry-type-display-leading, 4rem);
105
- letter-spacing: -0.04em;
106
110
  }
107
111
 
108
112
  /* ── Measure (max-inline-size) ─────────────────────────────────────────────
@@ -37,8 +37,10 @@
37
37
  {#if status === 'error' && fallbackSnippet}
38
38
  {@render fallbackSnippet()}
39
39
  {:else if status === 'error' && fallback}
40
+ <!-- dryui-allow raw-img: Image is the canonical component that renders the underlying img element. -->
40
41
  <img class={className} src={fallback} {alt} data-state="fallback" {...rest} />
41
42
  {:else}
43
+ <!-- dryui-allow raw-img: Image is the canonical component that renders the underlying img element. -->
42
44
  <img
43
45
  class={className}
44
46
  {src}
@@ -18,6 +18,7 @@ export declare function createDateViewController({ initialDate, locale }: DateVi
18
18
  interface PickerPopoverControllerConfig {
19
19
  triggerIdPrefix: string;
20
20
  contentIdPrefix: string;
21
+ uid: string;
21
22
  open: () => boolean;
22
23
  setOpen: (open: boolean) => void;
23
24
  disabled: () => boolean;
@@ -33,7 +34,7 @@ export interface PickerPopoverController {
33
34
  close: () => void;
34
35
  toggle: () => void;
35
36
  }
36
- export declare function createPickerPopoverController({ triggerIdPrefix, contentIdPrefix, open, setOpen, disabled, onShow, onClose }: PickerPopoverControllerConfig): {
37
+ export declare function createPickerPopoverController({ triggerIdPrefix, contentIdPrefix, uid, open, setOpen, disabled, onShow, onClose }: PickerPopoverControllerConfig): {
37
38
  readonly triggerId: string;
38
39
  readonly contentId: string;
39
40
  readonly triggerEl: HTMLElement | null;
@@ -1,4 +1,4 @@
1
- import { generateFormId, getWeekStartDay } from '@dryui/primitives';
1
+ import { getWeekStartDay } from '@dryui/primitives';
2
2
  import { SvelteDate } from 'svelte/reactivity';
3
3
  export function createDateViewController({ initialDate = null, locale }) {
4
4
  const seedDate = initialDate ? new SvelteDate(initialDate.getTime()) : new SvelteDate();
@@ -56,9 +56,9 @@ export function createDateViewController({ initialDate = null, locale }) {
56
56
  }
57
57
  };
58
58
  }
59
- export function createPickerPopoverController({ triggerIdPrefix, contentIdPrefix, open, setOpen, disabled, onShow, onClose }) {
60
- const triggerId = generateFormId(triggerIdPrefix);
61
- const contentId = generateFormId(contentIdPrefix);
59
+ export function createPickerPopoverController({ triggerIdPrefix, contentIdPrefix, uid, open, setOpen, disabled, onShow, onClose }) {
60
+ const triggerId = `${triggerIdPrefix}-${uid}`;
61
+ const contentId = `${contentIdPrefix}-${uid}`;
62
62
  let triggerEl = $state(null);
63
63
  return {
64
64
  get triggerId() {
@@ -16,6 +16,7 @@ export interface PositionedMenuRootState extends MenuRootState {
16
16
  }
17
17
  interface CreateMenuRootStateOptions {
18
18
  idBase: string;
19
+ uid: string;
19
20
  getOpen: () => boolean;
20
21
  setOpen: (value: boolean) => void;
21
22
  }
@@ -1,7 +1,6 @@
1
- import { generateFormId } from '@dryui/primitives';
2
1
  function createBaseMenuRootState(options) {
3
- const triggerId = generateFormId(`${options.idBase}-trigger`);
4
- const contentId = generateFormId(`${options.idBase}-content`);
2
+ const triggerId = `${options.idBase}-trigger-${options.uid}`;
3
+ const contentId = `${options.idBase}-content-${options.uid}`;
5
4
  return {
6
5
  triggerId,
7
6
  contentId,
@@ -319,6 +319,7 @@
319
319
 
320
320
  [data-modal-content][data-variant='drawer'][data-side='top'] [data-modal-panel] {
321
321
  --_drawer-rest-transform: translateY(0);
322
+ /* dryui-allow symmetric-exit-animation: this is the off-canvas enter position for a top drawer, not the exit animation. */
322
323
  --_drawer-enter-transform: translateY(-100%);
323
324
  grid-row: 1;
324
325
  height: var(--dry-drawer-size);
@@ -327,6 +328,7 @@
327
328
 
328
329
  [data-modal-content][data-variant='drawer'][data-side='bottom'] [data-modal-panel] {
329
330
  --_drawer-rest-transform: translateY(0);
331
+ /* dryui-allow symmetric-exit-animation: this is the off-canvas enter position for a bottom drawer, not the exit animation. */
330
332
  --_drawer-enter-transform: translateY(100%);
331
333
  grid-row: 2;
332
334
  height: var(--dry-drawer-size);
@@ -82,6 +82,7 @@
82
82
  margin: 0;
83
83
  display: inline-grid;
84
84
  padding: var(--dry-space-3);
85
+ /* dryui-allow solid-border-on-raised: date picker popovers need a clear calendar boundary. */
85
86
  border: 1px solid var(--dry-color-stroke-weak);
86
87
  border-radius: var(--dry-radius-lg);
87
88
  background: var(--dry-color-bg-overlay);
@@ -1,6 +1,5 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
- import { generateFormId } from '@dryui/primitives';
4
3
  import { setLinkPreviewCtx } from './context.svelte.js';
5
4
 
6
5
  interface Props {
@@ -12,8 +11,9 @@
12
11
 
13
12
  let { open = $bindable(false), openDelay = 700, closeDelay = 300, children }: Props = $props();
14
13
 
15
- const triggerId = generateFormId('link-preview-trigger');
16
- const contentId = generateFormId('link-preview-content');
14
+ const uid = $props.id();
15
+ const triggerId = `link-preview-trigger-${uid}`;
16
+ const contentId = `link-preview-content-${uid}`;
17
17
 
18
18
  let openTimeout: ReturnType<typeof setTimeout>;
19
19
  let closeTimeout: ReturnType<typeof setTimeout>;
@@ -48,6 +48,7 @@
48
48
  {...rest}
49
49
  >
50
50
  {#if showImage}
51
+ <!-- dryui-allow raw-img: LogoMark owns the intrinsic brand mark image and fallback lifecycle. -->
51
52
  <img {src} {alt} onerror={handleError} />
52
53
  {:else}
53
54
  <span aria-hidden="true">{getFallbackText()}</span>
@@ -5,22 +5,28 @@
5
5
  import { Separator } from '../separator/index.js';
6
6
  interface Props extends HTMLAttributes<HTMLDivElement> {
7
7
  content: string;
8
- sanitize?: boolean;
8
+ dangerouslyAllowRawHtml?: boolean;
9
9
  }
10
10
 
11
- let { content, sanitize = true, class: className, ...rest }: Props = $props();
11
+ let { content, dangerouslyAllowRawHtml = false, class: className, ...rest }: Props = $props();
12
12
 
13
- const nodes = $derived(parseMarkdownToAst(content, { sanitize }));
13
+ const nodes = $derived(parseMarkdownToAst(content, { sanitize: !dangerouslyAllowRawHtml }));
14
14
  </script>
15
15
 
16
16
  {#snippet renderNodes(items: MarkdownNode[])}
17
17
  {#each items as node}
18
18
  {#if node.type === 'heading'}
19
+ <!-- dryui-allow raw-heading: MarkdownRenderer maps parsed markdown heading nodes to their semantic HTML tag. -->
19
20
  {#if node.level === 1}<h1>{@html node.content}</h1>
21
+ <!-- dryui-allow raw-heading: MarkdownRenderer maps parsed markdown heading nodes to their semantic HTML tag. -->
20
22
  {:else if node.level === 2}<h2>{@html node.content}</h2>
23
+ <!-- dryui-allow raw-heading: MarkdownRenderer maps parsed markdown heading nodes to their semantic HTML tag. -->
21
24
  {:else if node.level === 3}<h3>{@html node.content}</h3>
25
+ <!-- dryui-allow raw-heading: MarkdownRenderer maps parsed markdown heading nodes to their semantic HTML tag. -->
22
26
  {:else if node.level === 4}<h4>{@html node.content}</h4>
27
+ <!-- dryui-allow raw-heading: MarkdownRenderer maps parsed markdown heading nodes to their semantic HTML tag. -->
23
28
  {:else if node.level === 5}<h5>{@html node.content}</h5>
29
+ <!-- dryui-allow raw-heading: MarkdownRenderer maps parsed markdown heading nodes to their semantic HTML tag. -->
24
30
  {:else}<h6>{@html node.content}</h6>
25
31
  {/if}
26
32
  {:else if node.type === 'paragraph'}
@@ -1,7 +1,7 @@
1
1
  import type { HTMLAttributes } from 'svelte/elements';
2
2
  interface Props extends HTMLAttributes<HTMLDivElement> {
3
3
  content: string;
4
- sanitize?: boolean;
4
+ dangerouslyAllowRawHtml?: boolean;
5
5
  }
6
6
  declare const MarkdownRenderer: import("svelte").Component<Props, {}, "">;
7
7
  type MarkdownRenderer = ReturnType<typeof MarkdownRenderer>;
@@ -1,7 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
- import { generateFormId } from '@dryui/primitives';
5
4
  import { getMegaMenuCtx, setMegaMenuItemCtx } from './context.svelte.js';
6
5
 
7
6
  interface Props extends HTMLAttributes<HTMLDivElement> {
@@ -11,9 +10,10 @@
11
10
  let { class: className, children, ...rest }: Props = $props();
12
11
 
13
12
  const ctx = getMegaMenuCtx();
14
- const itemId = generateFormId('mm-item');
15
- const triggerId = generateFormId('mm-trigger');
16
- const panelId = generateFormId('mm-panel');
13
+ const uid = $props.id();
14
+ const itemId = `mm-item-${uid}`;
15
+ const triggerId = `mm-trigger-${uid}`;
16
+ const panelId = `mm-panel-${uid}`;
17
17
 
18
18
  setMegaMenuItemCtx({
19
19
  itemId,
@@ -123,6 +123,7 @@
123
123
 
124
124
  background: var(--dry-color-bg-overlay);
125
125
  color: var(--dry-color-text-strong);
126
+ /* dryui-allow solid-border-on-raised: menu popover keeps a visible edge while floating above varied content. */
126
127
  border: 1px solid var(--dry-color-stroke-weak);
127
128
  border-radius: var(--dry-radius-popover);
128
129
  box-shadow: var(--dry-shadow-overlay);
@@ -1,7 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { onMount } from 'svelte';
3
3
  import type { Snippet } from 'svelte';
4
- import { generateFormId } from '@dryui/primitives';
5
4
  import { getMenubarCtx, setMenubarMenuCtx } from './context.svelte.js';
6
5
 
7
6
  interface Props {
@@ -10,7 +9,8 @@
10
9
 
11
10
  let { children }: Props = $props();
12
11
 
13
- const menuId = generateFormId('menubar-menu');
12
+ const uid = $props.id();
13
+ const menuId = `menubar-menu-${uid}`;
14
14
  const ctx = getMenubarCtx();
15
15
 
16
16
  onMount(() => {
@@ -82,6 +82,7 @@
82
82
  gap: var(--dry-space-1);
83
83
  padding: var(--dry-space-1);
84
84
  background: var(--dry-color-bg-overlay);
85
+ /* dryui-allow solid-border-on-raised: menubar root presents a persistent command strip boundary. */
85
86
  border: 1px solid var(--dry-color-stroke-weak);
86
87
  border-radius: var(--dry-radius-lg);
87
88
  }
@@ -87,6 +87,7 @@
87
87
  margin: 0;
88
88
  grid-template-columns: minmax(anchor-size(inline), max-content);
89
89
  background: var(--dry-color-bg-overlay);
90
+ /* dryui-allow solid-border-on-raised: popover menu keeps an explicit edge for contrast against busy app surfaces. */
90
91
  border: 1px solid var(--dry-color-stroke-weak);
91
92
  border-radius: var(--dry-radius-md);
92
93
  box-shadow: var(--dry-shadow-lg);
@@ -1,7 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
- import { generateFormId } from '@dryui/primitives';
5
4
  import { getMultiSelectComboboxCtx } from './context.svelte.js';
6
5
 
7
6
  interface Props extends HTMLAttributes<HTMLDivElement> {
@@ -24,7 +23,8 @@
24
23
  }: Props = $props();
25
24
 
26
25
  const ctx = getMultiSelectComboboxCtx();
27
- const id = generateFormId('multi-select-combobox-item');
26
+ const uid = $props.id();
27
+ const id = `multi-select-combobox-item-${uid}`;
28
28
 
29
29
  const isSelected = $derived(ctx.isSelected(value));
30
30
  const isUnavailable = $derived(disabled || !ctx.canSelect(value));
@@ -2,7 +2,6 @@
2
2
  import { untrack } from 'svelte';
3
3
  import type { Snippet } from 'svelte';
4
4
  import type { HTMLAttributes } from 'svelte/elements';
5
- import { generateFormId } from '@dryui/primitives';
6
5
  import { setMultiSelectComboboxCtx } from './context.svelte.js';
7
6
 
8
7
  interface Props extends HTMLAttributes<HTMLDivElement> {
@@ -31,8 +30,9 @@
31
30
  ...rest
32
31
  }: Props = $props();
33
32
 
34
- const inputId = generateFormId('multi-select-combobox-input');
35
- const contentId = generateFormId('multi-select-combobox-content');
33
+ const uid = $props.id();
34
+ const inputId = `multi-select-combobox-input-${uid}`;
35
+ const contentId = `multi-select-combobox-content-${uid}`;
36
36
 
37
37
  let rootEl = $state<HTMLDivElement | null>(null);
38
38
  let inputEl = $state<HTMLInputElement | null>(null);
@@ -251,6 +251,7 @@
251
251
  justify-content: start;
252
252
  gap: var(--dry-space-1_5);
253
253
  padding: var(--dry-space-2);
254
+ /* dryui-allow solid-border-on-raised: composite input needs a persistent field edge plus raised fill for token contrast. */
254
255
  border: 1px solid var(--dry-color-stroke-strong);
255
256
  border-radius: var(--dry-radius-md);
256
257
  background: var(--dry-color-bg-raised);
@@ -1,4 +1,3 @@
1
- import { generateFormId } from '@dryui/primitives';
2
1
  export interface NavigationMenuContext {
3
2
  readonly activeItem: string | null;
4
3
  openItem: (id: string) => void;
@@ -12,4 +11,3 @@ export interface NavigationMenuItemContext {
12
11
  readonly open: boolean;
13
12
  }
14
13
  export declare const setNavigationMenuItemCtx: (ctx: NavigationMenuItemContext) => NavigationMenuItemContext, getNavigationMenuItemCtx: () => NavigationMenuItemContext;
15
- export { generateFormId };
@@ -1,4 +1,3 @@
1
- import { createContext, generateFormId } from '@dryui/primitives';
1
+ import { createContext } from '@dryui/primitives';
2
2
  export const [setNavigationMenuCtx, getNavigationMenuCtx] = createContext('navigation-menu');
3
3
  export const [setNavigationMenuItemCtx, getNavigationMenuItemCtx] = createContext('navigation-menu-item');
4
- export { generateFormId };
@@ -1,10 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
- import {
4
- setNavigationMenuItemCtx,
5
- getNavigationMenuCtx,
6
- generateFormId
7
- } from './context.svelte.js';
3
+ import { setNavigationMenuItemCtx, getNavigationMenuCtx } from './context.svelte.js';
8
4
 
9
5
  interface Props {
10
6
  children: Snippet;
@@ -13,9 +9,10 @@
13
9
  let { children }: Props = $props();
14
10
 
15
11
  const ctx = getNavigationMenuCtx();
16
- const itemId = generateFormId('nav-item');
17
- const triggerId = generateFormId('nav-trigger');
18
- const contentId = generateFormId('nav-content');
12
+ const uid = $props.id();
13
+ const itemId = `nav-item-${uid}`;
14
+ const triggerId = `nav-trigger-${uid}`;
15
+ const contentId = `nav-content-${uid}`;
19
16
 
20
17
  setNavigationMenuItemCtx({
21
18
  itemId,
@@ -1,6 +1,5 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
- import { generateFormId } from '@dryui/primitives';
4
3
  import { setNotificationCenterCtx, type NotificationItem } from './context.svelte.js';
5
4
 
6
5
  interface Props {
@@ -15,8 +14,9 @@
15
14
  children
16
15
  }: Props = $props();
17
16
 
18
- const triggerId = generateFormId('nc-trigger');
19
- const panelId = generateFormId('nc-panel');
17
+ const uid = $props.id();
18
+ const triggerId = `nc-trigger-${uid}`;
19
+ const panelId = `nc-panel-${uid}`;
20
20
 
21
21
  const unreadCount = $derived(items.filter((item) => !item.read).length);
22
22