@pixerate/editor-svelte 0.22.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/README.md +6 -2
  2. package/dist/MergePreview.svelte +201 -0
  3. package/dist/MergePreview.svelte.d.ts +25 -0
  4. package/dist/MergePreview.svelte.d.ts.map +1 -0
  5. package/dist/StylesGallery.svelte +169 -0
  6. package/dist/StylesGallery.svelte.d.ts +27 -0
  7. package/dist/StylesGallery.svelte.d.ts.map +1 -0
  8. package/dist/TemplateRenderer.svelte +4 -10
  9. package/dist/TemplateRenderer.svelte.d.ts.map +1 -1
  10. package/dist/canvas/TokenBindingPicker.svelte +93 -0
  11. package/dist/canvas/TokenBindingPicker.svelte.d.ts +20 -0
  12. package/dist/canvas/TokenBindingPicker.svelte.d.ts.map +1 -0
  13. package/dist/canvas/TokenModeSwitcher.svelte +55 -0
  14. package/dist/canvas/TokenModeSwitcher.svelte.d.ts +18 -0
  15. package/dist/canvas/TokenModeSwitcher.svelte.d.ts.map +1 -0
  16. package/dist/canvas/createCanvasArrange.svelte.d.ts +4 -0
  17. package/dist/canvas/createCanvasArrange.svelte.d.ts.map +1 -1
  18. package/dist/canvas/createCanvasAutoLayout.svelte.d.ts +68 -0
  19. package/dist/canvas/createCanvasAutoLayout.svelte.d.ts.map +1 -0
  20. package/dist/canvas/createCanvasAutoLayout.svelte.js +78 -0
  21. package/dist/canvas/createCanvasTokens.svelte.d.ts +31 -0
  22. package/dist/canvas/createCanvasTokens.svelte.d.ts.map +1 -0
  23. package/dist/canvas/createCanvasTokens.svelte.js +50 -0
  24. package/dist/canvas/index.d.ts +7 -0
  25. package/dist/canvas/index.d.ts.map +1 -1
  26. package/dist/canvas/index.js +10 -0
  27. package/dist/fileImport.svelte.d.ts +8 -1
  28. package/dist/fileImport.svelte.d.ts.map +1 -1
  29. package/dist/fileImport.svelte.js +11 -1
  30. package/dist/find-replace/FindReplaceBar.svelte +203 -0
  31. package/dist/find-replace/FindReplaceBar.svelte.d.ts +15 -0
  32. package/dist/find-replace/FindReplaceBar.svelte.d.ts.map +1 -0
  33. package/dist/find-replace/findReplace.svelte.d.ts +27 -0
  34. package/dist/find-replace/findReplace.svelte.d.ts.map +1 -0
  35. package/dist/find-replace/findReplace.svelte.js +79 -0
  36. package/dist/find-replace/index.d.ts +6 -0
  37. package/dist/find-replace/index.d.ts.map +1 -0
  38. package/dist/find-replace/index.js +2 -0
  39. package/dist/generation/AssetMentionChips.svelte +130 -0
  40. package/dist/generation/AssetMentionChips.svelte.d.ts +21 -0
  41. package/dist/generation/AssetMentionChips.svelte.d.ts.map +1 -0
  42. package/dist/generation/GenerationHistory.svelte +161 -0
  43. package/dist/generation/GenerationHistory.svelte.d.ts +28 -0
  44. package/dist/generation/GenerationHistory.svelte.d.ts.map +1 -0
  45. package/dist/generation/GenerationSettings.svelte +174 -0
  46. package/dist/generation/GenerationSettings.svelte.d.ts +23 -0
  47. package/dist/generation/GenerationSettings.svelte.d.ts.map +1 -0
  48. package/dist/generation/JobProgress.svelte +53 -0
  49. package/dist/generation/JobProgress.svelte.d.ts +17 -0
  50. package/dist/generation/JobProgress.svelte.d.ts.map +1 -0
  51. package/dist/generation/index.d.ts +17 -0
  52. package/dist/generation/index.d.ts.map +1 -0
  53. package/dist/generation/index.js +7 -0
  54. package/dist/generation/plan.svelte.d.ts +16 -0
  55. package/dist/generation/plan.svelte.d.ts.map +1 -0
  56. package/dist/generation/plan.svelte.js +26 -0
  57. package/dist/generation/progress.svelte.d.ts +25 -0
  58. package/dist/generation/progress.svelte.d.ts.map +1 -0
  59. package/dist/generation/progress.svelte.js +36 -0
  60. package/dist/generation/records.svelte.d.ts +18 -0
  61. package/dist/generation/records.svelte.d.ts.map +1 -0
  62. package/dist/generation/records.svelte.js +66 -0
  63. package/dist/image-editor/AdjustmentLayersPanel.svelte +309 -0
  64. package/dist/image-editor/AdjustmentLayersPanel.svelte.d.ts +11 -0
  65. package/dist/image-editor/AdjustmentLayersPanel.svelte.d.ts.map +1 -0
  66. package/dist/image-editor/ControlSlider.svelte +39 -0
  67. package/dist/image-editor/ControlSlider.svelte.d.ts +13 -0
  68. package/dist/image-editor/ControlSlider.svelte.d.ts.map +1 -0
  69. package/dist/image-editor/CurvesEditor.svelte +105 -0
  70. package/dist/image-editor/CurvesEditor.svelte.d.ts +13 -0
  71. package/dist/image-editor/CurvesEditor.svelte.d.ts.map +1 -0
  72. package/dist/image-editor/Histogram.svelte +49 -0
  73. package/dist/image-editor/Histogram.svelte.d.ts +12 -0
  74. package/dist/image-editor/Histogram.svelte.d.ts.map +1 -0
  75. package/dist/image-editor/ImageEditor.svelte +104 -141
  76. package/dist/image-editor/ImageEditor.svelte.d.ts.map +1 -1
  77. package/dist/image-editor/RefineEdgePanel.svelte +180 -0
  78. package/dist/image-editor/RefineEdgePanel.svelte.d.ts +13 -0
  79. package/dist/image-editor/RefineEdgePanel.svelte.d.ts.map +1 -0
  80. package/dist/image-editor/SelectionOptionsBar.svelte +135 -0
  81. package/dist/image-editor/SelectionOptionsBar.svelte.d.ts +15 -0
  82. package/dist/image-editor/SelectionOptionsBar.svelte.d.ts.map +1 -0
  83. package/dist/image-editor/imageEditorState.svelte.d.ts +14 -1
  84. package/dist/image-editor/imageEditorState.svelte.d.ts.map +1 -1
  85. package/dist/image-editor/imageEditorState.svelte.js +40 -0
  86. package/dist/image-editor/index.d.ts +18 -0
  87. package/dist/image-editor/index.d.ts.map +1 -1
  88. package/dist/image-editor/index.js +6 -0
  89. package/dist/index.d.ts +12 -1
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.js +7 -0
  92. package/dist/merge.svelte.d.ts +51 -0
  93. package/dist/merge.svelte.d.ts.map +1 -0
  94. package/dist/merge.svelte.js +117 -0
  95. package/dist/paragraphStyles.svelte.d.ts +30 -0
  96. package/dist/paragraphStyles.svelte.d.ts.map +1 -0
  97. package/dist/paragraphStyles.svelte.js +44 -0
  98. package/dist/persistence/RecoveryPrompt.svelte +57 -0
  99. package/dist/persistence/RecoveryPrompt.svelte.d.ts +18 -0
  100. package/dist/persistence/RecoveryPrompt.svelte.d.ts.map +1 -0
  101. package/dist/persistence/autosave.svelte.d.ts +63 -0
  102. package/dist/persistence/autosave.svelte.d.ts.map +1 -0
  103. package/dist/persistence/autosave.svelte.js +110 -0
  104. package/dist/persistence/index.d.ts +6 -0
  105. package/dist/persistence/index.d.ts.map +1 -0
  106. package/dist/persistence/index.js +2 -0
  107. package/dist/review/TrackedChangesList.svelte +67 -0
  108. package/dist/review/TrackedChangesList.svelte.d.ts +13 -0
  109. package/dist/review/TrackedChangesList.svelte.d.ts.map +1 -0
  110. package/dist/review/documentCompare.svelte.d.ts +21 -0
  111. package/dist/review/documentCompare.svelte.d.ts.map +1 -0
  112. package/dist/review/documentCompare.svelte.js +23 -0
  113. package/dist/review/index.d.ts +4 -0
  114. package/dist/review/index.d.ts.map +1 -1
  115. package/dist/review/index.js +2 -0
  116. package/dist/review/review.svelte.d.ts +6 -0
  117. package/dist/review/review.svelte.d.ts.map +1 -1
  118. package/dist/review/review.svelte.js +3 -0
  119. package/dist/spreadsheet/FormulaBar.svelte +13 -4
  120. package/dist/spreadsheet/FormulaBar.svelte.d.ts.map +1 -1
  121. package/dist/spreadsheet/FormulaHints.svelte +7 -5
  122. package/dist/spreadsheet/FormulaHints.svelte.d.ts +3 -0
  123. package/dist/spreadsheet/FormulaHints.svelte.d.ts.map +1 -1
  124. package/dist/spreadsheet/NameManager.svelte +165 -0
  125. package/dist/spreadsheet/NameManager.svelte.d.ts +11 -0
  126. package/dist/spreadsheet/NameManager.svelte.d.ts.map +1 -0
  127. package/dist/spreadsheet/SheetTabs.svelte +161 -0
  128. package/dist/spreadsheet/SheetTabs.svelte.d.ts +10 -0
  129. package/dist/spreadsheet/SheetTabs.svelte.d.ts.map +1 -0
  130. package/dist/spreadsheet/SpreadsheetConditionalFormatPanel.svelte +184 -0
  131. package/dist/spreadsheet/SpreadsheetConditionalFormatPanel.svelte.d.ts +9 -0
  132. package/dist/spreadsheet/SpreadsheetConditionalFormatPanel.svelte.d.ts.map +1 -0
  133. package/dist/spreadsheet/SpreadsheetContextMenu.svelte +13 -5
  134. package/dist/spreadsheet/SpreadsheetContextMenu.svelte.d.ts +5 -1
  135. package/dist/spreadsheet/SpreadsheetContextMenu.svelte.d.ts.map +1 -1
  136. package/dist/spreadsheet/SpreadsheetDataValidationPanel.svelte +141 -0
  137. package/dist/spreadsheet/SpreadsheetDataValidationPanel.svelte.d.ts +9 -0
  138. package/dist/spreadsheet/SpreadsheetDataValidationPanel.svelte.d.ts.map +1 -0
  139. package/dist/spreadsheet/SpreadsheetEditor.svelte +199 -11
  140. package/dist/spreadsheet/SpreadsheetEditor.svelte.d.ts +2 -0
  141. package/dist/spreadsheet/SpreadsheetEditor.svelte.d.ts.map +1 -1
  142. package/dist/spreadsheet/SpreadsheetValidationDialog.svelte +92 -0
  143. package/dist/spreadsheet/SpreadsheetValidationDialog.svelte.d.ts +9 -0
  144. package/dist/spreadsheet/SpreadsheetValidationDialog.svelte.d.ts.map +1 -0
  145. package/dist/spreadsheet/index.d.ts +9 -0
  146. package/dist/spreadsheet/index.d.ts.map +1 -1
  147. package/dist/spreadsheet/index.js +5 -0
  148. package/dist/spreadsheet/spreadsheet.svelte.d.ts +57 -2
  149. package/dist/spreadsheet/spreadsheet.svelte.d.ts.map +1 -1
  150. package/dist/spreadsheet/spreadsheet.svelte.js +105 -6
  151. package/dist/tokens/TokenSwatch.svelte +14 -0
  152. package/dist/tokens/TokenSwatch.svelte.d.ts +8 -0
  153. package/dist/tokens/TokenSwatch.svelte.d.ts.map +1 -0
  154. package/dist/tokens/TokensPanel.svelte +323 -0
  155. package/dist/tokens/TokensPanel.svelte.d.ts +14 -0
  156. package/dist/tokens/TokensPanel.svelte.d.ts.map +1 -0
  157. package/dist/tokens/createTokens.svelte.d.ts +31 -0
  158. package/dist/tokens/createTokens.svelte.d.ts.map +1 -0
  159. package/dist/tokens/createTokens.svelte.js +43 -0
  160. package/dist/tokens/index.d.ts +7 -0
  161. package/dist/tokens/index.d.ts.map +1 -0
  162. package/dist/tokens/index.js +3 -0
  163. package/package.json +26 -2
