@pixerate/editor-svelte 0.198.1 → 0.199.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/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@ import type MergePreviewComponent from "./MergePreview.svelte";
8
8
  import type StylesGalleryComponent from "./StylesGallery.svelte";
9
9
  import type ExportDocxButtonComponent from "./ExportDocxButton.svelte";
10
10
  export * from "./editor.svelte.js";
11
+ export * from "./promptEditor.svelte.js";
11
12
  export * from "./navigationGuard.js";
12
13
  export * from "./createNavigationGuard.svelte.js";
13
14
  export * from "./history/index.js";
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./editor.svelte.js";
2
+ export * from "./promptEditor.svelte.js";
2
3
  export * from "./navigationGuard.js";
3
4
  export * from "./createNavigationGuard.svelte.js";
4
5
  export * from "./history/index.js";
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import StickerPicker from './StickerPicker.svelte';
3
3
  import { brushLoadColor, brushTakesPaint, downloadBytes, toggleStickerRef, DEFAULT_PAINT_GRADIENT, type PaintEditorController } from '@pixerate/editor/paint-editor';
4
- import { addBrushSourceFromFile, brushColourHex, brushPreviewColours, PAINT_PROFILE, type Brush, type BrushLibraryEntry } from '@pixerate/editor/brushes';
4
+ import { addBrushSourceFromFile, importBrushFile, brushColourHex, brushPreviewColours, PAINT_PROFILE, type Brush, type BrushLibraryEntry } from '@pixerate/editor/brushes';
5
5
  import { untrack } from 'svelte';
6
6
  import BrushEditor from '../brushes/BrushEditor.svelte';
7
7
  import BrushLibraryBrowser from '../brushes/BrushLibraryBrowser.svelte';
@@ -97,7 +97,7 @@
97
97
  {/if}
98
98
  {/snippet}
99
99
 
100
- <!-- The brush library (BrushLibraryBrowser) and the active brush in the BrushEditor, plus the brush gradient, sticker stamps, what the brush carries, import and export. Images picked for an image tip or grain go into the brush library as sources. -->
100
+ <!-- The brush library (BrushLibraryBrowser) and the active brush in the BrushEditor, plus the brush gradient, sticker stamps, what the brush carries, import and export. Images picked for an image tip or grain go into the brush library as sources; Import also reads brush packs (.abr, .brush, .brushset). -->
101
101
  <div class={`${panel} ${className}`} data-testid="brush-panel">
102
102
  <BrushLibraryBrowser {entries} activeId={brush.id} onselect={(id) => controller.setBrush({ id })} thumbnail={(id) => thumbs.get(id) ?? null} {thumbnailWidth} {thumbnailHeight} />
103
103
  <BrushEditor
@@ -116,14 +116,14 @@
116
116
  <input
117
117
  bind:this={fileInput}
118
118
  type="file"
119
- accept=".json,application/json"
119
+ accept=".json,.abr,.brush,.brushset,application/json"
120
120
  aria-label="Import brushes"
121
121
  class="hidden"
122
122
  onchange={(e) => {
123
123
  const input = e.currentTarget;
124
124
  const f = input.files?.[0];
125
125
  input.value = '';
126
- if (f) run(async () => controller.importBrushes(JSON.parse(await f.text())));
126
+ if (f) run(async () => (/\.json$/i.test(f.name) ? controller.importBrushes(JSON.parse(await f.text())) : importBrushFile(controller.brushLibrary, f)));
127
127
  }}
128
128
  />
129
129
  </div>
