@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.
Files changed (79) hide show
  1. package/dist/components/annotation-marker.svelte +163 -0
  2. package/dist/components/annotation-marker.svelte.d.ts +11 -0
  3. package/dist/components/annotation-popup.svelte +669 -0
  4. package/dist/components/annotation-popup.svelte.d.ts +42 -0
  5. package/dist/components/highlight-overlay.svelte +48 -0
  6. package/dist/components/highlight-overlay.svelte.d.ts +8 -0
  7. package/dist/components/settings-panel.svelte +446 -0
  8. package/dist/components/settings-panel.svelte.d.ts +24 -0
  9. package/dist/components/toolbar.svelte +1111 -0
  10. package/dist/components/toolbar.svelte.d.ts +46 -0
  11. package/dist/constants.d.ts +9 -0
  12. package/dist/constants.js +37 -0
  13. package/dist/feedback.svelte +2879 -0
  14. package/dist/feedback.svelte.d.ts +4 -0
  15. package/dist/index.d.ts +10 -0
  16. package/dist/index.js +7 -0
  17. package/dist/layout-mode/catalog.d.ts +16 -0
  18. package/dist/layout-mode/catalog.js +81 -0
  19. package/dist/layout-mode/component-actions.svelte +84 -0
  20. package/dist/layout-mode/component-actions.svelte.d.ts +18 -0
  21. package/dist/layout-mode/component-picker.svelte +73 -0
  22. package/dist/layout-mode/component-picker.svelte.d.ts +10 -0
  23. package/dist/layout-mode/design-mode.svelte +1115 -0
  24. package/dist/layout-mode/design-mode.svelte.d.ts +24 -0
  25. package/dist/layout-mode/design-palette.svelte +396 -0
  26. package/dist/layout-mode/design-palette.svelte.d.ts +20 -0
  27. package/dist/layout-mode/element-heuristics.d.ts +5 -0
  28. package/dist/layout-mode/element-heuristics.js +51 -0
  29. package/dist/layout-mode/freeze.d.ts +6 -0
  30. package/dist/layout-mode/freeze.js +163 -0
  31. package/dist/layout-mode/generated-library.d.ts +940 -0
  32. package/dist/layout-mode/generated-library.js +1445 -0
  33. package/dist/layout-mode/geometry.d.ts +38 -0
  34. package/dist/layout-mode/geometry.js +133 -0
  35. package/dist/layout-mode/history.d.ts +10 -0
  36. package/dist/layout-mode/history.js +45 -0
  37. package/dist/layout-mode/index.d.ts +23 -0
  38. package/dist/layout-mode/index.js +18 -0
  39. package/dist/layout-mode/live-mount.d.ts +20 -0
  40. package/dist/layout-mode/live-mount.js +70 -0
  41. package/dist/layout-mode/output.d.ts +26 -0
  42. package/dist/layout-mode/output.js +550 -0
  43. package/dist/layout-mode/placement-skeleton.d.ts +9 -0
  44. package/dist/layout-mode/placement-skeleton.js +535 -0
  45. package/dist/layout-mode/rearrange-overlay.svelte +1293 -0
  46. package/dist/layout-mode/rearrange-overlay.svelte.d.ts +18 -0
  47. package/dist/layout-mode/responsive-bar.svelte +39 -0
  48. package/dist/layout-mode/responsive-bar.svelte.d.ts +8 -0
  49. package/dist/layout-mode/route-creator.svelte +70 -0
  50. package/dist/layout-mode/route-creator.svelte.d.ts +8 -0
  51. package/dist/layout-mode/section-detection.d.ts +6 -0
  52. package/dist/layout-mode/section-detection.js +214 -0
  53. package/dist/layout-mode/spatial.d.ts +42 -0
  54. package/dist/layout-mode/spatial.js +156 -0
  55. package/dist/layout-mode/types.d.ts +144 -0
  56. package/dist/layout-mode/types.js +84 -0
  57. package/dist/types.d.ts +157 -0
  58. package/dist/types.js +1 -0
  59. package/dist/utils/dryui-detection.d.ts +1 -0
  60. package/dist/utils/dryui-detection.js +219 -0
  61. package/dist/utils/element-id.d.ts +12 -0
  62. package/dist/utils/element-id.js +333 -0
  63. package/dist/utils/freeze.d.ts +7 -0
  64. package/dist/utils/freeze.js +168 -0
  65. package/dist/utils/output.d.ts +15 -0
  66. package/dist/utils/output.js +245 -0
  67. package/dist/utils/selection.d.ts +22 -0
  68. package/dist/utils/selection.js +58 -0
  69. package/dist/utils/shadow-dom.d.ts +4 -0
  70. package/dist/utils/shadow-dom.js +39 -0
  71. package/dist/utils/storage.d.ts +30 -0
  72. package/dist/utils/storage.js +206 -0
  73. package/dist/utils/svelte-detection.d.ts +8 -0
  74. package/dist/utils/svelte-detection.js +86 -0
  75. package/dist/utils/svelte-meta.d.ts +6 -0
  76. package/dist/utils/svelte-meta.js +69 -0
  77. package/dist/utils/sync.d.ts +18 -0
  78. package/dist/utils/sync.js +62 -0
  79. 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
+ }