package/README.md CHANGED
@@ -69,14 +69,18 @@ Requires **Svelte 5** (the components use runes). Components ship as `.svelte` s
69
69
 
70
70
  `ReviewToolbar` steps through insertions, deletions, formatting and block changes and shows the current one's author and description (e.g. "Ada: Formatted: Bold"); hide it with `showDescription={false}` or restyle it with `descriptionClass`. Put `<CommentButton {editor} />` inside a `BubbleMenu` to comment on the selection. `createComments(editorOrStore)` and `createTrackChanges(editor)` are rune (and Svelte store) adapters for custom UIs.
71
71
 
72
+ `<FindReplaceBar {editor} />` is a Word-style find bar for editors with the `FindReplace` extension (`createRichTextPreset({ findReplace: true })`): it appears on Mod+F / Mod+H (or `editor.commands.openFindReplace()`), with find and replace fields, option toggles, a "3 of 12" counter, previous/next, Replace and Replace all; Enter / Shift+Enter step, Mod+Enter in the replace field replaces all, Escape closes (`onclose`). `createFindReplace(editor)` is the rune (and Svelte store) adapter for custom UIs.
73
+
72
74
  ## More exports
73
75
 
74
76
  | Import | Exports |
75
77
  | :--- | :--- |
76
78
  | `@pixerate/editor-svelte` | `EditableTextNodeEditor`, `BubbleMenu`, `TemplateRenderer`, `initiateEditor`, `createReactiveEditor`, `createHistory`, `createHistoryDirty`, `createHistoryShortcuts`, `navigationGuard` (action), `createNavigationGuard`, `SpreadsheetEditor`, `FormulaBar`, `FormulaHints`, `createReactiveSpreadsheet`, `DirtyTracker` |
