@clipkit/editor-core 1.0.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 (71) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +20 -0
  3. package/dist/asset-store.d.ts +40 -0
  4. package/dist/asset-store.d.ts.map +1 -0
  5. package/dist/asset-store.js +181 -0
  6. package/dist/asset-store.js.map +1 -0
  7. package/dist/context.d.ts +17 -0
  8. package/dist/context.d.ts.map +1 -0
  9. package/dist/context.js +23 -0
  10. package/dist/context.js.map +1 -0
  11. package/dist/index.d.ts +20 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +17 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/node-graph.d.ts +42 -0
  16. package/dist/node-graph.d.ts.map +1 -0
  17. package/dist/node-graph.js +123 -0
  18. package/dist/node-graph.js.map +1 -0
  19. package/dist/registry/build.d.ts +3 -0
  20. package/dist/registry/build.d.ts.map +1 -0
  21. package/dist/registry/build.js +84 -0
  22. package/dist/registry/build.js.map +1 -0
  23. package/dist/registry/configuration.d.ts +51 -0
  24. package/dist/registry/configuration.d.ts.map +1 -0
  25. package/dist/registry/configuration.js +92 -0
  26. package/dist/registry/configuration.js.map +1 -0
  27. package/dist/registry/derive.d.ts +30 -0
  28. package/dist/registry/derive.d.ts.map +1 -0
  29. package/dist/registry/derive.js +212 -0
  30. package/dist/registry/derive.js.map +1 -0
  31. package/dist/registry/overrides.d.ts +7 -0
  32. package/dist/registry/overrides.d.ts.map +1 -0
  33. package/dist/registry/overrides.js +322 -0
  34. package/dist/registry/overrides.js.map +1 -0
  35. package/dist/registry/types.d.ts +58 -0
  36. package/dist/registry/types.d.ts.map +1 -0
  37. package/dist/registry/types.js +7 -0
  38. package/dist/registry/types.js.map +1 -0
  39. package/dist/source-diff.d.ts +8 -0
  40. package/dist/source-diff.d.ts.map +1 -0
  41. package/dist/source-diff.js +148 -0
  42. package/dist/source-diff.js.map +1 -0
  43. package/dist/stage-utils.d.ts +170 -0
  44. package/dist/stage-utils.d.ts.map +1 -0
  45. package/dist/stage-utils.js +476 -0
  46. package/dist/stage-utils.js.map +1 -0
  47. package/dist/store.d.ts +123 -0
  48. package/dist/store.d.ts.map +1 -0
  49. package/dist/store.js +234 -0
  50. package/dist/store.js.map +1 -0
  51. package/dist/timeline-utils.d.ts +69 -0
  52. package/dist/timeline-utils.d.ts.map +1 -0
  53. package/dist/timeline-utils.js +211 -0
  54. package/dist/timeline-utils.js.map +1 -0
  55. package/dist/types.d.ts +6 -0
  56. package/dist/types.d.ts.map +1 -0
  57. package/dist/types.js +4 -0
  58. package/dist/types.js.map +1 -0
  59. package/dist/useEditor.d.ts +77 -0
  60. package/dist/useEditor.d.ts.map +1 -0
  61. package/dist/useEditor.js +74 -0
  62. package/dist/useEditor.js.map +1 -0
  63. package/dist/useEditorStore.d.ts +3 -0
  64. package/dist/useEditorStore.d.ts.map +1 -0
  65. package/dist/useEditorStore.js +15 -0
  66. package/dist/useEditorStore.js.map +1 -0
  67. package/dist/usePlaybackSession.d.ts +20 -0
  68. package/dist/usePlaybackSession.d.ts.map +1 -0
  69. package/dist/usePlaybackSession.js +120 -0
  70. package/dist/usePlaybackSession.js.map +1 -0
  71. package/package.json +39 -0
