@pixerate/editor-svelte 0.22.0 → 0.23.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 (85) hide show
  1. package/README.md +4 -0
  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/TemplateRenderer.svelte +4 -10
  6. package/dist/TemplateRenderer.svelte.d.ts.map +1 -1
  7. package/dist/canvas/createCanvasArrange.svelte.d.ts.map +1 -1
  8. package/dist/canvas/createCanvasAutoLayout.svelte.d.ts +68 -0
  9. package/dist/canvas/createCanvasAutoLayout.svelte.d.ts.map +1 -0
  10. package/dist/canvas/createCanvasAutoLayout.svelte.js +78 -0
  11. package/dist/canvas/index.d.ts +2 -0
  12. package/dist/canvas/index.d.ts.map +1 -1
  13. package/dist/canvas/index.js +3 -0
  14. package/dist/find-replace/FindReplaceBar.svelte +203 -0
  15. package/dist/find-replace/FindReplaceBar.svelte.d.ts +15 -0
  16. package/dist/find-replace/FindReplaceBar.svelte.d.ts.map +1 -0
  17. package/dist/find-replace/findReplace.svelte.d.ts +27 -0
  18. package/dist/find-replace/findReplace.svelte.d.ts.map +1 -0
  19. package/dist/find-replace/findReplace.svelte.js +79 -0
  20. package/dist/find-replace/index.d.ts +6 -0
  21. package/dist/find-replace/index.d.ts.map +1 -0
  22. package/dist/find-replace/index.js +2 -0
  23. package/dist/generation/GenerationSettings.svelte +174 -0
  24. package/dist/generation/GenerationSettings.svelte.d.ts +23 -0
  25. package/dist/generation/GenerationSettings.svelte.d.ts.map +1 -0
  26. package/dist/generation/JobProgress.svelte +53 -0
  27. package/dist/generation/JobProgress.svelte.d.ts +17 -0
  28. package/dist/generation/JobProgress.svelte.d.ts.map +1 -0
  29. package/dist/generation/index.d.ts +10 -0
  30. package/dist/generation/index.d.ts.map +1 -0
  31. package/dist/generation/index.js +4 -0
  32. package/dist/generation/plan.svelte.d.ts +16 -0
  33. package/dist/generation/plan.svelte.d.ts.map +1 -0
  34. package/dist/generation/plan.svelte.js +26 -0
  35. package/dist/generation/progress.svelte.d.ts +25 -0
  36. package/dist/generation/progress.svelte.d.ts.map +1 -0
  37. package/dist/generation/progress.svelte.js +36 -0
  38. package/dist/image-editor/AdjustmentLayersPanel.svelte +309 -0
  39. package/dist/image-editor/AdjustmentLayersPanel.svelte.d.ts +11 -0
  40. package/dist/image-editor/AdjustmentLayersPanel.svelte.d.ts.map +1 -0
  41. package/dist/image-editor/ControlSlider.svelte +39 -0
  42. package/dist/image-editor/ControlSlider.svelte.d.ts +13 -0
  43. package/dist/image-editor/ControlSlider.svelte.d.ts.map +1 -0
  44. package/dist/image-editor/CurvesEditor.svelte +105 -0
  45. package/dist/image-editor/CurvesEditor.svelte.d.ts +13 -0
  46. package/dist/image-editor/CurvesEditor.svelte.d.ts.map +1 -0
  47. package/dist/image-editor/Histogram.svelte +49 -0
  48. package/dist/image-editor/Histogram.svelte.d.ts +12 -0
  49. package/dist/image-editor/Histogram.svelte.d.ts.map +1 -0
  50. package/dist/image-editor/ImageEditor.svelte +32 -140
  51. package/dist/image-editor/ImageEditor.svelte.d.ts.map +1 -1
  52. package/dist/image-editor/imageEditorState.svelte.d.ts +14 -1
  53. package/dist/image-editor/imageEditorState.svelte.d.ts.map +1 -1
  54. package/dist/image-editor/imageEditorState.svelte.js +40 -0
  55. package/dist/image-editor/index.d.ts +12 -0
  56. package/dist/image-editor/index.d.ts.map +1 -1
  57. package/dist/image-editor/index.js +4 -0
  58. package/dist/index.d.ts +8 -1
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +5 -0
  61. package/dist/merge.svelte.d.ts +51 -0
  62. package/dist/merge.svelte.d.ts.map +1 -0
  63. package/dist/merge.svelte.js +117 -0
  64. package/dist/persistence/RecoveryPrompt.svelte +57 -0
  65. package/dist/persistence/RecoveryPrompt.svelte.d.ts +18 -0
  66. package/dist/persistence/RecoveryPrompt.svelte.d.ts.map +1 -0
  67. package/dist/persistence/autosave.svelte.d.ts +63 -0
  68. package/dist/persistence/autosave.svelte.d.ts.map +1 -0
  69. package/dist/persistence/autosave.svelte.js +110 -0
  70. package/dist/persistence/index.d.ts +6 -0
  71. package/dist/persistence/index.d.ts.map +1 -0
  72. package/dist/persistence/index.js +2 -0
  73. package/dist/review/documentCompare.svelte.d.ts +21 -0
  74. package/dist/review/documentCompare.svelte.d.ts.map +1 -0
  75. package/dist/review/documentCompare.svelte.js +23 -0
  76. package/dist/review/index.d.ts +1 -0
  77. package/dist/review/index.d.ts.map +1 -1
  78. package/dist/review/index.js +1 -0
  79. package/dist/spreadsheet/SpreadsheetContextMenu.svelte +6 -3
  80. package/dist/spreadsheet/SpreadsheetContextMenu.svelte.d.ts.map +1 -1
  81. package/dist/spreadsheet/SpreadsheetEditor.svelte +6 -2
  82. package/dist/spreadsheet/SpreadsheetEditor.svelte.d.ts.map +1 -1
  83. package/dist/spreadsheet/spreadsheet.svelte.d.ts +2 -0
  84. package/dist/spreadsheet/spreadsheet.svelte.d.ts.map +1 -1
  85. package/package.json +18 -2
