@pixerate/editor-svelte 0.23.0 → 0.25.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 (147) hide show
  1. package/README.md +4 -2
  2. package/dist/ExportDocxButton.svelte +48 -0
  3. package/dist/ExportDocxButton.svelte.d.ts +13 -0
  4. package/dist/ExportDocxButton.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/canvas/AnimationTimeline.svelte +217 -0
  9. package/dist/canvas/AnimationTimeline.svelte.d.ts +20 -0
  10. package/dist/canvas/AnimationTimeline.svelte.d.ts.map +1 -0
  11. package/dist/canvas/CanvasComponentsPanel.svelte +73 -0
  12. package/dist/canvas/CanvasComponentsPanel.svelte.d.ts +13 -0
  13. package/dist/canvas/CanvasComponentsPanel.svelte.d.ts.map +1 -0
  14. package/dist/canvas/CanvasInstanceInspector.svelte +136 -0
  15. package/dist/canvas/CanvasInstanceInspector.svelte.d.ts +16 -0
  16. package/dist/canvas/CanvasInstanceInspector.svelte.d.ts.map +1 -0
  17. package/dist/canvas/TokenBindingPicker.svelte +93 -0
  18. package/dist/canvas/TokenBindingPicker.svelte.d.ts +20 -0
  19. package/dist/canvas/TokenBindingPicker.svelte.d.ts.map +1 -0
  20. package/dist/canvas/TokenModeSwitcher.svelte +55 -0
  21. package/dist/canvas/TokenModeSwitcher.svelte.d.ts +18 -0
  22. package/dist/canvas/TokenModeSwitcher.svelte.d.ts.map +1 -0
  23. package/dist/canvas/createCanvasAnimation.svelte.d.ts +98 -0
  24. package/dist/canvas/createCanvasAnimation.svelte.d.ts.map +1 -0
  25. package/dist/canvas/createCanvasAnimation.svelte.js +79 -0
  26. package/dist/canvas/createCanvasArrange.svelte.d.ts +4 -0
  27. package/dist/canvas/createCanvasArrange.svelte.d.ts.map +1 -1
  28. package/dist/canvas/createCanvasAutoLayout.svelte.d.ts.map +1 -1
  29. package/dist/canvas/createCanvasComponents.svelte.d.ts +68 -0
  30. package/dist/canvas/createCanvasComponents.svelte.d.ts.map +1 -0
  31. package/dist/canvas/createCanvasComponents.svelte.js +31 -0
  32. package/dist/canvas/createCanvasTokens.svelte.d.ts +31 -0
  33. package/dist/canvas/createCanvasTokens.svelte.d.ts.map +1 -0
  34. package/dist/canvas/createCanvasTokens.svelte.js +50 -0
  35. package/dist/canvas/index.d.ts +12 -0
  36. package/dist/canvas/index.d.ts.map +1 -1
  37. package/dist/canvas/index.js +16 -0
  38. package/dist/docxExport.svelte.d.ts +26 -0
  39. package/dist/docxExport.svelte.d.ts.map +1 -0
  40. package/dist/docxExport.svelte.js +36 -0
  41. package/dist/fileImport.svelte.d.ts +8 -1
  42. package/dist/fileImport.svelte.d.ts.map +1 -1
  43. package/dist/fileImport.svelte.js +11 -1
  44. package/dist/generation/AssetMentionChips.svelte +130 -0
  45. package/dist/generation/AssetMentionChips.svelte.d.ts +21 -0
  46. package/dist/generation/AssetMentionChips.svelte.d.ts.map +1 -0
  47. package/dist/generation/GenerationHistory.svelte +161 -0
  48. package/dist/generation/GenerationHistory.svelte.d.ts +28 -0
  49. package/dist/generation/GenerationHistory.svelte.d.ts.map +1 -0
  50. package/dist/generation/index.d.ts +7 -0
  51. package/dist/generation/index.d.ts.map +1 -1
  52. package/dist/generation/index.js +3 -0
  53. package/dist/generation/records.svelte.d.ts +18 -0
  54. package/dist/generation/records.svelte.d.ts.map +1 -0
  55. package/dist/generation/records.svelte.js +66 -0
  56. package/dist/image-editor/ImageEditor.svelte +72 -1
  57. package/dist/image-editor/ImageEditor.svelte.d.ts.map +1 -1
  58. package/dist/image-editor/RefineEdgePanel.svelte +180 -0
  59. package/dist/image-editor/RefineEdgePanel.svelte.d.ts +13 -0
  60. package/dist/image-editor/RefineEdgePanel.svelte.d.ts.map +1 -0
  61. package/dist/image-editor/SelectionOptionsBar.svelte +135 -0
  62. package/dist/image-editor/SelectionOptionsBar.svelte.d.ts +15 -0
  63. package/dist/image-editor/SelectionOptionsBar.svelte.d.ts.map +1 -0
  64. package/dist/image-editor/index.d.ts +6 -0
  65. package/dist/image-editor/index.d.ts.map +1 -1
  66. package/dist/image-editor/index.js +2 -0
  67. package/dist/index.d.ts +9 -0
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +5 -0
  70. package/dist/paragraphStyles.svelte.d.ts +30 -0
  71. package/dist/paragraphStyles.svelte.d.ts.map +1 -0
  72. package/dist/paragraphStyles.svelte.js +44 -0
  73. package/dist/review/TrackedChangesList.svelte +67 -0
  74. package/dist/review/TrackedChangesList.svelte.d.ts +13 -0
  75. package/dist/review/TrackedChangesList.svelte.d.ts.map +1 -0
  76. package/dist/review/index.d.ts +3 -0
  77. package/dist/review/index.d.ts.map +1 -1
  78. package/dist/review/index.js +1 -0
  79. package/dist/review/review.svelte.d.ts +6 -0
  80. package/dist/review/review.svelte.d.ts.map +1 -1
  81. package/dist/review/review.svelte.js +3 -0
  82. package/dist/spreadsheet/FormulaBar.svelte +13 -4
  83. package/dist/spreadsheet/FormulaBar.svelte.d.ts.map +1 -1
  84. package/dist/spreadsheet/FormulaHints.svelte +7 -5
  85. package/dist/spreadsheet/FormulaHints.svelte.d.ts +3 -0
  86. package/dist/spreadsheet/FormulaHints.svelte.d.ts.map +1 -1
  87. package/dist/spreadsheet/NameManager.svelte +165 -0
  88. package/dist/spreadsheet/NameManager.svelte.d.ts +11 -0
  89. package/dist/spreadsheet/NameManager.svelte.d.ts.map +1 -0
  90. package/dist/spreadsheet/SheetTabs.svelte +161 -0
  91. package/dist/spreadsheet/SheetTabs.svelte.d.ts +10 -0
  92. package/dist/spreadsheet/SheetTabs.svelte.d.ts.map +1 -0
  93. package/dist/spreadsheet/SpreadsheetConditionalFormatPanel.svelte +184 -0
  94. package/dist/spreadsheet/SpreadsheetConditionalFormatPanel.svelte.d.ts +9 -0
  95. package/dist/spreadsheet/SpreadsheetConditionalFormatPanel.svelte.d.ts.map +1 -0
  96. package/dist/spreadsheet/SpreadsheetContextMenu.svelte +9 -4
  97. package/dist/spreadsheet/SpreadsheetContextMenu.svelte.d.ts +5 -1
  98. package/dist/spreadsheet/SpreadsheetContextMenu.svelte.d.ts.map +1 -1
  99. package/dist/spreadsheet/SpreadsheetDataValidationPanel.svelte +141 -0
  100. package/dist/spreadsheet/SpreadsheetDataValidationPanel.svelte.d.ts +9 -0
  101. package/dist/spreadsheet/SpreadsheetDataValidationPanel.svelte.d.ts.map +1 -0
  102. package/dist/spreadsheet/SpreadsheetEditor.svelte +273 -19
  103. package/dist/spreadsheet/SpreadsheetEditor.svelte.d.ts +8 -0
  104. package/dist/spreadsheet/SpreadsheetEditor.svelte.d.ts.map +1 -1
  105. package/dist/spreadsheet/SpreadsheetGroupRow.svelte +31 -3
  106. package/dist/spreadsheet/SpreadsheetGroupRow.svelte.d.ts +6 -0
  107. package/dist/spreadsheet/SpreadsheetGroupRow.svelte.d.ts.map +1 -1
  108. package/dist/spreadsheet/SpreadsheetValidationDialog.svelte +92 -0
  109. package/dist/spreadsheet/SpreadsheetValidationDialog.svelte.d.ts +9 -0
  110. package/dist/spreadsheet/SpreadsheetValidationDialog.svelte.d.ts.map +1 -0
  111. package/dist/spreadsheet/index.d.ts +10 -0
  112. package/dist/spreadsheet/index.d.ts.map +1 -1
  113. package/dist/spreadsheet/index.js +6 -0
  114. package/dist/spreadsheet/spreadsheet.svelte.d.ts +56 -3
  115. package/dist/spreadsheet/spreadsheet.svelte.d.ts.map +1 -1
  116. package/dist/spreadsheet/spreadsheet.svelte.js +105 -6
  117. package/dist/spreadsheet/viewport.svelte.d.ts +26 -0
  118. package/dist/spreadsheet/viewport.svelte.d.ts.map +1 -0
  119. package/dist/spreadsheet/viewport.svelte.js +93 -0
  120. package/dist/tables/InsertTableGrid.svelte +85 -0
  121. package/dist/tables/InsertTableGrid.svelte.d.ts +21 -0
  122. package/dist/tables/InsertTableGrid.svelte.d.ts.map +1 -0
  123. package/dist/tables/TableBubbleMenu.svelte +43 -0
  124. package/dist/tables/TableBubbleMenu.svelte.d.ts +18 -0
  125. package/dist/tables/TableBubbleMenu.svelte.d.ts.map +1 -0
  126. package/dist/tables/TableToolbar.svelte +81 -0
  127. package/dist/tables/TableToolbar.svelte.d.ts +19 -0
  128. package/dist/tables/TableToolbar.svelte.d.ts.map +1 -0
  129. package/dist/tables/index.d.ts +12 -0
  130. package/dist/tables/index.d.ts.map +1 -0
  131. package/dist/tables/index.js +4 -0
  132. package/dist/tables/tableMenu.svelte.d.ts +21 -0
  133. package/dist/tables/tableMenu.svelte.d.ts.map +1 -0
  134. package/dist/tables/tableMenu.svelte.js +27 -0
  135. package/dist/tokens/TokenSwatch.svelte +14 -0
  136. package/dist/tokens/TokenSwatch.svelte.d.ts +8 -0
  137. package/dist/tokens/TokenSwatch.svelte.d.ts.map +1 -0
  138. package/dist/tokens/TokensPanel.svelte +323 -0
  139. package/dist/tokens/TokensPanel.svelte.d.ts +14 -0
  140. package/dist/tokens/TokensPanel.svelte.d.ts.map +1 -0
  141. package/dist/tokens/createTokens.svelte.d.ts +31 -0
  142. package/dist/tokens/createTokens.svelte.d.ts.map +1 -0
  143. package/dist/tokens/createTokens.svelte.js +43 -0
  144. package/dist/tokens/index.d.ts +7 -0
  145. package/dist/tokens/index.d.ts.map +1 -0
  146. package/dist/tokens/index.js +3 -0
  147. package/package.json +10 -2