77
- | `@pixerate/editor-svelte/spreadsheet` | `SpreadsheetEditor`, `FormulaBar`, `FormulaHints`, `SpreadsheetContextMenu`, `SpreadsheetGroupRow`, `createReactiveSpreadsheet` (also re-exported from the root entry) |
78
- | `@pixerate/editor-svelte/image-editor` | `ImageEditor`, `createReactiveImageEditor`: crop, rotate/flip, adjustments, annotations, depth masks, and `onInpaint` / `onRemoveBackground` AI hooks |
79
+ | `@pixerate/editor-svelte/spreadsheet` | `SpreadsheetEditor`, `FormulaBar`, `FormulaHints`, `SpreadsheetContextMenu`, `SpreadsheetGroupRow`, `SheetTabs`, `NameManager`, `createReactiveSpreadsheet` (also re-exported from the root entry) |
80
+ | `@pixerate/editor-svelte/image-editor` | `ImageEditor`, `createReactiveImageEditor`: crop, rotate/flip, adjustments, annotations, depth masks, Select & Mask (`SelectionOptionsBar`, `RefineEdgePanel`: magic wand, marquees, lasso, Refine Edge, layer masks), and `onInpaint` / `onRemoveBackground` AI hooks |
79
81
  | `@pixerate/editor-svelte/commands` | `CommandPalette`, `createCommandPalette` (`hotkey: "Mod+K"`; call `destroy()` when done), `createCommandShortcuts`, `useRegisterCommands` over a `CommandRegistry` from `@pixerate/editor/commands` (also re-exported from the root entry) |
