@dryui/ui 1.9.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) 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
@@ -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 { setPopoverCtx } from './context.svelte.js';
5
4
 
6
5
  interface Props {
@@ -10,8 +9,9 @@
10
9
 
11
10
  let { open = $bindable(false), children }: Props = $props();
12
11
 
13
- const triggerId = generateFormId('popover-trigger');
14
- const contentId = generateFormId('popover-content');
12
+ const uid = $props.id();
13
+ const triggerId = `popover-trigger-${uid}`;
14
+ const contentId = `popover-content-${uid}`;
15
15
 
16
16
  setPopoverCtx({
17
17
  get open() {
@@ -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 { createId } from '@dryui/primitives';
5
4
  import { setRadioGroupCtx } from './context.svelte.js';
6
5
 
7
6
  interface Props extends HTMLAttributes<HTMLDivElement> {
@@ -25,7 +24,8 @@
25
24
  ...rest
26
25
  }: Props = $props();
27
26
 
28
- const fallbackName = createId('dryui-radio');
27
+ const uid = $props.id();
28
+ const fallbackName = `dryui-radio-${uid}`;
29
29
  const groupName = $derived(name ?? fallbackName);
30
30
 
31
31
  setRadioGroupCtx({
@@ -1,6 +1,9 @@
1
1
  <script lang="ts">
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
- import { getRichTextEditorCtx } from '@dryui/primitives/rich-text-editor';
3
+ import {
4
+ getRichTextEditorCtx,
5
+ setSanitizedRichTextHtml
6
+ } from '@dryui/primitives/rich-text-editor';
4
7
 
5
8
  interface Props extends HTMLAttributes<HTMLDivElement> {}
6
9
 
@@ -12,17 +15,19 @@
12
15
 
13
16
  // Register the content element with the context
14
17
  $effect(() => {
15
- if (contentEl) {
16
- ctx.contentEl = contentEl;
17
- }
18
+ if (!contentEl) return;
19
+ ctx.contentEl = contentEl;
20
+ return () => {
21
+ if (ctx.contentEl === contentEl) ctx.contentEl = null;
22
+ };
18
23
  });
19
24
 
20
25
  // Sync value into contenteditable (also clears hint elements on mount)
21
26
  $effect(() => {
22
27
  if (!contentEl) return;
23
- const html = ctx.html || '';
28
+ const html = ctx.sanitizeHtml(ctx.html || '');
24
29
  if (contentEl.innerHTML !== html && document.activeElement !== contentEl) {
25
- contentEl.innerHTML = html;
30
+ setSanitizedRichTextHtml(contentEl, html);
26
31
  }
27
32
  });
28
33
 
@@ -96,8 +101,11 @@
96
101
  onkeydown={handleKeydown}
97
102
  {...rest}
98
103
  >
104
+ <!-- dryui-allow raw-heading: hidden seed nodes make Svelte retain scoped styles for sanitized contenteditable HTML. -->
99
105
  <h1>.</h1>
106
+ <!-- dryui-allow raw-heading: hidden seed nodes make Svelte retain scoped styles for sanitized contenteditable HTML. -->
100
107
  <h2>.</h2>
108
+ <!-- dryui-allow raw-heading: hidden seed nodes make Svelte retain scoped styles for sanitized contenteditable HTML. -->
101
109
  <h3>.</h3>
102
110
  <p></p>
103
111
  <ul><li></li></ul>
@@ -1,9 +1,15 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
- import { setRichTextEditorCtx } from '@dryui/primitives/rich-text-editor';
4
+ import {
5
+ sanitizeRichTextElement,
6
+ sanitizeRichTextHtml,
7
+ sanitizeRichTextUrl,
8
+ setRichTextEditorCtx
9
+ } from '@dryui/primitives/rich-text-editor';
5
10
 
6
11
  interface Props extends HTMLAttributes<HTMLDivElement> {
12
+ /** HTML is sanitized before rendering and before bind:value updates are emitted. */
7
13
  value?: string;
8
14
  placeholder?: string;
9
15
  readonly?: boolean;
@@ -66,7 +72,7 @@
66
72
 
67
73
  function syncValue() {
68
74
  if (ctx.contentEl) {
69
- value = ctx.contentEl.innerHTML;
75
+ value = sanitizeRichTextElement(ctx.contentEl);
70
76
  }
71
77
  }
72
78
 
@@ -96,7 +102,7 @@
96
102
  return currentLink;
97
103
  },
98
104
  get html() {
99
- return value;
105
+ return sanitizeRichTextHtml(value);
100
106
  },
101
107
  get readonly() {
102
108
  return readonlyProp;
@@ -137,13 +143,16 @@
137
143
  },
138
144
  insertLink(url: string) {
139
145
  if (readonlyProp) return;
146
+ const safeUrl = sanitizeRichTextUrl(url);
147
+ if (!safeUrl) return;
148
+
140
149
  const sel = window.getSelection();
141
150
  if (!sel || sel.rangeCount === 0) return;
142
151
 
143
152
  if (sel.isCollapsed) {
144
153
  const a = document.createElement('a');
145
- a.href = url;
146
- a.textContent = url;
154
+ a.href = safeUrl;
155
+ a.textContent = safeUrl;
147
156
  a.target = '_blank';
148
157
  a.rel = 'noopener noreferrer';
149
158
  const range = sel.getRangeAt(0);
@@ -153,9 +162,9 @@
153
162
  sel.removeAllRanges();
154
163
  sel.addRange(range);
155
164
  } else {
156
- document.execCommand('createLink', false, url);
165
+ document.execCommand('createLink', false, safeUrl);
157
166
  if (ctx.contentEl) {
158
- const links = ctx.contentEl.querySelectorAll('a[href="' + CSS.escape(url) + '"]');
167
+ const links = ctx.contentEl.querySelectorAll('a[href="' + CSS.escape(safeUrl) + '"]');
159
168
  links.forEach((link) => {
160
169
  link.setAttribute('target', '_blank');
161
170
  link.setAttribute('rel', 'noopener noreferrer');
@@ -169,10 +178,11 @@
169
178
  execCommand('unlink');
170
179
  },
171
180
  getContent() {
172
- return ctx.contentEl?.innerHTML ?? '';
181
+ return sanitizeRichTextHtml(ctx.contentEl?.innerHTML ?? '');
173
182
  },
174
183
  updateState,
175
- syncValue
184
+ syncValue,
185
+ sanitizeHtml: sanitizeRichTextHtml
176
186
  });
177
187
  </script>
178
188
 
@@ -1,6 +1,7 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
3
  interface Props extends HTMLAttributes<HTMLDivElement> {
4
+ /** HTML is sanitized before rendering and before bind:value updates are emitted. */
4
5
  value?: string;
5
6
  placeholder?: string;
6
7
  readonly?: boolean;
@@ -516,6 +516,7 @@
516
516
  gap: var(--dry-space-2);
517
517
  padding: var(--dry-space-2);
518
518
  background: var(--dry-color-bg-overlay);
519
+ /* dryui-allow solid-border-on-raised: inline link editor is a small popover that needs a distinct edge. */
519
520
  border: 1px solid var(--dry-rte-border);
520
521
  border-radius: var(--dry-radius-md);
521
522
  box-shadow: var(--dry-shadow-md);
@@ -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 { setSelectCtx } from './context.svelte.js';
5
4
  import SelectTrigger from './select-trigger-button.svelte';
6
5
  import SelectValue from './select-value.svelte';
@@ -35,8 +34,9 @@
35
34
  options?.map((opt) => (typeof opt === 'string' ? { value: opt, label: opt } : opt))
36
35
  );
37
36
 
38
- const triggerId = generateFormId('select-trigger');
39
- const contentId = generateFormId('select-content');
37
+ const uid = $props.id();
38
+ const triggerId = `select-trigger-${uid}`;
39
+ const contentId = `select-content-${uid}`;
40
40
 
41
41
  let displayText = $state('');
42
42
  let triggerEl = $state<HTMLElement | null>(null);
@@ -86,6 +86,7 @@
86
86
  [data-part='root'] {
87
87
  display: block;
88
88
  padding: var(--dry-space-2);
89
+ /* dryui-allow solid-border-on-raised: tag editor is a form field with token chips on a raised input surface. */
89
90
  border: 1px solid var(--dry-color-stroke-strong);
90
91
  border-radius: var(--dry-radius-md);
91
92
  background: var(--dry-color-bg-raised);
@@ -11,16 +11,22 @@
11
11
  </script>
12
12
 
13
13
  {#if level === 1}
14
+ <!-- dryui-allow raw-heading: Timeline.Title owns its semantic heading element while applying timeline-specific title styling. -->
14
15
  <h1 data-part="title" data-level={level} class={className} {...rest}>{@render children()}</h1>
15
16
  {:else if level === 2}
17
+ <!-- dryui-allow raw-heading: Timeline.Title owns its semantic heading element while applying timeline-specific title styling. -->
16
18
  <h2 data-part="title" data-level={level} class={className} {...rest}>{@render children()}</h2>
17
19
  {:else if level === 3}
20
+ <!-- dryui-allow raw-heading: Timeline.Title owns its semantic heading element while applying timeline-specific title styling. -->
18
21
  <h3 data-part="title" data-level={level} class={className} {...rest}>{@render children()}</h3>
19
22
  {:else if level === 4}
23
+ <!-- dryui-allow raw-heading: Timeline.Title owns its semantic heading element while applying timeline-specific title styling. -->
20
24
  <h4 data-part="title" data-level={level} class={className} {...rest}>{@render children()}</h4>
21
25
  {:else if level === 5}
26
+ <!-- dryui-allow raw-heading: Timeline.Title owns its semantic heading element while applying timeline-specific title styling. -->
22
27
  <h5 data-part="title" data-level={level} class={className} {...rest}>{@render children()}</h5>
23
28
  {:else}
29
+ <!-- dryui-allow raw-heading: Timeline.Title owns its semantic heading element while applying timeline-specific title styling. -->
24
30
  <h6 data-part="title" data-level={level} class={className} {...rest}>{@render children()}</h6>
25
31
  {/if}
26
32
 
@@ -108,6 +108,7 @@
108
108
  }
109
109
  }
110
110
 
111
+ /* dryui-allow ad-hoc-enter-keyframe: toast predates motion wrappers and keeps reduced-motion handling in this file. */
111
112
  @keyframes slideIn {
112
113
  from {
113
114
  opacity: 0;
@@ -89,6 +89,7 @@
89
89
  gap: var(--dry-space-1);
90
90
  padding: var(--dry-space-1);
91
91
  background: var(--dry-color-bg-overlay);
92
+ /* dryui-allow solid-border-on-raised: toolbar chrome needs a visible control group boundary. */
92
93
  border: 1px solid var(--dry-color-stroke-weak);
93
94
  border-radius: var(--dry-radius-lg);
94
95
  }
@@ -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 { setTooltipCtx } from './context.svelte.js';
5
4
 
6
5
  interface Props {
@@ -13,8 +12,9 @@
13
12
 
14
13
  let open = $state(false);
15
14
 
16
- const triggerId = generateFormId('tooltip-trigger');
17
- const contentId = generateFormId('tooltip-content');
15
+ const uid = $props.id();
16
+ const triggerId = `tooltip-trigger-${uid}`;
17
+ const contentId = `tooltip-content-${uid}`;
18
18
 
19
19
  let openTimeout: ReturnType<typeof setTimeout>;
20
20
  let closeTimeout: ReturnType<typeof setTimeout>;
@@ -101,6 +101,7 @@
101
101
  {/if}
102
102
  {:else}
103
103
  {#if poster}
104
+ <!-- dryui-allow raw-img: VideoEmbed owns the poster media element inside the play overlay surface. -->
104
105
  <img data-part="poster" src={poster} alt="" />
105
106
  {/if}
106
107
  <span class="play-btn-slot">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dryui/ui",
3
- "version": "1.9.0",
3
+ "version": "2.0.0",
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",
@@ -817,13 +817,13 @@
817
817
  "check:publish-hygiene": "bun run check:publint && bun run check:attw"
818
818
  },
819
819
  "dependencies": {
820
- "@dryui/primitives": "^1.9.0"
820
+ "@dryui/primitives": "^2.0.0"
821
821
  },
822
822
  "peerDependencies": {
823
823
  "svelte": "^5.55.4"
824
824
  },
825
825
  "devDependencies": {
826
- "@dryui/lint": "^0.6.0",
826
+ "@dryui/lint": "^0.7.0",
827
827
  "svelte": "^5.55.4",
828
828
  "@sveltejs/package": "^2.5.7",
829
829
  "svelte-check": "^4.4.6",