package/README.md CHANGED
@@ -69,6 +69,8 @@ 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
+ Tables are on by default in `createRichTextPreset` (`table: false` turns them off). `<TableToolbar {editor} />` shows row, column, merge/split, header and alignment buttons while the selection is in a table (disabled buttons explain why in their tooltip; `items` picks the buttons, `showWhenOutside` keeps it visible). `<TableBubbleMenu {editor} />` shows it in a bubble menu next to the cursor, and `<InsertTableGrid {editor} oninsert={…} />` is a Word-style size picker. `createTableMenu(editor)` is the rune adapter for custom UIs.
73
+
72
74
  `<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
75
 
74
76
  ## More exports
@@ -76,8 +78,8 @@ Requires **Svelte 5** (the components use runes). Components ship as `.svelte` s
76
78
  | Import | Exports |
77
79
  | :--- | :--- |
78
80
  | `@pixerate/editor-svelte` | `EditableTextNodeEditor`, `BubbleMenu`, `TemplateRenderer`, `initiateEditor`, `createReactiveEditor`, `createHistory`, `createHistoryDirty`, `createHistoryShortcuts`, `navigationGuard` (action), `createNavigationGuard`, `SpreadsheetEditor`, `FormulaBar`, `FormulaHints`, `createReactiveSpreadsheet`, `DirtyTracker` |
79
- | `@pixerate/editor-svelte/spreadsheet` | `SpreadsheetEditor`, `FormulaBar`, `FormulaHints`, `SpreadsheetContextMenu`, `SpreadsheetGroupRow`, `createReactiveSpreadsheet` (also re-exported from the root entry) |
80
- | `@pixerate/editor-svelte/image-editor` | `ImageEditor`, `createReactiveImageEditor`: crop, rotate/flip, adjustments, annotations, depth masks, and `onInpaint` / `onRemoveBackground` AI hooks |
81
+ | `@pixerate/editor-svelte/spreadsheet` | `SpreadsheetEditor` (virtualised for large sheets, `virtualize` prop), `FormulaBar`, `FormulaHints`, `SpreadsheetContextMenu`, `SpreadsheetGroupRow`, `SheetTabs`, `NameManager`, `createReactiveSpreadsheet`, `createSpreadsheetViewport` (also re-exported from the root entry) |
82
+ | `@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 |
81
83
  | `@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
84
  | `@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