82
+ | `@pixerate/editor-svelte/persistence` | `createAutosave` (crash-recovery snapshots bound to a history / dirty flag, flushed on `pagehide`; call `destroy()` when done) and `<RecoveryPrompt>` (also re-exported from the root entry) |
83
+ | `@pixerate/editor-svelte/generation` | `GenerationSettings` (controls generated from a model's capabilities, reference slots by role, plan adjustments; `onchange` / `onaddreference`), `createGenerationPlan(() => request, () => caps)`, `JobProgress`, `createJobProgress` (call `destroy()` when done) over `@pixerate/editor/generation` (also re-exported from the root entry) |
80
84
  | `@pixerate/editor-svelte/canvas` | Runes for `@xyflow/svelte`: `createCanvasGraph`, `createCanvasClipboard`, `createCanvasDocking`, `createCanvasShortcuts`, `createCanvasInteractions`, `createCanvasExplosion`, `createCanvasNodeSync`, `createCanvasMultiDrag`, `createStreamingCanvas`, `AgentPresenceLayer`, `FloatingHorizontalScrollbar`, plus layout helpers |
81
85
 
82
86
  Prop types are exported alongside each component, for example `EditableTextNodeEditorProps`, `BubbleMenuProps`, `ImageEditorProps` and `SpreadsheetEditorProps`.
@@ -0,0 +1,201 @@
1
+ <!--
2
+ Mail-merge preview: paste or pick a CSV / JSON data source, check the
3
+ automatic field mapping, step through records with the resolved prompt shown
4
+ by TemplateRenderer, and merge every record into text or JSON.
5
+ Inputs are nested in their labels, which associates them without ids.
6
+ -->
7
+ <script lang="ts">
8
+ import type {
9
+ ColorGradient,
10
+ MergeRecordsOptions,
11
+ MergeRecordsResult,
12
+ ParseDataSourceOptions,
13
+ Template,
14
+ } from "@pixerate/editor";
15
+ import TemplateRenderer from "./TemplateRenderer.svelte";
16
+ import { createMergePreview } from "./merge.svelte.js";
17
+
18
+ interface Props {
19
+ /** The prompt merged with each record. */
20
+ content: string;
21
+ templates?: Template[];
22
+ bindings?: Record<string, string>;
23
+ templateColorMap?: Map<string, ColorGradient>;
24
+ /** Initial CSV / TSV / JSON data source text. */
25
+ defaultSource?: string;
26
+ parseOptions?: ParseDataSourceOptions;
27
+ /** Drop records matching this condition (e.g. `"status=void"`) when merging. */
28
+ skipIf?: MergeRecordsOptions["skipIf"];
29
+ /** Separator between merged outputs (default a blank line; `"none"` for nothing). */
30
+ separator?: string;
31
+ maxOutputs?: number;
32
+ /** Called after "Merge all". */
33
+ onMerge?: (result: MergeRecordsResult) => void;
34
+ /** Base file name for downloads (default `merged`). */
35
+ downloadName?: string;
36
+ class?: string;
37
+ }
38
+
39
+ let {
40
+ content,
41
+ templates,
42
+ bindings,
43
+ templateColorMap,
44
+ defaultSource = "",
45
+ parseOptions,
46
+ skipIf,
47
+ separator,
48
+ maxOutputs,
49
+ onMerge,
50
+ downloadName = "merged",
51
+ class: className = "flex flex-col gap-3 text-sm",
52
+ }: Props = $props();
53
+
54
+ // svelte-ignore state_referenced_locally
55
+ const merge = createMergePreview(
56
+ () => ({ template: content, templates, bindings, parseOptions, skipIf, separator, maxOutputs }),
57
+ defaultSource,
58
+ );
59
+ let copied = $state(false);
60
+
61
+ const plural = (n: number, word: string) => `${n} ${word}${n === 1 ? "" : "s"}`;
62
+
63
+ function downloadText(text: string, fileName: string, type: string) {
64
+ if (typeof document === "undefined" || typeof URL.createObjectURL !== "function") return;
65
+ const url = URL.createObjectURL(new Blob([text], { type }));
66
+ const link = document.createElement("a");
67
+ link.href = url;
68
+ link.download = fileName;
69
+ link.click();
70
+ setTimeout(() => URL.revokeObjectURL(url), 0);
71
+ }
72
+
73
+ async function copy() {
74
+ try {
75
+ await navigator.clipboard.writeText(merge.serialize("text"));
76
+ copied = true;
77
+ } catch {
78
+ copied = false;
79
+ }
80
+ }
81
+
82
+ function mergeAll() {
83
+ const result = merge.merge();
84
+ copied = false;
85
+ onMerge?.(result);
86
+ }
87
+ </script>
88
+
89
+ <div class={className} data-merge-preview="">
90
+ <label class="flex flex-col gap-1">
91
+ <span class="font-medium">Data source (CSV, TSV or JSON)</span>
92
+ <textarea
93
+ data-merge-source=""
94
+ value={merge.sourceText}
95
+ rows={5}
96
+ spellcheck={false}
97
+ oninput={(event) => merge.setSourceText(event.currentTarget.value)}
98
+ class="px-2 py-1 font-mono text-xs rounded-md border border-violet-400/40 bg-transparent"
99
+ ></textarea>
100
+ </label>
101
+ <div class="flex flex-wrap items-center gap-2">
102
+ <input
103
+ type="file"
104
+ accept=".csv,.tsv,.txt,.json,text/csv,application/json"
105
+ aria-label="Load data source file"
106
+ data-merge-file=""
107
+ onchange={(event) => {
108
+ const file = event.currentTarget.files?.[0];
109
+ if (file) void merge.loadFile(file);
110
+ }}
111
+ />
112
+ <span data-merge-summary="" class="opacity-70"
113
+ >{`${plural(merge.source.records.length, "record")} · ${plural(merge.source.fields.length, "field")}`}</span
114
+ >
115
+ </div>
116
+ {#if merge.source.warnings.length > 0 && merge.sourceText.trim() !== ""}
117
+ <ul data-merge-warnings="" class="text-amber-600 text-xs">
118
+ {#each merge.source.warnings as warning}
119
+ <li>{warning.message}</li>
120
+ {/each}
121
+ </ul>
122
+ {/if}
123
+
124
+ {#if merge.variables.length > 0}
125
+ <fieldset class="flex flex-col gap-1">
126
+ <legend class="font-medium">Field mapping</legend>
127
+ {#each merge.variables as variable (variable)}
128
+ <label class="flex items-center gap-2" data-merge-mapping={variable}>
129
+ <span class="min-w-24 font-mono">{`{${variable}}`}</span>
130
+ <select
131
+ value={merge.mapping[variable] ?? ""}
132
+ aria-label={`Field for ${variable}`}
133
+ onchange={(event) => merge.setFieldMapping(variable, event.currentTarget.value || null)}
134
+ class="px-1 py-0.5 rounded-md border border-violet-400/40 bg-transparent"
135
+ >
136
+ <option value="">(none)</option>
137
+ {#each merge.source.fields as field (field)}
138
+ <option value={field}>{field}</option>
139
+ {/each}
140
+ </select>
141
+ </label>
142
+ {/each}
143
+ </fieldset>
144
+ {/if}
145
+
146
+ <div class="flex items-center gap-2">
147
+ <button type="button" data-merge-prev="" onclick={merge.prev} disabled={merge.index <= 0}>Prev</button>
148
+ <span data-merge-position=""
149
+ >{merge.count === 0 ? "No records" : `Record ${merge.index + 1} of ${merge.count}`}</span
150
+ >
151
+ <button
152
+ type="button"
153
+ data-merge-next=""
154
+ onclick={merge.next}
155
+ disabled={merge.index >= merge.count - 1}>Next</button
156
+ >
157
+ </div>
158
+
159
+ <div data-merge-output="">
160
+ <TemplateRenderer
161
+ {content}
162
+ templates={templates ?? []}
163
+ {bindings}
164
+ {templateColorMap}
165
+ resolve={!!templates && templates.length > 0}
166
+ values={merge.values}
167
+ />
168
+ </div>
169
+
170
+ <div class="flex flex-wrap items-center gap-2">
171
+ <button type="button" data-merge-all="" disabled={merge.count === 0} onclick={mergeAll}>Merge all</button>
172
+ {#if merge.result}
173
+ <span data-merge-result=""
174
+ >{`${merge.result.outputs.length} merged${merge.result.skipped.length ? `, ${merge.result.skipped.length} skipped` : ""}${merge.result.truncated ? " (stopped early)" : ""}`}</span
175
+ >
176
+ <button type="button" data-merge-copy="" onclick={copy}>{copied ? "Copied" : "Copy"}</button>
177
+ <button
178
+ type="button"
179
+ data-merge-download="text"
180
+ onclick={() => downloadText(merge.serialize("text"), `${downloadName}.txt`, "text/plain")}
181
+ >Download .txt</button
182
+ >
183
+ <button
184
+ type="button"
185
+ data-merge-download="json"
186
+ onclick={() => downloadText(merge.serialize("json"), `${downloadName}.json`, "application/json")}
187
+ >Download .json</button
188
+ >
189
+ {/if}
190
+ </div>
191
+ {#if merge.result}
192
+ <textarea
193
+ readonly
194
+ aria-label="Merged output"
195
+ data-merge-text=""
196
+ rows={6}
197
+ value={merge.serialize("text")}
198
+ class="px-2 py-1 font-mono text-xs rounded-md border border-violet-400/40 bg-transparent"
199
+ ></textarea>
200
+ {/if}
201
+ </div>
@@ -0,0 +1,25 @@
1
+ import type { ColorGradient, MergeRecordsOptions, MergeRecordsResult, ParseDataSourceOptions, Template } from "@pixerate/editor";
2
+ interface Props {
3
+ /** The prompt merged with each record. */
4
+ content: string;
5
+ templates?: Template[];
6
+ bindings?: Record<string, string>;
7
+ templateColorMap?: Map<string, ColorGradient>;
8
+ /** Initial CSV / TSV / JSON data source text. */
9
+ defaultSource?: string;
10
+ parseOptions?: ParseDataSourceOptions;
11
+ /** Drop records matching this condition (e.g. `"status=void"`) when merging. */
12
+ skipIf?: MergeRecordsOptions["skipIf"];
13
+ /** Separator between merged outputs (default a blank line; `"none"` for nothing). */
14
+ separator?: string;
15
+ maxOutputs?: number;
16
+ /** Called after "Merge all". */
17
+ onMerge?: (result: MergeRecordsResult) => void;
18
+ /** Base file name for downloads (default `merged`). */
19
+ downloadName?: string;
20
+ class?: string;
21
+ }
22
+ declare const MergePreview: import("svelte").Component<Props, {}, "">;
23
+ type MergePreview = ReturnType<typeof MergePreview>;
24
+ export default MergePreview;
25
+ //# sourceMappingURL=MergePreview.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MergePreview.svelte.d.ts","sourceRoot":"","sources":["../src/MergePreview.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACR,aAAa,EACb,mBAAmB,EACnB,kBAAkB,EAClB,sBAAsB,EACtB,QAAQ,EACT,MAAM,kBAAkB,CAAC;AAK1B,UAAU,KAAK;IACb,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC9C,iDAAiD;IACjD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,sBAAsB,CAAC;IACtC,gFAAgF;IAChF,MAAM,CAAC,EAAE,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IACvC,qFAAqF;IACrF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gCAAgC;IAChC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAC/C,uDAAuD;IACvD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAyHH,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -0,0 +1,169 @@
1
+ <script lang="ts">
2
+ import type { Editor } from "@tiptap/core";
3
+ import { createParagraphStyles, type SvelteParagraphStyles } from "./paragraphStyles.svelte.js";
4
+
5
+ interface Props {
6
+ editor?: Editor | null;
7
+ /**
8
+ * `dropdown` (default): a button showing the current style that opens the
9
+ * list. `gallery`: the styles are always shown, in a row.
10
+ */
11
+ variant?: "dropdown" | "gallery";
12
+ /** List every available paragraph style instead of the gallery (quick) styles. */
13
+ showAllStyles?: boolean;
14
+ /** Show the "Update to match selection" and "New style from selection" actions. Defaults to `true`. */
15
+ showActions?: boolean;
16
+ /** Preview each style's font, weight, style and color in its entry. Defaults to `true`. */
17
+ preview?: boolean;
18
+ class?: string;
19
+ itemClass?: string;
20
+ activeItemClass?: string;
21
+ /** Called after a style is applied, updated or created. */
22
+ onchange?: (event: { type: "apply" | "update" | "create"; id: string }) => void;
23
+ }
24
+
25
+ let {
26
+ editor = null,
27
+ variant = "dropdown",
28
+ showAllStyles = false,
29
+ showActions = true,
30
+ preview = true,
31
+ class: className = "relative inline-flex flex-wrap items-center gap-1 text-sm",
32
+ itemClass = "rounded px-2 py-1 text-left text-xs text-foreground hover:bg-muted",
33
+ activeItemClass = "bg-primary/10 text-primary",
34
+ onchange,
35
+ }: Props = $props();
36
+
37
+ let styles = $state.raw<SvelteParagraphStyles>(createParagraphStyles(null));
38
+ $effect(() => {
39
+ const s = createParagraphStyles(editor);
40
+ styles = s;
41
+ return () => s.destroy();
42
+ });
43
+
44
+ let open = $state(false);
45
+ let creating = $state(false);
46
+ let name = $state("");
47
+
48
+ let list = $derived(showAllStyles ? styles.current.styles : styles.current.gallery);
49
+ let current = $derived(styles.currentStyle);
50
+ let expanded = $derived(variant === "gallery" || open);
51
+
52
+ function close() {
53
+ if (variant === "dropdown") open = false;
54
+ creating = false;
55
+ name = "";
56
+ }
57
+
58
+ function previewStyle(id: string): string | undefined {
59
+ if (!preview || !styles.registry) return undefined;
60
+ return Object.entries(styles.registry.previewDeclarations(id))
61
+ .map(([property, value]) => `${property}: ${value}`)
62
+ .join("; ");
63
+ }
64
+
65
+ function apply(id: string) {
66
+ if (styles.apply(id)) onchange?.({ type: "apply", id });
67
+ close();
68
+ }
69
+
70
+ function update() {
71
+ if (current && styles.updateFromSelection(current.id)) {
72
+ onchange?.({ type: "update", id: current.id });
73
+ }
74
+ close();
75
+ }
76
+
77
+ function create(event: SubmitEvent) {
78
+ event.preventDefault();
79
+ const id = name.trim() ? styles.createFromSelection(name) : null;
80
+ if (id) onchange?.({ type: "create", id });
81
+ close();
82
+ }
83
+
84
+ const keepSelection = (event: MouseEvent) => event.preventDefault();
85
+ </script>
86
+
87
+ <div class={className} role="group" aria-label="Paragraph styles" data-styles-gallery={variant}>
88
+ {#if variant === "dropdown"}
89
+ <button
90
+ type="button"
91
+ class={itemClass}
92
+ aria-haspopup="listbox"
93
+ aria-expanded={open}
94
+ disabled={!editor || !styles.registry}
95
+ onmousedown={keepSelection}
96
+ onclick={() => (open = !open)}
97
+ >
98
+ {current?.name ?? "Styles"} ▾
99
+ </button>
100
+ {/if}
101
+ {#if expanded}
102
+ <div
103
+ class={variant === "dropdown"
104
+ ? "absolute left-0 top-full z-20 mt-1 flex min-w-[12rem] flex-col gap-0.5 rounded-md border border-border bg-background p-1 shadow-md"
105
+ : "flex flex-wrap items-center gap-1"}
106
+ >
107
+ <div
108
+ role="listbox"
109
+ aria-label="Styles"
110
+ class={variant === "dropdown" ? "flex flex-col gap-0.5" : "flex flex-wrap gap-1"}
111
+ >
112
+ {#each list as style (style.id)}
113
+ {@const active = style.id === styles.current.currentStyleId}
114
+ <button
115
+ type="button"
116
+ role="option"
117
+ aria-selected={active}
118
+ data-style-id={style.id}
119
+ class={active ? `${itemClass} ${activeItemClass}` : itemClass}
120
+ style={previewStyle(style.id)}
121
+ onmousedown={keepSelection}
122
+ onclick={() => apply(style.id)}
123
+ >
124
+ {style.name}
125
+ </button>
126
+ {/each}
127
+ </div>
128
+ {#if showActions}
129
+ <div
130
+ class={variant === "dropdown"
131
+ ? "mt-1 flex flex-col gap-0.5 border-t border-border pt-1"
132
+ : "flex items-center gap-1"}
133
+ >
134
+ <button
135
+ type="button"
136
+ class={itemClass}
137
+ disabled={!current}
138
+ onmousedown={keepSelection}
139
+ onclick={update}
140
+ >
141
+ {current ? `Update ${current.name} to match selection` : "Update to match selection"}
142
+ </button>
143
+ {#if creating}
144
+ <form class="flex items-center gap-1" onsubmit={create}>
145
+ <!-- svelte-ignore a11y_autofocus -->
146
+ <input
147
+ aria-label="New style name"
148
+ class="w-32 rounded border border-border bg-background px-1 py-0.5 text-xs"
149
+ bind:value={name}
150
+ autofocus
151
+ />
152
+ <button type="submit" class={itemClass} disabled={!name.trim()}>Create</button>
153
+ </form>
154
+ {:else}
155
+ <button
156
+ type="button"
157
+ class={itemClass}
158
+ disabled={!editor || !styles.registry}
159
+ onmousedown={keepSelection}
160
+ onclick={() => (creating = true)}
161
+ >
162
+ New style from selection
163
+ </button>
164
+ {/if}
165
+ </div>
166
+ {/if}
167
+ </div>
168
+ {/if}
169
+ </div>
@@ -0,0 +1,27 @@
1
+ import type { Editor } from "@tiptap/core";
2
+ interface Props {
3
+ editor?: Editor | null;
4
+ /**
5
+ * `dropdown` (default): a button showing the current style that opens the
6
+ * list. `gallery`: the styles are always shown, in a row.
7
+ */
8
+ variant?: "dropdown" | "gallery";
9
+ /** List every available paragraph style instead of the gallery (quick) styles. */
10
+ showAllStyles?: boolean;
11
+ /** Show the "Update to match selection" and "New style from selection" actions. Defaults to `true`. */
12
+ showActions?: boolean;
13
+ /** Preview each style's font, weight, style and color in its entry. Defaults to `true`. */
14
+ preview?: boolean;
15
+ class?: string;
16
+ itemClass?: string;
17
+ activeItemClass?: string;
18
+ /** Called after a style is applied, updated or created. */
19
+ onchange?: (event: {
20
+ type: "apply" | "update" | "create";
21
+ id: string;
22
+ }) => void;
23
+ }
24
+ declare const StylesGallery: import("svelte").Component<Props, {}, "">;
25
+ type StylesGallery = ReturnType<typeof StylesGallery>;
26
+ export default StylesGallery;
27
+ //# sourceMappingURL=StylesGallery.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StylesGallery.svelte.d.ts","sourceRoot":"","sources":["../src/StylesGallery.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAIzC,UAAU,KAAK;IACb,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB;;;OAGG;IACH,OAAO,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;IACjC,kFAAkF;IAClF,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,uGAAuG;IACvG,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,2FAA2F;IAC3F,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACjF;AAgHH,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -1,6 +1,8 @@
1
1
  <script lang="ts">
2
2
  import {
3
3
  evaluatePromptTokens,
4
+ describeConditionalTag,
5
+ formatConditionalTag,
4
6
  resolveTemplates,
5
7
  type Template,
6
8
  type ColorGradient,
@@ -36,8 +38,6 @@
36
38
  onVariableClick,
37
39
  }: Props = $props();
38
40
 
39
- const CONDITIONAL_PREFIX = { if: "?", unless: "!", end: "/" } as const;
40
-
41
41
  let displayText = $derived(
42
42
  resolve && templates.length > 0
43
43
  ? resolveTemplates(content, templates, { bindings })
@@ -58,12 +58,6 @@
58
58
  .join("\n");
59
59
  }
60
60
 
61
- function conditionalTitle(token: (typeof tokens)[number]): string {
62
- if (token.conditional === "end") return `End of section: ${token.value}`;
63
- return token.conditional === "if"
64
- ? `Shown when "${token.value}" has a value`
65
- : `Shown when "${token.value}" is empty`;
66
- }
67
61
  </script>
68
62
 
69
63
  <div class={className}>
@@ -102,8 +96,8 @@
102
96
  <span
103
97
  data-conditional={token.conditional}
104
98
  class="inline-flex items-center px-1 py-0.5 mx-0.5 text-xs font-medium rounded-md border border-amber-400/40 bg-amber-400/15 text-amber-700"
105
- title={conditionalTitle(token)}
106
- >{`{${CONDITIONAL_PREFIX[token.conditional]}${token.value}}`}</span>
99
+ title={describeConditionalTag(token)}
100
+ >{formatConditionalTag(token)}</span>
107
101
  {:else if token.type === "instruction"}
108
102
  <span
109
103
  class="inline-flex items-center px-1.5 py-0.5 mx-0.5 text-xs italic font-medium rounded-md border border-rose-400/40 bg-rose-400/15 text-rose-600"
@@ -1 +1 @@
1
- {"version":3,"file":"TemplateRenderer.svelte.d.ts","sourceRoot":"","sources":["../src/TemplateRenderer.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAGH,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,YAAY,EAClB,MAAM,kBAAkB,CAAC;AAG1B,UAAU,KAAK;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;IACvB,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC9C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC;;;OAGG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,eAAe,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;CAClD;AA6EH,QAAA,MAAM,gBAAgB,2CAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"TemplateRenderer.svelte.d.ts","sourceRoot":"","sources":["../src/TemplateRenderer.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAKH,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,YAAY,EAClB,MAAM,kBAAkB,CAAC;AAG1B,UAAU,KAAK;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;IACvB,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC9C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC;;;OAGG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,eAAe,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;CAClD;AAqEH,QAAA,MAAM,gBAAgB,2CAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
@@ -0,0 +1,93 @@
1
+ <script lang="ts">
2
+ import {
3
+ bindCanvasToken,
4
+ getBindableTokenProperties,
5
+ getEffectiveTokenModes,
6
+ getNodeTokenBindings,
7
+ getTokensForProperty,
8
+ type CanvasTokenOptions
9
+ } from '@pixerate/editor/canvas';
10
+ import { resolveToken, type TokenState } from '@pixerate/editor/tokens';
11
+ import type { CanvasNode } from './types.js';
12
+
13
+ interface Props {
14
+ state: TokenState;
15
+ nodes: CanvasNode[];
16
+ /** Nodes to bind (the first one decides which properties are listed). */
17
+ selectedIds: readonly string[];
18
+ /** Receives the nodes with the binding applied and resolved. */
19
+ onchange: (nodes: CanvasNode[]) => void;
20
+ options?: CanvasTokenOptions;
21
+ /** Limit to these property keys (in this order). */
22
+ properties?: readonly string[];
23
+ onerror?: (error: Error) => void;
24
+ class?: string;
25
+ }
26
+
27
+ let { state, nodes, selectedIds, onchange, options = {}, properties, onerror, class: className = '' }: Props = $props();
28
+
29
+ const selected = $derived(nodes.filter((n) => selectedIds.includes(n.id)));
30
+ const first = $derived(selected[0]);
31
+ const rows = $derived.by(() => {
32
+ if (!first) return [];
33
+ const all = getBindableTokenProperties(first as any, options);
34
+ return properties ? properties.map((key) => all.find((p) => p.key === key)).filter((p) => !!p) : all;
35
+ });
36
+ const modes = $derived(first ? getEffectiveTokenModes(first as any, nodes as any, state, options) : {});
37
+
38
+ function valueOf(key: string) {
39
+ const ids = new Set(selected.map((n) => getNodeTokenBindings(n as any)[key] ?? ''));
40
+ return ids.size === 1 ? [...ids][0] : '__mixed';
41
+ }
42
+
43
+ function change(key: string, id: string) {
44
+ try {
45
+ onchange(bindCanvasToken(nodes as any[], selectedIds, key, id || null, state, options));
46
+ } catch (error) {
47
+ onerror?.(error as Error);
48
+ }
49
+ }
50
+ </script>
51
+
52
+ {#if first}
53
+ <div data-token-binding-picker="" class="flex flex-col gap-1 text-xs text-foreground {className}">
54
+ {#each rows as property (property!.key)}
55
+ {@const value = valueOf(property!.key)}
56
+ {@const resolved = value && value !== '__mixed' ? resolveToken(state, value, modes) : null}
57
+ <label class="flex items-center gap-2" data-token-property={property!.key}>
58
+ <span class="w-28 shrink-0 text-muted-foreground">{property!.label}</span>
59
+ <select
60
+ aria-label="{property!.label} variable"
61
+ class="min-w-0 flex-1 rounded border border-border bg-background px-1 py-0.5 text-xs"
62
+ {value}
63
+ onchange={(e) => change(property!.key, e.currentTarget.value)}
64
+ >
65
+ {#if value === '__mixed'}<option value="__mixed">Mixed</option>{/if}
66
+ <option value="">None</option>
67
+ {#each getTokensForProperty(state, property!, first as any) as v (v.id)}
68
+ {@const collection = state.collections.find((c) => c.id === v.collectionId)}
69
+ <option value={v.id}>{v.name}{collection && state.collections.length > 1 ? ` (${collection.name})` : ''}</option>
70
+ {/each}
71
+ </select>
72
+ <span class="w-16 shrink-0 truncate text-right text-muted-foreground" data-token-resolved="">
73
+ {#if resolved?.ok}
74
+ {#if property!.type === 'color'}
75
+ <span
76
+ class="inline-block h-3 w-3 rounded border border-border align-middle"
77
+ style:background={String(resolved.value)}
78
+ title={String(resolved.value)}
79
+ ></span>
80
+ {:else}
81
+ {String(resolved.value)}
82
+ {/if}
83
+ {:else if resolved}
84
+ <span title={resolved.message}>⚠</span>
85
+ {/if}
86
+ </span>
87
+ </label>
88
+ {/each}
89
+ {#if state.collections.length === 0}
90
+ <p class="text-muted-foreground">No variables yet.</p>
91
+ {/if}
92
+ </div>
93
+ {/if}
@@ -0,0 +1,20 @@
1
+ import { type CanvasTokenOptions } from '@pixerate/editor/canvas';
2
+ import { type TokenState } from '@pixerate/editor/tokens';
3
+ import type { CanvasNode } from './types.js';
4
+ interface Props {
5
+ state: TokenState;
6
+ nodes: CanvasNode[];
7
+ /** Nodes to bind (the first one decides which properties are listed). */
8
+ selectedIds: readonly string[];
9
+ /** Receives the nodes with the binding applied and resolved. */
10
+ onchange: (nodes: CanvasNode[]) => void;
11
+ options?: CanvasTokenOptions;
12
+ /** Limit to these property keys (in this order). */
13
+ properties?: readonly string[];
14
+ onerror?: (error: Error) => void;
15
+ class?: string;
16
+ }
17
+ declare const TokenBindingPicker: import("svelte").Component<Props, {}, "">;
18
+ type TokenBindingPicker = ReturnType<typeof TokenBindingPicker>;
19
+ export default TokenBindingPicker;
20
+ //# sourceMappingURL=TokenBindingPicker.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TokenBindingPicker.svelte.d.ts","sourceRoot":"","sources":["../../src/canvas/TokenBindingPicker.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAML,KAAK,kBAAkB,EACvB,MAAM,yBAAyB,CAAC;AAClC,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACxE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG5C,UAAU,KAAK;IACd,KAAK,EAAE,UAAU,CAAC;IAClB,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,yEAAyE;IACzE,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,gEAAgE;IAChE,QAAQ,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,IAAI,CAAC;IACxC,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,oDAAoD;IACpD,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAoEF,QAAA,MAAM,kBAAkB,2CAAwC,CAAC;AACjE,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAChE,eAAe,kBAAkB,CAAC"}