@@ -0,0 +1,43 @@
1
+ import type { Component } from "svelte";
2
+ import { Editor } from "@tiptap/core";
3
+ import { type LoadingNodeViewState, type PromptEditorConfig } from "@pixerate/editor";
4
+ type MaybeGetter<T> = T | (() => T);
5
+ export interface UsePromptEditorProps extends PromptEditorConfig {
6
+ /** Optional DOM element to mount the editor into (or use `mount()` / `use:prompt.action`). */
7
+ element?: HTMLElement;
8
+ /**
9
+ * Svelte component drawn inside each `loadingNode` placeholder instead of the default SVG spinner,
10
+ * e.g. your design system's loader. It receives `view` (`id`, `label`). Read when a
11
+ * placeholder renders, so changing it does not recreate the editor.
12
+ */
13
+ loadingIndicator?: Component<{
14
+ view: LoadingNodeViewState;
15
+ }>;
16
+ }
17
+ export interface SveltePromptEditor {
18
+ /** The active TipTap `Editor` instance (reactive). */
19
+ readonly editor: Editor | null;
20
+ /** Alias for `editor` (matches `createReactiveEditor`). */
21
+ readonly current: Editor | null;
22
+ /** Mounts the editor DOM into `element`. */
23
+ mount(element: HTMLElement): Editor | null;
24
+ /** Svelte action (`use:prompt.action`) that mounts the editor DOM into `node`. */
25
+ action(node: HTMLElement): {
26
+ destroy(): void;
27
+ };
28
+ /** Destroys the editor instance. */
29
+ destroy(): void;
30
+ }
31
+ /**
32
+ * Reactive prompt editor for Svelte 5 (the counterpart of React's `usePromptEditor`).
33
+ * Manages plain-text serialization, animated gradient tokens, `{{template}}` autocomplete,
34
+ * `/command` slash triggers, milestone tracking (`used_variable`, `used_magic`),
35
+ * plain-text clipboard handling, and custom `loadingNode` indicators without recreating
36
+ * the editor when content, editability, placeholder, className, or inline `templateColorMap`
37
+ * references update.
38
+ *
39
+ * Pass a getter `() => ({ content, onContentChange, ... })` to react to prop/state updates.
40
+ */
41
+ export declare function usePromptEditor(props: MaybeGetter<UsePromptEditorProps>): SveltePromptEditor;
42
+ export declare const createPromptEditor: typeof usePromptEditor;
43
+ export {};
@@ -0,0 +1,100 @@
1
+ import { Editor } from "@tiptap/core";
2
+ import { createPromptEditorOptions, promptEditorConfigKey, syncPromptEditor, } from "@pixerate/editor";
3
+ import { createSvelteLoadingNodeView } from "./loading-node/index.js";
4
+ const read = (value) => typeof value === "function" ? value() : value;
5
+ /**
6
+ * Reactive prompt editor for Svelte 5 (the counterpart of React's `usePromptEditor`).
7
+ * Manages plain-text serialization, animated gradient tokens, `{{template}}` autocomplete,
8
+ * `/command` slash triggers, milestone tracking (`used_variable`, `used_magic`),
9
+ * plain-text clipboard handling, and custom `loadingNode` indicators without recreating
10
+ * the editor when content, editability, placeholder, className, or inline `templateColorMap`
11
+ * references update.
12
+ *
13
+ * Pass a getter `() => ({ content, onContentChange, ... })` to react to prop/state updates.
14
+ */
15
+ export function usePromptEditor(props) {
16
+ let editorState = $state.raw(null);
17
+ let currentEditor = null;
18
+ let currentConfigKey = null;
19
+ let mountedElement;
20
+ let latestProps = read(props);
21
+ const reachedMilestones = new Set();
22
+ // One node view for the editor's lifetime: swapping indicator components does not recreate
23
+ // the editor. It is only installed while `loadingIndicator` is set (part of the config key).
24
+ const dynamicLoadingNodeView = (dom, state) => {
25
+ const Comp = latestProps.loadingIndicator;
26
+ if (!Comp)
27
+ return { update() { }, destroy() { } };
28
+ return createSvelteLoadingNodeView(Comp)(dom, state);
29
+ };
30
+ function buildEditor(p) {
31
+ return new Editor({
32
+ ...createPromptEditorOptions(() => latestProps, {
33
+ loadingNodeView: p.loadingIndicator ? dynamicLoadingNodeView : null,
34
+ reachedMilestones,
35
+ }),
36
+ element: p.element ?? mountedElement,
37
+ });
38
+ }
39
+ function syncProps(p) {
40
+ const previous = latestProps;
41
+ latestProps = p;
42
+ const nextKey = promptEditorConfigKey(p, { hasLoadingNodeView: !!p.loadingIndicator });
43
+ if (!currentEditor || currentEditor.isDestroyed || currentConfigKey !== nextKey) {
44
+ currentEditor?.destroy();
45
+ currentConfigKey = nextKey;
46
+ currentEditor = buildEditor(p);
47
+ editorState = currentEditor;
48
+ return;
49
+ }
50
+ // Content is applied only when the prop changed, so echoes of `onContentChange` and
51
+ // unrelated prop updates never reset the document or the cursor.
52
+ syncPromptEditor(currentEditor, p, previous);
53
+ }
54
+ // Initialize synchronously so `prompt.editor` is immediately available, then track updates reactively.
55
+ syncProps(latestProps);
56
+ $effect(() => {
57
+ const next = read(props);
58
+ syncProps(next);
59
+ });
60
+ $effect(() => () => {
61
+ currentEditor?.destroy();
62
+ currentEditor = null;
63
+ currentConfigKey = null;
64
+ });
65
+ function mount(element) {
66
+ mountedElement = element;
67
+ if (currentEditor && !currentEditor.isDestroyed) {
68
+ if (currentEditor.view.dom.parentElement !== element) {
69
+ element.appendChild(currentEditor.view.dom);
70
+ }
71
+ }
72
+ return currentEditor;
73
+ }
74
+ return {
75
+ get editor() {
76
+ return editorState;
77
+ },
78
+ get current() {
79
+ return editorState;
80
+ },
81
+ mount,
82
+ action(node) {
83
+ mount(node);
84
+ return {
85
+ destroy() {
86
+ if (mountedElement === node) {
87
+ mountedElement = undefined;
88
+ }
89
+ },
90
+ };
91
+ },
92
+ destroy() {
93
+ currentEditor?.destroy();
94
+ currentEditor = null;
95
+ currentConfigKey = null;
96
+ editorState = null;
97
+ },
98
+ };
99
+ }
100
+ export const createPromptEditor = usePromptEditor;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixerate/editor-svelte",
3
- "version": "0.198.1",
3
+ "version": "0.199.0",
4
4
  "description": "Svelte bindings and prompt/rich-text components for @pixerate/editor",
5
5
  "author": "Pixerate",
6
6
  "license": "MIT",
@@ -184,7 +184,7 @@
184
184
  "**/*.css"
185
185
  ],
186
186
  "dependencies": {
187
- "@pixerate/editor": "0.268.1"
187
+ "@pixerate/editor": "0.269.0"
188
188
  },
189
189
  "peerDependencies": {
190
190
  "@dagrejs/dagre": "^1.1.4",