@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,669 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+ import { Badge } from '@dryui/ui/badge';
4
+ import { Button } from '@dryui/ui/button';
5
+ import { Card } from '@dryui/ui/card';
6
+ import { ChatMessage } from '@dryui/ui/chat-message';
7
+ import { Collapsible } from '@dryui/ui/collapsible';
8
+ import { Field } from '@dryui/ui/field';
9
+ import { Flex } from '@dryui/ui/flex';
10
+ import { Input } from '@dryui/ui/input';
11
+ import { Label } from '@dryui/ui/label';
12
+ import { SwatchStrip } from '@dryui/ui/swatch-strip';
13
+ import { RelativeTime } from '@dryui/ui/relative-time';
14
+ import { Stack } from '@dryui/ui/stack';
15
+ import { Text } from '@dryui/ui/text';
16
+ import { Textarea } from '@dryui/ui/textarea';
17
+ import { ANNOTATION_COLOR_OPTIONS, DEFAULT_SETTINGS } from '../constants.js';
18
+ import type { Annotation } from '../types.js';
19
+
20
+ interface Props {
21
+ element: string;
22
+ initialValue?: string;
23
+ selectedText?: string;
24
+ computedStyles?: string;
25
+ color?: Annotation['color'];
26
+ status?: Annotation['status'];
27
+ thread?: Annotation['thread'];
28
+ resolvedAt?: Annotation['resolvedAt'];
29
+ resolvedBy?: Annotation['resolvedBy'];
30
+ resolutionNote?: Annotation['resolutionNote'];
31
+ showDelete?: boolean;
32
+ showStatusActions?: boolean;
33
+ fieldLabel?: string;
34
+ helperText?: string;
35
+ placeholder?: string;
36
+ position: { x: number; y: number };
37
+ submitLabel?: string;
38
+ oncolorchange?: (color: Annotation['color']) => void;
39
+ onsubmit: (text: string, meta?: { resolutionNote?: string }) => void;
40
+ oncancel: () => void;
41
+ ondelete?: () => void;
42
+ onacknowledge?: (meta?: { resolutionNote?: string }) => void;
43
+ onresolve?: (meta?: { resolutionNote?: string }) => void;
44
+ ondismiss?: (meta?: { resolutionNote?: string }) => void;
45
+ onreply?: (text: string) => void;
46
+ }
47
+
48
+ const EXIT_DURATION_MS = 150;
49
+ const SHAKE_DURATION_MS = 250;
50
+
51
+ let {
52
+ element,
53
+ initialValue = '',
54
+ selectedText,
55
+ computedStyles,
56
+ color = DEFAULT_SETTINGS.annotationColor,
57
+ status = 'pending',
58
+ thread = [],
59
+ resolvedAt,
60
+ resolvedBy,
61
+ resolutionNote,
62
+ showDelete = false,
63
+ showStatusActions = false,
64
+ fieldLabel = 'Feedback',
65
+ helperText = 'Enter submits. Shift+Enter inserts a new line.',
66
+ placeholder = 'Add feedback...',
67
+ position,
68
+ submitLabel,
69
+ oncolorchange,
70
+ onsubmit,
71
+ oncancel,
72
+ ondelete,
73
+ onacknowledge,
74
+ onresolve,
75
+ ondismiss,
76
+ onreply,
77
+ }: Props = $props();
78
+
79
+ let comment = $state('');
80
+ let replyText = $state('');
81
+ let resolutionNoteText = $state('');
82
+ let stylesExpanded = $state(false);
83
+ let phase = $state<'enter' | 'entered' | 'exit'>('enter');
84
+ let shaking = $state(false);
85
+ let surface = $state<HTMLDivElement>();
86
+ let textarea = $state<HTMLTextAreaElement>();
87
+ let popupOpen = $state(true);
88
+ let surfaceVersion = $state(0);
89
+
90
+ let closing = false;
91
+ let closeTimer: ReturnType<typeof setTimeout> | undefined;
92
+ let shakeTimer: ReturnType<typeof setTimeout> | undefined;
93
+
94
+ const computedStyleEntries = $derived(
95
+ computedStyles
96
+ ? computedStyles
97
+ .split(';')
98
+ .map((entry) => entry.trim())
99
+ .filter(Boolean)
100
+ : [],
101
+ );
102
+ const threadMessages = $derived(thread ?? []);
103
+ const statusLabel = $derived(
104
+ status === 'acknowledged'
105
+ ? 'Acknowledged'
106
+ : status === 'resolved'
107
+ ? 'Resolved'
108
+ : status === 'dismissed'
109
+ ? 'Dismissed'
110
+ : status === 'failed'
111
+ ? 'Failed'
112
+ : 'Pending',
113
+ );
114
+ const statusColor = $derived(
115
+ status === 'acknowledged'
116
+ ? 'yellow'
117
+ : status === 'resolved'
118
+ ? 'green'
119
+ : status === 'dismissed'
120
+ ? 'gray'
121
+ : status === 'failed'
122
+ ? 'red'
123
+ : 'blue',
124
+ );
125
+ const showLifecycleBlock = $derived(
126
+ showStatusActions ||
127
+ threadMessages.length > 0 ||
128
+ Boolean(resolvedAt) ||
129
+ Boolean(resolvedBy) ||
130
+ Boolean(resolutionNote),
131
+ );
132
+ const showResolutionField = $derived(showStatusActions || Boolean(resolutionNote));
133
+
134
+ function focusTextarea() {
135
+ if (!textarea) return;
136
+ textarea.focus();
137
+ const end = textarea.value.length;
138
+ textarea.setSelectionRange(end, end);
139
+ }
140
+
141
+ function attachTextarea(node: HTMLTextAreaElement) {
142
+ textarea = node;
143
+ return () => {
144
+ if (textarea === node) {
145
+ textarea = undefined;
146
+ }
147
+ };
148
+ }
149
+
150
+ function getResolvedPosition() {
151
+ const measuredWidth = surface?.offsetWidth || surface?.getBoundingClientRect().width || Math.min(392, window.innerWidth - 24);
152
+ const measuredHeight = surface?.offsetHeight || surface?.getBoundingClientRect().height || 0;
153
+ const width = measuredWidth + 16;
154
+ const height = measuredHeight + 16;
155
+ const padding = 12;
156
+
157
+ return {
158
+ x: Math.max(padding, Math.min(position.x, window.innerWidth - width - padding)),
159
+ y: Math.max(padding, Math.min(position.y, window.innerHeight - height - padding)),
160
+ };
161
+ }
162
+
163
+ const resolvedPosition = $derived.by(() => {
164
+ surfaceVersion;
165
+ return getResolvedPosition();
166
+ });
167
+
168
+ function attachSurface(node: HTMLDivElement) {
169
+ surface = node;
170
+
171
+ const update = () => {
172
+ requestAnimationFrame(() => {
173
+ if (surface === node) {
174
+ surfaceVersion += 1;
175
+ }
176
+ });
177
+ };
178
+
179
+ const resizeObserver = typeof ResizeObserver !== 'undefined'
180
+ ? new ResizeObserver(update)
181
+ : null;
182
+
183
+ resizeObserver?.observe(node);
184
+ window.addEventListener('resize', update);
185
+ update();
186
+
187
+ return () => {
188
+ resizeObserver?.disconnect();
189
+ window.removeEventListener('resize', update);
190
+ if (surface === node) {
191
+ surface = undefined;
192
+ }
193
+ };
194
+ }
195
+
196
+ function scheduleCancel() {
197
+ if (closing) return;
198
+ closing = true;
199
+ phase = 'exit';
200
+ closeTimer = setTimeout(() => {
201
+ popupOpen = false;
202
+ oncancel();
203
+ }, EXIT_DURATION_MS);
204
+ }
205
+
206
+ function handleDismiss() {
207
+ scheduleCancel();
208
+ }
209
+
210
+ function shake() {
211
+ if (shakeTimer) clearTimeout(shakeTimer);
212
+ shaking = true;
213
+ shakeTimer = setTimeout(() => {
214
+ shaking = false;
215
+ focusTextarea();
216
+ }, SHAKE_DURATION_MS);
217
+ }
218
+
219
+ function handleSubmit() {
220
+ const trimmed = comment.trim();
221
+ if (!trimmed) {
222
+ shake();
223
+ return;
224
+ }
225
+
226
+ onsubmit(trimmed, currentMeta());
227
+ }
228
+
229
+ function currentMeta(): { resolutionNote?: string } | undefined {
230
+ const nextResolutionNote = resolutionNoteText.trim();
231
+ return nextResolutionNote ? { resolutionNote: nextResolutionNote } : undefined;
232
+ }
233
+
234
+ function handleAcknowledge() {
235
+ onacknowledge?.(currentMeta());
236
+ }
237
+
238
+ function handleResolve() {
239
+ onresolve?.(currentMeta());
240
+ }
241
+
242
+ function handleDismissStatus() {
243
+ ondismiss?.(currentMeta());
244
+ }
245
+
246
+ function handleReply() {
247
+ const trimmed = replyText.trim();
248
+ if (!trimmed) {
249
+ return;
250
+ }
251
+
252
+ onreply?.(trimmed);
253
+ replyText = '';
254
+ }
255
+
256
+ function formatThreadTimestamp(timestamp: number): string {
257
+ return new Intl.DateTimeFormat(undefined, {
258
+ hour: 'numeric',
259
+ minute: '2-digit',
260
+ }).format(timestamp);
261
+ }
262
+
263
+ function handleKeydown(event: KeyboardEvent) {
264
+ if (event.key === 'Enter' && !event.shiftKey && !event.isComposing) {
265
+ if (event.target !== textarea) {
266
+ return;
267
+ }
268
+
269
+ event.preventDefault();
270
+ handleSubmit();
271
+ }
272
+ }
273
+
274
+ onMount(() => {
275
+ comment = initialValue;
276
+ resolutionNoteText = resolutionNote ?? '';
277
+ const focusTimer = setTimeout(() => {
278
+ focusTextarea();
279
+ phase = 'entered';
280
+ }, 40);
281
+
282
+ const handlePointerDown = (event: PointerEvent) => {
283
+ const target = event.target as Node;
284
+ if (target instanceof Element && target.closest('[data-feedback-popup]')) {
285
+ return;
286
+ }
287
+
288
+ handleDismiss();
289
+ };
290
+
291
+ const handleEscape = (event: KeyboardEvent) => {
292
+ if (event.key !== 'Escape') return;
293
+ event.preventDefault();
294
+ handleDismiss();
295
+ };
296
+
297
+ document.addEventListener('pointerdown', handlePointerDown);
298
+ document.addEventListener('keydown', handleEscape, true);
299
+
300
+ return () => {
301
+ clearTimeout(focusTimer);
302
+ if (closeTimer) clearTimeout(closeTimer);
303
+ if (shakeTimer) clearTimeout(shakeTimer);
304
+ document.removeEventListener('pointerdown', handlePointerDown);
305
+ document.removeEventListener('keydown', handleEscape, true);
306
+ };
307
+ });
308
+ </script>
309
+
310
+ {#if popupOpen}
311
+ <div
312
+ data-feedback-popup
313
+ data-dryui-feedback
314
+ role="dialog"
315
+ aria-label="Annotation form"
316
+ class="popup-shell"
317
+ tabindex="-1"
318
+ style={`left:${resolvedPosition.x}px; top:${resolvedPosition.y}px;`}
319
+ onkeydown={handleKeydown}
320
+ onclick={(event) => event.stopPropagation()}
321
+ >
322
+ <div
323
+ {@attach attachSurface}
324
+ class="popup-surface"
325
+ class:enter={phase === 'enter'}
326
+ class:entered={phase === 'entered'}
327
+ class:exit={phase === 'exit'}
328
+ class:shake={shaking}
329
+ >
330
+ <Card.Root variant="elevated" size="sm" style="width: 100%;">
331
+ <Card.Header>
332
+ <Stack gap="sm">
333
+ <Text size="sm" color="secondary">Annotation target</Text>
334
+ <Text size="md">{element}</Text>
335
+ </Stack>
336
+ </Card.Header>
337
+
338
+ <Card.Content>
339
+ <Stack gap="md">
340
+ {#if selectedText}
341
+ <Stack gap="sm">
342
+ <Text size="sm" color="secondary">Selected text</Text>
343
+ <div class="quote-block">
344
+ <Text size="sm">"{selectedText}"</Text>
345
+ </div>
346
+ </Stack>
347
+ {/if}
348
+
349
+ {#if showLifecycleBlock}
350
+ <Stack gap="sm">
351
+ <Stack direction="horizontal" gap="sm" align="center">
352
+ <Text size="sm" color="secondary">Annotation status</Text>
353
+ <Badge
354
+ variant="soft"
355
+ color={statusColor}
356
+ size="sm"
357
+ data-testid="popup-status-badge"
358
+ >
359
+ {statusLabel}
360
+ </Badge>
361
+ </Stack>
362
+
363
+ {#if resolvedBy || resolvedAt}
364
+ <Stack direction="horizontal" gap="sm" align="center">
365
+ <Text size="sm" color="secondary">
366
+ {statusLabel}
367
+ {#if resolvedBy}
368
+ by {resolvedBy}
369
+ {/if}
370
+ </Text>
371
+ {#if resolvedAt}
372
+ <Text size="sm" color="secondary">
373
+ <RelativeTime date={resolvedAt} />
374
+ </Text>
375
+ {/if}
376
+ </Stack>
377
+ {/if}
378
+
379
+ {#if threadMessages.length > 0}
380
+ <Stack gap="sm">
381
+ <Stack direction="horizontal" gap="sm" align="center">
382
+ <Text size="sm" color="secondary">Thread</Text>
383
+ <Badge variant="outline" color="gray" size="sm" data-testid="popup-thread-count">
384
+ {threadMessages.length}
385
+ </Badge>
386
+ </Stack>
387
+
388
+ <div class="thread-block">
389
+ <Stack gap="sm" data-testid="popup-thread">
390
+ {#each threadMessages as message (message.id)}
391
+ <ChatMessage
392
+ role={message.role === 'human' ? 'user' : 'assistant'}
393
+ variant={message.role === 'human' ? 'sent' : 'received'}
394
+ name={message.role === 'human' ? 'You' : 'Agent'}
395
+ timestamp={formatThreadTimestamp(message.timestamp)}
396
+ >
397
+ {message.content}
398
+ </ChatMessage>
399
+ {/each}
400
+ </Stack>
401
+ </div>
402
+ </Stack>
403
+ {/if}
404
+ </Stack>
405
+ {/if}
406
+
407
+ <Field.Root>
408
+ <Label>Marker color</Label>
409
+ <Flex wrap="wrap" gap="md">
410
+ {#each ANNOTATION_COLOR_OPTIONS as option (option.value)}
411
+ <Button
412
+ variant="bare"
413
+ data-testid={`popup-color-${option.value}`}
414
+ onclick={() => oncolorchange?.(option.value)}
415
+ >
416
+ <Stack align="center" gap="sm">
417
+ <SwatchStrip
418
+ colors={[option.swatch]}
419
+ size="sm"
420
+ rounded={false}
421
+ />
422
+ <Text as="span" size="sm" font="mono" color={color === option.value ? 'default' : 'muted'}>
423
+ {option.label.toLowerCase()}
424
+ </Text>
425
+ </Stack>
426
+ </Button>
427
+ {/each}
428
+ </Flex>
429
+ </Field.Root>
430
+
431
+ <Field.Root>
432
+ <Label>{fieldLabel}</Label>
433
+ <Textarea
434
+ {@attach attachTextarea}
435
+ bind:value={comment}
436
+ size="md"
437
+ {placeholder}
438
+ style="min-height: 7rem;"
439
+ />
440
+ </Field.Root>
441
+
442
+ {#if showResolutionField}
443
+ <Field.Root>
444
+ <Label>Resolution note</Label>
445
+ <Input
446
+ bind:value={resolutionNoteText}
447
+ size="md"
448
+ placeholder="Summarize what changed or why the note was dismissed"
449
+ data-testid="resolution-note-input"
450
+ />
451
+ </Field.Root>
452
+ {/if}
453
+
454
+ {#if onreply}
455
+ <Field.Root>
456
+ <Label>Reply</Label>
457
+ <Stack direction="horizontal" gap="sm" align="end">
458
+ <Input
459
+ bind:value={replyText}
460
+ size="md"
461
+ placeholder="Reply in the thread"
462
+ data-testid="reply-input"
463
+ />
464
+ <Button
465
+ type="button"
466
+ variant="outline"
467
+ size="sm"
468
+ data-testid="reply-btn"
469
+ onclick={handleReply}
470
+ >
471
+ Add reply
472
+ </Button>
473
+ </Stack>
474
+ </Field.Root>
475
+ {/if}
476
+
477
+ <Text size="sm" color="secondary">
478
+ {helperText}
479
+ </Text>
480
+
481
+ {#if computedStyleEntries.length > 0}
482
+ <Collapsible.Root open={stylesExpanded}>
483
+ <Collapsible.Trigger
484
+ type="button"
485
+ data-testid="popup-computed-styles"
486
+ onclick={() => {
487
+ stylesExpanded = !stylesExpanded;
488
+ }}
489
+ style="
490
+ width: 100%;
491
+ display: inline-flex;
492
+ align-items: center;
493
+ justify-content: space-between;
494
+ padding: 0.625rem 0.75rem;
495
+ border-radius: var(--dry-radius-md, 12px);
496
+ border: 1px solid var(--dry-color-stroke-weak, rgba(148, 163, 184, 0.28));
497
+ background: var(--dry-color-bg-base, rgba(15, 23, 42, 0.02));
498
+ color: inherit;
499
+ font: inherit;
500
+ "
501
+ >
502
+ <span>Computed styles</span>
503
+ <span aria-hidden="true">{stylesExpanded ? '-' : '+'}</span>
504
+ </Collapsible.Trigger>
505
+ <Collapsible.Content>
506
+ <div class="styles-block">
507
+ {#each computedStyleEntries as entry (entry)}
508
+ <Text size="sm" style="font-family: var(--dry-font-mono, 'SFMono-Regular', 'Consolas', monospace);">
509
+ {entry}
510
+ </Text>
511
+ {/each}
512
+ </div>
513
+ </Collapsible.Content>
514
+ </Collapsible.Root>
515
+ {/if}
516
+ </Stack>
517
+ </Card.Content>
518
+
519
+ <Card.Footer>
520
+ <Stack gap="sm">
521
+ {#if showStatusActions}
522
+ <Flex wrap="wrap" gap="sm">
523
+ {#if onacknowledge && status !== 'acknowledged'}
524
+ <Button
525
+ type="button"
526
+ variant="ghost"
527
+ size="sm"
528
+ data-testid="acknowledge-btn"
529
+ onclick={handleAcknowledge}
530
+ >
531
+ Acknowledge feedback
532
+ </Button>
533
+ {/if}
534
+
535
+ {#if onresolve && status !== 'resolved'}
536
+ <Button
537
+ type="button"
538
+ variant="ghost"
539
+ size="sm"
540
+ data-testid="resolve-btn"
541
+ onclick={handleResolve}
542
+ >
543
+ Resolve feedback
544
+ </Button>
545
+ {/if}
546
+
547
+ {#if ondismiss && status !== 'dismissed'}
548
+ <Button
549
+ type="button"
550
+ variant="ghost"
551
+ color="danger"
552
+ size="sm"
553
+ data-testid="dismiss-btn"
554
+ onclick={handleDismissStatus}
555
+ >
556
+ Dismiss feedback
557
+ </Button>
558
+ {/if}
559
+ </Flex>
560
+ {/if}
561
+
562
+ <Stack direction="horizontal" gap="sm" align="center">
563
+ <Button type="button" variant="solid" size="sm" data-testid="submit-btn" onclick={handleSubmit}>
564
+ {submitLabel ?? (initialValue ? 'Save' : 'Add')}
565
+ </Button>
566
+ <Button type="button" variant="ghost" size="sm" data-testid="cancel-btn" onclick={handleDismiss}>
567
+ Cancel
568
+ </Button>
569
+ {#if showDelete && ondelete}
570
+ <Button
571
+ type="button"
572
+ variant="ghost"
573
+ color="danger"
574
+ size="sm"
575
+ data-testid="delete-btn"
576
+ onclick={ondelete}
577
+ >
578
+ Delete
579
+ </Button>
580
+ {/if}
581
+ </Stack>
582
+ </Stack>
583
+ </Card.Footer>
584
+ </Card.Root>
585
+ </div>
586
+ </div>
587
+ {/if}
588
+
589
+ <style>
590
+ .popup-shell {
591
+ position: fixed;
592
+ inset: unset;
593
+ margin: 0;
594
+ z-index: 10001;
595
+ min-width: 0;
596
+ width: min(344px, calc(100vw - 24px));
597
+ }
598
+
599
+ .popup-surface {
600
+ width: 100%;
601
+ max-height: calc(100vh - 24px);
602
+ overflow: auto;
603
+ pointer-events: auto;
604
+ transform-origin: top left;
605
+ transition:
606
+ opacity 0.15s ease,
607
+ transform 0.15s ease;
608
+ }
609
+
610
+ .popup-surface.enter,
611
+ .popup-surface.entered {
612
+ opacity: 1;
613
+ transform: scale(1);
614
+ }
615
+
616
+ .popup-surface.enter {
617
+ opacity: 0;
618
+ transform: scale(0.96);
619
+ }
620
+
621
+ .popup-surface.exit {
622
+ opacity: 0;
623
+ transform: scale(0.96);
624
+ }
625
+
626
+ .popup-surface.shake {
627
+ animation: popup-shake 0.25s ease;
628
+ }
629
+
630
+ .quote-block {
631
+ padding: 0.75rem;
632
+ border-radius: var(--dry-radius-md, 12px);
633
+ background: var(--dry-color-bg-subtle, rgba(148, 163, 184, 0.14));
634
+ border: 1px solid var(--dry-color-stroke-weak, rgba(148, 163, 184, 0.28));
635
+ }
636
+
637
+ .styles-block {
638
+ display: grid;
639
+ gap: 0.35rem;
640
+ margin-top: 0.5rem;
641
+ padding: 0.75rem;
642
+ border-radius: var(--dry-radius-md, 12px);
643
+ background: var(--dry-color-bg-subtle, rgba(148, 163, 184, 0.14));
644
+ border: 1px solid var(--dry-color-stroke-weak, rgba(148, 163, 184, 0.28));
645
+ }
646
+
647
+ .thread-block {
648
+ max-height: 13rem;
649
+ padding: 0.5rem;
650
+ border-radius: var(--dry-radius-md, 12px);
651
+ background: var(--dry-color-bg-subtle, rgba(148, 163, 184, 0.14));
652
+ border: 1px solid var(--dry-color-stroke-weak, rgba(148, 163, 184, 0.28));
653
+ }
654
+
655
+ @keyframes popup-shake {
656
+ 0%,
657
+ 100% {
658
+ transform: translateX(0);
659
+ }
660
+
661
+ 25% {
662
+ transform: translateX(-6px);
663
+ }
664
+
665
+ 75% {
666
+ transform: translateX(6px);
667
+ }
668
+ }
669
+ </style>
@@ -0,0 +1,42 @@
1
+ import type { Annotation } from '../types.js';
2
+ interface Props {
3
+ element: string;
4
+ initialValue?: string;
5
+ selectedText?: string;
6
+ computedStyles?: string;
7
+ color?: Annotation['color'];
8
+ status?: Annotation['status'];
9
+ thread?: Annotation['thread'];
10
+ resolvedAt?: Annotation['resolvedAt'];
11
+ resolvedBy?: Annotation['resolvedBy'];
12
+ resolutionNote?: Annotation['resolutionNote'];
13
+ showDelete?: boolean;
14
+ showStatusActions?: boolean;
15
+ fieldLabel?: string;
16
+ helperText?: string;
17
+ placeholder?: string;
18
+ position: {
19
+ x: number;
20
+ y: number;
21
+ };
22
+ submitLabel?: string;
23
+ oncolorchange?: (color: Annotation['color']) => void;
24
+ onsubmit: (text: string, meta?: {
25
+ resolutionNote?: string;
26
+ }) => void;
27
+ oncancel: () => void;
28
+ ondelete?: () => void;
29
+ onacknowledge?: (meta?: {
30
+ resolutionNote?: string;
31
+ }) => void;
32
+ onresolve?: (meta?: {
33
+ resolutionNote?: string;
34
+ }) => void;
35
+ ondismiss?: (meta?: {
36
+ resolutionNote?: string;
37
+ }) => void;
38
+ onreply?: (text: string) => void;
39
+ }
40
+ declare const AnnotationPopup: import("svelte").Component<Props, {}, "">;
41
+ type AnnotationPopup = ReturnType<typeof AnnotationPopup>;
42
+ export default AnnotationPopup;