@aicut/react 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ziqiang
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,143 @@
1
+ # @aicut/react
2
+
3
+ React 18 / 19 wrapper around **[@aicut/core](https://www.npmjs.com/package/@aicut/core)** — a canvas-rendered video editor component you can drop into any host app. Import the core stylesheet once and you're done.
4
+
5
+ ```bash
6
+ pnpm add @aicut/react @aicut/core
7
+ ```
8
+
9
+ ## Quick start
10
+
11
+ ```tsx
12
+ import { useRef } from "react";
13
+ import {
14
+ VideoEditor,
15
+ type VideoEditorApi,
16
+ type Project,
17
+ } from "@aicut/react";
18
+ import "@aicut/core/styles.css";
19
+
20
+ const project: Project = {
21
+ version: 1,
22
+ sources: [{ id: "s1", url: "/media/a.mp4", kind: "video", name: "a.mp4" }],
23
+ tracks: [
24
+ { id: "t1", kind: "video", clips: [
25
+ { id: "c1", sourceId: "s1", in: 0, out: 5000, start: 0 },
26
+ ]},
27
+ ],
28
+ };
29
+
30
+ export function Editor() {
31
+ const apiRef = useRef<VideoEditorApi | null>(null);
32
+ return (
33
+ <VideoEditor
34
+ apiRef={apiRef}
35
+ defaultProject={project}
36
+ onChange={(p) => console.log("autosave", p)}
37
+ onExport={(p) => fetch("/api/export", {
38
+ method: "POST",
39
+ body: JSON.stringify({ project: p }),
40
+ })}
41
+ style={{ height: 600 }}
42
+ />
43
+ );
44
+ }
45
+ ```
46
+
47
+ The component is **uncontrolled for project state** — the editor owns the current project. To restore from JSON later, `apiRef.current?.setProject(saved)`.
48
+
49
+ ## Props
50
+
51
+ | Prop | Type | Notes |
52
+ | --- | --- | --- |
53
+ | `defaultProject` | `Project` | Initial project. Read once on mount. |
54
+ | `theme` | `Theme` | CSS variable overrides. Reactive — mirrors to `editor.setTheme`. |
55
+ | `locale` | `Partial<Locale>` | UI strings. English by default; pass `localeZh` for Chinese. Reactive. |
56
+ | `toolbarLeft` | `ReactNode` | Portaled into the editor toolbar's left bookend slot. |
57
+ | `toolbarRight` | `ReactNode` | Portaled into the right slot — host's Export button lives here. |
58
+ | `apiRef` | `Ref<VideoEditorApi \| null>` | Imperative API handle. |
59
+ | `onReady` | `(api) => void` | Fires synchronously on mount. |
60
+ | `onChange` | `(project) => void` | Any model mutation. |
61
+ | `onExport` | `(project) => void` | Fired by `api.requestExport()`. |
62
+ | `onTimeUpdate` | `(ms) => void` | Playback tick. |
63
+ | `onPlay` / `onPause` | `() => void` | |
64
+ | `onSelectionChange` | `(clipId \| null) => void` | |
65
+ | `onError` | `(err) => void` | |
66
+ | `className` / `style` | `string` / `CSSProperties` | Forwarded to the host `<div>`. |
67
+
68
+ The `apiRef` value implements **every method on `EditorApi`** — `play`, `pause`, `seek`, `split`, `trimLeft`, `trimRight`, `setProject`, `getProject`, `addSource`, `addTrack`, `removeClip`, `setSelection`, `undo`, `redo`, `setTheme`, `setLocale`, `requestExport`, etc. See [`@aicut/core`](https://www.npmjs.com/package/@aicut/core) for the full surface.
69
+
70
+ ## Custom toolbar controls
71
+
72
+ Drop any React node into `toolbarLeft` / `toolbarRight`. The library renders nothing into the slots and hides the separator until they're populated.
73
+
74
+ ```tsx
75
+ <VideoEditor
76
+ apiRef={apiRef}
77
+ defaultProject={project}
78
+ toolbarLeft={
79
+ <select value={aspect} onChange={(e) => setAspect(e.target.value)}>
80
+ <option value="16:9">16:9</option>
81
+ <option value="9:16">9:16</option>
82
+ <option value="1:1">1:1</option>
83
+ </select>
84
+ }
85
+ toolbarRight={
86
+ <button onClick={() => apiRef.current?.requestExport()}>Export</button>
87
+ }
88
+ />
89
+ ```
90
+
91
+ `api.requestExport()` fires the `export` event with the current project JSON, which flows back through your `onExport` prop. From there, POST it to your own backend.
92
+
93
+ ## Theming
94
+
95
+ ```tsx
96
+ <VideoEditor
97
+ theme={{
98
+ controlsBg: "#f6f6f8",
99
+ controlsText: "rgba(0, 0, 0, 0.78)",
100
+ controlsBorder: "rgba(0, 0, 0, 0.08)",
101
+ controlsHover: "rgba(0, 0, 0, 0.06)",
102
+ controlsActive: "rgba(0, 0, 0, 0.08)",
103
+ previewBg: "#e4e4e7", // letterbox colour around the video
104
+ }}
105
+ /* … */
106
+ />
107
+ ```
108
+
109
+ The `theme` prop is reactive — swap it any time and the editor calls `setTheme` internally.
110
+
111
+ ## i18n
112
+
113
+ English is default. Pass the bundled `localeZh` for Chinese, or a partial object to override specific keys.
114
+
115
+ ```tsx
116
+ import { VideoEditor, localeZh } from "@aicut/react";
117
+
118
+ <VideoEditor locale={localeZh} /* … */ />
119
+ <VideoEditor locale={{ undo: "Annuler" }} /* … */ />
120
+ ```
121
+
122
+ ## Standalone `<Timeline>`
123
+
124
+ Use the canvas timeline without the rest of the editor.
125
+
126
+ ```tsx
127
+ import { Timeline, type TimelineApi } from "@aicut/react";
128
+
129
+ <Timeline
130
+ apiRef={timelineRef}
131
+ defaultProject={singleClipProject}
132
+ showHeader={false}
133
+ readOnly
134
+ toolbar // 36px top strip
135
+ toolbarLeft={<span>Picked at {ms / 1000}s</span>}
136
+ toolbarRight={<button onClick={pick}>Use frame</button>}
137
+ onSeek={(ms) => setPicked(ms)}
138
+ />
139
+ ```
140
+
141
+ ## License
142
+
143
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,198 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ Timeline: () => Timeline,
24
+ VideoEditor: () => VideoEditor,
25
+ createEmptyProject: () => import_core3.createEmptyProject,
26
+ createId: () => import_core3.createId,
27
+ localeEn: () => import_core3.localeEn,
28
+ localeZh: () => import_core3.localeZh
29
+ });
30
+ module.exports = __toCommonJS(index_exports);
31
+
32
+ // src/VideoEditor.tsx
33
+ var import_react = require("react");
34
+ var import_react_dom = require("react-dom");
35
+ var import_core = require("@aicut/core");
36
+ var import_jsx_runtime = require("react/jsx-runtime");
37
+ function VideoEditor(props) {
38
+ const hostRef = (0, import_react.useRef)(null);
39
+ const editorRef = (0, import_react.useRef)(null);
40
+ const [slots, setSlots] = (0, import_react.useState)(null);
41
+ const cbRef = (0, import_react.useRef)(props);
42
+ cbRef.current = props;
43
+ (0, import_react.useEffect)(() => {
44
+ const host = hostRef.current;
45
+ if (!host) return;
46
+ const editor = import_core.Editor.create({
47
+ container: host,
48
+ project: cbRef.current.defaultProject,
49
+ theme: cbRef.current.theme,
50
+ locale: cbRef.current.locale
51
+ });
52
+ editorRef.current = editor;
53
+ setSlots({ left: editor.toolbarLeft, right: editor.toolbarRight });
54
+ const offs = [
55
+ editor.on("change", ({ project }) => cbRef.current.onChange?.(project)),
56
+ editor.on("export", ({ project }) => cbRef.current.onExport?.(project)),
57
+ editor.on("time", ({ timeMs }) => cbRef.current.onTimeUpdate?.(timeMs)),
58
+ editor.on("play", () => cbRef.current.onPlay?.()),
59
+ editor.on("pause", () => cbRef.current.onPause?.()),
60
+ editor.on(
61
+ "selectionChange",
62
+ ({ clipId }) => cbRef.current.onSelectionChange?.(clipId)
63
+ ),
64
+ editor.on("error", ({ error }) => cbRef.current.onError?.(error))
65
+ ];
66
+ cbRef.current.onReady?.(editor);
67
+ return () => {
68
+ for (const off of offs) off();
69
+ editor.destroy();
70
+ editorRef.current = null;
71
+ setSlots(null);
72
+ };
73
+ }, []);
74
+ (0, import_react.useEffect)(() => {
75
+ if (props.theme) editorRef.current?.setTheme(props.theme);
76
+ }, [props.theme]);
77
+ (0, import_react.useEffect)(() => {
78
+ if (props.locale) editorRef.current?.setLocale(props.locale);
79
+ }, [props.locale]);
80
+ (0, import_react.useImperativeHandle)(
81
+ props.apiRef,
82
+ () => editorRef.current,
83
+ [slots]
84
+ );
85
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
86
+ "div",
87
+ {
88
+ ref: hostRef,
89
+ className: props.className,
90
+ style: props.style,
91
+ "data-aicut-host": "",
92
+ children: [
93
+ slots && props.toolbarLeft != null ? (0, import_react_dom.createPortal)(props.toolbarLeft, slots.left) : null,
94
+ slots && props.toolbarRight != null ? (0, import_react_dom.createPortal)(props.toolbarRight, slots.right) : null
95
+ ]
96
+ }
97
+ );
98
+ }
99
+
100
+ // src/Timeline.tsx
101
+ var import_react2 = require("react");
102
+ var import_react_dom2 = require("react-dom");
103
+ var import_core2 = require("@aicut/core");
104
+ var import_jsx_runtime2 = require("react/jsx-runtime");
105
+ function Timeline(props) {
106
+ const hostRef = (0, import_react2.useRef)(null);
107
+ const tlRef = (0, import_react2.useRef)(null);
108
+ const [slots, setSlots] = (0, import_react2.useState)(null);
109
+ const cbRef = (0, import_react2.useRef)(props);
110
+ cbRef.current = props;
111
+ (0, import_react2.useEffect)(() => {
112
+ const host = hostRef.current;
113
+ if (!host) return;
114
+ const tl = import_core2.Timeline.create({
115
+ container: host,
116
+ project: cbRef.current.defaultProject,
117
+ pxPerSec: cbRef.current.defaultScale,
118
+ time: cbRef.current.defaultTime,
119
+ selectedClipId: cbRef.current.defaultSelectedClipId ?? null,
120
+ showHeader: cbRef.current.showHeader,
121
+ readOnly: cbRef.current.readOnly,
122
+ snap: cbRef.current.snap,
123
+ autoFit: cbRef.current.autoFit,
124
+ locale: cbRef.current.locale,
125
+ toolbar: cbRef.current.toolbar,
126
+ onSeek: (t) => cbRef.current.onSeek?.(t),
127
+ onSelectClip: (id) => cbRef.current.onSelectClip?.(id),
128
+ onScaleChange: (s) => cbRef.current.onScaleChange?.(s),
129
+ onMoveClip: (id, opts) => cbRef.current.onMoveClip?.(id, opts),
130
+ onResizeClip: (id, e) => cbRef.current.onResizeClip?.(id, e),
131
+ onChange: (p) => cbRef.current.onChange?.(p)
132
+ });
133
+ tlRef.current = tl;
134
+ if (tl.toolbarLeft && tl.toolbarRight) {
135
+ setSlots({ left: tl.toolbarLeft, right: tl.toolbarRight });
136
+ }
137
+ return () => {
138
+ tl.destroy();
139
+ tlRef.current = null;
140
+ setSlots(null);
141
+ };
142
+ }, []);
143
+ (0, import_react2.useEffect)(() => {
144
+ if (props.locale) tlRef.current?.setLocale(props.locale);
145
+ }, [props.locale]);
146
+ (0, import_react2.useImperativeHandle)(
147
+ props.apiRef,
148
+ () => {
149
+ const tl = tlRef.current;
150
+ if (!tl) return null;
151
+ return {
152
+ setProject: (p) => tl.setProject(p),
153
+ getProject: () => tl.getProject(),
154
+ setTime: (t) => tl.setTime(t),
155
+ getTime: () => tl.getTime(),
156
+ setScale: (s) => tl.setScale(s),
157
+ getScale: () => tl.getScale(),
158
+ setSelection: (id) => tl.setSelection(id),
159
+ getSelection: () => tl.getSelection(),
160
+ setSnap: (s) => tl.setSnap(s),
161
+ fitToWindow: () => tl.fitToWindow(),
162
+ getDebugInfo: () => tl.getDebugInfo()
163
+ };
164
+ },
165
+ // Same caveat as VideoEditor.tsx — factory must re-run once the
166
+ // timeline is created in useEffect, otherwise apiRef.current is
167
+ // null forever. `slots` flips from null to a real value the
168
+ // instant the timeline is ready, so it's the cleanest trigger.
169
+ [slots]
170
+ );
171
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
172
+ "div",
173
+ {
174
+ ref: hostRef,
175
+ className: props.className,
176
+ style: { width: "100%", height: 240, ...props.style },
177
+ "data-aicut-timeline-host": "",
178
+ children: [
179
+ slots && props.toolbarLeft != null ? (0, import_react_dom2.createPortal)(props.toolbarLeft, slots.left) : null,
180
+ slots && props.toolbarRight != null ? (0, import_react_dom2.createPortal)(props.toolbarRight, slots.right) : null
181
+ ]
182
+ }
183
+ );
184
+ void {};
185
+ }
186
+
187
+ // src/index.ts
188
+ var import_core3 = require("@aicut/core");
189
+ // Annotate the CommonJS export names for ESM import in node:
190
+ 0 && (module.exports = {
191
+ Timeline,
192
+ VideoEditor,
193
+ createEmptyProject,
194
+ createId,
195
+ localeEn,
196
+ localeZh
197
+ });
198
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/VideoEditor.tsx","../src/Timeline.tsx"],"sourcesContent":["export { VideoEditor } from \"./VideoEditor.js\";\nexport type { VideoEditorProps, VideoEditorApi } from \"./VideoEditor.js\";\nexport { Timeline } from \"./Timeline.js\";\nexport type { TimelineProps, TimelineApi } from \"./Timeline.js\";\nexport type {\n Project,\n MediaSource,\n Track,\n Clip,\n Ms,\n Theme,\n EditorApi,\n Locale,\n} from \"@aicut/core\";\nexport { createEmptyProject, createId, localeEn, localeZh } from \"@aicut/core\";\n","import {\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n type CSSProperties,\n type ReactNode,\n type Ref,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport {\n Editor,\n type EditorApi,\n type Locale,\n type Ms,\n type Project,\n type Theme,\n} from \"@aicut/core\";\n\nexport type VideoEditorApi = EditorApi;\n\nexport interface VideoEditorProps {\n /**\n * Initial project. Read once on mount — to swap projects after mount,\n * call `apiRef.current.setProject(...)` so React doesn't reinstantiate\n * the editor and lose playback state.\n */\n defaultProject?: Project;\n /** CSS variable overrides applied on mount and whenever this ref changes. */\n theme?: Theme;\n /**\n * UI string overrides (English default). Mirror prop — switching the\n * value calls `editor.setLocale` and the toolbar / canvas labels\n * update in place. Use `localeZh` from `@aicut/core` for Chinese.\n */\n locale?: Partial<Locale>;\n\n className?: string;\n style?: CSSProperties;\n\n /** Imperative handle for cut/seek/getProject/setProject/etc. */\n apiRef?: Ref<VideoEditorApi | null>;\n\n onReady?: (api: VideoEditorApi) => void;\n onChange?: (project: Project) => void;\n onExport?: (project: Project) => void;\n onTimeUpdate?: (timeMs: Ms) => void;\n onPlay?: () => void;\n onPause?: () => void;\n onSelectionChange?: (clipId: string | null) => void;\n onError?: (error: Error) => void;\n\n /**\n * Rendered into the very left of the editor's top toolbar — host\n * adds anything here (size dropdown, branding, status badge). The\n * library reserves no space for it; if you pass nothing, no\n * separator appears.\n */\n toolbarLeft?: ReactNode;\n /** Same as `toolbarLeft` but at the very right of the toolbar. */\n toolbarRight?: ReactNode;\n}\n\n/**\n * Declarative React shell over `@aicut/core` `Editor`. Mounts the\n * editor instance once, mirrors prop changes (`theme`) into it, and\n * forwards events as React-style callbacks.\n *\n * Intentionally uncontrolled for project state — the editor owns the\n * current project. Use `onChange` to persist and `apiRef.setProject`\n * to restore.\n */\nexport function VideoEditor(props: VideoEditorProps) {\n const hostRef = useRef<HTMLDivElement | null>(null);\n const editorRef = useRef<Editor | null>(null);\n // Toolbar slot DOM nodes don't exist until the editor mounts; we\n // hold them in state so React re-runs the render after mount and\n // the portals attach. Tracked separately for left + right because\n // each is independently controlled by host props.\n const [slots, setSlots] = useState<{\n left: HTMLElement;\n right: HTMLElement;\n } | null>(null);\n\n // Latest-callback refs so the effect that creates the editor doesn't\n // re-run on every parent render just because props.onChange is a new\n // identity — the editor would otherwise be torn down constantly.\n const cbRef = useRef(props);\n cbRef.current = props;\n\n useEffect(() => {\n const host = hostRef.current;\n if (!host) return;\n const editor = Editor.create({\n container: host,\n project: cbRef.current.defaultProject,\n theme: cbRef.current.theme,\n locale: cbRef.current.locale,\n });\n editorRef.current = editor;\n setSlots({ left: editor.toolbarLeft, right: editor.toolbarRight });\n\n const offs = [\n editor.on(\"change\", ({ project }) => cbRef.current.onChange?.(project)),\n editor.on(\"export\", ({ project }) => cbRef.current.onExport?.(project)),\n editor.on(\"time\", ({ timeMs }) => cbRef.current.onTimeUpdate?.(timeMs)),\n editor.on(\"play\", () => cbRef.current.onPlay?.()),\n editor.on(\"pause\", () => cbRef.current.onPause?.()),\n editor.on(\"selectionChange\", ({ clipId }) =>\n cbRef.current.onSelectionChange?.(clipId),\n ),\n editor.on(\"error\", ({ error }) => cbRef.current.onError?.(error)),\n ];\n\n cbRef.current.onReady?.(editor);\n\n return () => {\n for (const off of offs) off();\n editor.destroy();\n editorRef.current = null;\n setSlots(null);\n };\n // Editor lifecycle is tied to mount; we deliberately don't list\n // any reactive deps. `theme` changes are pushed through the\n // separate effect below.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n useEffect(() => {\n if (props.theme) editorRef.current?.setTheme(props.theme);\n }, [props.theme]);\n\n useEffect(() => {\n if (props.locale) editorRef.current?.setLocale(props.locale);\n }, [props.locale]);\n\n // Deps must include `slots`. Without it, the factory ran once during\n // the first commit — BEFORE the useEffect above had a chance to\n // create the editor — so `apiRef.current` was permanently locked to\n // null. `slots` flips from null to a real value the same instant\n // the editor is created, so it's the cleanest re-run trigger.\n useImperativeHandle<VideoEditorApi | null, VideoEditorApi | null>(\n props.apiRef,\n () => editorRef.current,\n [slots],\n );\n\n return (\n <div\n ref={hostRef}\n className={props.className}\n style={props.style}\n data-aicut-host=\"\"\n >\n {slots && props.toolbarLeft != null\n ? createPortal(props.toolbarLeft, slots.left)\n : null}\n {slots && props.toolbarRight != null\n ? createPortal(props.toolbarRight, slots.right)\n : null}\n </div>\n );\n}\n","import {\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n type CSSProperties,\n type ReactNode,\n type Ref,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport {\n Timeline as CoreTimeline,\n type Clip,\n type Locale,\n type Ms,\n type Project,\n type TimelineOptions,\n} from \"@aicut/core\";\n\n/** Imperative handle exposed via `apiRef`. */\nexport interface TimelineApi {\n setProject(p: Project): void;\n getProject(): Project;\n setTime(t: Ms): void;\n getTime(): Ms;\n setScale(pxPerSec: number): void;\n getScale(): number;\n setSelection(id: string | null): void;\n getSelection(): string | null;\n setSnap(snap: boolean): void;\n fitToWindow(): void;\n getDebugInfo(): ReturnType<CoreTimeline[\"getDebugInfo\"]>;\n}\n\nexport interface TimelineProps {\n /** Initial project. Use `apiRef.current.setProject(...)` to swap. */\n defaultProject: Project;\n /** Initial scale (px/sec). Defaults to 80; auto-fits on first render. */\n defaultScale?: number;\n /** Initial playhead position. */\n defaultTime?: Ms;\n /** Initial selection. */\n defaultSelectedClipId?: string | null;\n\n /** Hide the left header column (compact / frame-picker mode). */\n showHeader?: boolean;\n /** Disable all editing interactions. */\n readOnly?: boolean;\n /** Snap to clip edges + playhead when dragging. Default true. */\n snap?: boolean;\n /** Apply fit-to-window on mount once duration is known. Default true. */\n autoFit?: boolean;\n /** UI string overrides (English default). */\n locale?: Partial<Locale>;\n /**\n * Render a 36px top toolbar strip with empty left/right flex slots\n * for host-supplied controls. Default false. Pair with `toolbarLeft`\n * / `toolbarRight` to inject content.\n */\n toolbar?: boolean;\n /** Rendered into the left slot of the timeline toolbar (toolbar must be true). */\n toolbarLeft?: ReactNode;\n /** Rendered into the right slot of the timeline toolbar. */\n toolbarRight?: ReactNode;\n\n className?: string;\n style?: CSSProperties;\n\n apiRef?: Ref<TimelineApi | null>;\n\n onSeek?: (timeMs: Ms) => void;\n onSelectClip?: (clipId: string | null) => void;\n onScaleChange?: (pxPerSec: number) => void;\n onMoveClip?: TimelineOptions[\"onMoveClip\"];\n onResizeClip?: TimelineOptions[\"onResizeClip\"];\n onChange?: (project: Project) => void;\n}\n\n/**\n * Standalone, framework-agnostic canvas Timeline wrapped for React.\n * Mount it without an `Editor` for use cases like a video frame-picker:\n *\n * ```tsx\n * <Timeline\n * defaultProject={{ version: 1, sources: [video], tracks: [{ id, kind: \"video\", clips: [{...}] }] }}\n * showHeader={false}\n * readOnly\n * onSeek={(ms) => setCurrentMs(ms)}\n * />\n * ```\n *\n * Uncontrolled for `project` and `pxPerSec` — the underlying Timeline\n * owns them and reports changes via callbacks. Call methods on\n * `apiRef.current` to drive it imperatively (mirroring ag-Grid /\n * VideoEditor patterns).\n */\nexport function Timeline(props: TimelineProps) {\n const hostRef = useRef<HTMLDivElement | null>(null);\n const tlRef = useRef<CoreTimeline | null>(null);\n const [slots, setSlots] = useState<{\n left: HTMLElement;\n right: HTMLElement;\n } | null>(null);\n\n // Latest-callback ref so the create-once effect doesn't tear the\n // timeline down on every render just because callback identities\n // change.\n const cbRef = useRef(props);\n cbRef.current = props;\n\n useEffect(() => {\n const host = hostRef.current;\n if (!host) return;\n const tl = CoreTimeline.create({\n container: host,\n project: cbRef.current.defaultProject,\n pxPerSec: cbRef.current.defaultScale,\n time: cbRef.current.defaultTime,\n selectedClipId: cbRef.current.defaultSelectedClipId ?? null,\n showHeader: cbRef.current.showHeader,\n readOnly: cbRef.current.readOnly,\n snap: cbRef.current.snap,\n autoFit: cbRef.current.autoFit,\n locale: cbRef.current.locale,\n toolbar: cbRef.current.toolbar,\n onSeek: (t) => cbRef.current.onSeek?.(t),\n onSelectClip: (id) => cbRef.current.onSelectClip?.(id),\n onScaleChange: (s) => cbRef.current.onScaleChange?.(s),\n onMoveClip: (id, opts) => cbRef.current.onMoveClip?.(id, opts),\n onResizeClip: (id, e) => cbRef.current.onResizeClip?.(id, e),\n onChange: (p) => cbRef.current.onChange?.(p),\n });\n tlRef.current = tl;\n if (tl.toolbarLeft && tl.toolbarRight) {\n setSlots({ left: tl.toolbarLeft, right: tl.toolbarRight });\n }\n return () => {\n tl.destroy();\n tlRef.current = null;\n setSlots(null);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n useEffect(() => {\n if (props.locale) tlRef.current?.setLocale(props.locale);\n }, [props.locale]);\n\n useImperativeHandle<TimelineApi | null, TimelineApi | null>(\n props.apiRef,\n () => {\n const tl = tlRef.current;\n if (!tl) return null;\n return {\n setProject: (p) => tl.setProject(p),\n getProject: () => tl.getProject(),\n setTime: (t) => tl.setTime(t),\n getTime: () => tl.getTime(),\n setScale: (s) => tl.setScale(s),\n getScale: () => tl.getScale(),\n setSelection: (id) => tl.setSelection(id),\n getSelection: () => tl.getSelection(),\n setSnap: (s) => tl.setSnap(s),\n fitToWindow: () => tl.fitToWindow(),\n getDebugInfo: () => tl.getDebugInfo(),\n };\n },\n // Same caveat as VideoEditor.tsx — factory must re-run once the\n // timeline is created in useEffect, otherwise apiRef.current is\n // null forever. `slots` flips from null to a real value the\n // instant the timeline is ready, so it's the cleanest trigger.\n [slots],\n );\n\n return (\n <div\n ref={hostRef}\n className={props.className}\n style={{ width: \"100%\", height: 240, ...props.style }}\n data-aicut-timeline-host=\"\"\n >\n {slots && props.toolbarLeft != null\n ? createPortal(props.toolbarLeft, slots.left)\n : null}\n {slots && props.toolbarRight != null\n ? createPortal(props.toolbarRight, slots.right)\n : null}\n </div>\n );\n\n // Type-only re-export used to keep React/Vue prop typings in lockstep\n // with the core. Reference here so the symbol isn't tree-shaken.\n void ({} as Clip);\n}\n"],"mappings":"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","names":["import_react","import_react_dom","import_core","import_jsx_runtime","CoreTimeline","import_core"]}
@@ -0,0 +1,128 @@
1
+ import * as react from 'react';
2
+ import { CSSProperties, Ref, ReactNode } from 'react';
3
+ import { Project, Theme, Locale, EditorApi, Ms, Timeline as Timeline$1, TimelineOptions } from '@aicut/core';
4
+ export { Clip, EditorApi, Locale, MediaSource, Ms, Project, Theme, Track, createEmptyProject, createId, localeEn, localeZh } from '@aicut/core';
5
+
6
+ type VideoEditorApi = EditorApi;
7
+ interface VideoEditorProps {
8
+ /**
9
+ * Initial project. Read once on mount — to swap projects after mount,
10
+ * call `apiRef.current.setProject(...)` so React doesn't reinstantiate
11
+ * the editor and lose playback state.
12
+ */
13
+ defaultProject?: Project;
14
+ /** CSS variable overrides applied on mount and whenever this ref changes. */
15
+ theme?: Theme;
16
+ /**
17
+ * UI string overrides (English default). Mirror prop — switching the
18
+ * value calls `editor.setLocale` and the toolbar / canvas labels
19
+ * update in place. Use `localeZh` from `@aicut/core` for Chinese.
20
+ */
21
+ locale?: Partial<Locale>;
22
+ className?: string;
23
+ style?: CSSProperties;
24
+ /** Imperative handle for cut/seek/getProject/setProject/etc. */
25
+ apiRef?: Ref<VideoEditorApi | null>;
26
+ onReady?: (api: VideoEditorApi) => void;
27
+ onChange?: (project: Project) => void;
28
+ onExport?: (project: Project) => void;
29
+ onTimeUpdate?: (timeMs: Ms) => void;
30
+ onPlay?: () => void;
31
+ onPause?: () => void;
32
+ onSelectionChange?: (clipId: string | null) => void;
33
+ onError?: (error: Error) => void;
34
+ /**
35
+ * Rendered into the very left of the editor's top toolbar — host
36
+ * adds anything here (size dropdown, branding, status badge). The
37
+ * library reserves no space for it; if you pass nothing, no
38
+ * separator appears.
39
+ */
40
+ toolbarLeft?: ReactNode;
41
+ /** Same as `toolbarLeft` but at the very right of the toolbar. */
42
+ toolbarRight?: ReactNode;
43
+ }
44
+ /**
45
+ * Declarative React shell over `@aicut/core` `Editor`. Mounts the
46
+ * editor instance once, mirrors prop changes (`theme`) into it, and
47
+ * forwards events as React-style callbacks.
48
+ *
49
+ * Intentionally uncontrolled for project state — the editor owns the
50
+ * current project. Use `onChange` to persist and `apiRef.setProject`
51
+ * to restore.
52
+ */
53
+ declare function VideoEditor(props: VideoEditorProps): react.JSX.Element;
54
+
55
+ /** Imperative handle exposed via `apiRef`. */
56
+ interface TimelineApi {
57
+ setProject(p: Project): void;
58
+ getProject(): Project;
59
+ setTime(t: Ms): void;
60
+ getTime(): Ms;
61
+ setScale(pxPerSec: number): void;
62
+ getScale(): number;
63
+ setSelection(id: string | null): void;
64
+ getSelection(): string | null;
65
+ setSnap(snap: boolean): void;
66
+ fitToWindow(): void;
67
+ getDebugInfo(): ReturnType<Timeline$1["getDebugInfo"]>;
68
+ }
69
+ interface TimelineProps {
70
+ /** Initial project. Use `apiRef.current.setProject(...)` to swap. */
71
+ defaultProject: Project;
72
+ /** Initial scale (px/sec). Defaults to 80; auto-fits on first render. */
73
+ defaultScale?: number;
74
+ /** Initial playhead position. */
75
+ defaultTime?: Ms;
76
+ /** Initial selection. */
77
+ defaultSelectedClipId?: string | null;
78
+ /** Hide the left header column (compact / frame-picker mode). */
79
+ showHeader?: boolean;
80
+ /** Disable all editing interactions. */
81
+ readOnly?: boolean;
82
+ /** Snap to clip edges + playhead when dragging. Default true. */
83
+ snap?: boolean;
84
+ /** Apply fit-to-window on mount once duration is known. Default true. */
85
+ autoFit?: boolean;
86
+ /** UI string overrides (English default). */
87
+ locale?: Partial<Locale>;
88
+ /**
89
+ * Render a 36px top toolbar strip with empty left/right flex slots
90
+ * for host-supplied controls. Default false. Pair with `toolbarLeft`
91
+ * / `toolbarRight` to inject content.
92
+ */
93
+ toolbar?: boolean;
94
+ /** Rendered into the left slot of the timeline toolbar (toolbar must be true). */
95
+ toolbarLeft?: ReactNode;
96
+ /** Rendered into the right slot of the timeline toolbar. */
97
+ toolbarRight?: ReactNode;
98
+ className?: string;
99
+ style?: CSSProperties;
100
+ apiRef?: Ref<TimelineApi | null>;
101
+ onSeek?: (timeMs: Ms) => void;
102
+ onSelectClip?: (clipId: string | null) => void;
103
+ onScaleChange?: (pxPerSec: number) => void;
104
+ onMoveClip?: TimelineOptions["onMoveClip"];
105
+ onResizeClip?: TimelineOptions["onResizeClip"];
106
+ onChange?: (project: Project) => void;
107
+ }
108
+ /**
109
+ * Standalone, framework-agnostic canvas Timeline wrapped for React.
110
+ * Mount it without an `Editor` for use cases like a video frame-picker:
111
+ *
112
+ * ```tsx
113
+ * <Timeline
114
+ * defaultProject={{ version: 1, sources: [video], tracks: [{ id, kind: "video", clips: [{...}] }] }}
115
+ * showHeader={false}
116
+ * readOnly
117
+ * onSeek={(ms) => setCurrentMs(ms)}
118
+ * />
119
+ * ```
120
+ *
121
+ * Uncontrolled for `project` and `pxPerSec` — the underlying Timeline
122
+ * owns them and reports changes via callbacks. Call methods on
123
+ * `apiRef.current` to drive it imperatively (mirroring ag-Grid /
124
+ * VideoEditor patterns).
125
+ */
126
+ declare function Timeline(props: TimelineProps): react.JSX.Element;
127
+
128
+ export { Timeline, type TimelineApi, type TimelineProps, VideoEditor, type VideoEditorApi, type VideoEditorProps };
@@ -0,0 +1,128 @@
1
+ import * as react from 'react';
2
+ import { CSSProperties, Ref, ReactNode } from 'react';
3
+ import { Project, Theme, Locale, EditorApi, Ms, Timeline as Timeline$1, TimelineOptions } from '@aicut/core';
4
+ export { Clip, EditorApi, Locale, MediaSource, Ms, Project, Theme, Track, createEmptyProject, createId, localeEn, localeZh } from '@aicut/core';
5
+
6
+ type VideoEditorApi = EditorApi;
7
+ interface VideoEditorProps {
8
+ /**
9
+ * Initial project. Read once on mount — to swap projects after mount,
10
+ * call `apiRef.current.setProject(...)` so React doesn't reinstantiate
11
+ * the editor and lose playback state.
12
+ */
13
+ defaultProject?: Project;
14
+ /** CSS variable overrides applied on mount and whenever this ref changes. */
15
+ theme?: Theme;
16
+ /**
17
+ * UI string overrides (English default). Mirror prop — switching the
18
+ * value calls `editor.setLocale` and the toolbar / canvas labels
19
+ * update in place. Use `localeZh` from `@aicut/core` for Chinese.
20
+ */
21
+ locale?: Partial<Locale>;
22
+ className?: string;
23
+ style?: CSSProperties;
24
+ /** Imperative handle for cut/seek/getProject/setProject/etc. */
25
+ apiRef?: Ref<VideoEditorApi | null>;
26
+ onReady?: (api: VideoEditorApi) => void;
27
+ onChange?: (project: Project) => void;
28
+ onExport?: (project: Project) => void;
29
+ onTimeUpdate?: (timeMs: Ms) => void;
30
+ onPlay?: () => void;
31
+ onPause?: () => void;
32
+ onSelectionChange?: (clipId: string | null) => void;
33
+ onError?: (error: Error) => void;
34
+ /**
35
+ * Rendered into the very left of the editor's top toolbar — host
36
+ * adds anything here (size dropdown, branding, status badge). The
37
+ * library reserves no space for it; if you pass nothing, no
38
+ * separator appears.
39
+ */
40
+ toolbarLeft?: ReactNode;
41
+ /** Same as `toolbarLeft` but at the very right of the toolbar. */
42
+ toolbarRight?: ReactNode;
43
+ }
44
+ /**
45
+ * Declarative React shell over `@aicut/core` `Editor`. Mounts the
46
+ * editor instance once, mirrors prop changes (`theme`) into it, and
47
+ * forwards events as React-style callbacks.
48
+ *
49
+ * Intentionally uncontrolled for project state — the editor owns the
50
+ * current project. Use `onChange` to persist and `apiRef.setProject`
51
+ * to restore.
52
+ */
53
+ declare function VideoEditor(props: VideoEditorProps): react.JSX.Element;
54
+
55
+ /** Imperative handle exposed via `apiRef`. */
56
+ interface TimelineApi {
57
+ setProject(p: Project): void;
58
+ getProject(): Project;
59
+ setTime(t: Ms): void;
60
+ getTime(): Ms;
61
+ setScale(pxPerSec: number): void;
62
+ getScale(): number;
63
+ setSelection(id: string | null): void;
64
+ getSelection(): string | null;
65
+ setSnap(snap: boolean): void;
66
+ fitToWindow(): void;
67
+ getDebugInfo(): ReturnType<Timeline$1["getDebugInfo"]>;
68
+ }
69
+ interface TimelineProps {
70
+ /** Initial project. Use `apiRef.current.setProject(...)` to swap. */
71
+ defaultProject: Project;
72
+ /** Initial scale (px/sec). Defaults to 80; auto-fits on first render. */
73
+ defaultScale?: number;
74
+ /** Initial playhead position. */
75
+ defaultTime?: Ms;
76
+ /** Initial selection. */
77
+ defaultSelectedClipId?: string | null;
78
+ /** Hide the left header column (compact / frame-picker mode). */
79
+ showHeader?: boolean;
80
+ /** Disable all editing interactions. */
81
+ readOnly?: boolean;
82
+ /** Snap to clip edges + playhead when dragging. Default true. */
83
+ snap?: boolean;
84
+ /** Apply fit-to-window on mount once duration is known. Default true. */
85
+ autoFit?: boolean;
86
+ /** UI string overrides (English default). */
87
+ locale?: Partial<Locale>;
88
+ /**
89
+ * Render a 36px top toolbar strip with empty left/right flex slots
90
+ * for host-supplied controls. Default false. Pair with `toolbarLeft`
91
+ * / `toolbarRight` to inject content.
92
+ */
93
+ toolbar?: boolean;
94
+ /** Rendered into the left slot of the timeline toolbar (toolbar must be true). */
95
+ toolbarLeft?: ReactNode;
96
+ /** Rendered into the right slot of the timeline toolbar. */
97
+ toolbarRight?: ReactNode;
98
+ className?: string;
99
+ style?: CSSProperties;
100
+ apiRef?: Ref<TimelineApi | null>;
101
+ onSeek?: (timeMs: Ms) => void;
102
+ onSelectClip?: (clipId: string | null) => void;
103
+ onScaleChange?: (pxPerSec: number) => void;
104
+ onMoveClip?: TimelineOptions["onMoveClip"];
105
+ onResizeClip?: TimelineOptions["onResizeClip"];
106
+ onChange?: (project: Project) => void;
107
+ }
108
+ /**
109
+ * Standalone, framework-agnostic canvas Timeline wrapped for React.
110
+ * Mount it without an `Editor` for use cases like a video frame-picker:
111
+ *
112
+ * ```tsx
113
+ * <Timeline
114
+ * defaultProject={{ version: 1, sources: [video], tracks: [{ id, kind: "video", clips: [{...}] }] }}
115
+ * showHeader={false}
116
+ * readOnly
117
+ * onSeek={(ms) => setCurrentMs(ms)}
118
+ * />
119
+ * ```
120
+ *
121
+ * Uncontrolled for `project` and `pxPerSec` — the underlying Timeline
122
+ * owns them and reports changes via callbacks. Call methods on
123
+ * `apiRef.current` to drive it imperatively (mirroring ag-Grid /
124
+ * VideoEditor patterns).
125
+ */
126
+ declare function Timeline(props: TimelineProps): react.JSX.Element;
127
+
128
+ export { Timeline, type TimelineApi, type TimelineProps, VideoEditor, type VideoEditorApi, type VideoEditorProps };
package/dist/index.js ADDED
@@ -0,0 +1,180 @@
1
+ // src/VideoEditor.tsx
2
+ import {
3
+ useEffect,
4
+ useImperativeHandle,
5
+ useRef,
6
+ useState
7
+ } from "react";
8
+ import { createPortal } from "react-dom";
9
+ import {
10
+ Editor
11
+ } from "@aicut/core";
12
+ import { jsxs } from "react/jsx-runtime";
13
+ function VideoEditor(props) {
14
+ const hostRef = useRef(null);
15
+ const editorRef = useRef(null);
16
+ const [slots, setSlots] = useState(null);
17
+ const cbRef = useRef(props);
18
+ cbRef.current = props;
19
+ useEffect(() => {
20
+ const host = hostRef.current;
21
+ if (!host) return;
22
+ const editor = Editor.create({
23
+ container: host,
24
+ project: cbRef.current.defaultProject,
25
+ theme: cbRef.current.theme,
26
+ locale: cbRef.current.locale
27
+ });
28
+ editorRef.current = editor;
29
+ setSlots({ left: editor.toolbarLeft, right: editor.toolbarRight });
30
+ const offs = [
31
+ editor.on("change", ({ project }) => cbRef.current.onChange?.(project)),
32
+ editor.on("export", ({ project }) => cbRef.current.onExport?.(project)),
33
+ editor.on("time", ({ timeMs }) => cbRef.current.onTimeUpdate?.(timeMs)),
34
+ editor.on("play", () => cbRef.current.onPlay?.()),
35
+ editor.on("pause", () => cbRef.current.onPause?.()),
36
+ editor.on(
37
+ "selectionChange",
38
+ ({ clipId }) => cbRef.current.onSelectionChange?.(clipId)
39
+ ),
40
+ editor.on("error", ({ error }) => cbRef.current.onError?.(error))
41
+ ];
42
+ cbRef.current.onReady?.(editor);
43
+ return () => {
44
+ for (const off of offs) off();
45
+ editor.destroy();
46
+ editorRef.current = null;
47
+ setSlots(null);
48
+ };
49
+ }, []);
50
+ useEffect(() => {
51
+ if (props.theme) editorRef.current?.setTheme(props.theme);
52
+ }, [props.theme]);
53
+ useEffect(() => {
54
+ if (props.locale) editorRef.current?.setLocale(props.locale);
55
+ }, [props.locale]);
56
+ useImperativeHandle(
57
+ props.apiRef,
58
+ () => editorRef.current,
59
+ [slots]
60
+ );
61
+ return /* @__PURE__ */ jsxs(
62
+ "div",
63
+ {
64
+ ref: hostRef,
65
+ className: props.className,
66
+ style: props.style,
67
+ "data-aicut-host": "",
68
+ children: [
69
+ slots && props.toolbarLeft != null ? createPortal(props.toolbarLeft, slots.left) : null,
70
+ slots && props.toolbarRight != null ? createPortal(props.toolbarRight, slots.right) : null
71
+ ]
72
+ }
73
+ );
74
+ }
75
+
76
+ // src/Timeline.tsx
77
+ import {
78
+ useEffect as useEffect2,
79
+ useImperativeHandle as useImperativeHandle2,
80
+ useRef as useRef2,
81
+ useState as useState2
82
+ } from "react";
83
+ import { createPortal as createPortal2 } from "react-dom";
84
+ import {
85
+ Timeline as CoreTimeline
86
+ } from "@aicut/core";
87
+ import { jsxs as jsxs2 } from "react/jsx-runtime";
88
+ function Timeline(props) {
89
+ const hostRef = useRef2(null);
90
+ const tlRef = useRef2(null);
91
+ const [slots, setSlots] = useState2(null);
92
+ const cbRef = useRef2(props);
93
+ cbRef.current = props;
94
+ useEffect2(() => {
95
+ const host = hostRef.current;
96
+ if (!host) return;
97
+ const tl = CoreTimeline.create({
98
+ container: host,
99
+ project: cbRef.current.defaultProject,
100
+ pxPerSec: cbRef.current.defaultScale,
101
+ time: cbRef.current.defaultTime,
102
+ selectedClipId: cbRef.current.defaultSelectedClipId ?? null,
103
+ showHeader: cbRef.current.showHeader,
104
+ readOnly: cbRef.current.readOnly,
105
+ snap: cbRef.current.snap,
106
+ autoFit: cbRef.current.autoFit,
107
+ locale: cbRef.current.locale,
108
+ toolbar: cbRef.current.toolbar,
109
+ onSeek: (t) => cbRef.current.onSeek?.(t),
110
+ onSelectClip: (id) => cbRef.current.onSelectClip?.(id),
111
+ onScaleChange: (s) => cbRef.current.onScaleChange?.(s),
112
+ onMoveClip: (id, opts) => cbRef.current.onMoveClip?.(id, opts),
113
+ onResizeClip: (id, e) => cbRef.current.onResizeClip?.(id, e),
114
+ onChange: (p) => cbRef.current.onChange?.(p)
115
+ });
116
+ tlRef.current = tl;
117
+ if (tl.toolbarLeft && tl.toolbarRight) {
118
+ setSlots({ left: tl.toolbarLeft, right: tl.toolbarRight });
119
+ }
120
+ return () => {
121
+ tl.destroy();
122
+ tlRef.current = null;
123
+ setSlots(null);
124
+ };
125
+ }, []);
126
+ useEffect2(() => {
127
+ if (props.locale) tlRef.current?.setLocale(props.locale);
128
+ }, [props.locale]);
129
+ useImperativeHandle2(
130
+ props.apiRef,
131
+ () => {
132
+ const tl = tlRef.current;
133
+ if (!tl) return null;
134
+ return {
135
+ setProject: (p) => tl.setProject(p),
136
+ getProject: () => tl.getProject(),
137
+ setTime: (t) => tl.setTime(t),
138
+ getTime: () => tl.getTime(),
139
+ setScale: (s) => tl.setScale(s),
140
+ getScale: () => tl.getScale(),
141
+ setSelection: (id) => tl.setSelection(id),
142
+ getSelection: () => tl.getSelection(),
143
+ setSnap: (s) => tl.setSnap(s),
144
+ fitToWindow: () => tl.fitToWindow(),
145
+ getDebugInfo: () => tl.getDebugInfo()
146
+ };
147
+ },
148
+ // Same caveat as VideoEditor.tsx — factory must re-run once the
149
+ // timeline is created in useEffect, otherwise apiRef.current is
150
+ // null forever. `slots` flips from null to a real value the
151
+ // instant the timeline is ready, so it's the cleanest trigger.
152
+ [slots]
153
+ );
154
+ return /* @__PURE__ */ jsxs2(
155
+ "div",
156
+ {
157
+ ref: hostRef,
158
+ className: props.className,
159
+ style: { width: "100%", height: 240, ...props.style },
160
+ "data-aicut-timeline-host": "",
161
+ children: [
162
+ slots && props.toolbarLeft != null ? createPortal2(props.toolbarLeft, slots.left) : null,
163
+ slots && props.toolbarRight != null ? createPortal2(props.toolbarRight, slots.right) : null
164
+ ]
165
+ }
166
+ );
167
+ void {};
168
+ }
169
+
170
+ // src/index.ts
171
+ import { createEmptyProject, createId, localeEn, localeZh } from "@aicut/core";
172
+ export {
173
+ Timeline,
174
+ VideoEditor,
175
+ createEmptyProject,
176
+ createId,
177
+ localeEn,
178
+ localeZh
179
+ };
180
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/VideoEditor.tsx","../src/Timeline.tsx","../src/index.ts"],"sourcesContent":["import {\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n type CSSProperties,\n type ReactNode,\n type Ref,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport {\n Editor,\n type EditorApi,\n type Locale,\n type Ms,\n type Project,\n type Theme,\n} from \"@aicut/core\";\n\nexport type VideoEditorApi = EditorApi;\n\nexport interface VideoEditorProps {\n /**\n * Initial project. Read once on mount — to swap projects after mount,\n * call `apiRef.current.setProject(...)` so React doesn't reinstantiate\n * the editor and lose playback state.\n */\n defaultProject?: Project;\n /** CSS variable overrides applied on mount and whenever this ref changes. */\n theme?: Theme;\n /**\n * UI string overrides (English default). Mirror prop — switching the\n * value calls `editor.setLocale` and the toolbar / canvas labels\n * update in place. Use `localeZh` from `@aicut/core` for Chinese.\n */\n locale?: Partial<Locale>;\n\n className?: string;\n style?: CSSProperties;\n\n /** Imperative handle for cut/seek/getProject/setProject/etc. */\n apiRef?: Ref<VideoEditorApi | null>;\n\n onReady?: (api: VideoEditorApi) => void;\n onChange?: (project: Project) => void;\n onExport?: (project: Project) => void;\n onTimeUpdate?: (timeMs: Ms) => void;\n onPlay?: () => void;\n onPause?: () => void;\n onSelectionChange?: (clipId: string | null) => void;\n onError?: (error: Error) => void;\n\n /**\n * Rendered into the very left of the editor's top toolbar — host\n * adds anything here (size dropdown, branding, status badge). The\n * library reserves no space for it; if you pass nothing, no\n * separator appears.\n */\n toolbarLeft?: ReactNode;\n /** Same as `toolbarLeft` but at the very right of the toolbar. */\n toolbarRight?: ReactNode;\n}\n\n/**\n * Declarative React shell over `@aicut/core` `Editor`. Mounts the\n * editor instance once, mirrors prop changes (`theme`) into it, and\n * forwards events as React-style callbacks.\n *\n * Intentionally uncontrolled for project state — the editor owns the\n * current project. Use `onChange` to persist and `apiRef.setProject`\n * to restore.\n */\nexport function VideoEditor(props: VideoEditorProps) {\n const hostRef = useRef<HTMLDivElement | null>(null);\n const editorRef = useRef<Editor | null>(null);\n // Toolbar slot DOM nodes don't exist until the editor mounts; we\n // hold them in state so React re-runs the render after mount and\n // the portals attach. Tracked separately for left + right because\n // each is independently controlled by host props.\n const [slots, setSlots] = useState<{\n left: HTMLElement;\n right: HTMLElement;\n } | null>(null);\n\n // Latest-callback refs so the effect that creates the editor doesn't\n // re-run on every parent render just because props.onChange is a new\n // identity — the editor would otherwise be torn down constantly.\n const cbRef = useRef(props);\n cbRef.current = props;\n\n useEffect(() => {\n const host = hostRef.current;\n if (!host) return;\n const editor = Editor.create({\n container: host,\n project: cbRef.current.defaultProject,\n theme: cbRef.current.theme,\n locale: cbRef.current.locale,\n });\n editorRef.current = editor;\n setSlots({ left: editor.toolbarLeft, right: editor.toolbarRight });\n\n const offs = [\n editor.on(\"change\", ({ project }) => cbRef.current.onChange?.(project)),\n editor.on(\"export\", ({ project }) => cbRef.current.onExport?.(project)),\n editor.on(\"time\", ({ timeMs }) => cbRef.current.onTimeUpdate?.(timeMs)),\n editor.on(\"play\", () => cbRef.current.onPlay?.()),\n editor.on(\"pause\", () => cbRef.current.onPause?.()),\n editor.on(\"selectionChange\", ({ clipId }) =>\n cbRef.current.onSelectionChange?.(clipId),\n ),\n editor.on(\"error\", ({ error }) => cbRef.current.onError?.(error)),\n ];\n\n cbRef.current.onReady?.(editor);\n\n return () => {\n for (const off of offs) off();\n editor.destroy();\n editorRef.current = null;\n setSlots(null);\n };\n // Editor lifecycle is tied to mount; we deliberately don't list\n // any reactive deps. `theme` changes are pushed through the\n // separate effect below.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n useEffect(() => {\n if (props.theme) editorRef.current?.setTheme(props.theme);\n }, [props.theme]);\n\n useEffect(() => {\n if (props.locale) editorRef.current?.setLocale(props.locale);\n }, [props.locale]);\n\n // Deps must include `slots`. Without it, the factory ran once during\n // the first commit — BEFORE the useEffect above had a chance to\n // create the editor — so `apiRef.current` was permanently locked to\n // null. `slots` flips from null to a real value the same instant\n // the editor is created, so it's the cleanest re-run trigger.\n useImperativeHandle<VideoEditorApi | null, VideoEditorApi | null>(\n props.apiRef,\n () => editorRef.current,\n [slots],\n );\n\n return (\n <div\n ref={hostRef}\n className={props.className}\n style={props.style}\n data-aicut-host=\"\"\n >\n {slots && props.toolbarLeft != null\n ? createPortal(props.toolbarLeft, slots.left)\n : null}\n {slots && props.toolbarRight != null\n ? createPortal(props.toolbarRight, slots.right)\n : null}\n </div>\n );\n}\n","import {\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n type CSSProperties,\n type ReactNode,\n type Ref,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport {\n Timeline as CoreTimeline,\n type Clip,\n type Locale,\n type Ms,\n type Project,\n type TimelineOptions,\n} from \"@aicut/core\";\n\n/** Imperative handle exposed via `apiRef`. */\nexport interface TimelineApi {\n setProject(p: Project): void;\n getProject(): Project;\n setTime(t: Ms): void;\n getTime(): Ms;\n setScale(pxPerSec: number): void;\n getScale(): number;\n setSelection(id: string | null): void;\n getSelection(): string | null;\n setSnap(snap: boolean): void;\n fitToWindow(): void;\n getDebugInfo(): ReturnType<CoreTimeline[\"getDebugInfo\"]>;\n}\n\nexport interface TimelineProps {\n /** Initial project. Use `apiRef.current.setProject(...)` to swap. */\n defaultProject: Project;\n /** Initial scale (px/sec). Defaults to 80; auto-fits on first render. */\n defaultScale?: number;\n /** Initial playhead position. */\n defaultTime?: Ms;\n /** Initial selection. */\n defaultSelectedClipId?: string | null;\n\n /** Hide the left header column (compact / frame-picker mode). */\n showHeader?: boolean;\n /** Disable all editing interactions. */\n readOnly?: boolean;\n /** Snap to clip edges + playhead when dragging. Default true. */\n snap?: boolean;\n /** Apply fit-to-window on mount once duration is known. Default true. */\n autoFit?: boolean;\n /** UI string overrides (English default). */\n locale?: Partial<Locale>;\n /**\n * Render a 36px top toolbar strip with empty left/right flex slots\n * for host-supplied controls. Default false. Pair with `toolbarLeft`\n * / `toolbarRight` to inject content.\n */\n toolbar?: boolean;\n /** Rendered into the left slot of the timeline toolbar (toolbar must be true). */\n toolbarLeft?: ReactNode;\n /** Rendered into the right slot of the timeline toolbar. */\n toolbarRight?: ReactNode;\n\n className?: string;\n style?: CSSProperties;\n\n apiRef?: Ref<TimelineApi | null>;\n\n onSeek?: (timeMs: Ms) => void;\n onSelectClip?: (clipId: string | null) => void;\n onScaleChange?: (pxPerSec: number) => void;\n onMoveClip?: TimelineOptions[\"onMoveClip\"];\n onResizeClip?: TimelineOptions[\"onResizeClip\"];\n onChange?: (project: Project) => void;\n}\n\n/**\n * Standalone, framework-agnostic canvas Timeline wrapped for React.\n * Mount it without an `Editor` for use cases like a video frame-picker:\n *\n * ```tsx\n * <Timeline\n * defaultProject={{ version: 1, sources: [video], tracks: [{ id, kind: \"video\", clips: [{...}] }] }}\n * showHeader={false}\n * readOnly\n * onSeek={(ms) => setCurrentMs(ms)}\n * />\n * ```\n *\n * Uncontrolled for `project` and `pxPerSec` — the underlying Timeline\n * owns them and reports changes via callbacks. Call methods on\n * `apiRef.current` to drive it imperatively (mirroring ag-Grid /\n * VideoEditor patterns).\n */\nexport function Timeline(props: TimelineProps) {\n const hostRef = useRef<HTMLDivElement | null>(null);\n const tlRef = useRef<CoreTimeline | null>(null);\n const [slots, setSlots] = useState<{\n left: HTMLElement;\n right: HTMLElement;\n } | null>(null);\n\n // Latest-callback ref so the create-once effect doesn't tear the\n // timeline down on every render just because callback identities\n // change.\n const cbRef = useRef(props);\n cbRef.current = props;\n\n useEffect(() => {\n const host = hostRef.current;\n if (!host) return;\n const tl = CoreTimeline.create({\n container: host,\n project: cbRef.current.defaultProject,\n pxPerSec: cbRef.current.defaultScale,\n time: cbRef.current.defaultTime,\n selectedClipId: cbRef.current.defaultSelectedClipId ?? null,\n showHeader: cbRef.current.showHeader,\n readOnly: cbRef.current.readOnly,\n snap: cbRef.current.snap,\n autoFit: cbRef.current.autoFit,\n locale: cbRef.current.locale,\n toolbar: cbRef.current.toolbar,\n onSeek: (t) => cbRef.current.onSeek?.(t),\n onSelectClip: (id) => cbRef.current.onSelectClip?.(id),\n onScaleChange: (s) => cbRef.current.onScaleChange?.(s),\n onMoveClip: (id, opts) => cbRef.current.onMoveClip?.(id, opts),\n onResizeClip: (id, e) => cbRef.current.onResizeClip?.(id, e),\n onChange: (p) => cbRef.current.onChange?.(p),\n });\n tlRef.current = tl;\n if (tl.toolbarLeft && tl.toolbarRight) {\n setSlots({ left: tl.toolbarLeft, right: tl.toolbarRight });\n }\n return () => {\n tl.destroy();\n tlRef.current = null;\n setSlots(null);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n useEffect(() => {\n if (props.locale) tlRef.current?.setLocale(props.locale);\n }, [props.locale]);\n\n useImperativeHandle<TimelineApi | null, TimelineApi | null>(\n props.apiRef,\n () => {\n const tl = tlRef.current;\n if (!tl) return null;\n return {\n setProject: (p) => tl.setProject(p),\n getProject: () => tl.getProject(),\n setTime: (t) => tl.setTime(t),\n getTime: () => tl.getTime(),\n setScale: (s) => tl.setScale(s),\n getScale: () => tl.getScale(),\n setSelection: (id) => tl.setSelection(id),\n getSelection: () => tl.getSelection(),\n setSnap: (s) => tl.setSnap(s),\n fitToWindow: () => tl.fitToWindow(),\n getDebugInfo: () => tl.getDebugInfo(),\n };\n },\n // Same caveat as VideoEditor.tsx — factory must re-run once the\n // timeline is created in useEffect, otherwise apiRef.current is\n // null forever. `slots` flips from null to a real value the\n // instant the timeline is ready, so it's the cleanest trigger.\n [slots],\n );\n\n return (\n <div\n ref={hostRef}\n className={props.className}\n style={{ width: \"100%\", height: 240, ...props.style }}\n data-aicut-timeline-host=\"\"\n >\n {slots && props.toolbarLeft != null\n ? createPortal(props.toolbarLeft, slots.left)\n : null}\n {slots && props.toolbarRight != null\n ? createPortal(props.toolbarRight, slots.right)\n : null}\n </div>\n );\n\n // Type-only re-export used to keep React/Vue prop typings in lockstep\n // with the core. Reference here so the symbol isn't tree-shaken.\n void ({} as Clip);\n}\n","export { VideoEditor } from \"./VideoEditor.js\";\nexport type { VideoEditorProps, VideoEditorApi } from \"./VideoEditor.js\";\nexport { Timeline } from \"./Timeline.js\";\nexport type { TimelineProps, TimelineApi } from \"./Timeline.js\";\nexport type {\n Project,\n MediaSource,\n Track,\n Clip,\n Ms,\n Theme,\n EditorApi,\n Locale,\n} from \"@aicut/core\";\nexport { createEmptyProject, createId, localeEn, localeZh } from \"@aicut/core\";\n"],"mappings":"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","names":["useEffect","useImperativeHandle","useRef","useState","createPortal","jsxs"]}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@aicut/react",
3
+ "version": "0.1.0",
4
+ "description": "React wrapper for the AiCut video editor — thin declarative shell over @aicut/core.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "README.md"
20
+ ],
21
+ "dependencies": {
22
+ "@aicut/core": "0.1.0"
23
+ },
24
+ "peerDependencies": {
25
+ "react": "^18.0.0 || ^19.0.0",
26
+ "react-dom": "^18.0.0 || ^19.0.0"
27
+ },
28
+ "devDependencies": {
29
+ "@types/react": "^19.0.1",
30
+ "@types/react-dom": "^19.0.2",
31
+ "react": "^19.0.0",
32
+ "react-dom": "^19.0.0",
33
+ "tsup": "^8.3.5",
34
+ "typescript": "^5.7.2"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "scripts": {
40
+ "build": "tsup",
41
+ "dev": "tsup --watch",
42
+ "typecheck": "tsc --noEmit"
43
+ }
44
+ }