@dryui/feedback 0.0.2
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/components/annotation-marker.svelte +163 -0
- package/dist/components/annotation-marker.svelte.d.ts +11 -0
- package/dist/components/annotation-popup.svelte +669 -0
- package/dist/components/annotation-popup.svelte.d.ts +42 -0
- package/dist/components/highlight-overlay.svelte +48 -0
- package/dist/components/highlight-overlay.svelte.d.ts +8 -0
- package/dist/components/settings-panel.svelte +446 -0
- package/dist/components/settings-panel.svelte.d.ts +24 -0
- package/dist/components/toolbar.svelte +1111 -0
- package/dist/components/toolbar.svelte.d.ts +46 -0
- package/dist/constants.d.ts +9 -0
- package/dist/constants.js +37 -0
- package/dist/feedback.svelte +2879 -0
- package/dist/feedback.svelte.d.ts +4 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +7 -0
- package/dist/layout-mode/catalog.d.ts +16 -0
- package/dist/layout-mode/catalog.js +81 -0
- package/dist/layout-mode/component-actions.svelte +84 -0
- package/dist/layout-mode/component-actions.svelte.d.ts +18 -0
- package/dist/layout-mode/component-picker.svelte +73 -0
- package/dist/layout-mode/component-picker.svelte.d.ts +10 -0
- package/dist/layout-mode/design-mode.svelte +1115 -0
- package/dist/layout-mode/design-mode.svelte.d.ts +24 -0
- package/dist/layout-mode/design-palette.svelte +396 -0
- package/dist/layout-mode/design-palette.svelte.d.ts +20 -0
- package/dist/layout-mode/element-heuristics.d.ts +5 -0
- package/dist/layout-mode/element-heuristics.js +51 -0
- package/dist/layout-mode/freeze.d.ts +6 -0
- package/dist/layout-mode/freeze.js +163 -0
- package/dist/layout-mode/generated-library.d.ts +940 -0
- package/dist/layout-mode/generated-library.js +1445 -0
- package/dist/layout-mode/geometry.d.ts +38 -0
- package/dist/layout-mode/geometry.js +133 -0
- package/dist/layout-mode/history.d.ts +10 -0
- package/dist/layout-mode/history.js +45 -0
- package/dist/layout-mode/index.d.ts +23 -0
- package/dist/layout-mode/index.js +18 -0
- package/dist/layout-mode/live-mount.d.ts +20 -0
- package/dist/layout-mode/live-mount.js +70 -0
- package/dist/layout-mode/output.d.ts +26 -0
- package/dist/layout-mode/output.js +550 -0
- package/dist/layout-mode/placement-skeleton.d.ts +9 -0
- package/dist/layout-mode/placement-skeleton.js +535 -0
- package/dist/layout-mode/rearrange-overlay.svelte +1293 -0
- package/dist/layout-mode/rearrange-overlay.svelte.d.ts +18 -0
- package/dist/layout-mode/responsive-bar.svelte +39 -0
- package/dist/layout-mode/responsive-bar.svelte.d.ts +8 -0
- package/dist/layout-mode/route-creator.svelte +70 -0
- package/dist/layout-mode/route-creator.svelte.d.ts +8 -0
- package/dist/layout-mode/section-detection.d.ts +6 -0
- package/dist/layout-mode/section-detection.js +214 -0
- package/dist/layout-mode/spatial.d.ts +42 -0
- package/dist/layout-mode/spatial.js +156 -0
- package/dist/layout-mode/types.d.ts +144 -0
- package/dist/layout-mode/types.js +84 -0
- package/dist/types.d.ts +157 -0
- package/dist/types.js +1 -0
- package/dist/utils/dryui-detection.d.ts +1 -0
- package/dist/utils/dryui-detection.js +219 -0
- package/dist/utils/element-id.d.ts +12 -0
- package/dist/utils/element-id.js +333 -0
- package/dist/utils/freeze.d.ts +7 -0
- package/dist/utils/freeze.js +168 -0
- package/dist/utils/output.d.ts +15 -0
- package/dist/utils/output.js +245 -0
- package/dist/utils/selection.d.ts +22 -0
- package/dist/utils/selection.js +58 -0
- package/dist/utils/shadow-dom.d.ts +4 -0
- package/dist/utils/shadow-dom.js +39 -0
- package/dist/utils/storage.d.ts +30 -0
- package/dist/utils/storage.js +206 -0
- package/dist/utils/svelte-detection.d.ts +8 -0
- package/dist/utils/svelte-detection.js +86 -0
- package/dist/utils/svelte-meta.d.ts +6 -0
- package/dist/utils/svelte-meta.js +69 -0
- package/dist/utils/sync.d.ts +18 -0
- package/dist/utils/sync.js +62 -0
- package/package.json +65 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type CanvasWidth, type DesignPlacement, type LayoutModeComponentType, type Rect } from './types.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
placements: DesignPlacement[];
|
|
4
|
+
activeComponent: LayoutModeComponentType | null;
|
|
5
|
+
wireframe?: boolean;
|
|
6
|
+
passthrough?: boolean;
|
|
7
|
+
extraSnapRects?: Rect[];
|
|
8
|
+
deselectSignal?: number;
|
|
9
|
+
clearSignal?: number;
|
|
10
|
+
exiting?: boolean;
|
|
11
|
+
canvasWidth?: CanvasWidth;
|
|
12
|
+
class?: string;
|
|
13
|
+
onChange?: (placements: DesignPlacement[]) => void;
|
|
14
|
+
onActiveComponentChange?: (type: LayoutModeComponentType | null) => void;
|
|
15
|
+
onInteractionChange?: (active: boolean) => void;
|
|
16
|
+
onSelectionChange?: (selectedIds: Set<string>, isShift: boolean) => void;
|
|
17
|
+
onDragMove?: (dx: number, dy: number) => void;
|
|
18
|
+
onDragEnd?: (dx: number, dy: number, committed: boolean) => void;
|
|
19
|
+
onCanvasWidthChange?: (width: CanvasWidth) => void;
|
|
20
|
+
onHistoryPush?: () => void;
|
|
21
|
+
}
|
|
22
|
+
declare const DesignMode: import("svelte").Component<Props, {}, "activeComponent">;
|
|
23
|
+
type DesignMode = ReturnType<typeof DesignMode>;
|
|
24
|
+
export default DesignMode;
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Accordion, Badge, Button, Card, EmptyState, Flex, Input, ScrollArea, Stack, Text, Textarea } from '@dryui/ui';
|
|
3
|
+
import { renderPlacementSkeleton } from './placement-skeleton.js';
|
|
4
|
+
import { COMPONENT_REGISTRY, type CanvasPurpose, type LayoutModeComponentType } from './types.js';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
open?: boolean;
|
|
8
|
+
value?: LayoutModeComponentType | null;
|
|
9
|
+
purpose?: CanvasPurpose;
|
|
10
|
+
wireframePrompt?: string;
|
|
11
|
+
wireframe?: boolean;
|
|
12
|
+
placementCount?: number;
|
|
13
|
+
sectionCount?: number;
|
|
14
|
+
onSelect?: (type: LayoutModeComponentType) => void;
|
|
15
|
+
onDetectSections?: () => void;
|
|
16
|
+
onPurposeChange?: (purpose: CanvasPurpose) => void;
|
|
17
|
+
onWireframePromptChange?: (value: string) => void;
|
|
18
|
+
onDragStart?: (type: LayoutModeComponentType, event: MouseEvent) => void;
|
|
19
|
+
onClear?: () => void;
|
|
20
|
+
class?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let {
|
|
24
|
+
open = $bindable(false),
|
|
25
|
+
value = $bindable(null),
|
|
26
|
+
purpose = $bindable('replace-current'),
|
|
27
|
+
wireframePrompt = $bindable(''),
|
|
28
|
+
wireframe = false,
|
|
29
|
+
placementCount = 0,
|
|
30
|
+
sectionCount = 0,
|
|
31
|
+
onSelect,
|
|
32
|
+
onDetectSections,
|
|
33
|
+
onPurposeChange,
|
|
34
|
+
onWireframePromptChange,
|
|
35
|
+
onDragStart,
|
|
36
|
+
onClear,
|
|
37
|
+
class: className,
|
|
38
|
+
}: Props = $props();
|
|
39
|
+
|
|
40
|
+
const scrollId = 'dryui-feedback-layout-palette-scroll';
|
|
41
|
+
|
|
42
|
+
let fadeTop = $state(false);
|
|
43
|
+
let fadeBottom = $state(false);
|
|
44
|
+
let searchQuery = $state('');
|
|
45
|
+
let expandedSections = $state<string[]>(COMPONENT_REGISTRY.map((section) => section.section));
|
|
46
|
+
|
|
47
|
+
const blankCanvas = $derived(purpose === 'new-page' || wireframe);
|
|
48
|
+
const totalCount = $derived(placementCount + sectionCount);
|
|
49
|
+
const countLabel = $derived.by(() => {
|
|
50
|
+
if (totalCount === 0) {
|
|
51
|
+
return blankCanvas ? 'No wireframe components yet' : 'No layout changes yet';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (blankCanvas) {
|
|
55
|
+
return `${totalCount} ${totalCount === 1 ? 'Component' : 'Components'}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return `${totalCount} ${totalCount === 1 ? 'Change' : 'Changes'}`;
|
|
59
|
+
});
|
|
60
|
+
const countSummary = $derived.by(() => {
|
|
61
|
+
const parts: string[] = [];
|
|
62
|
+
if (placementCount > 0) {
|
|
63
|
+
parts.push(`${placementCount} placed`);
|
|
64
|
+
}
|
|
65
|
+
if (sectionCount > 0) {
|
|
66
|
+
parts.push(`${sectionCount} captured`);
|
|
67
|
+
}
|
|
68
|
+
return parts.join(' · ');
|
|
69
|
+
});
|
|
70
|
+
const filteredRegistry = $derived.by(() => {
|
|
71
|
+
const query = searchQuery.trim().toLowerCase();
|
|
72
|
+
if (!query) {
|
|
73
|
+
return COMPONENT_REGISTRY;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return COMPONENT_REGISTRY.map((section) => ({
|
|
77
|
+
...section,
|
|
78
|
+
items: section.items.filter((item) => {
|
|
79
|
+
const haystack = [
|
|
80
|
+
item.label,
|
|
81
|
+
item.type,
|
|
82
|
+
item.sourceName,
|
|
83
|
+
item.description,
|
|
84
|
+
item.guidance,
|
|
85
|
+
item.structure,
|
|
86
|
+
section.section,
|
|
87
|
+
...item.tags,
|
|
88
|
+
]
|
|
89
|
+
.filter(Boolean)
|
|
90
|
+
.join(' ')
|
|
91
|
+
.toLowerCase();
|
|
92
|
+
return haystack.includes(query);
|
|
93
|
+
}),
|
|
94
|
+
})).filter((section) => section.items.length > 0);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
function paletteShellClass(): string {
|
|
98
|
+
return ['layout-palette-shell', className].filter(Boolean).join(' ');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function selectComponent(type: LayoutModeComponentType) {
|
|
102
|
+
value = type;
|
|
103
|
+
onSelect?.(type);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function selectPurpose(next: CanvasPurpose) {
|
|
107
|
+
purpose = next;
|
|
108
|
+
onPurposeChange?.(next);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function toggleWireframe() {
|
|
112
|
+
selectPurpose(blankCanvas ? 'replace-current' : 'new-page');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function updatePrompt(event: Event) {
|
|
116
|
+
const next = (event.currentTarget as HTMLTextAreaElement).value;
|
|
117
|
+
wireframePrompt = next;
|
|
118
|
+
onWireframePromptChange?.(next);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function resolveScrollElement(target?: EventTarget | null): HTMLElement | null {
|
|
122
|
+
if (target instanceof HTMLElement) {
|
|
123
|
+
return target;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return document.getElementById(scrollId);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function syncScrollFade(target?: EventTarget | null) {
|
|
130
|
+
const element = resolveScrollElement(target);
|
|
131
|
+
if (!element) {
|
|
132
|
+
fadeTop = false;
|
|
133
|
+
fadeBottom = false;
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
fadeTop = element.scrollTop > 4;
|
|
138
|
+
fadeBottom = element.scrollTop + element.clientHeight < element.scrollHeight - 4;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function queueScrollFadeSync() {
|
|
142
|
+
requestAnimationFrame(() => syncScrollFade());
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function syncPaletteChrome(_key: string) {
|
|
146
|
+
return () => {
|
|
147
|
+
queueScrollFadeSync();
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function applyPreview(node: HTMLElement, type: LayoutModeComponentType) {
|
|
152
|
+
node.innerHTML = renderPlacementSkeleton({
|
|
153
|
+
type,
|
|
154
|
+
width: 24,
|
|
155
|
+
height: 18,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function mountPreview(type: LayoutModeComponentType) {
|
|
160
|
+
return (node: HTMLElement) => {
|
|
161
|
+
applyPreview(node, type);
|
|
162
|
+
|
|
163
|
+
return () => {
|
|
164
|
+
node.innerHTML = '';
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function handleSearchInput() {
|
|
170
|
+
queueScrollFadeSync();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function clearSearch() {
|
|
174
|
+
searchQuery = '';
|
|
175
|
+
queueScrollFadeSync();
|
|
176
|
+
}
|
|
177
|
+
</script>
|
|
178
|
+
|
|
179
|
+
{#if open}
|
|
180
|
+
<div
|
|
181
|
+
class={paletteShellClass()}
|
|
182
|
+
data-layout-mode-palette
|
|
183
|
+
data-dryui-feedback
|
|
184
|
+
>
|
|
185
|
+
<Card.Root
|
|
186
|
+
variant="elevated"
|
|
187
|
+
size="sm"
|
|
188
|
+
style={`inline-size:min(22rem, calc(100vw - 1.5rem)); max-inline-size:100%; max-block-size:min(72vh, 44rem); overflow:hidden; border-color:${blankCanvas ? 'color-mix(in srgb, #f97316 24%, var(--dry-card-border, rgba(15, 23, 42, 0.08)))' : 'var(--dry-card-border, rgba(15, 23, 42, 0.08))'}; box-shadow:0 28px 64px color-mix(in srgb, var(--dry-color-bg-overlay, #0f172a) 14%, transparent);`}
|
|
189
|
+
>
|
|
190
|
+
<Card.Header style="display:flex; flex-direction:column; gap:0.75rem; padding:0.9rem 1rem 0.75rem;">
|
|
191
|
+
<Stack gap="sm">
|
|
192
|
+
<Flex justify="between" align="start" gap="sm" wrap="wrap">
|
|
193
|
+
<Stack gap="sm">
|
|
194
|
+
<Text as="div" size="md">Layout Mode</Text>
|
|
195
|
+
<Text as="div" size="sm" color="secondary">
|
|
196
|
+
Search the placement library, sketch a new wireframe, or capture the current page structure before you hand off layout feedback to an agent.
|
|
197
|
+
</Text>
|
|
198
|
+
</Stack>
|
|
199
|
+
<Badge variant="soft" color={blankCanvas ? 'orange' : 'blue'}>
|
|
200
|
+
{blankCanvas ? 'Wireframe' : 'Live page'}
|
|
201
|
+
</Badge>
|
|
202
|
+
</Flex>
|
|
203
|
+
|
|
204
|
+
<Input
|
|
205
|
+
bind:value={searchQuery}
|
|
206
|
+
placeholder="Search components"
|
|
207
|
+
aria-label="Search layout components"
|
|
208
|
+
oninput={handleSearchInput}
|
|
209
|
+
/>
|
|
210
|
+
</Stack>
|
|
211
|
+
</Card.Header>
|
|
212
|
+
|
|
213
|
+
<Card.Content style="display:flex; flex-direction:column; gap:0.75rem; padding:0 0 0.75rem;">
|
|
214
|
+
<div style="padding-inline:1rem;">
|
|
215
|
+
<Button
|
|
216
|
+
data-layout-wireframe-toggle
|
|
217
|
+
variant={blankCanvas ? 'solid' : 'outline'}
|
|
218
|
+
size="sm"
|
|
219
|
+
onclick={toggleWireframe}
|
|
220
|
+
style={`inline-size:100%; justify-content:flex-start; gap:0.55rem; ${blankCanvas ? 'background:#f97316; border-color:#f97316; color:white;' : 'border-style:dashed;'}`}
|
|
221
|
+
>
|
|
222
|
+
<span aria-hidden="true" style="display:flex; inline-size:0.875rem; block-size:0.875rem; align-items:center; justify-content:center; flex-shrink:0;">
|
|
223
|
+
<svg viewBox="0 0 14 14" width="14" height="14" fill="none">
|
|
224
|
+
<rect x="1" y="1" width="12" height="12" rx="2" stroke="currentColor" stroke-width="1"></rect>
|
|
225
|
+
<circle cx="4.5" cy="4.5" r="0.8" fill="currentColor" opacity=".6"></circle>
|
|
226
|
+
<circle cx="7" cy="4.5" r="0.8" fill="currentColor" opacity=".6"></circle>
|
|
227
|
+
<circle cx="9.5" cy="4.5" r="0.8" fill="currentColor" opacity=".6"></circle>
|
|
228
|
+
<circle cx="4.5" cy="7" r="0.8" fill="currentColor" opacity=".6"></circle>
|
|
229
|
+
<circle cx="7" cy="7" r="0.8" fill="currentColor" opacity=".6"></circle>
|
|
230
|
+
<circle cx="9.5" cy="7" r="0.8" fill="currentColor" opacity=".6"></circle>
|
|
231
|
+
<circle cx="4.5" cy="9.5" r="0.8" fill="currentColor" opacity=".6"></circle>
|
|
232
|
+
<circle cx="7" cy="9.5" r="0.8" fill="currentColor" opacity=".6"></circle>
|
|
233
|
+
<circle cx="9.5" cy="9.5" r="0.8" fill="currentColor" opacity=".6"></circle>
|
|
234
|
+
</svg>
|
|
235
|
+
</span>
|
|
236
|
+
<span>{blankCanvas ? 'Return to current page' : 'Wireframe new page'}</span>
|
|
237
|
+
</Button>
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
<div
|
|
241
|
+
data-layout-wireframe-prompt-wrap
|
|
242
|
+
style={`display:grid; grid-template-rows:${blankCanvas ? '1fr' : '0fr'}; opacity:${blankCanvas ? 1 : 0}; transition:grid-template-rows 160ms ease, opacity 120ms ease;`}
|
|
243
|
+
>
|
|
244
|
+
<div style="overflow:hidden; padding-inline:1rem;">
|
|
245
|
+
<Textarea
|
|
246
|
+
bind:value={wireframePrompt}
|
|
247
|
+
placeholder="Describe this page to provide additional context for your agent."
|
|
248
|
+
style="inline-size:100%; margin-block-start:0.125rem; min-block-size:5rem;"
|
|
249
|
+
oninput={updatePrompt}
|
|
250
|
+
/>
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
|
|
254
|
+
<div
|
|
255
|
+
style="position:relative; min-block-size:0; padding-inline:0.5rem;"
|
|
256
|
+
{@attach syncPaletteChrome(`${open}:${blankCanvas}:${placementCount}:${sectionCount}:${filteredRegistry.length}`)}
|
|
257
|
+
>
|
|
258
|
+
{#if fadeTop}
|
|
259
|
+
<div
|
|
260
|
+
aria-hidden="true"
|
|
261
|
+
style="pointer-events:none; position:absolute; inset-inline:0.5rem; inset-block-start:0; block-size:1rem; z-index:2; background:linear-gradient(180deg, color-mix(in srgb, var(--dry-card-bg, white) 96%, transparent), transparent);"
|
|
262
|
+
></div>
|
|
263
|
+
{/if}
|
|
264
|
+
{#if fadeBottom}
|
|
265
|
+
<div
|
|
266
|
+
aria-hidden="true"
|
|
267
|
+
style="pointer-events:none; position:absolute; inset-inline:0.5rem; inset-block-end:0; block-size:1rem; z-index:2; background:linear-gradient(0deg, color-mix(in srgb, var(--dry-card-bg, white) 96%, transparent), transparent);"
|
|
268
|
+
></div>
|
|
269
|
+
{/if}
|
|
270
|
+
|
|
271
|
+
<ScrollArea
|
|
272
|
+
id={scrollId}
|
|
273
|
+
orientation="vertical"
|
|
274
|
+
onscroll={(event) => syncScrollFade(event.currentTarget)}
|
|
275
|
+
style="display:flex; flex-direction:column; gap:0.9rem; max-block-size:min(44vh, 27rem); padding:0 0.5rem 0.125rem;"
|
|
276
|
+
>
|
|
277
|
+
{#if filteredRegistry.length > 0}
|
|
278
|
+
<Accordion.Root type="multiple" bind:value={expandedSections}>
|
|
279
|
+
{#each filteredRegistry as section (section.section)}
|
|
280
|
+
<Accordion.Item value={section.section}>
|
|
281
|
+
<Accordion.Trigger>
|
|
282
|
+
<Flex justify="between" align="center" gap="sm" style="inline-size:100%; min-inline-size:0;">
|
|
283
|
+
<Text as="span" size="sm" weight="medium">{section.section}</Text>
|
|
284
|
+
<Text as="span" size="sm" color="secondary">{section.items.length}</Text>
|
|
285
|
+
</Flex>
|
|
286
|
+
</Accordion.Trigger>
|
|
287
|
+
<Accordion.Content>
|
|
288
|
+
<Stack gap="sm" style="padding-block-start:0.35rem;">
|
|
289
|
+
{#each section.items as item (item.type)}
|
|
290
|
+
<Button
|
|
291
|
+
data-layout-palette-item={item.type}
|
|
292
|
+
variant={value === item.type ? 'soft' : 'ghost'}
|
|
293
|
+
size="sm"
|
|
294
|
+
aria-pressed={value === item.type}
|
|
295
|
+
style={`inline-size:100%; justify-content:flex-start; padding-inline:0.625rem; ${value === item.type ? `border-color:${blankCanvas ? '#f97316' : 'var(--dry-color-fill-brand, #7c3aed)'};` : ''}`}
|
|
296
|
+
onmousedown={(event) => {
|
|
297
|
+
if (event.button !== 0) return;
|
|
298
|
+
onDragStart?.(item.type, event);
|
|
299
|
+
}}
|
|
300
|
+
onclick={() => selectComponent(item.type)}
|
|
301
|
+
>
|
|
302
|
+
<span style="display:flex; align-items:center; gap:0.625rem; inline-size:100%; min-inline-size:0;">
|
|
303
|
+
<span
|
|
304
|
+
data-layout-palette-item-preview
|
|
305
|
+
aria-hidden="true"
|
|
306
|
+
style={`display:block; inline-size:1.5rem; block-size:1.125rem; border-radius:0.3rem; overflow:hidden; flex-shrink:0; background:${blankCanvas ? 'rgba(249, 115, 22, 0.08)' : 'color-mix(in srgb, var(--dry-color-fill-brand, #7c3aed) 6%, transparent)'};`}
|
|
307
|
+
>
|
|
308
|
+
<span {@attach mountPreview(item.type)}></span>
|
|
309
|
+
</span>
|
|
310
|
+
<span style="display:flex; flex-direction:column; align-items:flex-start; gap:0.15rem; inline-size:100%; min-inline-size:0;">
|
|
311
|
+
<span style="display:flex; align-items:center; justify-content:space-between; gap:0.5rem; inline-size:100%; min-inline-size:0;">
|
|
312
|
+
<Text as="span" size="sm" style="min-inline-size:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">
|
|
313
|
+
{item.label}
|
|
314
|
+
</Text>
|
|
315
|
+
<span style="flex-shrink:0; font-size:0.6875rem; opacity:0.58;">
|
|
316
|
+
{item.width}x{item.height}
|
|
317
|
+
</span>
|
|
318
|
+
</span>
|
|
319
|
+
<span style="display:flex; align-items:center; gap:0.4rem; min-inline-size:0; inline-size:100%;">
|
|
320
|
+
<Badge variant="soft" color={item.sourceKind === 'block' ? 'warning' : 'info'}>
|
|
321
|
+
{item.sourceLabel}
|
|
322
|
+
</Badge>
|
|
323
|
+
<Text
|
|
324
|
+
as="span"
|
|
325
|
+
size="sm"
|
|
326
|
+
color="secondary"
|
|
327
|
+
style="min-inline-size:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;"
|
|
328
|
+
>
|
|
329
|
+
{item.description}
|
|
330
|
+
</Text>
|
|
331
|
+
</span>
|
|
332
|
+
</span>
|
|
333
|
+
</span>
|
|
334
|
+
</Button>
|
|
335
|
+
{/each}
|
|
336
|
+
</Stack>
|
|
337
|
+
</Accordion.Content>
|
|
338
|
+
</Accordion.Item>
|
|
339
|
+
{/each}
|
|
340
|
+
</Accordion.Root>
|
|
341
|
+
{:else}
|
|
342
|
+
<EmptyState.Root style="padding:1rem 0.5rem 1.25rem;">
|
|
343
|
+
<EmptyState.Icon>
|
|
344
|
+
<span aria-hidden="true">?</span>
|
|
345
|
+
</EmptyState.Icon>
|
|
346
|
+
<EmptyState.Title>No components found</EmptyState.Title>
|
|
347
|
+
<EmptyState.Description>
|
|
348
|
+
Try a broader search term or clear the filter to browse the full layout catalog.
|
|
349
|
+
</EmptyState.Description>
|
|
350
|
+
<EmptyState.Action>
|
|
351
|
+
<Button variant="outline" size="sm" onclick={clearSearch}>Clear search</Button>
|
|
352
|
+
</EmptyState.Action>
|
|
353
|
+
</EmptyState.Root>
|
|
354
|
+
{/if}
|
|
355
|
+
</ScrollArea>
|
|
356
|
+
</div>
|
|
357
|
+
</Card.Content>
|
|
358
|
+
|
|
359
|
+
<Card.Footer style="display:flex; align-items:center; justify-content:space-between; gap:0.75rem; padding:0.75rem 1rem 0.9rem;">
|
|
360
|
+
<Stack gap="sm">
|
|
361
|
+
<Text as="div" size="sm">{countLabel}</Text>
|
|
362
|
+
{#if countSummary}
|
|
363
|
+
<Text as="div" size="sm" color="secondary">{countSummary}</Text>
|
|
364
|
+
{/if}
|
|
365
|
+
</Stack>
|
|
366
|
+
|
|
367
|
+
<Flex gap="sm" wrap="wrap" justify="end">
|
|
368
|
+
{#if !blankCanvas && sectionCount === 0 && onDetectSections}
|
|
369
|
+
<Button variant="outline" size="sm" onclick={onDetectSections}>Capture sections</Button>
|
|
370
|
+
{/if}
|
|
371
|
+
{#if totalCount > 0 && onClear}
|
|
372
|
+
<Button variant="ghost" size="sm" onclick={onClear}>Clear</Button>
|
|
373
|
+
{/if}
|
|
374
|
+
</Flex>
|
|
375
|
+
</Card.Footer>
|
|
376
|
+
</Card.Root>
|
|
377
|
+
</div>
|
|
378
|
+
{/if}
|
|
379
|
+
|
|
380
|
+
<style>
|
|
381
|
+
.layout-palette-shell {
|
|
382
|
+
position: fixed;
|
|
383
|
+
inset-inline-end: 1rem;
|
|
384
|
+
inset-block-end: 4.75rem;
|
|
385
|
+
z-index: 100002;
|
|
386
|
+
pointer-events: auto;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
@media (max-width: 720px) {
|
|
390
|
+
.layout-palette-shell {
|
|
391
|
+
inset-inline-start: 0.75rem;
|
|
392
|
+
inset-inline-end: 0.75rem;
|
|
393
|
+
inset-block-end: 5.5rem;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
</style>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type CanvasPurpose, type LayoutModeComponentType } from './types.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
open?: boolean;
|
|
4
|
+
value?: LayoutModeComponentType | null;
|
|
5
|
+
purpose?: CanvasPurpose;
|
|
6
|
+
wireframePrompt?: string;
|
|
7
|
+
wireframe?: boolean;
|
|
8
|
+
placementCount?: number;
|
|
9
|
+
sectionCount?: number;
|
|
10
|
+
onSelect?: (type: LayoutModeComponentType) => void;
|
|
11
|
+
onDetectSections?: () => void;
|
|
12
|
+
onPurposeChange?: (purpose: CanvasPurpose) => void;
|
|
13
|
+
onWireframePromptChange?: (value: string) => void;
|
|
14
|
+
onDragStart?: (type: LayoutModeComponentType, event: MouseEvent) => void;
|
|
15
|
+
onClear?: () => void;
|
|
16
|
+
class?: string;
|
|
17
|
+
}
|
|
18
|
+
declare const DesignPalette: import("svelte").Component<Props, {}, "value" | "open" | "purpose" | "wireframePrompt">;
|
|
19
|
+
type DesignPalette = ReturnType<typeof DesignPalette>;
|
|
20
|
+
export default DesignPalette;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Rect } from './types.js';
|
|
2
|
+
export declare const INTERACTIVE_TAGS: Set<string>;
|
|
3
|
+
export declare function isElementFixed(el: Element): boolean;
|
|
4
|
+
export declare function isSelectableAreaTarget(el: Element): boolean;
|
|
5
|
+
export declare function rectFromElement(el: Element): Rect;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export const INTERACTIVE_TAGS = new Set([
|
|
2
|
+
'a',
|
|
3
|
+
'article',
|
|
4
|
+
'button',
|
|
5
|
+
'img',
|
|
6
|
+
'input',
|
|
7
|
+
'label',
|
|
8
|
+
'option',
|
|
9
|
+
'section',
|
|
10
|
+
'select',
|
|
11
|
+
'summary',
|
|
12
|
+
'svg',
|
|
13
|
+
'textarea',
|
|
14
|
+
]);
|
|
15
|
+
export function isElementFixed(el) {
|
|
16
|
+
let current = el;
|
|
17
|
+
while (current instanceof HTMLElement) {
|
|
18
|
+
const style = window.getComputedStyle(current);
|
|
19
|
+
if (style.position === 'fixed' || style.position === 'sticky')
|
|
20
|
+
return true;
|
|
21
|
+
current = current.parentElement;
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
export function isSelectableAreaTarget(el) {
|
|
26
|
+
if (!(el instanceof HTMLElement || el instanceof SVGElement))
|
|
27
|
+
return false;
|
|
28
|
+
const rect = el.getBoundingClientRect();
|
|
29
|
+
if (rect.width < 4 || rect.height < 4)
|
|
30
|
+
return false;
|
|
31
|
+
if (el instanceof HTMLElement) {
|
|
32
|
+
const style = window.getComputedStyle(el);
|
|
33
|
+
if (style.display === 'none' || style.visibility === 'hidden')
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
const tag = el.tagName.toLowerCase();
|
|
37
|
+
if (INTERACTIVE_TAGS.has(tag))
|
|
38
|
+
return true;
|
|
39
|
+
if (el.children.length === 0)
|
|
40
|
+
return Boolean(el.textContent?.trim());
|
|
41
|
+
return Boolean(el.textContent?.trim()) && el.children.length <= 2;
|
|
42
|
+
}
|
|
43
|
+
export function rectFromElement(el) {
|
|
44
|
+
const rect = el.getBoundingClientRect();
|
|
45
|
+
return {
|
|
46
|
+
x: rect.x,
|
|
47
|
+
y: rect.y,
|
|
48
|
+
width: rect.width,
|
|
49
|
+
height: rect.height,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const originalSetTimeout: typeof setTimeout;
|
|
2
|
+
export declare const originalSetInterval: typeof setInterval;
|
|
3
|
+
export declare const originalRequestAnimationFrame: typeof requestAnimationFrame;
|
|
4
|
+
export declare function freezeLayoutModeAnimations(): void;
|
|
5
|
+
export declare function unfreezeLayoutModeAnimations(): void;
|
|
6
|
+
export declare function isLayoutModeFrozen(): boolean;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
const STATE_KEY = '__dryui_layout_mode_freeze';
|
|
2
|
+
const STYLE_ID = 'dryui-layout-mode-freeze-styles';
|
|
3
|
+
function getState() {
|
|
4
|
+
if (typeof window === 'undefined' ||
|
|
5
|
+
typeof window.setTimeout !== 'function' ||
|
|
6
|
+
typeof window.setInterval !== 'function' ||
|
|
7
|
+
typeof window.requestAnimationFrame !== 'function') {
|
|
8
|
+
return {
|
|
9
|
+
frozen: false,
|
|
10
|
+
installed: true,
|
|
11
|
+
origSetTimeout: setTimeout,
|
|
12
|
+
origSetInterval: setInterval,
|
|
13
|
+
origRAF: () => 0,
|
|
14
|
+
pausedAnimations: [],
|
|
15
|
+
timeoutQueue: [],
|
|
16
|
+
rafQueue: [],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const global = window;
|
|
20
|
+
if (!global[STATE_KEY]) {
|
|
21
|
+
global[STATE_KEY] = {
|
|
22
|
+
frozen: false,
|
|
23
|
+
installed: false,
|
|
24
|
+
origSetTimeout: window.setTimeout.bind(window),
|
|
25
|
+
origSetInterval: window.setInterval.bind(window),
|
|
26
|
+
origRAF: window.requestAnimationFrame.bind(window),
|
|
27
|
+
pausedAnimations: [],
|
|
28
|
+
timeoutQueue: [],
|
|
29
|
+
rafQueue: [],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return global[STATE_KEY];
|
|
33
|
+
}
|
|
34
|
+
const state = getState();
|
|
35
|
+
if (typeof window !== 'undefined' &&
|
|
36
|
+
typeof window.setTimeout === 'function' &&
|
|
37
|
+
typeof window.setInterval === 'function' &&
|
|
38
|
+
typeof window.requestAnimationFrame === 'function' &&
|
|
39
|
+
!state.installed) {
|
|
40
|
+
window.setTimeout = ((handler, timeout, ...args) => {
|
|
41
|
+
if (typeof handler === 'string') {
|
|
42
|
+
return state.origSetTimeout(handler, timeout, ...args);
|
|
43
|
+
}
|
|
44
|
+
return state.origSetTimeout((...invokeArgs) => {
|
|
45
|
+
if (state.frozen) {
|
|
46
|
+
state.timeoutQueue.push(() => handler(...invokeArgs));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
handler(...invokeArgs);
|
|
50
|
+
}, timeout, ...args);
|
|
51
|
+
});
|
|
52
|
+
window.setInterval = ((handler, timeout, ...args) => {
|
|
53
|
+
if (typeof handler === 'string') {
|
|
54
|
+
return state.origSetInterval(handler, timeout, ...args);
|
|
55
|
+
}
|
|
56
|
+
return state.origSetInterval((...invokeArgs) => {
|
|
57
|
+
if (!state.frozen) {
|
|
58
|
+
handler(...invokeArgs);
|
|
59
|
+
}
|
|
60
|
+
}, timeout, ...args);
|
|
61
|
+
});
|
|
62
|
+
window.requestAnimationFrame = ((callback) => {
|
|
63
|
+
return state.origRAF((timestamp) => {
|
|
64
|
+
if (state.frozen) {
|
|
65
|
+
state.rafQueue.push(callback);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
callback(timestamp);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
state.installed = true;
|
|
72
|
+
}
|
|
73
|
+
function shouldExclude(element) {
|
|
74
|
+
return Boolean(element?.closest('[data-dryui-feedback]'));
|
|
75
|
+
}
|
|
76
|
+
export const originalSetTimeout = state.origSetTimeout;
|
|
77
|
+
export const originalSetInterval = state.origSetInterval;
|
|
78
|
+
export const originalRequestAnimationFrame = state.origRAF;
|
|
79
|
+
export function freezeLayoutModeAnimations() {
|
|
80
|
+
if (typeof document === 'undefined' || state.frozen)
|
|
81
|
+
return;
|
|
82
|
+
state.frozen = true;
|
|
83
|
+
state.timeoutQueue = [];
|
|
84
|
+
state.rafQueue = [];
|
|
85
|
+
const style = document.getElementById(STYLE_ID) ?? document.createElement('style');
|
|
86
|
+
style.id = STYLE_ID;
|
|
87
|
+
style.textContent = `
|
|
88
|
+
*:not([data-dryui-feedback]):not([data-dryui-feedback] *) ,
|
|
89
|
+
*:not([data-dryui-feedback]):not([data-dryui-feedback] *)::before,
|
|
90
|
+
*:not([data-dryui-feedback]):not([data-dryui-feedback] *)::after {
|
|
91
|
+
animation-play-state: paused !important;
|
|
92
|
+
transition: none !important;
|
|
93
|
+
}
|
|
94
|
+
`;
|
|
95
|
+
document.head.appendChild(style);
|
|
96
|
+
state.pausedAnimations = [];
|
|
97
|
+
try {
|
|
98
|
+
document.getAnimations().forEach((animation) => {
|
|
99
|
+
if (animation.playState !== 'running')
|
|
100
|
+
return;
|
|
101
|
+
const target = animation.effect?.target;
|
|
102
|
+
if (!shouldExclude(target)) {
|
|
103
|
+
animation.pause();
|
|
104
|
+
state.pausedAnimations.push(animation);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// Not all browser environments expose getAnimations.
|
|
110
|
+
}
|
|
111
|
+
document.querySelectorAll('video').forEach((video) => {
|
|
112
|
+
if (!video.paused) {
|
|
113
|
+
video.dataset.wasPaused = 'false';
|
|
114
|
+
video.pause();
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
export function unfreezeLayoutModeAnimations() {
|
|
119
|
+
if (typeof document === 'undefined' || !state.frozen)
|
|
120
|
+
return;
|
|
121
|
+
state.frozen = false;
|
|
122
|
+
const timeoutQueue = state.timeoutQueue;
|
|
123
|
+
state.timeoutQueue = [];
|
|
124
|
+
for (const callback of timeoutQueue) {
|
|
125
|
+
state.origSetTimeout(() => {
|
|
126
|
+
if (state.frozen) {
|
|
127
|
+
state.timeoutQueue.push(callback);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
callback();
|
|
131
|
+
}, 0);
|
|
132
|
+
}
|
|
133
|
+
const rafQueue = state.rafQueue;
|
|
134
|
+
state.rafQueue = [];
|
|
135
|
+
for (const callback of rafQueue) {
|
|
136
|
+
state.origRAF((timestamp) => {
|
|
137
|
+
if (state.frozen) {
|
|
138
|
+
state.rafQueue.push(callback);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
callback(timestamp);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
for (const animation of state.pausedAnimations) {
|
|
145
|
+
try {
|
|
146
|
+
animation.play();
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// ignore resume failures
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
state.pausedAnimations = [];
|
|
153
|
+
document.getElementById(STYLE_ID)?.remove();
|
|
154
|
+
document.querySelectorAll('video').forEach((video) => {
|
|
155
|
+
if (video.dataset.wasPaused === 'false') {
|
|
156
|
+
video.play().catch(() => { });
|
|
157
|
+
delete video.dataset.wasPaused;
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
export function isLayoutModeFrozen() {
|
|
162
|
+
return state.frozen;
|
|
163
|
+
}
|