@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.
- package/README.md +6 -2
- package/dist/MergePreview.svelte +201 -0
- package/dist/MergePreview.svelte.d.ts +25 -0
- package/dist/MergePreview.svelte.d.ts.map +1 -0
- package/dist/StylesGallery.svelte +169 -0
- package/dist/StylesGallery.svelte.d.ts +27 -0
- package/dist/StylesGallery.svelte.d.ts.map +1 -0
- package/dist/TemplateRenderer.svelte +4 -10
- package/dist/TemplateRenderer.svelte.d.ts.map +1 -1
- package/dist/canvas/TokenBindingPicker.svelte +93 -0
- package/dist/canvas/TokenBindingPicker.svelte.d.ts +20 -0
- package/dist/canvas/TokenBindingPicker.svelte.d.ts.map +1 -0
- package/dist/canvas/TokenModeSwitcher.svelte +55 -0
- package/dist/canvas/TokenModeSwitcher.svelte.d.ts +18 -0
- package/dist/canvas/TokenModeSwitcher.svelte.d.ts.map +1 -0
- package/dist/canvas/createCanvasArrange.svelte.d.ts +4 -0
- package/dist/canvas/createCanvasArrange.svelte.d.ts.map +1 -1
- package/dist/canvas/createCanvasAutoLayout.svelte.d.ts +68 -0
- package/dist/canvas/createCanvasAutoLayout.svelte.d.ts.map +1 -0
- package/dist/canvas/createCanvasAutoLayout.svelte.js +78 -0
- package/dist/canvas/createCanvasTokens.svelte.d.ts +31 -0
- package/dist/canvas/createCanvasTokens.svelte.d.ts.map +1 -0
- package/dist/canvas/createCanvasTokens.svelte.js +50 -0
- package/dist/canvas/index.d.ts +7 -0
- package/dist/canvas/index.d.ts.map +1 -1
- package/dist/canvas/index.js +10 -0
- package/dist/fileImport.svelte.d.ts +8 -1
- package/dist/fileImport.svelte.d.ts.map +1 -1
- package/dist/fileImport.svelte.js +11 -1
- package/dist/find-replace/FindReplaceBar.svelte +203 -0
- package/dist/find-replace/FindReplaceBar.svelte.d.ts +15 -0
- package/dist/find-replace/FindReplaceBar.svelte.d.ts.map +1 -0
- package/dist/find-replace/findReplace.svelte.d.ts +27 -0
- package/dist/find-replace/findReplace.svelte.d.ts.map +1 -0
- package/dist/find-replace/findReplace.svelte.js +79 -0
- package/dist/find-replace/index.d.ts +6 -0
- package/dist/find-replace/index.d.ts.map +1 -0
- package/dist/find-replace/index.js +2 -0
- package/dist/generation/AssetMentionChips.svelte +130 -0
- package/dist/generation/AssetMentionChips.svelte.d.ts +21 -0
- package/dist/generation/AssetMentionChips.svelte.d.ts.map +1 -0
- package/dist/generation/GenerationHistory.svelte +161 -0
- package/dist/generation/GenerationHistory.svelte.d.ts +28 -0
- package/dist/generation/GenerationHistory.svelte.d.ts.map +1 -0
- package/dist/generation/GenerationSettings.svelte +174 -0
- package/dist/generation/GenerationSettings.svelte.d.ts +23 -0
- package/dist/generation/GenerationSettings.svelte.d.ts.map +1 -0
- package/dist/generation/JobProgress.svelte +53 -0
- package/dist/generation/JobProgress.svelte.d.ts +17 -0
- package/dist/generation/JobProgress.svelte.d.ts.map +1 -0
- package/dist/generation/index.d.ts +17 -0
- package/dist/generation/index.d.ts.map +1 -0
- package/dist/generation/index.js +7 -0
- package/dist/generation/plan.svelte.d.ts +16 -0
- package/dist/generation/plan.svelte.d.ts.map +1 -0
- package/dist/generation/plan.svelte.js +26 -0
- package/dist/generation/progress.svelte.d.ts +25 -0
- package/dist/generation/progress.svelte.d.ts.map +1 -0
- package/dist/generation/progress.svelte.js +36 -0
- package/dist/generation/records.svelte.d.ts +18 -0
- package/dist/generation/records.svelte.d.ts.map +1 -0
- package/dist/generation/records.svelte.js +66 -0
- package/dist/image-editor/AdjustmentLayersPanel.svelte +309 -0
- package/dist/image-editor/AdjustmentLayersPanel.svelte.d.ts +11 -0
- package/dist/image-editor/AdjustmentLayersPanel.svelte.d.ts.map +1 -0
- package/dist/image-editor/ControlSlider.svelte +39 -0
- package/dist/image-editor/ControlSlider.svelte.d.ts +13 -0
- package/dist/image-editor/ControlSlider.svelte.d.ts.map +1 -0
- package/dist/image-editor/CurvesEditor.svelte +105 -0
- package/dist/image-editor/CurvesEditor.svelte.d.ts +13 -0
- package/dist/image-editor/CurvesEditor.svelte.d.ts.map +1 -0
- package/dist/image-editor/Histogram.svelte +49 -0
- package/dist/image-editor/Histogram.svelte.d.ts +12 -0
- package/dist/image-editor/Histogram.svelte.d.ts.map +1 -0
- package/dist/image-editor/ImageEditor.svelte +104 -141
- package/dist/image-editor/ImageEditor.svelte.d.ts.map +1 -1
- package/dist/image-editor/RefineEdgePanel.svelte +180 -0
- package/dist/image-editor/RefineEdgePanel.svelte.d.ts +13 -0
- package/dist/image-editor/RefineEdgePanel.svelte.d.ts.map +1 -0
- package/dist/image-editor/SelectionOptionsBar.svelte +135 -0
- package/dist/image-editor/SelectionOptionsBar.svelte.d.ts +15 -0
- package/dist/image-editor/SelectionOptionsBar.svelte.d.ts.map +1 -0
- package/dist/image-editor/imageEditorState.svelte.d.ts +14 -1
- package/dist/image-editor/imageEditorState.svelte.d.ts.map +1 -1
- package/dist/image-editor/imageEditorState.svelte.js +40 -0
- package/dist/image-editor/index.d.ts +18 -0
- package/dist/image-editor/index.d.ts.map +1 -1
- package/dist/image-editor/index.js +6 -0
- package/dist/index.d.ts +12 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/merge.svelte.d.ts +51 -0
- package/dist/merge.svelte.d.ts.map +1 -0
- package/dist/merge.svelte.js +117 -0
- package/dist/paragraphStyles.svelte.d.ts +30 -0
- package/dist/paragraphStyles.svelte.d.ts.map +1 -0
- package/dist/paragraphStyles.svelte.js +44 -0
- package/dist/persistence/RecoveryPrompt.svelte +57 -0
- package/dist/persistence/RecoveryPrompt.svelte.d.ts +18 -0
- package/dist/persistence/RecoveryPrompt.svelte.d.ts.map +1 -0
- package/dist/persistence/autosave.svelte.d.ts +63 -0
- package/dist/persistence/autosave.svelte.d.ts.map +1 -0
- package/dist/persistence/autosave.svelte.js +110 -0
- package/dist/persistence/index.d.ts +6 -0
- package/dist/persistence/index.d.ts.map +1 -0
- package/dist/persistence/index.js +2 -0
- package/dist/review/TrackedChangesList.svelte +67 -0
- package/dist/review/TrackedChangesList.svelte.d.ts +13 -0
- package/dist/review/TrackedChangesList.svelte.d.ts.map +1 -0
- package/dist/review/documentCompare.svelte.d.ts +21 -0
- package/dist/review/documentCompare.svelte.d.ts.map +1 -0
- package/dist/review/documentCompare.svelte.js +23 -0
- package/dist/review/index.d.ts +4 -0
- package/dist/review/index.d.ts.map +1 -1
- package/dist/review/index.js +2 -0
- package/dist/review/review.svelte.d.ts +6 -0
- package/dist/review/review.svelte.d.ts.map +1 -1
- package/dist/review/review.svelte.js +3 -0
- package/dist/spreadsheet/FormulaBar.svelte +13 -4
- package/dist/spreadsheet/FormulaBar.svelte.d.ts.map +1 -1
- package/dist/spreadsheet/FormulaHints.svelte +7 -5
- package/dist/spreadsheet/FormulaHints.svelte.d.ts +3 -0
- package/dist/spreadsheet/FormulaHints.svelte.d.ts.map +1 -1
- package/dist/spreadsheet/NameManager.svelte +165 -0
- package/dist/spreadsheet/NameManager.svelte.d.ts +11 -0
- package/dist/spreadsheet/NameManager.svelte.d.ts.map +1 -0
- package/dist/spreadsheet/SheetTabs.svelte +161 -0
- package/dist/spreadsheet/SheetTabs.svelte.d.ts +10 -0
- package/dist/spreadsheet/SheetTabs.svelte.d.ts.map +1 -0
- package/dist/spreadsheet/SpreadsheetConditionalFormatPanel.svelte +184 -0
- package/dist/spreadsheet/SpreadsheetConditionalFormatPanel.svelte.d.ts +9 -0
- package/dist/spreadsheet/SpreadsheetConditionalFormatPanel.svelte.d.ts.map +1 -0
- package/dist/spreadsheet/SpreadsheetContextMenu.svelte +13 -5
- package/dist/spreadsheet/SpreadsheetContextMenu.svelte.d.ts +5 -1
- package/dist/spreadsheet/SpreadsheetContextMenu.svelte.d.ts.map +1 -1
- package/dist/spreadsheet/SpreadsheetDataValidationPanel.svelte +141 -0
- package/dist/spreadsheet/SpreadsheetDataValidationPanel.svelte.d.ts +9 -0
- package/dist/spreadsheet/SpreadsheetDataValidationPanel.svelte.d.ts.map +1 -0
- package/dist/spreadsheet/SpreadsheetEditor.svelte +199 -11
- package/dist/spreadsheet/SpreadsheetEditor.svelte.d.ts +2 -0
- package/dist/spreadsheet/SpreadsheetEditor.svelte.d.ts.map +1 -1
- package/dist/spreadsheet/SpreadsheetValidationDialog.svelte +92 -0
- package/dist/spreadsheet/SpreadsheetValidationDialog.svelte.d.ts +9 -0
- package/dist/spreadsheet/SpreadsheetValidationDialog.svelte.d.ts.map +1 -0
- package/dist/spreadsheet/index.d.ts +9 -0
- package/dist/spreadsheet/index.d.ts.map +1 -1
- package/dist/spreadsheet/index.js +5 -0
- package/dist/spreadsheet/spreadsheet.svelte.d.ts +57 -2
- package/dist/spreadsheet/spreadsheet.svelte.d.ts.map +1 -1
- package/dist/spreadsheet/spreadsheet.svelte.js +105 -6
- package/dist/tokens/TokenSwatch.svelte +14 -0
- package/dist/tokens/TokenSwatch.svelte.d.ts +8 -0
- package/dist/tokens/TokenSwatch.svelte.d.ts.map +1 -0
- package/dist/tokens/TokensPanel.svelte +323 -0
- package/dist/tokens/TokensPanel.svelte.d.ts +14 -0
- package/dist/tokens/TokensPanel.svelte.d.ts.map +1 -0
- package/dist/tokens/createTokens.svelte.d.ts +31 -0
- package/dist/tokens/createTokens.svelte.d.ts.map +1 -0
- package/dist/tokens/createTokens.svelte.js +43 -0
- package/dist/tokens/index.d.ts +7 -0
- package/dist/tokens/index.d.ts.map +1 -0
- package/dist/tokens/index.js +3 -0
- 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={
|
|
106
|
-
>{
|
|
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,
|
|
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"}
|