@@ -0,0 +1,203 @@
1
+ <script lang="ts">
2
+ import type { Editor } from "@tiptap/core";
3
+ import { FIND_OPTION_TOGGLES, formatFindStatus } from "@pixerate/editor";
4
+ import { createFindReplace, type SvelteFindReplace } from "./findReplace.svelte.js";
5
+
6
+ interface Props {
7
+ editor?: Editor | null;
8
+ class?: string;
9
+ /** Render even while the find state is closed (default: only after Mod+F / Mod+H / `openFindReplace`). */
10
+ alwaysOpen?: boolean;
11
+ /** Show the "More options" toggle. Default `true`. */
12
+ showMoreOptions?: boolean;
13
+ /** Called after the bar closes (Escape or the close button). */
14
+ onclose?: () => void;
15
+ }
16
+
17
+ let {
18
+ editor = null,
19
+ class: className = "flex flex-col gap-1 rounded-md border border-border bg-background p-1 text-sm shadow-sm",
20
+ alwaysOpen = false,
21
+ showMoreOptions = true,
22
+ onclose,
23
+ }: Props = $props();
24
+
25
+ const inputClass =
26
+ "min-w-0 flex-1 rounded border border-input bg-background px-2 py-1 text-xs text-foreground focus:outline-none focus:ring-1 focus:ring-ring";
27
+ const buttonClass =
28
+ "rounded px-2 py-1 text-xs text-foreground hover:bg-muted disabled:opacity-40 disabled:hover:bg-transparent";
29
+ const toggleClass = (on: boolean) =>
30
+ `${buttonClass} font-mono ${on ? "bg-primary/10 text-primary" : "text-muted-foreground"}`;
31
+ const basicToggles = FIND_OPTION_TOGGLES.filter((t) => !t.advanced);
32
+ const advancedToggles = FIND_OPTION_TOGGLES.filter((t) => t.advanced);
33
+
34
+ let find = $state.raw<SvelteFindReplace>(createFindReplace(null));
35
+ let moreOpen = $state(false);
36
+ let findInput = $state<HTMLInputElement | null>(null);
37
+
38
+ $effect(() => {
39
+ const f = createFindReplace(editor);
40
+ find = f;
41
+ return () => f.destroy();
42
+ });
43
+
44
+ let visible = $derived(find.available && (alwaysOpen || find.open));
45
+ let showReplace = $derived(find.mode === "replace");
46
+ let status = $derived(
47
+ formatFindStatus({
48
+ query: find.query,
49
+ matches: find.matches,
50
+ currentIndex: find.currentIndex,
51
+ error: find.error,
52
+ truncated: find.truncated,
53
+ }),
54
+ );
55
+ let hasMatches = $derived(find.matchCount > 0);
56
+
57
+ // Mod+F / Mod+H (in the editor or the bar) focus and select the find field.
58
+ $effect(() => {
59
+ if (!visible || find.focusRequest === 0 || !findInput) return;
60
+ findInput.focus();
61
+ findInput.select();
62
+ });
63
+
64
+ const isMod = (e: KeyboardEvent) => e.metaKey || e.ctrlKey;
65
+
66
+ function close() {
67
+ find.close();
68
+ editor?.commands.focus();
69
+ onclose?.();
70
+ }
71
+
72
+ function onBarKeyDown(e: KeyboardEvent) {
73
+ if (e.key === "Escape") {
74
+ e.preventDefault();
75
+ close();
76
+ } else if (isMod(e) && (e.key === "f" || e.key === "F")) {
77
+ e.preventDefault();
78
+ find.show("find");
79
+ } else if (isMod(e) && (e.key === "h" || e.key === "H")) {
80
+ e.preventDefault();
81
+ find.show("replace");
82
+ }
83
+ }
84
+
85
+ function onFindKeyDown(e: KeyboardEvent) {
86
+ if (e.key !== "Enter") return;
87
+ e.preventDefault();
88
+ if (e.shiftKey) find.previous();
89
+ else find.next();
90
+ }
91
+
92
+ function onReplaceKeyDown(e: KeyboardEvent) {
93
+ if (e.key !== "Enter") return;
94
+ e.preventDefault();
95
+ if (isMod(e)) find.replaceAll();
96
+ else find.replace();
97
+ }
98
+ </script>
99
+
100
+ {#if visible}
101
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
102
+ <div class={className} role="search" aria-label="Find and replace" onkeydown={onBarKeyDown}>
103
+ <div class="flex items-center gap-1">
104
+ <button
105
+ type="button"
106
+ class={buttonClass}
107
+ aria-label="Toggle replace"
108
+ aria-expanded={showReplace}
109
+ onclick={() => find.show(showReplace ? "find" : "replace")}
110
+ >
111
+ {showReplace ? "▾" : "▸"}
112
+ </button>
113
+ <input
114
+ bind:this={findInput}
115
+ type="text"
116
+ class={inputClass}
117
+ placeholder="Find"
118
+ aria-label="Find"
119
+ aria-invalid={!!find.error}
120
+ value={find.query}
121
+ oninput={(e) => find.setQuery(e.currentTarget.value)}
122
+ onkeydown={onFindKeyDown}
123
+ />
124
+ {#each basicToggles as t (t.key)}
125
+ <button
126
+ type="button"
127
+ class={toggleClass(!!find.options[t.key])}
128
+ aria-label={t.label}
129
+ title={t.label}
130
+ aria-pressed={!!find.options[t.key]}
131
+ onclick={() => find.toggleOption(t.key)}
132
+ >
133
+ {t.short}
134
+ </button>
135
+ {/each}
136
+ <span
137
+ class={`min-w-[5rem] px-1 text-center text-xs ${find.error ? "text-destructive" : "text-muted-foreground"}`}
138
+ aria-live="polite"
139
+ role={find.error ? "alert" : "status"}
140
+ data-testid="find-status"
141
+ >
142
+ {status}
143
+ </span>
144
+ <button type="button" class={buttonClass} aria-label="Previous match" disabled={!hasMatches} onclick={() => find.previous()}>
145
+ &uarr;
146
+ </button>
147
+ <button type="button" class={buttonClass} aria-label="Next match" disabled={!hasMatches} onclick={() => find.next()}>
148
+ &darr;
149
+ </button>
150
+ {#if showMoreOptions}
151
+ <button
152
+ type="button"
153
+ class={buttonClass}
154
+ aria-label="More options"
155
+ aria-expanded={moreOpen}
156
+ onclick={() => (moreOpen = !moreOpen)}
157
+ >
158
+ &hellip;
159
+ </button>
160
+ {/if}
161
+ <button type="button" class={buttonClass} aria-label="Close find" onclick={close}>&times;</button>
162
+ </div>
163
+ {#if showReplace}
164
+ <div class="flex items-center gap-1 pl-7">
165
+ <input
166
+ type="text"
167
+ class={inputClass}
168
+ placeholder="Replace"
169
+ aria-label="Replace"
170
+ value={find.replacement}
171
+ oninput={(e) => find.setReplacement(e.currentTarget.value)}
172
+ onkeydown={onReplaceKeyDown}
173
+ />
174
+ <button
175
+ type="button"
176
+ class={buttonClass}
177
+ disabled={!hasMatches || find.currentIndex < 0}
178
+ onclick={() => find.replace()}
179
+ >
180
+ Replace
181
+ </button>
182
+ <button type="button" class={buttonClass} disabled={!hasMatches} onclick={() => find.replaceAll()}>
183
+ Replace all
184
+ </button>
185
+ {#if find.lastReplaceCount !== null}
186
+ <span class="px-1 text-xs text-muted-foreground" aria-live="polite">
187
+ {find.lastReplaceCount === 1 ? "1 replaced" : `${find.lastReplaceCount} replaced`}
188
+ </span>
189
+ {/if}
190
+ </div>
191
+ {/if}
192
+ {#if showMoreOptions && moreOpen}
193
+ <div class="flex flex-wrap items-center gap-x-3 gap-y-1 pl-7 text-xs text-muted-foreground">
194
+ {#each advancedToggles as t (t.key)}
195
+ <label class="flex items-center gap-1">
196
+ <input type="checkbox" checked={!!find.options[t.key]} onchange={() => find.toggleOption(t.key)} />
197
+ {t.label}
198
+ </label>
199
+ {/each}
200
+ </div>
201
+ {/if}
202
+ </div>
203
+ {/if}
@@ -0,0 +1,15 @@
1
+ import type { Editor } from "@tiptap/core";
2
+ interface Props {
3
+ editor?: Editor | null;
4
+ class?: string;
5
+ /** Render even while the find state is closed (default: only after Mod+F / Mod+H / `openFindReplace`). */
6
+ alwaysOpen?: boolean;
7
+ /** Show the "More options" toggle. Default `true`. */
8
+ showMoreOptions?: boolean;
9
+ /** Called after the bar closes (Escape or the close button). */
10
+ onclose?: () => void;
11
+ }
12
+ declare const FindReplaceBar: import("svelte").Component<Props, {}, "">;
13
+ type FindReplaceBar = ReturnType<typeof FindReplaceBar>;
14
+ export default FindReplaceBar;
15
+ //# sourceMappingURL=FindReplaceBar.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FindReplaceBar.svelte.d.ts","sourceRoot":"","sources":["../../src/find-replace/FindReplaceBar.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAKzC,UAAU,KAAK;IACb,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0GAA0G;IAC1G,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,sDAAsD;IACtD,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAsJH,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
@@ -0,0 +1,27 @@
1
+ import type { Editor } from "@tiptap/core";
2
+ import { type FindOptions, type FindReplaceMode, type FindReplaceSnapshot } from "@pixerate/editor";
3
+ export interface SvelteFindReplace extends FindReplaceSnapshot {
4
+ /** Whether the editor has the `FindReplace` extension. */
5
+ readonly available: boolean;
6
+ readonly matchCount: number;
7
+ show(mode?: FindReplaceMode): void;
8
+ close(): void;
9
+ setQuery(query: string): void;
10
+ setReplacement(replacement: string): void;
11
+ setOptions(options: Partial<FindOptions>): void;
12
+ toggleOption(option: keyof FindOptions): void;
13
+ next(): void;
14
+ previous(): void;
15
+ replace(): void;
16
+ replaceAll(): void;
17
+ clear(): void;
18
+ subscribe(run: (value: FindReplaceSnapshot) => void): () => void;
19
+ destroy(): void;
20
+ }
21
+ /**
22
+ * Reactive (runes) and Svelte-store-compatible view of an editor's Find and
23
+ * Replace state (`FindReplace` extension) with its commands. Call
24
+ * `destroy()` when done (or create it inside a component).
25
+ */
26
+ export declare function createFindReplace(editor: Editor | null | undefined): SvelteFindReplace;
27
+ //# sourceMappingURL=findReplace.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"findReplace.svelte.d.ts","sourceRoot":"","sources":["../../src/find-replace/findReplace.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACzB,MAAM,kBAAkB,CAAC;AAE1B,MAAM,WAAW,iBAAkB,SAAQ,mBAAmB;IAC5D,0DAA0D;IAC1D,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,IAAI,CAAC,IAAI,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IACnC,KAAK,IAAI,IAAI,CAAC;IACd,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC;IAChD,YAAY,CAAC,MAAM,EAAE,MAAM,WAAW,GAAG,IAAI,CAAC;IAC9C,IAAI,IAAI,IAAI,CAAC;IACb,QAAQ,IAAI,IAAI,CAAC;IACjB,OAAO,IAAI,IAAI,CAAC;IAChB,UAAU,IAAI,IAAI,CAAC;IACnB,KAAK,IAAI,IAAI,CAAC;IACd,SAAS,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IACjE,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,iBAAiB,CAyEtF"}
@@ -0,0 +1,79 @@
1
+ import { EMPTY_FIND_REPLACE_SNAPSHOT, getFindReplaceStore, } from "@pixerate/editor";
2
+ /**
3
+ * Reactive (runes) and Svelte-store-compatible view of an editor's Find and
4
+ * Replace state (`FindReplace` extension) with its commands. Call
5
+ * `destroy()` when done (or create it inside a component).
6
+ */
7
+ export function createFindReplace(editor) {
8
+ const store = getFindReplaceStore(editor ?? null);
9
+ let snapshot = $state.raw(store?.getSnapshot() ?? EMPTY_FIND_REPLACE_SNAPSHOT);
10
+ const unsubscribe = store
11
+ ? store.subscribe((value) => {
12
+ snapshot = value;
13
+ })
14
+ : () => { };
15
+ const run = (fn) => {
16
+ if (editor && !editor.isDestroyed && store)
17
+ fn(editor);
18
+ };
19
+ return {
20
+ get available() {
21
+ return !!store;
22
+ },
23
+ get open() {
24
+ return snapshot.open;
25
+ },
26
+ get mode() {
27
+ return snapshot.mode;
28
+ },
29
+ get query() {
30
+ return snapshot.query;
31
+ },
32
+ get replacement() {
33
+ return snapshot.replacement;
34
+ },
35
+ get options() {
36
+ return snapshot.options;
37
+ },
38
+ get matches() {
39
+ return snapshot.matches;
40
+ },
41
+ get matchCount() {
42
+ return snapshot.matches.length;
43
+ },
44
+ get currentIndex() {
45
+ return snapshot.currentIndex;
46
+ },
47
+ get error() {
48
+ return snapshot.error;
49
+ },
50
+ get truncated() {
51
+ return snapshot.truncated;
52
+ },
53
+ get lastReplaceCount() {
54
+ return snapshot.lastReplaceCount;
55
+ },
56
+ get focusRequest() {
57
+ return snapshot.focusRequest;
58
+ },
59
+ show: (mode = "find") => run((e) => e.commands.openFindReplace({ mode })),
60
+ close: () => run((e) => e.commands.closeFindReplace()),
61
+ setQuery: (query) => run((e) => e.commands.setSearch({ query })),
62
+ setReplacement: (replacement) => run((e) => e.commands.setSearch({ replacement })),
63
+ setOptions: (options) => run((e) => e.commands.setSearch({ options })),
64
+ toggleOption: (option) => run((e) => e.commands.setSearch({ options: { [option]: !snapshot.options[option] } })),
65
+ next: () => run((e) => e.commands.findNext()),
66
+ previous: () => run((e) => e.commands.findPrevious()),
67
+ replace: () => run((e) => e.commands.replaceCurrent()),
68
+ replaceAll: () => run((e) => e.commands.replaceAll()),
69
+ clear: () => run((e) => e.commands.clearSearch()),
70
+ subscribe(runFn) {
71
+ if (!store) {
72
+ runFn(EMPTY_FIND_REPLACE_SNAPSHOT);
73
+ return () => { };
74
+ }
75
+ return store.subscribe(runFn);
76
+ },
77
+ destroy: unsubscribe,
78
+ };
79
+ }
@@ -0,0 +1,6 @@
1
+ import type { ComponentProps } from "svelte";
2
+ import type FindReplaceBarComponent from "./FindReplaceBar.svelte";
3
+ export { default as FindReplaceBar } from "./FindReplaceBar.svelte";
4
+ export * from "./findReplace.svelte.js";
5
+ export type FindReplaceBarProps = ComponentProps<typeof FindReplaceBarComponent>;
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/find-replace/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,KAAK,uBAAuB,MAAM,yBAAyB,CAAC;AAEnE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACpE,cAAc,yBAAyB,CAAC;AAExC,MAAM,MAAM,mBAAmB,GAAG,cAAc,CAAC,OAAO,uBAAuB,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { default as FindReplaceBar } from "./FindReplaceBar.svelte";
2
+ export * from "./findReplace.svelte.js";
@@ -0,0 +1,174 @@
1
+ <script lang="ts" module>
2
+ let nextId = 0;
3
+ </script>
4
+
5
+ <script lang="ts">
6
+ import type { Snippet } from 'svelte';
7
+ import {
8
+ getGenerationControls,
9
+ getReferenceSlots,
10
+ getRequestValue,
11
+ parseControlValue,
12
+ planGenerationRequest,
13
+ removeReference,
14
+ setRequestValue,
15
+ type GenerationControl,
16
+ type GenerationPlan,
17
+ type GenerationReference,
18
+ type GenerationRequest,
19
+ type ModelCapabilities,
20
+ type PlanStrategy,
21
+ type ReferenceRole
22
+ } from '@pixerate/editor/generation';
23
+
24
+ interface Props {
25
+ capabilities: ModelCapabilities;
26
+ /** The request being edited (controlled). */
27
+ value: GenerationRequest;
28
+ onchange: (next: GenerationRequest) => void;
29
+ /** A plan computed elsewhere; otherwise one is computed with `strategy`. */
30
+ plan?: GenerationPlan;
31
+ /** Strategy for the built-in plan (default `nearest`). */
32
+ strategy?: PlanStrategy;
33
+ /** List plan adjustments and errors under the controls (default `true`). */
34
+ showAdjustments?: boolean;
35
+ /** Shows an "Add" button per reference role; you pick the asset and call `onchange`. */
36
+ onaddreference?: (role: ReferenceRole) => void;
37
+ /** Custom rendering of a reference chip (default: its id). */
38
+ reference?: Snippet<[GenerationReference]>;
39
+ class?: string;
40
+ }
41
+
42
+ let {
43
+ capabilities,
44
+ value,
45
+ onchange,
46
+ plan: planProp,
47
+ strategy,
48
+ showAdjustments = true,
49
+ onaddreference,
50
+ reference,
51
+ class: className = ''
52
+ }: Props = $props();
53
+
54
+ const baseId = `generation-settings-${++nextId}`;
55
+ const controls = $derived(getGenerationControls(capabilities));
56
+ const plan = $derived(planProp ?? planGenerationRequest(value, capabilities, { strategy }));
57
+ const slots = $derived(getReferenceSlots(capabilities, value));
58
+
59
+ const inputClass =
60
+ 'w-full rounded-md border border-border bg-background px-2 py-1 text-sm outline-none focus:ring-1 focus:ring-ring';
61
+
62
+ const shown = (control: GenerationControl) => getRequestValue(value, control.key) ?? control.defaultValue;
63
+ const update = (control: GenerationControl, raw: string | boolean) =>
64
+ onchange(setRequestValue(value, control.key, parseControlValue(control, raw)));
65
+ </script>
66
+
67
+ <!-- Headless-styled settings form generated from a model's capabilities. -->
68
+ <div class="flex flex-col gap-3 text-sm {className}" data-generation-settings="" data-model={capabilities.id}>
69
+ {#each controls as control (control.key)}
70
+ {@const id = `${baseId}-${control.key}`}
71
+ {@const current = shown(control)}
72
+ <div class="flex flex-col gap-1" data-control={control.key}>
73
+ <label for={id} class="text-xs font-medium text-muted-foreground" title={control.description}>{control.label}</label>
74
+ {#if control.kind === 'checkbox'}
75
+ <input
76
+ {id}
77
+ type="checkbox"
78
+ data-field={control.key}
79
+ checked={Boolean(current)}
80
+ onchange={(e) => update(control, e.currentTarget.checked)}
81
+ />
82
+ {:else if control.kind === 'select'}
83
+ <select
84
+ {id}
85
+ data-field={control.key}
86
+ class={inputClass}
87
+ value={current === undefined ? '' : String(current)}
88
+ onchange={(e) => update(control, e.currentTarget.value)}
89
+ >
90
+ {#if current === undefined}<option value="">Default</option>{/if}
91
+ {#each control.options ?? [] as option (option.value)}
92
+ <option value={String(option.value)}>{option.label}</option>
93
+ {/each}
94
+ </select>
95
+ {:else if control.kind === 'textarea'}
96
+ <textarea
97
+ {id}
98
+ data-field={control.key}
99
+ class={inputClass}
100
+ rows="2"
101
+ maxlength={control.maxLength}
102
+ value={typeof getRequestValue(value, control.key) === 'string' ? (getRequestValue(value, control.key) as string) : ''}
103
+ oninput={(e) => update(control, e.currentTarget.value)}
104
+ ></textarea>
105
+ {:else}
106
+ <div class="flex items-center gap-2">
107
+ <input
108
+ {id}
109
+ type={control.kind === 'range' ? 'range' : 'number'}
110
+ data-field={control.key}
111
+ class={control.kind === 'range' ? 'flex-1' : inputClass}
112
+ min={control.min}
113
+ max={control.max}
114
+ step={control.step ?? 'any'}
115
+ value={typeof current === 'number' ? current : control.kind === 'range' ? (control.min ?? 0) : ''}
116
+ oninput={(e) => update(control, e.currentTarget.value)}
117
+ />
118
+ {#if control.kind === 'range'}
119
+ <span class="w-10 text-right tabular-nums text-muted-foreground" data-field-value="">
120
+ {typeof current === 'number' ? current : '–'}
121
+ </span>
122
+ {/if}
123
+ </div>
124
+ {/if}
125
+ </div>
126
+ {/each}
127
+
128
+ {#each slots as slot (slot.role)}
129
+ <fieldset class="flex flex-col gap-1" data-reference-role={slot.role}>
130
+ <legend class="text-xs font-medium text-muted-foreground" title={slot.description}>
131
+ {slot.label}
132
+ <span class="tabular-nums" data-reference-count="">({slot.references.length}/{slot.max})</span>
133
+ {#if slot.min > 0}<span class="text-destructive"> *</span>{/if}
134
+ </legend>
135
+ <ul class="flex flex-wrap gap-1">
136
+ {#each slot.references as ref (ref.id)}
137
+ <li class="flex items-center gap-1 rounded border border-border px-1.5 py-0.5" data-reference-id={ref.id}>
138
+ {#if reference}{@render reference(ref)}{:else}<span class="max-w-[10rem] truncate">{ref.id}</span>{/if}
139
+ <button
140
+ type="button"
141
+ aria-label="Remove {slot.label} {ref.id}"
142
+ class="text-muted-foreground hover:text-foreground"
143
+ onclick={() => onchange(removeReference(value, slot.role, ref.id))}>×</button
144
+ >
145
+ </li>
146
+ {/each}
147
+ {#if onaddreference && slot.canAdd}
148
+ <li>
149
+ <button
150
+ type="button"
151
+ class="rounded border border-dashed border-border px-1.5 py-0.5 text-muted-foreground hover:text-foreground"
152
+ onclick={() => onaddreference(slot.role)}>Add</button
153
+ >
154
+ </li>
155
+ {/if}
156
+ </ul>
157
+ </fieldset>
158
+ {/each}
159
+
160
+ {#if showAdjustments && plan.adjustments.length > 0}
161
+ <ul role="status" class="flex flex-col gap-0.5 text-xs text-muted-foreground" data-generation-adjustments="">
162
+ {#each plan.adjustments as adjustment, index (index)}
163
+ <li data-adjustment-field={adjustment.field} data-adjustment-kind={adjustment.kind}>{adjustment.reason}</li>
164
+ {/each}
165
+ </ul>
166
+ {/if}
167
+ {#if showAdjustments && plan.errors.length > 0}
168
+ <ul role="alert" class="flex flex-col gap-0.5 text-xs text-destructive" data-generation-errors="">
169
+ {#each plan.errors as error, index (index)}
170
+ <li data-error-field={error.field}>{error.message}</li>
171
+ {/each}
172
+ </ul>
173
+ {/if}
174
+ </div>
@@ -0,0 +1,23 @@
1
+ import type { Snippet } from 'svelte';
2
+ import { type GenerationPlan, type GenerationReference, type GenerationRequest, type ModelCapabilities, type PlanStrategy, type ReferenceRole } from '@pixerate/editor/generation';
3
+ interface Props {
4
+ capabilities: ModelCapabilities;
5
+ /** The request being edited (controlled). */
6
+ value: GenerationRequest;
7
+ onchange: (next: GenerationRequest) => void;
8
+ /** A plan computed elsewhere; otherwise one is computed with `strategy`. */
9
+ plan?: GenerationPlan;
10
+ /** Strategy for the built-in plan (default `nearest`). */
11
+ strategy?: PlanStrategy;
12
+ /** List plan adjustments and errors under the controls (default `true`). */
13
+ showAdjustments?: boolean;
14
+ /** Shows an "Add" button per reference role; you pick the asset and call `onchange`. */
15
+ onaddreference?: (role: ReferenceRole) => void;
16
+ /** Custom rendering of a reference chip (default: its id). */
17
+ reference?: Snippet<[GenerationReference]>;
18
+ class?: string;
19
+ }
20
+ declare const GenerationSettings: import("svelte").Component<Props, {}, "">;
21
+ type GenerationSettings = ReturnType<typeof GenerationSettings>;
22
+ export default GenerationSettings;
23
+ //# sourceMappingURL=GenerationSettings.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GenerationSettings.svelte.d.ts","sourceRoot":"","sources":["../../src/generation/GenerationSettings.svelte.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EASH,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,aAAa,EACnB,MAAM,6BAA6B,CAAC;AAGrC,UAAU,KAAK;IACb,YAAY,EAAE,iBAAiB,CAAC;IAChC,6CAA6C;IAC7C,KAAK,EAAE,iBAAiB,CAAC;IACzB,QAAQ,EAAE,CAAC,IAAI,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC5C,4EAA4E;IAC5E,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,4EAA4E;IAC5E,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,wFAAwF;IACxF,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,8DAA8D;IAC9D,SAAS,CAAC,EAAE,OAAO,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAyGH,QAAA,MAAM,kBAAkB,2CAAwC,CAAC;AACjE,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAChE,eAAe,kBAAkB,CAAC"}
@@ -0,0 +1,53 @@
1
+ <script lang="ts">
2
+ import { estimateJobProgress } from '@pixerate/editor/generation';
3
+
4
+ interface Props {
5
+ startedAt: number | Date | string;
6
+ expectedMs?: number;
7
+ done?: boolean;
8
+ /** Fixed current time (disables ticking; useful for SSR and tests). */
9
+ now?: number | Date | string;
10
+ /** Update interval while running in ms (default 1000). */
11
+ intervalMs?: number;
12
+ /** Text shown before the time label, e.g. the model name. */
13
+ title?: string;
14
+ /** Extra classes for the root. */
15
+ class?: string;
16
+ }
17
+
18
+ let { startedAt, expectedMs, done = false, now, intervalMs = 1000, title, class: className = '' }: Props = $props();
19
+
20
+ let tick = $state(Date.now());
21
+ const ticking = $derived(!done && now === undefined);
22
+
23
+ $effect(() => {
24
+ if (!ticking) return;
25
+ tick = Date.now();
26
+ const id = setInterval(() => (tick = Date.now()), Math.max(16, intervalMs));
27
+ return () => clearInterval(id);
28
+ });
29
+
30
+ const progress = $derived(estimateJobProgress({ startedAt, expectedMs, done, now: now ?? tick }));
31
+ </script>
32
+
33
+ <!-- Headless-styled, time-based job progress: held at 95% until `done`, with a "~2m left" / "Almost done…" label. -->
34
+ <div class="flex flex-col gap-1 text-sm {className}" data-job-progress="" data-done={done ? '' : undefined}>
35
+ <div class="flex items-center justify-between gap-2 text-muted-foreground">
36
+ {#if title}<span class="truncate text-foreground">{title}</span>{/if}
37
+ <span data-job-progress-label="" aria-live="polite">{progress.label}</span>
38
+ </div>
39
+ <div
40
+ role="progressbar"
41
+ aria-label={title ?? 'Generation progress'}
42
+ aria-valuemin={0}
43
+ aria-valuemax={100}
44
+ aria-valuenow={progress.percent}
45
+ aria-valuetext="{progress.percent}%, {progress.label}"
46
+ class="h-1.5 w-full overflow-hidden rounded-full bg-muted"
47
+ >
48
+ <div
49
+ class="h-full rounded-full bg-primary transition-[width] duration-500"
50
+ style="width: {progress.percent}%; background: var(--primary, currentColor);"
51
+ ></div>
52
+ </div>
53
+ </div>
@@ -0,0 +1,17 @@
1
+ interface Props {
2
+ startedAt: number | Date | string;
3
+ expectedMs?: number;
4
+ done?: boolean;
5
+ /** Fixed current time (disables ticking; useful for SSR and tests). */
6
+ now?: number | Date | string;
7
+ /** Update interval while running in ms (default 1000). */
8
+ intervalMs?: number;
9
+ /** Text shown before the time label, e.g. the model name. */
10
+ title?: string;
11
+ /** Extra classes for the root. */
12
+ class?: string;
13
+ }
14
+ declare const JobProgress: import("svelte").Component<Props, {}, "">;
15
+ type JobProgress = ReturnType<typeof JobProgress>;
16
+ export default JobProgress;
17
+ //# sourceMappingURL=JobProgress.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JobProgress.svelte.d.ts","sourceRoot":"","sources":["../../src/generation/JobProgress.svelte.ts"],"names":[],"mappings":"AAME,UAAU,KAAK;IACb,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAAC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,uEAAuE;IACvE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAAC;IAC7B,0DAA0D;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAiCH,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { ComponentProps } from "svelte";
2
+ import type GenerationSettingsComponent from "./GenerationSettings.svelte";
3
+ import type JobProgressComponent from "./JobProgress.svelte";
4
+ export { default as GenerationSettings } from "./GenerationSettings.svelte";
5
+ export { default as JobProgress } from "./JobProgress.svelte";
6
+ export * from "./plan.svelte.js";
7
+ export * from "./progress.svelte.js";
8
+ export type GenerationSettingsProps = ComponentProps<typeof GenerationSettingsComponent>;
9
+ export type JobProgressProps = ComponentProps<typeof JobProgressComponent>;
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/generation/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,KAAK,2BAA2B,MAAM,6BAA6B,CAAC;AAC3E,OAAO,KAAK,oBAAoB,MAAM,sBAAsB,CAAC;AAE7D,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AAErC,MAAM,MAAM,uBAAuB,GAAG,cAAc,CAAC,OAAO,2BAA2B,CAAC,CAAC;AACzF,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC,OAAO,oBAAoB,CAAC,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { default as GenerationSettings } from "./GenerationSettings.svelte";
2
+ export { default as JobProgress } from "./JobProgress.svelte";
3
+ export * from "./plan.svelte.js";
4
+ export * from "./progress.svelte.js";
@@ -0,0 +1,16 @@
1
+ import { type GenerationPlan, type GenerationRequest, type ModelCapabilities, type PlanAdjustment, type PlanError, type PlanOptions } from "@pixerate/editor/generation";
2
+ export interface SvelteGenerationPlan {
3
+ readonly plan: GenerationPlan;
4
+ readonly ok: boolean;
5
+ /** The adjusted request. */
6
+ readonly request: GenerationRequest;
7
+ readonly adjustments: PlanAdjustment[];
8
+ readonly errors: PlanError[];
9
+ }
10
+ /**
11
+ * Reactive `planGenerationRequest` (Svelte 5 runes). Pass getters so the plan
12
+ * follows `$state` request/capabilities/options:
13
+ * `createGenerationPlan(() => request, () => caps, () => ({ strategy }))`.
14
+ */
15
+ export declare function createGenerationPlan(request: () => GenerationRequest, capabilities: () => ModelCapabilities, options?: () => PlanOptions): SvelteGenerationPlan;
16
+ //# sourceMappingURL=plan.svelte.d.ts.map