85
  | `@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) |
@@ -0,0 +1,48 @@
1
+ <script lang="ts">
2
+ import type { Editor } from "@tiptap/core";
3
+ import type { DocxExportControllerOptions, DocxExportReport } from "@pixerate/editor/export";
4
+ import { createDocxExport } from "./docxExport.svelte.js";
5
+
6
+ interface Props extends DocxExportControllerOptions {
7
+ editor?: Editor | null;
8
+ class?: string;
9
+ children?: import("svelte").Snippet;
10
+ /** Called after the download with the export report. */
11
+ onExported?: (report: DocxExportReport) => void;
12
+ }
13
+
14
+ let {
15
+ editor = null,
16
+ class: className = "rounded px-2 py-1 text-xs text-foreground hover:bg-muted disabled:opacity-50",
17
+ children,
18
+ onExported,
19
+ ...options
20
+ }: Props = $props();
21
+
22
+ // Options are applied (and kept current) by the effect below.
23
+ const docx = createDocxExport(() => editor);
24
+ $effect(() => {
25
+ docx.setOptions({ ...options });
26
+ });
27
+
28
+ async function exportNow() {
29
+ const report = await docx.download();
30
+ if (report) onExported?.(report);
31
+ }
32
+ </script>
33
+
34
+ <!-- A button that downloads the editor's document as a Word file (`.docx`). -->
35
+ <button
36
+ type="button"
37
+ class={className}
38
+ disabled={!editor || docx.isExporting}
39
+ aria-busy={docx.isExporting || undefined}
40
+ title={docx.error ? `Export failed: ${docx.error.message}` : undefined}
41
+ onclick={exportNow}
42
+ >
43
+ {#if children}
44
+ {@render children()}
45
+ {:else}
46
+ Export to Word
47
+ {/if}
48
+ </button>
@@ -0,0 +1,13 @@
1
+ import type { Editor } from "@tiptap/core";
2
+ import type { DocxExportControllerOptions, DocxExportReport } from "@pixerate/editor/export";
3
+ interface Props extends DocxExportControllerOptions {
4
+ editor?: Editor | null;
5
+ class?: string;
6
+ children?: import("svelte").Snippet;
7
+ /** Called after the download with the export report. */
8
+ onExported?: (report: DocxExportReport) => void;
9
+ }
10
+ declare const ExportDocxButton: import("svelte").Component<Props, {}, "">;
11
+ type ExportDocxButton = ReturnType<typeof ExportDocxButton>;
12
+ export default ExportDocxButton;
13
+ //# sourceMappingURL=ExportDocxButton.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExportDocxButton.svelte.d.ts","sourceRoot":"","sources":["../src/ExportDocxButton.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,2BAA2B,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAI3F,UAAU,KAAM,SAAQ,2BAA2B;IACjD,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAC;IACpC,wDAAwD;IACxD,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;CACjD;AAoCH,QAAA,MAAM,gBAAgB,2CAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,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"}
@@ -0,0 +1,217 @@
1
+ <script lang="ts">
2
+ import { onDestroy } from 'svelte';
3
+ import {
4
+ ANIMATION_EASING_LABELS,
5
+ ANIMATION_EASING_PRESETS,
6
+ formatAnimationTime,
7
+ getTimelineTicks,
8
+ timeToX,
9
+ xToTime,
10
+ type AnimationLoopMode,
11
+ type KeyframeRef
12
+ } from '@pixerate/editor/canvas';
13
+ import type { SvelteCanvasAnimation } from './createCanvasAnimation.svelte.js';
14
+ import type { CanvasNode } from './types.js';
15
+
16
+ interface Props {
17
+ /** The `createCanvasAnimation` rune. */
18
+ animation: SvelteCanvasAnimation<any>;
19
+ /** Nodes whose tracks are listed (default: the selected nodes). */
20
+ nodeIds?: readonly string[];
21
+ /** Width of the keyframe lanes in px (default 360). */
22
+ width?: number;
23
+ /** Snap distance in screen px while dragging keyframes (default 8; hold Alt to turn snapping off). */
24
+ snapThreshold?: number;
25
+ labelOf?: (node: CanvasNode) => string;
26
+ /** Bump to re-read the rows after changing the nodes outside the rune. */
27
+ revision?: unknown;
28
+ class?: string;
29
+ }
30
+
31
+ let { animation, nodeIds, width = 360, snapThreshold = 8, labelOf, revision, class: className = '' }: Props = $props();
32
+
33
+ const LOOP_LABELS: Record<AnimationLoopMode, string> = { none: 'Once', loop: 'Loop', 'ping-pong': 'Ping-pong' };
34
+ const labelWidth = 120;
35
+
36
+ const state = $derived(animation.state);
37
+ const duration = $derived(state.duration);
38
+ const rows = $derived.by(() => {
39
+ void state;
40
+ void revision;
41
+ void animation.revision;
42
+ return animation.timelineRows(nodeIds ?? undefined, labelOf ? { labelOf: labelOf as any } : {});
43
+ });
44
+ const ticks = $derived(getTimelineTicks(duration, width));
45
+ const playheadX = $derived(timeToX(state.time, duration, width));
46
+ const easingValue = (easing: unknown) => (typeof easing === 'string' ? easing : easing ? 'custom' : 'linear');
47
+ const selectedEasing = $derived.by(() => {
48
+ const first = state.selection[0];
49
+ if (!first) return '';
50
+ for (const row of rows) for (const k of row.keyframes) if (k.selected && k.ref.property === first.property && k.ref.nodeId === first.nodeId) return easingValue(k.easing);
51
+ return '';
52
+ });
53
+ const multiNode = $derived(new Set(rows.map((r) => r.nodeId)).size > 1);
54
+
55
+ let dragStartX: number | null = null;
56
+ const cleanups: (() => void)[] = [];
57
+ const listen = (move: (e: PointerEvent) => void, up: () => void) => {
58
+ const end = () => {
59
+ window.removeEventListener('pointermove', move);
60
+ window.removeEventListener('pointerup', end);
61
+ up();
62
+ };
63
+ window.addEventListener('pointermove', move);
64
+ window.addEventListener('pointerup', end);
65
+ cleanups.push(() => {
66
+ window.removeEventListener('pointermove', move);
67
+ window.removeEventListener('pointerup', end);
68
+ });
69
+ };
70
+
71
+ function onRulerDown(event: PointerEvent) {
72
+ const left = (event.currentTarget as Element).getBoundingClientRect().left;
73
+ animation.scrub(xToTime(event.clientX - left, duration, width));
74
+ listen(
75
+ (e) => animation.scrub(xToTime(e.clientX - left, duration, width)),
76
+ () => animation.endScrub()
77
+ );
78
+ }
79
+
80
+ function onKeyframeDown(event: PointerEvent, ref: KeyframeRef) {
81
+ event.stopPropagation();
82
+ event.preventDefault();
83
+ if (event.shiftKey || event.metaKey || event.ctrlKey) {
84
+ animation.selectKeyframes([ref], 'toggle');
85
+ return;
86
+ }
87
+ animation.beginKeyframeDrag([ref]);
88
+ dragStartX = event.clientX;
89
+ const msPerPx = width > 0 ? duration / width : 0;
90
+ listen(
91
+ (e) => {
92
+ if (dragStartX === null) return;
93
+ animation.updateKeyframeDrag((e.clientX - dragStartX) * msPerPx, { snap: !e.altKey, thresholdMs: snapThreshold * msPerPx });
94
+ },
95
+ () => {
96
+ if (dragStartX === null) return;
97
+ dragStartX = null;
98
+ animation.endKeyframeDrag();
99
+ }
100
+ );
101
+ }
102
+
103
+ function onWindowKey(event: KeyboardEvent) {
104
+ if (event.key === 'Escape' && dragStartX !== null) {
105
+ dragStartX = null;
106
+ animation.cancelKeyframeDrag();
107
+ }
108
+ }
109
+
110
+ function onTimelineKey(event: KeyboardEvent) {
111
+ if ((event.key === 'Delete' || event.key === 'Backspace') && state.selection.length > 0) {
112
+ event.preventDefault();
113
+ animation.deleteKeyframes();
114
+ }
115
+ }
116
+
117
+ onDestroy(() => cleanups.forEach((fn) => fn()));
118
+
119
+ const btn = 'rounded border border-border px-2 py-0.5 text-xs hover:bg-muted disabled:opacity-50';
120
+ </script>
121
+
122
+ <svelte:window onkeydown={onWindowKey} />
123
+
124
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex, a11y_no_static_element_interactions -->
125
+ <div data-animation-timeline="" class="flex flex-col gap-1 text-xs text-foreground {className}" tabindex="0" onkeydown={onTimelineKey}>
126
+ <div class="flex flex-wrap items-center gap-2" role="toolbar" aria-label="Animation">
127
+ <button type="button" class={btn} data-action="toggle-play" aria-label={state.playing ? 'Pause' : 'Play'} onclick={() => animation.toggle()}>
128
+ {state.playing ? '❚❚' : '▶'}
129
+ </button>
130
+ <button
131
+ type="button"
132
+ class="{btn} {state.recording ? 'text-red-500' : ''}"
133
+ data-action="record"
134
+ aria-pressed={state.recording}
135
+ aria-label="Record keyframes"
136
+ onclick={() => animation.toggleRecording()}
137
+ >
138
+ ● Rec
139
+ </button>
140
+ <button type="button" class={btn} data-action="add-keyframe" onclick={() => animation.addKeyframe()}>◆ Key</button>
141
+ <span data-time="" class="tabular-nums text-muted-foreground">{formatAnimationTime(state.time)} / {formatAnimationTime(duration)}</span>
142
+ <select
143
+ aria-label="Easing"
144
+ data-easing-picker=""
145
+ class="rounded border border-border bg-background px-1 py-0.5"
146
+ value={selectedEasing}
147
+ disabled={state.selection.length === 0}
148
+ onchange={(e) => {
149
+ const value = (e.currentTarget as HTMLSelectElement).value;
150
+ if (value && value !== 'custom') animation.setEasing(value);
151
+ }}
152
+ >
153
+ <option value="">Easing…</option>
154
+ {#each ANIMATION_EASING_PRESETS as preset (preset)}
155
+ <option value={preset}>{ANIMATION_EASING_LABELS[preset]}</option>
156
+ {/each}
157
+ {#if selectedEasing === 'custom'}<option value="custom">Custom</option>{/if}
158
+ </select>
159
+ <select
160
+ aria-label="Loop"
161
+ data-loop-picker=""
162
+ class="rounded border border-border bg-background px-1 py-0.5"
163
+ value={state.loop}
164
+ onchange={(e) => animation.setLoop((e.currentTarget as HTMLSelectElement).value as AnimationLoopMode)}
165
+ >
166
+ {#each Object.keys(LOOP_LABELS) as mode (mode)}
167
+ <option value={mode}>{LOOP_LABELS[mode as AnimationLoopMode]}</option>
168
+ {/each}
169
+ </select>
170
+ </div>
171
+ <div class="flex">
172
+ <div style="width: {labelWidth}px" class="shrink-0"></div>
173
+ <div data-timeline-ruler="" class="relative h-5 cursor-ew-resize border-b border-border" style="width: {width}px" onpointerdown={onRulerDown}>
174
+ {#each ticks as tick (tick.time)}
175
+ <span class="absolute top-0 -translate-x-1/2 text-[10px] text-muted-foreground" style="left: {tick.x}px">{tick.label}</span>
176
+ {/each}
177
+ <div data-playhead="" class="absolute top-0 h-5 w-px bg-red-500" style="left: {playheadX}px"></div>
178
+ </div>
179
+ </div>
180
+ {#if rows.length === 0}
181
+ <p class="text-muted-foreground" data-timeline-empty="">Select an animated node, or press ◆ Key to add a keyframe.</p>
182
+ {:else}
183
+ {#each rows as row (row.id)}
184
+ <div class="flex items-center" data-track={row.id}>
185
+ <label class="flex shrink-0 items-center gap-1 truncate pr-2" style="width: {labelWidth}px" title="{row.nodeLabel} · {row.label}">
186
+ <input
187
+ type="checkbox"
188
+ aria-label="Enable {row.label}"
189
+ checked={row.enabled}
190
+ onchange={(e) => animation.setTrackEnabled(row.nodeId, row.property, (e.currentTarget as HTMLInputElement).checked)}
191
+ />
192
+ <span class="truncate">{multiNode ? `${row.nodeLabel} · ` : ''}{row.label}</span>
193
+ </label>
194
+ <div data-lane="" class="relative h-5 rounded bg-muted/40 {row.enabled ? '' : 'opacity-50'}" style="width: {width}px">
195
+ <div class="pointer-events-none absolute top-0 h-5 w-px bg-red-500/60" style="left: {playheadX}px"></div>
196
+ {#if state.drag && state.drag.target !== null}
197
+ <div data-snap-target="" class="pointer-events-none absolute top-0 h-5 w-px bg-sky-400" style="left: {timeToX(state.drag.target, duration, width)}px"></div>
198
+ {/if}
199
+ {#each row.keyframes as k (k.ref.time)}
200
+ <button
201
+ type="button"
202
+ data-keyframe={k.ref.time}
203
+ aria-pressed={k.selected}
204
+ aria-label="{row.label} keyframe at {formatAnimationTime(k.ref.time)}"
205
+ title="{formatAnimationTime(k.time)} · {String(k.value)}"
206
+ class="absolute top-1/2 h-2.5 w-2.5 -translate-x-1/2 -translate-y-1/2 rotate-45 border {k.selected
207
+ ? 'border-sky-300 bg-sky-500'
208
+ : 'border-foreground/60 bg-background'} {k.easing === 'hold' ? 'rounded-none' : 'rounded-[1px]'}"
209
+ style="left: {timeToX(k.time, duration, width)}px"
210
+ onpointerdown={(e) => onKeyframeDown(e, k.ref)}
211
+ ></button>
212
+ {/each}
213
+ </div>
214
+ </div>
215
+ {/each}
216
+ {/if}
217
+ </div>
@@ -0,0 +1,20 @@
1
+ import type { SvelteCanvasAnimation } from './createCanvasAnimation.svelte.js';
2
+ import type { CanvasNode } from './types.js';
3
+ interface Props {
4
+ /** The `createCanvasAnimation` rune. */
5
+ animation: SvelteCanvasAnimation<any>;
6
+ /** Nodes whose tracks are listed (default: the selected nodes). */
7
+ nodeIds?: readonly string[];
8
+ /** Width of the keyframe lanes in px (default 360). */
9
+ width?: number;
10
+ /** Snap distance in screen px while dragging keyframes (default 8; hold Alt to turn snapping off). */
11
+ snapThreshold?: number;
12
+ labelOf?: (node: CanvasNode) => string;
13
+ /** Bump to re-read the rows after changing the nodes outside the rune. */
14
+ revision?: unknown;
15
+ class?: string;
16
+ }
17
+ declare const AnimationTimeline: import("svelte").Component<Props, {}, "">;
18
+ type AnimationTimeline = ReturnType<typeof AnimationTimeline>;
19
+ export default AnimationTimeline;
20
+ //# sourceMappingURL=AnimationTimeline.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnimationTimeline.svelte.d.ts","sourceRoot":"","sources":["../../src/canvas/AnimationTimeline.svelte.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AAC/E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG5C,UAAU,KAAK;IACd,wCAAwC;IACxC,SAAS,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAC;IACtC,mEAAmE;IACnE,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sGAAsG;IACtG,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,MAAM,CAAC;IACvC,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAoKF,QAAA,MAAM,iBAAiB,2CAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}
@@ -0,0 +1,73 @@
1
+ <script lang="ts">
2
+ import { CANVAS_COMPONENT_DRAG_TYPE, listCanvasComponents } from '@pixerate/editor/canvas';
3
+ import type { CanvasNode } from './types.js';
4
+
5
+ interface Props {
6
+ nodes: CanvasNode[];
7
+ /** Inserts an instance of a component (or a set's default variant); called on click. */
8
+ oninsert: (componentId: string) => void;
9
+ /** Show the search field (default `true`). */
10
+ search?: boolean;
11
+ class?: string;
12
+ }
13
+
14
+ let { nodes, oninsert, search = true, class: className = '' }: Props = $props();
15
+
16
+ let query = $state('');
17
+ const listed = $derived(listCanvasComponents(nodes as any[]));
18
+ const q = $derived(query.trim().toLowerCase());
19
+ const match = (name: string) => !q || name.toLowerCase().includes(q);
20
+ const lone = $derived(listed.components.filter((c) => !c.setId && match(c.name)));
21
+ const sets = $derived(listed.sets.filter((s) => match(s.name) || s.variants.some((v) => match(v.name))));
22
+
23
+ function dragStart(event: DragEvent, id: string, name: string) {
24
+ if (!event.dataTransfer) return;
25
+ event.dataTransfer.setData(CANVAS_COMPONENT_DRAG_TYPE, id);
26
+ event.dataTransfer.setData('text/plain', name);
27
+ event.dataTransfer.effectAllowed = 'copy';
28
+ }
29
+ </script>
30
+
31
+ {#snippet item(id: string, name: string, count: number, kind: string)}
32
+ <button
33
+ type="button"
34
+ draggable="true"
35
+ data-component-id={id}
36
+ data-kind={kind}
37
+ class="flex w-full items-center justify-between gap-2 rounded px-2 py-1 text-left text-xs hover:bg-muted"
38
+ title="Insert an instance of {name}"
39
+ onclick={() => oninsert(id)}
40
+ ondragstart={(e) => dragStart(e, id, name)}
41
+ >
42
+ <span class="truncate"><span aria-hidden="true" class="mr-1 text-muted-foreground">◇</span>{name}</span>
43
+ <span class="text-[10px] text-muted-foreground" data-instance-count="">{count}</span>
44
+ </button>
45
+ {/snippet}
46
+
47
+ <div data-components-panel="" class="flex flex-col gap-1 text-xs text-foreground {className}">
48
+ {#if search}
49
+ <input
50
+ type="search"
51
+ aria-label="Search components"
52
+ placeholder="Search components"
53
+ class="rounded border border-border bg-background px-2 py-1 text-xs"
54
+ oninput={(e) => (query = e.currentTarget.value)}
55
+ />
56
+ {/if}
57
+ {#each sets as set (set.id)}
58
+ <div data-component-set={set.id} class="flex flex-col">
59
+ {@render item(set.id, set.name, set.variants.reduce((sum, v) => sum + v.instanceCount, 0), 'set')}
60
+ <div class="ml-3 flex flex-col border-l border-border pl-1">
61
+ {#each set.variants as v (v.id)}
62
+ {@render item(v.id, v.name, v.instanceCount, 'variant')}
63
+ {/each}
64
+ </div>
65
+ </div>
66
+ {/each}
67
+ {#each lone as c (c.id)}
68
+ {@render item(c.id, c.name, c.instanceCount, 'component')}
69
+ {/each}
70
+ {#if listed.components.length === 0}
71
+ <p class="text-muted-foreground">No components yet. Select a frame and choose Create Component.</p>
72
+ {/if}
73
+ </div>
@@ -0,0 +1,13 @@
1
+ import type { CanvasNode } from './types.js';
2
+ interface Props {
3
+ nodes: CanvasNode[];
4
+ /** Inserts an instance of a component (or a set's default variant); called on click. */
5
+ oninsert: (componentId: string) => void;
6
+ /** Show the search field (default `true`). */
7
+ search?: boolean;
8
+ class?: string;
9
+ }
10
+ declare const CanvasComponentsPanel: import("svelte").Component<Props, {}, "">;
11
+ type CanvasComponentsPanel = ReturnType<typeof CanvasComponentsPanel>;
12
+ export default CanvasComponentsPanel;
13
+ //# sourceMappingURL=CanvasComponentsPanel.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CanvasComponentsPanel.svelte.d.ts","sourceRoot":"","sources":["../../src/canvas/CanvasComponentsPanel.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG5C,UAAU,KAAK;IACd,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,wFAAwF;IACxF,QAAQ,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,8CAA8C;IAC9C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAsDF,QAAA,MAAM,qBAAqB,2CAAwC,CAAC;AACpE,KAAK,qBAAqB,GAAG,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACtE,eAAe,qBAAqB,CAAC"}