@@ -0,0 +1,211 @@
1
+ // Pure helpers for the Timeline — time ↔ px math, layer listing,
2
+ // snapping, and overlap detection. No React, no DOM, no store.
3
+ // ── Element field accessors with defaults ──────────────────────────
4
+ export function elementTime(el) {
5
+ return typeof el.time === 'number' ? el.time : 0;
6
+ }
7
+ export function elementLayer(el) {
8
+ return typeof el.layer === 'number' ? el.layer : 1;
9
+ }
10
+ export function elementDuration(el, sourceDuration) {
11
+ if (typeof el.duration === 'number')
12
+ return el.duration;
13
+ // 'auto' / 'end' / undefined → take what's left in the composition.
14
+ return Math.max(0.1, sourceDuration - elementTime(el));
15
+ }
16
+ export function elementLabel(el) {
17
+ if (el.type === 'text') {
18
+ const t = el.text;
19
+ return t ? t.slice(0, 28) : 'text';
20
+ }
21
+ if (el.type === 'shape') {
22
+ const s = el.shape;
23
+ return s ?? 'shape';
24
+ }
25
+ if (el.type === 'image' || el.type === 'video' || el.type === 'audio') {
26
+ const src = el.source;
27
+ if (src) {
28
+ const last = src.split('/').pop() ?? src;
29
+ return last.slice(0, 24);
30
+ }
31
+ return el.type;
32
+ }
33
+ if (el.type === 'caption')
34
+ return 'captions';
35
+ if (el.type === 'group') {
36
+ const n = (el.elements?.length ?? 0);
37
+ return `group · ${n}`;
38
+ }
39
+ return el.type;
40
+ }
41
+ // ── Layout: list used layers (ascending — layer 1 is the top row / on top) ──
42
+ export function listUsedLayers(source) {
43
+ const set = new Set();
44
+ for (const el of source.elements)
45
+ set.add(elementLayer(el));
46
+ return Array.from(set).sort((a, b) => a - b);
47
+ }
48
+ // ── Edit-time layer invariant (correct-by-construction; NOT a load normalize) ──
49
+ /** A container's elements sorted FRONT-TO-BACK (layer ascending; layer 1 = front). */
50
+ export function byLayer(elements) {
51
+ return [...elements].sort((a, b) => elementLayer(a) - elementLayer(b));
52
+ }
53
+ /**
54
+ * Stamp dense, unique `layer` values onto a container from its desired
55
+ * FRONT-TO-BACK order: index 0 → layer 1 (front / on top), last → layer N.
56
+ * Run on add / reorder / delete so the editor always writes valid, uniquely
57
+ * layered sources — correct-by-construction at edit time, NOT a load pass.
58
+ * Returns new element objects (all other fields untouched).
59
+ */
60
+ export function reassignLayers(frontToBack) {
61
+ return frontToBack.map((el, i) => ({ ...el, layer: i + 1 }));
62
+ }
63
+ // ── Snap math ──────────────────────────────────────────────────────
64
+ /**
65
+ * Snap `value` to the nearest target within `threshold`. Returns the
66
+ * snapped value AND the target that won, so callers can render a
67
+ * visual snap indicator. If no target wins, returns the input
68
+ * unchanged with `target: null`.
69
+ */
70
+ export function snapTo(value, targets, threshold) {
71
+ let bestTarget = null;
72
+ let bestDist = threshold;
73
+ for (const target of targets) {
74
+ const dist = Math.abs(value - target);
75
+ if (dist < bestDist) {
76
+ bestDist = dist;
77
+ bestTarget = target;
78
+ }
79
+ }
80
+ if (bestTarget === null)
81
+ return { value, target: null };
82
+ return { value: bestTarget, target: bestTarget };
83
+ }
84
+ /**
85
+ * Build the snap target list — composition bounds, the playhead, whole
86
+ * seconds, and every non-dragged element's start + end. Recurses into
87
+ * compositions.
88
+ */
89
+ export function buildSnapTargets(source, sourceDuration, excludeIds, playhead) {
90
+ const targets = [0, sourceDuration, playhead];
91
+ for (let s = 0; s <= Math.ceil(sourceDuration); s++)
92
+ targets.push(s);
93
+ collectElementEdges(source.elements, sourceDuration, excludeIds, targets);
94
+ return targets;
95
+ }
96
+ function collectElementEdges(elements, sourceDuration, excludeIds, out) {
97
+ for (const el of elements) {
98
+ if (el.id && excludeIds.has(el.id))
99
+ continue;
100
+ const t = elementTime(el);
101
+ out.push(t, t + elementDuration(el, sourceDuration));
102
+ if (el.type === 'group') {
103
+ collectElementEdges(el.elements, sourceDuration, excludeIds, out);
104
+ }
105
+ }
106
+ }
107
+ // ── Overlap detection ──────────────────────────────────────────────
108
+ /**
109
+ * Returns true if `[start, end)` on `layer` overlaps any non-excluded
110
+ * element on the same layer. Used to reject invalid drops.
111
+ */
112
+ export function hasOverlapOnLayer(source, sourceDuration, layer, start, end, excludeIds) {
113
+ for (const el of source.elements) {
114
+ if (el.id && excludeIds.has(el.id))
115
+ continue;
116
+ if (elementLayer(el) !== layer)
117
+ continue;
118
+ const elStart = elementTime(el);
119
+ const elEnd = elStart + elementDuration(el, sourceDuration);
120
+ if (start < elEnd && end > elStart)
121
+ return true;
122
+ }
123
+ return false;
124
+ }
125
+ // ── Source duration (mirrors the engine's computeDuration) ─────────
126
+ export function computeSourceDuration(source) {
127
+ if (typeof source.duration === 'number')
128
+ return source.duration;
129
+ let max = 0;
130
+ for (const el of source.elements) {
131
+ const t = elementTime(el);
132
+ const d = typeof el.duration === 'number' ? el.duration : 0;
133
+ if (t + d > max)
134
+ max = t + d;
135
+ }
136
+ return max;
137
+ }
138
+ // ── Ruler ticks + time formatting ───────────────────────────────────
139
+ //
140
+ // Shared by every shell's ruler/readout so 100s-long sources stay
141
+ // readable at any zoom: the tick interval adapts to px-per-second and
142
+ // labels switch to m:ss (and h:mm:ss) once times leave the seconds
143
+ // range.
144
+ /** Candidate major-tick intervals, seconds. */
145
+ const TICK_STEPS = [
146
+ 0.1, 0.25, 0.5, 1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 1200, 1800, 3600,
147
+ ];
148
+ /**
149
+ * Pick the smallest tick interval whose labels stay at least
150
+ * `minLabelPx` apart at the current zoom. `minor` subdivides each
151
+ * major span into 5 for unlabeled sub-ticks.
152
+ */
153
+ export function chooseTickInterval(pxPerSec, minLabelPx = 64) {
154
+ const major = TICK_STEPS.find((s) => s * pxPerSec >= minLabelPx) ??
155
+ TICK_STEPS[TICK_STEPS.length - 1];
156
+ return { major, minor: major / 5 };
157
+ }
158
+ /**
159
+ * Format a time for display. The UNIT is chosen from `unitFor`
160
+ * (default: the value itself) so a readout pair like "0:02.57 /
161
+ * 1:46.00" stays in one consistent format — pass the larger value
162
+ * (total duration) as `unitFor` for both sides.
163
+ *
164
+ * < 60s → "2.57s"
165
+ * < 1h → "1:46.00"
166
+ * ≥ 1h → "1:02:03.00"
167
+ */
168
+ export function formatTimecode(t, unitFor = t) {
169
+ const v = Math.max(0, t);
170
+ if (unitFor < 60)
171
+ return `${v.toFixed(2)}s`;
172
+ const h = Math.floor(v / 3600);
173
+ const m = Math.floor((v % 3600) / 60);
174
+ const ss = (v % 60).toFixed(2).padStart(5, '0');
175
+ return unitFor < 3600 && h === 0
176
+ ? `${m}:${ss}`
177
+ : `${h}:${String(m).padStart(2, '0')}:${ss}`;
178
+ }
179
+ /**
180
+ * Format a ruler tick label. Whole-unit display (no trailing
181
+ * decimals): "5s" / "2.5s" below a minute, "1:30" / "1:02:30" above
182
+ * (unit again chosen by `unitFor`, normally the source duration).
183
+ */
184
+ export function formatTickLabel(t, unitFor = t,
185
+ /** The tick interval. When sub-second, labels gain decimals so a
186
+ * zoomed-in long composition shows 0.5s / 1:02.50 instead of
187
+ * repeating whole seconds. */
188
+ step = 1) {
189
+ const sub = step < 1;
190
+ // Decimal places needed to represent the step exactly (0.5→1,
191
+ // 0.25→2, 0.1→1, ≥1→0) so labels never round a tick to a wrong value.
192
+ const dec = decimalPlaces(step);
193
+ if (unitFor < 60) {
194
+ return `${parseFloat(t.toFixed(dec))}s`;
195
+ }
196
+ const h = Math.floor(t / 3600);
197
+ const m = Math.floor((t % 3600) / 60);
198
+ const secRaw = t % 60;
199
+ const s = sub
200
+ ? secRaw.toFixed(dec).padStart(3 + dec, '0')
201
+ : String(Math.round(secRaw)).padStart(2, '0');
202
+ return unitFor < 3600 && h === 0
203
+ ? `${m}:${s}`
204
+ : `${h}:${String(m).padStart(2, '0')}:${s}`;
205
+ }
206
+ function decimalPlaces(n) {
207
+ const s = String(n);
208
+ const i = s.indexOf('.');
209
+ return i < 0 ? 0 : s.length - i - 1;
210
+ }
211
+ //# sourceMappingURL=timeline-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeline-utils.js","sourceRoot":"","sources":["../src/timeline-utils.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * The dock tools that add new elements. Keep this list narrow — adding
3
+ * a tool means adding an `AddXView` panel and a default element shape.
4
+ */
5
+ export type ToolId = 'text' | 'shape' | 'image' | 'video' | 'audio' | 'caption';
6
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,MAAM,MAAM,MAAM,GACd,MAAM,GACN,OAAO,GACP,OAAO,GACP,OAAO,GACP,OAAO,GACP,SAAS,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,4 @@
1
+ // Shared editor-core types. Shell-specific types (component props,
2
+ // layout) live in the shells.
3
+ export {};
4
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,8BAA8B"}
@@ -0,0 +1,77 @@
1
+ import type { Element, Source } from '@clipkit/protocol';
2
+ import type { EditorUiState } from './store.js';
3
+ import type { ToolId } from './types.js';
4
+ export interface UseEditorReturn {
5
+ /** Patch one element by id. Merges; nested objects aren't recursed. */
6
+ updateElement: (id: string, patch: Partial<Element>) => void;
7
+ /** Append an element to the top-level `elements` array. */
8
+ addElement: (element: Element) => void;
9
+ /** Patch source-level fields (composition settings). */
10
+ patchSource: (patch: Record<string, unknown>, opts?: {
11
+ skipHistory?: boolean;
12
+ }) => void;
13
+ /**
14
+ * Replace the WHOLE document (e.g. a JSON-pane commit). One history
15
+ * entry; selection clears, since ids may no longer exist in the new
16
+ * tree. The engine syncs through the normal source subscription.
17
+ */
18
+ replaceSource: (source: Source) => void;
19
+ /** Remove an element by id. Clears it from selection if selected. */
20
+ removeElement: (id: string) => void;
21
+ /**
22
+ * Batched element update — commit a multi-element edit as one undoable
23
+ * action. When `opts.skipHistory` is true, no history entry is pushed
24
+ * (used during live drag dispatches after `pushHistory()` was called
25
+ * at drag start).
26
+ */
27
+ moveElements: (updates: ReadonlyArray<{
28
+ id: string;
29
+ patch: Partial<Element>;
30
+ }>, opts?: {
31
+ skipHistory?: boolean;
32
+ }) => void;
33
+ /**
34
+ * Snapshot the current source into history. Call once at the start of
35
+ * a live interactive operation (drag, resize, rotate) — the operation
36
+ * dispatches with `{ skipHistory: true }` thereafter, so the whole
37
+ * thing is one undoable action.
38
+ */
39
+ pushHistory: () => void;
40
+ /**
41
+ * Toggle the engine's interactive mode. While true, the engine renders
42
+ * only the current frame on each setSource (no look-ahead buffering).
43
+ * Wrap drag/resize/rotate with `setInteractive(true)` … `(false)` so
44
+ * the canvas updates live but doesn't waste cycles producing frames
45
+ * the next edit invalidates.
46
+ */
47
+ setInteractive: (value: boolean) => void;
48
+ /**
49
+ * Force-sync the engine with the current store source synchronously.
50
+ * The Editor's normal subscription coalesces via rAF, which can leave
51
+ * the engine one frame behind at the end of an interactive operation
52
+ * (mouseup before the rAF fires). Call this at the end of a drag /
53
+ * resize / rotate to guarantee the engine has the latest state before
54
+ * `setInteractive(false)` resumes look-ahead production.
55
+ */
56
+ flushPendingSource: () => void;
57
+ setSelection: (ids: string[]) => void;
58
+ selectOne: (id: string) => void;
59
+ clearSelection: () => void;
60
+ /**
61
+ * Set the active dock tool. Clicking the same tool twice toggles it
62
+ * off. The Panel routes off this value.
63
+ */
64
+ setTool: (tool: ToolId | null) => void;
65
+ /** Patch the editor's UI state (panel layout, stage viewport, etc.). */
66
+ setUiState: (patch: Partial<EditorUiState>) => void;
67
+ play: () => Promise<void>;
68
+ pause: () => void;
69
+ seek: (time: number) => void;
70
+ togglePlay: () => Promise<void>;
71
+ undo: () => void;
72
+ redo: () => void;
73
+ canUndo: () => boolean;
74
+ canRedo: () => boolean;
75
+ }
76
+ export declare function useEditor(): UseEditorReturn;
77
+ //# sourceMappingURL=useEditor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useEditor.d.ts","sourceRoot":"","sources":["../src/useEditor.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAGzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEzC,MAAM,WAAW,eAAe;IAG9B,uEAAuE;IACvE,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;IAC7D,2DAA2D;IAC3D,UAAU,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,wDAAwD;IACxD,WAAW,EAAE,CACX,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,IAAI,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,OAAO,CAAA;KAAE,KAC7B,IAAI,CAAC;IACV;;;;OAIG;IACH,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,qEAAqE;IACrE,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC;;;;;OAKG;IACH,YAAY,EAAE,CACZ,OAAO,EAAE,aAAa,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;KAAE,CAAC,EAC/D,IAAI,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,OAAO,CAAA;KAAE,KAC7B,IAAI,CAAC;IAEV;;;;;OAKG;IACH,WAAW,EAAE,MAAM,IAAI,CAAC;IAExB;;;;;;OAMG;IACH,cAAc,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAEzC;;;;;;;OAOG;IACH,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAI/B,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACtC,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,cAAc,EAAE,MAAM,IAAI,CAAC;IAE3B;;;OAGG;IACH,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAIvC,wEAAwE;IACxE,UAAU,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,aAAa,CAAC,KAAK,IAAI,CAAC;IAIpD,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAIhC,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,OAAO,EAAE,MAAM,OAAO,CAAC;IACvB,OAAO,EAAE,MAAM,OAAO,CAAC;CACxB;AAED,wBAAgB,SAAS,IAAI,eAAe,CA4D3C"}
@@ -0,0 +1,74 @@
1
+ // useEditor — the facade hook every consumer (UI components,
2
+ // programmatic embedders) uses to drive the editor.
3
+ //
4
+ // Wraps the store's low-level mutators with engine-aware behavior and
5
+ // presents a clean action surface. Splits cleanly from state reads:
6
+ //
7
+ // const { updateElement, play, undo } = useEditor(); // actions
8
+ // const source = useEditorStore((s) => s.source); // state
9
+ //
10
+ // Don't reach for `engineRef.current.foo()` in components. Add a
11
+ // method here instead.
12
+ import { useMemo } from 'react';
13
+ import { useEditorContext } from './context.js';
14
+ import { computeElementPatches } from './source-diff.js';
15
+ export function useEditor() {
16
+ const { store, engine } = useEditorContext();
17
+ // The store object reference is stable; getState/setState are stable.
18
+ // We memoize the action surface so destructuring doesn't churn on
19
+ // every render of the consuming component.
20
+ return useMemo(() => ({
21
+ updateElement: (id, patch) => store.getState()._updateElement(id, patch),
22
+ addElement: (element) => store.getState()._addElement(element),
23
+ patchSource: (patch, opts) => store.getState()._patchSource(patch, opts),
24
+ replaceSource: (source) => store.getState()._setSource(source),
25
+ removeElement: (id) => store.getState()._removeElement(id),
26
+ moveElements: (updates, opts) => store.getState()._moveElements(updates, opts),
27
+ pushHistory: () => store.getState()._pushHistory(),
28
+ setInteractive: (value) => engine?.setInteractive(value),
29
+ flushPendingSource: () => {
30
+ if (!engine)
31
+ return;
32
+ const state = store.getState();
33
+ if (!state.playback.ready)
34
+ return;
35
+ const source = state.source;
36
+ if (source === engine.source)
37
+ return;
38
+ const patches = computeElementPatches(engine.source, source);
39
+ if (patches === null) {
40
+ void engine.setSource(source);
41
+ }
42
+ else if (patches.length > 0) {
43
+ engine.patchElements(source, patches);
44
+ }
45
+ },
46
+ setSelection: (ids) => store.getState()._setSelection(ids),
47
+ selectOne: (id) => store.getState()._setSelection([id]),
48
+ clearSelection: () => store.getState()._setSelection([]),
49
+ setTool: (tool) => {
50
+ const current = store.getState().tool;
51
+ store.getState()._setTool(current === tool ? null : tool);
52
+ },
53
+ setUiState: (patch) => store.getState()._setUiState(patch),
54
+ play: async () => {
55
+ if (engine)
56
+ await engine.play();
57
+ },
58
+ pause: () => engine?.pause(),
59
+ seek: (time) => engine?.seek(time),
60
+ togglePlay: async () => {
61
+ if (!engine)
62
+ return;
63
+ if (engine.playing)
64
+ engine.pause();
65
+ else
66
+ await engine.play();
67
+ },
68
+ undo: () => store.getState()._undo(),
69
+ redo: () => store.getState()._redo(),
70
+ canUndo: () => store.getState().history.past.length > 0,
71
+ canRedo: () => store.getState().history.future.length > 0,
72
+ }), [store, engine]);
73
+ }
74
+ //# sourceMappingURL=useEditor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useEditor.js","sourceRoot":"","sources":["../src/useEditor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { EditorState } from './store.js';
2
+ export declare function useEditorStore<T>(selector: (state: EditorState) => T): T;
3
+ //# sourceMappingURL=useEditorStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useEditorStore.d.ts","sourceRoot":"","sources":["../src/useEditorStore.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,wBAAgB,cAAc,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,CAAC,GAAG,CAAC,CAGxE"}
@@ -0,0 +1,15 @@
1
+ // useEditorStore — public state-reading hook. Components select the
2
+ // slices they need; Zustand handles fine-grained subscriptions.
3
+ //
4
+ // const source = useEditorStore((s) => s.source);
5
+ // const selection = useEditorStore((s) => s.selection);
6
+ // const time = useEditorStore((s) => s.playback.time);
7
+ //
8
+ // Pair with `useEditor()` for dispatching actions.
9
+ import { useStore } from 'zustand';
10
+ import { useEditorContext } from './context.js';
11
+ export function useEditorStore(selector) {
12
+ const { store } = useEditorContext();
13
+ return useStore(store, selector);
14
+ }
15
+ //# sourceMappingURL=useEditorStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useEditorStore.js","sourceRoot":"","sources":["../src/useEditorStore.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,gEAAgE;AAChE,EAAE;AACF,oDAAoD;AACpD,0DAA0D;AAC1D,yDAAyD;AACzD,EAAE;AACF,mDAAmD;AAEnD,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGhD,MAAM,UAAU,cAAc,CAAI,QAAmC;IACnE,MAAM,EAAE,KAAK,EAAE,GAAG,gBAAgB,EAAE,CAAC;IACrC,OAAO,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;AACnC,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { type RefObject } from 'react';
2
+ import { PlaybackEngine } from '@clipkit/playback';
3
+ import type { Source } from '@clipkit/protocol';
4
+ import type { EditorStore } from './store.js';
5
+ export interface PlaybackSessionOptions {
6
+ /**
7
+ * Override the renderer's backend selection. Defaults to `'auto'`
8
+ * (WebGPU → WebGL2 fallback). Forced backends are mostly useful for
9
+ * debugging.
10
+ */
11
+ backend?: 'auto' | 'webgpu' | 'webgl2';
12
+ /**
13
+ * Fired whenever a source change is dispatched to the engine — after
14
+ * every mutation, undo, or external setSource. Use this to persist
15
+ * user edits.
16
+ */
17
+ onSourceChange?: (source: Source) => void;
18
+ }
19
+ export declare function usePlaybackSession(store: EditorStore, canvasRef: RefObject<HTMLCanvasElement | null>, options?: PlaybackSessionOptions): PlaybackEngine | null;
20
+ //# sourceMappingURL=usePlaybackSession.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePlaybackSession.d.ts","sourceRoot":"","sources":["../src/usePlaybackSession.ts"],"names":[],"mappings":"AAQA,OAAO,EAAuB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACvC;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3C;AAED,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,WAAW,EAClB,SAAS,EAAE,SAAS,CAAC,iBAAiB,GAAG,IAAI,CAAC,EAC9C,OAAO,GAAE,sBAA2B,GACnC,cAAc,GAAG,IAAI,CAqHvB"}
@@ -0,0 +1,120 @@
1
+ // usePlaybackSession — constructs the PlaybackEngine against a mounted
2
+ // canvas and wires it to the editor store: engine events → playback
3
+ // state, source changes → two-tier engine dispatch, loop toggle →
4
+ // engine. Extracted verbatim from the basic editor's root so every
5
+ // shell shares one engine-sync implementation.
6
+ 'use client';
7
+ import { useEffect, useState } from 'react';
8
+ import { PlaybackEngine } from '@clipkit/playback';
9
+ import { computeElementPatches } from './source-diff.js';
10
+ export function usePlaybackSession(store, canvasRef, options = {}) {
11
+ const { backend = 'auto', onSourceChange } = options;
12
+ const [engine, setEngine] = useState(null);
13
+ // ── Construct the engine once the canvas is mounted ────────────────
14
+ useEffect(() => {
15
+ const canvas = canvasRef.current;
16
+ if (!canvas)
17
+ return;
18
+ const nextEngine = new PlaybackEngine({
19
+ displayCanvas: canvas,
20
+ source: store.getState().source,
21
+ backend,
22
+ });
23
+ setEngine(nextEngine);
24
+ const offTime = nextEngine.onTime((t) => store.getState()._setPlaybackState({ time: t }));
25
+ const offPlaying = nextEngine.onPlayingChange((p) => store.getState()._setPlaybackState({ playing: p }));
26
+ const offError = nextEngine.onError((e) => store.getState()._setPlaybackState({ error: e.message }));
27
+ nextEngine.ready
28
+ .then(() => store.getState()._setPlaybackState({
29
+ ready: true,
30
+ duration: nextEngine.duration,
31
+ }))
32
+ .catch((err) => store.getState()._setPlaybackState({
33
+ error: err instanceof Error ? err.message : String(err),
34
+ }));
35
+ return () => {
36
+ offTime();
37
+ offPlaying();
38
+ offError();
39
+ nextEngine.dispose();
40
+ setEngine(null);
41
+ };
42
+ }, [store, backend, canvasRef]);
43
+ // ── Push source changes into the engine ────────────────────────────
44
+ //
45
+ // Two-tier dispatch path:
46
+ // 1. Diff the new source against the engine's current source.
47
+ // 2. If only patchable fields changed → engine.patchElements(...).
48
+ // Tiny payload, no audio reschedule, no preload, no full
49
+ // runtime reload of the source dictionary.
50
+ // 3. Otherwise (structural / timing / asset change) →
51
+ // engine.setSource(next) — the full path with preload + audio.
52
+ //
53
+ // rAF coalescing: multiple store updates within the same animation
54
+ // frame collapse into one dispatch. Only the latest source reaches
55
+ // the engine.
56
+ useEffect(() => {
57
+ if (!engine)
58
+ return;
59
+ let scheduled = null;
60
+ let rafId = null;
61
+ // Flat stage view (lens rule) drops the camera from the source the
62
+ // PREVIEW engine sees — orthographic, gizmo-correct editing — while
63
+ // the store's source (and persistence) keep the camera untouched.
64
+ const forEngine = (s) => store.getState().ui.stageView === 'flat' && s.camera
65
+ ? { ...s, camera: undefined }
66
+ : s;
67
+ const flush = () => {
68
+ rafId = null;
69
+ const next = scheduled;
70
+ scheduled = null;
71
+ if (next === null)
72
+ return;
73
+ if (!store.getState().playback.ready)
74
+ return;
75
+ const engineNext = forEngine(next);
76
+ const patches = computeElementPatches(engine.source, engineNext);
77
+ if (patches === null) {
78
+ // Structural / timing / asset change — full path.
79
+ void engine.setSource(engineNext);
80
+ }
81
+ else if (patches.length > 0) {
82
+ // Visual-only change — patch path (engine swaps source ref,
83
+ // sends only the deltas to the worker).
84
+ engine.patchElements(engineNext, patches);
85
+ }
86
+ // patches.length === 0 → nothing to do (already in sync).
87
+ // Persist the REAL source (with camera), not the preview variant.
88
+ onSourceChange?.(next);
89
+ };
90
+ const unsubscribe = store.subscribe((state, prev) => {
91
+ // Re-push on a source change OR a stage-view toggle (flat⇄camera
92
+ // adds/removes the camera the preview engine sees).
93
+ if (state.source === prev.source && state.ui.stageView === prev.ui.stageView)
94
+ return;
95
+ scheduled = state.source;
96
+ if (rafId === null)
97
+ rafId = requestAnimationFrame(flush);
98
+ });
99
+ return () => {
100
+ unsubscribe();
101
+ if (rafId !== null)
102
+ cancelAnimationFrame(rafId);
103
+ };
104
+ }, [engine, store, onSourceChange]);
105
+ // ── Loop toggle → engine ───────────────────────────────────────────
106
+ // Subscribe to ui.loop and forward to the engine. Cheaper than
107
+ // reading from a hook in a child (would re-render the whole shell
108
+ // on every loop toggle).
109
+ useEffect(() => {
110
+ if (!engine)
111
+ return;
112
+ engine.setLoop(store.getState().ui.loop);
113
+ return store.subscribe((state, prev) => {
114
+ if (state.ui.loop !== prev.ui.loop)
115
+ engine.setLoop(state.ui.loop);
116
+ });
117
+ }, [engine, store]);
118
+ return engine;
119
+ }
120
+ //# sourceMappingURL=usePlaybackSession.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePlaybackSession.js","sourceRoot":"","sources":["../src/usePlaybackSession.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@clipkit/editor-core",
3
+ "version": "1.0.0",
4
+ "description": "Clipkit editor core — the shared data layer under every editor shell: document store (undo/redo, selection), engine-sync session hook, source diffing, and the pure stage/timeline math. UI-free.",
5
+ "license": "Apache-2.0",
6
+ "repository": { "type": "git", "url": "git+https://github.com/clipkit-video/clipkit.git", "directory": "packages/editor-core" },
7
+ "homepage": "https://clipkit.dev",
8
+ "bugs": "https://github.com/clipkit-video/clipkit/issues",
9
+ "type": "module",
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js",
16
+ "default": "./dist/index.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "README.md",
22
+ "LICENSE"
23
+ ],
24
+ "scripts": {
25
+ "build": "tsc",
26
+ "dev": "tsc --watch",
27
+ "typecheck": "tsc --noEmit",
28
+ "clean": "rm -rf dist"
29
+ },
30
+ "dependencies": {
31
+ "@clipkit/playback": "^1.0.0",
32
+ "@clipkit/protocol": "^1.0.0",
33
+ "immer": "^10.1.0",
34
+ "zustand": "^5.0.0"
35
+ },
36
+ "peerDependencies": {
37
+ "react": "^19.0.0"
38
+ }
39
+ }