@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.
- package/dist/alert-dialog/alert-dialog-root.svelte +2 -2
- package/dist/avatar/avatar.svelte +2 -0
- package/dist/badge/badge.svelte +34 -32
- package/dist/button/button.svelte +67 -38
- package/dist/chromatic-aberration/chromatic-aberration.svelte +2 -2
- package/dist/collapsible/collapsible-root.svelte +3 -2
- package/dist/collapsible/context.svelte.d.ts +1 -2
- package/dist/collapsible/context.svelte.js +1 -2
- package/dist/color-picker/color-picker-channel-input.svelte +1 -0
- package/dist/combobox/combobox-content.svelte +1 -0
- package/dist/combobox/combobox-input-root.svelte +3 -3
- package/dist/command-palette/command-palette-dialog-root.svelte +3 -3
- package/dist/command-palette/command-palette-item.svelte +3 -3
- package/dist/context-menu/context-menu-root.svelte +2 -0
- package/dist/date-field/date-field-segment.svelte +26 -4
- package/dist/date-picker/datepicker-input-root.svelte +2 -0
- package/dist/date-range-picker/date-range-picker-root.svelte +2 -0
- package/dist/dialog/dialog-root.svelte +2 -2
- package/dist/drag-and-drop/drag-and-drop-root.svelte +34 -3
- package/dist/drawer/drawer-root.svelte +2 -2
- package/dist/dropdown-menu/dropdown-menu-root.svelte +2 -0
- package/dist/field/field-root.svelte +3 -2
- package/dist/file-select/file-select-root.svelte +1 -0
- package/dist/file-upload/file-upload-item.svelte +1 -0
- package/dist/heading/heading.svelte +6 -2
- package/dist/image/image.svelte +2 -0
- package/dist/internal/date-family-controller.svelte.d.ts +2 -1
- package/dist/internal/date-family-controller.svelte.js +4 -4
- package/dist/internal/menu-root-state.svelte.d.ts +1 -0
- package/dist/internal/menu-root-state.svelte.js +2 -3
- package/dist/internal/modal-content.svelte +2 -0
- package/dist/internal/picker-popover-content.svelte +1 -0
- package/dist/link-preview/link-preview-root.svelte +3 -3
- package/dist/logo-mark/logo-mark.svelte +1 -0
- package/dist/markdown-renderer/markdown-renderer.svelte +9 -3
- package/dist/markdown-renderer/markdown-renderer.svelte.d.ts +1 -1
- package/dist/mega-menu/mega-menu-item.svelte +4 -4
- package/dist/menubar/menubar-content.svelte +1 -0
- package/dist/menubar/menubar-menu.svelte +2 -2
- package/dist/menubar/menubar-root.svelte +1 -0
- package/dist/multi-select-combobox/multi-select-combobox-content.svelte +1 -0
- package/dist/multi-select-combobox/multi-select-combobox-item.svelte +2 -2
- package/dist/multi-select-combobox/multi-select-combobox-root-input.svelte +4 -3
- package/dist/navigation-menu/context.svelte.d.ts +0 -2
- package/dist/navigation-menu/context.svelte.js +1 -2
- package/dist/navigation-menu/navigation-menu-item.svelte +5 -8
- package/dist/notification-center/notification-center-root.svelte +3 -3
- package/dist/popover/popover-root.svelte +3 -3
- package/dist/radio-group/radio-group.svelte +2 -2
- package/dist/rich-text-editor/rich-text-editor-content.svelte +14 -6
- package/dist/rich-text-editor/rich-text-editor-root.svelte +19 -9
- package/dist/rich-text-editor/rich-text-editor-root.svelte.d.ts +1 -0
- package/dist/rich-text-editor/rich-text-editor-toolbar-button-input.svelte +1 -0
- package/dist/select/select-root-input.svelte +3 -3
- package/dist/tags-input/tags-input-root.svelte +1 -0
- package/dist/timeline/timeline-title.svelte +6 -0
- package/dist/toast/toast-root.svelte +1 -0
- package/dist/toolbar/toolbar-root.svelte +1 -0
- package/dist/tooltip/tooltip-root.svelte +3 -3
- package/dist/video-embed/video-embed-button.svelte +1 -0
- package/package.json +3 -3
- package/skills/dryui/SKILL.md +4 -1
- package/skills/dryui/rules/composition.md +1 -1
- package/skills/dryui/rules/theming.md +2 -2
|
@@ -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
|
|
14
|
-
const
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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
|
|
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 =
|
|
146
|
-
a.textContent =
|
|
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,
|
|
165
|
+
document.execCommand('createLink', false, safeUrl);
|
|
157
166
|
if (ctx.contentEl) {
|
|
158
|
-
const links = ctx.contentEl.querySelectorAll('a[href="' + CSS.escape(
|
|
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
|
|
39
|
-
const
|
|
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
|
|
|
@@ -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
|
|
17
|
-
const
|
|
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>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dryui/ui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.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",
|
|
@@ -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": "^
|
|
820
|
+
"@dryui/primitives": "^2.0.1"
|
|
821
821
|
},
|
|
822
822
|
"peerDependencies": {
|
|
823
823
|
"svelte": "^5.55.4"
|
|
824
824
|
},
|
|
825
825
|
"devDependencies": {
|
|
826
|
-
"@dryui/lint": "^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",
|
package/skills/dryui/SKILL.md
CHANGED
|
@@ -228,7 +228,7 @@ Use these to look up APIs, discover components, plan setup, and validate code.
|
|
|
228
228
|
|
|
229
229
|
1. `dryui info <Component>` or `dryui compose "<query>"` before writing so you confirm kind, required parts, bindables, and canonical usage. If MCP is available, `ask --scope component` and `ask --scope recipe` are the equivalent surface.
|
|
230
230
|
2. Build the route or component with raw CSS grid, `Container` for constrained width, and `@container` for responsive layout.
|
|
231
|
-
3. Run `check`
|
|
231
|
+
3. Run `dryui check [path]` or MCP `check` after implementation to catch composition drift, layout violations, and accessibility regressions. Use `dryui check --visual <url>` or MCP `check` with `visualUrl` when rendered pixels need review.
|
|
232
232
|
4. Never guess component shape from memory. DryUI is intentionally strict, and the lookup cost is lower than rework.
|
|
233
233
|
|
|
234
234
|
### CLI (default entry point)
|
|
@@ -243,6 +243,8 @@ dryui info <component> # Look up component API
|
|
|
243
243
|
dryui compose "date input" # Composition guidance
|
|
244
244
|
dryui detect [path] # Check project setup
|
|
245
245
|
dryui install [path] # Print install plan
|
|
246
|
+
dryui check [path] # Validate file, theme, directory, or workspace
|
|
247
|
+
dryui check --visual <url> # Screenshot a URL and critique rendered polish
|
|
246
248
|
dryui list # List components
|
|
247
249
|
dryui tokens --category color # Browse design tokens
|
|
248
250
|
dryui ambient # SessionStart context
|
|
@@ -260,6 +262,7 @@ Without a global install, prefix any command with `bunx @dryui/cli …` or `npx
|
|
|
260
262
|
| Lookup & composition | `ask --scope component`, `ask --scope recipe`, `ask --scope list` |
|
|
261
263
|
| Validation | `check <file.svelte>`, `check <theme.css>` |
|
|
262
264
|
| Audit | `check`, `check <directory>` |
|
|
265
|
+
| Rendered UI | `check` with `visualUrl`, or direct `check-vision` |
|
|
263
266
|
|
|
264
267
|
Categories: action, input, form, layout, navigation, overlay, display, feedback, interaction, utility
|
|
265
268
|
|
|
@@ -457,7 +457,7 @@ DryUI is a presentation and accessibility system, not a workflow engine. For dep
|
|
|
457
457
|
- Normalize route/session state in script before rendering DryUI inputs.
|
|
458
458
|
- Reset dependent `Select.Root` values when their parent choice changes; do not rely on stale child state surviving domain changes.
|
|
459
459
|
- Use raw CSS grid to lay out planner sections, and keep orchestration logic in route-level stores or derived state.
|
|
460
|
-
- Run `dryui info <Component>` or `dryui compose "<pattern>"` before introducing a new field shape, then
|
|
460
|
+
- Run `dryui info <Component>` or `dryui compose "<pattern>"` before introducing a new field shape, then run `dryui check [path]` or MCP `check` after the flow is wired. Use `dryui check --visual <url>` or MCP `check` with `visualUrl` when the rendered page needs visual review.
|
|
461
461
|
|
|
462
462
|
```svelte
|
|
463
463
|
<script lang="ts">
|
|
@@ -288,10 +288,10 @@ Ensure sufficient contrast between text and background.
|
|
|
288
288
|
|
|
289
289
|
## Validating Theme CSS
|
|
290
290
|
|
|
291
|
-
Use `check <theme.css>`
|
|
291
|
+
Use `dryui check <theme.css>` or MCP `check <theme.css>` to catch theme issues. Without either surface, validate by rebuilding the app with `@dryui/lint` wired and checking the resulting diagnostics:
|
|
292
292
|
|
|
293
293
|
```bash
|
|
294
|
-
check src/styles/global.css
|
|
294
|
+
dryui check src/styles/global.css
|
|
295
295
|
```
|
|
296
296
|
|
|
297
297
|
Common diagnostic codes:
|