@aicut/react 0.1.1 → 0.2.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/README.md CHANGED
@@ -145,6 +145,48 @@ import { VideoEditor, localeZh } from "@aicut/react";
145
145
 
146
146
  `locale` is reactive too — runtime swap re-titles the toolbar and re-paints canvas labels in place.
147
147
 
148
+ ## `<LightingEditor>` (opt-in sub-entry)
149
+
150
+ A 3D lighting director for AI relighting flows — separate component that doesn't pull three.js into the rest of your bundle.
151
+
152
+ ```tsx
153
+ import { useRef } from "react";
154
+ import {
155
+ LightingEditor,
156
+ type LightingEditorApi,
157
+ type LightingConfig,
158
+ } from "@aicut/react/lighting";
159
+ import "@aicut/core/styles.css";
160
+
161
+ function Relight() {
162
+ const apiRef = useRef<LightingEditorApi | null>(null);
163
+ return (
164
+ <LightingEditor
165
+ apiRef={apiRef}
166
+ subjectImageUrl="/frames/subject.jpg"
167
+ smartEnabled
168
+ smartPanel={
169
+ <>
170
+ <textarea placeholder="Describe the mood…" />
171
+ <button onClick={() => apiRef.current?.requestGenerate()}>
172
+ Generate
173
+ </button>
174
+ </>
175
+ }
176
+ onChange={(cfg: LightingConfig) => console.log(cfg)}
177
+ onGenerate={(cfg) =>
178
+ fetch("/relight", {
179
+ method: "POST",
180
+ body: JSON.stringify(cfg),
181
+ })
182
+ }
183
+ />
184
+ );
185
+ }
186
+ ```
187
+
188
+ Props: `subjectImageUrl`, `defaultConfig`, `defaultView`, `theme`, `locale`, `smartEnabled`, `smartOpen`, `smartPanel`, `onChange`, `onGenerate`, `onSmartOpenChange`. The host's `smartPanel` is portaled into the editor's slot; the library renders the × close button + a "Smart mode" header pill to re-open it.
189
+
148
190
  ## Standalone `<Timeline>`
149
191
 
150
192
  Use the canvas timeline without the rest of the editor — frame-pickers, thumbnail strips, read-only previews.
@@ -0,0 +1,130 @@
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/lighting.ts
21
+ var lighting_exports = {};
22
+ __export(lighting_exports, {
23
+ DEFAULT_LIGHTING_CONFIG: () => import_lighting2.DEFAULT_LIGHTING_CONFIG,
24
+ LightingEditor: () => LightingEditor,
25
+ PRESET_DIRECTIONS: () => import_lighting2.PRESET_DIRECTIONS,
26
+ lightingLocaleEn: () => import_lighting2.lightingLocaleEn,
27
+ lightingLocaleZh: () => import_lighting2.lightingLocaleZh,
28
+ mergeLightingLocale: () => import_lighting2.mergeLightingLocale,
29
+ snapToPreset: () => import_lighting2.snapToPreset
30
+ });
31
+ module.exports = __toCommonJS(lighting_exports);
32
+
33
+ // src/LightingEditor.tsx
34
+ var import_react = require("react");
35
+ var import_react_dom = require("react-dom");
36
+ var import_lighting = require("@aicut/core/lighting");
37
+ var import_jsx_runtime = require("react/jsx-runtime");
38
+ function LightingEditor(props) {
39
+ const hostRef = (0, import_react.useRef)(null);
40
+ const editorRef = (0, import_react.useRef)(null);
41
+ const [slot, setSlot] = (0, import_react.useState)(null);
42
+ const cbRef = (0, import_react.useRef)(props);
43
+ cbRef.current = props;
44
+ (0, import_react.useEffect)(() => {
45
+ const host = hostRef.current;
46
+ if (!host) return;
47
+ const editor = import_lighting.LightingEditor.create({
48
+ container: host,
49
+ subjectImageUrl: cbRef.current.subjectImageUrl,
50
+ config: cbRef.current.defaultConfig,
51
+ view: cbRef.current.defaultView,
52
+ smartEnabled: cbRef.current.smartEnabled,
53
+ smartOpen: cbRef.current.smartOpen,
54
+ theme: cbRef.current.theme,
55
+ locale: cbRef.current.locale,
56
+ onChange: (cfg) => cbRef.current.onChange?.(cfg),
57
+ onGenerate: (cfg) => cbRef.current.onGenerate?.(cfg),
58
+ onSmartOpenChange: (open) => cbRef.current.onSmartOpenChange?.(open)
59
+ });
60
+ editorRef.current = editor;
61
+ setSlot(editor.smartSlot);
62
+ return () => {
63
+ editor.destroy();
64
+ editorRef.current = null;
65
+ setSlot(null);
66
+ };
67
+ }, []);
68
+ (0, import_react.useEffect)(() => {
69
+ if (props.theme) editorRef.current?.setTheme(props.theme);
70
+ }, [props.theme]);
71
+ (0, import_react.useEffect)(() => {
72
+ editorRef.current?.setLocale(props.locale ?? {});
73
+ }, [props.locale]);
74
+ (0, import_react.useEffect)(() => {
75
+ if (props.subjectImageUrl)
76
+ editorRef.current?.setSubjectImage(props.subjectImageUrl);
77
+ }, [props.subjectImageUrl]);
78
+ (0, import_react.useEffect)(() => {
79
+ if (props.smartEnabled !== void 0)
80
+ editorRef.current?.setSmartEnabled(props.smartEnabled);
81
+ }, [props.smartEnabled]);
82
+ (0, import_react.useEffect)(() => {
83
+ if (props.smartOpen !== void 0)
84
+ editorRef.current?.setSmartOpen(props.smartOpen);
85
+ }, [props.smartOpen]);
86
+ (0, import_react.useImperativeHandle)(
87
+ props.apiRef,
88
+ () => {
89
+ const ed = editorRef.current;
90
+ if (!ed) return null;
91
+ return {
92
+ setConfig: (p) => ed.setConfig(p),
93
+ getConfig: () => ed.getConfig(),
94
+ setSubjectImage: (url) => ed.setSubjectImage(url),
95
+ setView: (v) => ed.setView(v),
96
+ getView: () => ed.getView(),
97
+ setSmartEnabled: (en) => ed.setSmartEnabled(en),
98
+ isSmartEnabled: () => ed.isSmartEnabled(),
99
+ setSmartOpen: (open) => ed.setSmartOpen(open),
100
+ isSmartOpen: () => ed.isSmartOpen(),
101
+ requestGenerate: () => ed.requestGenerate()
102
+ };
103
+ },
104
+ [slot]
105
+ );
106
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
107
+ "div",
108
+ {
109
+ ref: hostRef,
110
+ className: props.className,
111
+ style: props.style,
112
+ "data-aicut-lighting-host": "",
113
+ children: slot && props.smartPanel != null ? (0, import_react_dom.createPortal)(props.smartPanel, slot) : null
114
+ }
115
+ );
116
+ }
117
+
118
+ // src/lighting.ts
119
+ var import_lighting2 = require("@aicut/core/lighting");
120
+ // Annotate the CommonJS export names for ESM import in node:
121
+ 0 && (module.exports = {
122
+ DEFAULT_LIGHTING_CONFIG,
123
+ LightingEditor,
124
+ PRESET_DIRECTIONS,
125
+ lightingLocaleEn,
126
+ lightingLocaleZh,
127
+ mergeLightingLocale,
128
+ snapToPreset
129
+ });
130
+ //# sourceMappingURL=lighting.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lighting.ts","../src/LightingEditor.tsx"],"sourcesContent":["/**\n * @aicut/react/lighting — separate entry that pulls three.js. Users\n * who never import this path don't pay the three.js bundle cost.\n */\nexport { LightingEditor } from \"./LightingEditor.js\";\nexport type {\n LightingEditorProps,\n LightingEditorApi,\n} from \"./LightingEditor.js\";\n\n// Re-export the data + locale exports from the core sub-entry so\n// hosts only need a single import line for everything lighting-related.\nexport {\n DEFAULT_LIGHTING_CONFIG,\n PRESET_DIRECTIONS,\n lightingLocaleEn,\n lightingLocaleZh,\n mergeLightingLocale,\n snapToPreset,\n} from \"@aicut/core/lighting\";\nexport type {\n KeyPreset,\n LightingConfig,\n LightingEditorOptions,\n LightingLocale,\n LightingView,\n} from \"@aicut/core/lighting\";\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 LightingEditor as CoreLightingEditor,\n type LightingConfig,\n type LightingEditorOptions,\n type LightingView,\n} from \"@aicut/core/lighting\";\nimport type { Theme } from \"@aicut/core\";\n\n/**\n * Imperative handle. Mirrors the core class's mutating surface, plus\n * a `requestGenerate()` shortcut so host buttons inside the smart slot\n * don't need to thread the api ref to fire onGenerate.\n */\nexport interface LightingEditorApi {\n setConfig(partial: Partial<LightingConfig>): void;\n getConfig(): LightingConfig;\n setSubjectImage(url: string): void;\n setView(v: LightingView): void;\n getView(): LightingView;\n setSmartEnabled(enabled: boolean): void;\n isSmartEnabled(): boolean;\n setSmartOpen(open: boolean): void;\n isSmartOpen(): boolean;\n requestGenerate(): void;\n}\n\nexport interface LightingEditorProps {\n /** Initial subject image (URL or data URI). */\n subjectImageUrl?: string;\n /** Initial config. */\n defaultConfig?: Partial<LightingConfig>;\n /** Initial view. Default `\"perspective\"`. */\n defaultView?: LightingView;\n /** Theme — reactive (calls editor.setTheme). */\n theme?: Theme;\n /** Locale partial — reactive (calls editor.setLocale). */\n locale?: LightingEditorOptions[\"locale\"];\n\n /**\n * Any React node — portaled into the editor's smart slot. Host uses\n * this for prompt textarea, preset grid, generate button, anything.\n * The library renders nothing into the slot until you populate it.\n */\n smartPanel?: ReactNode;\n /**\n * Whether the Smart mode feature is wired in at all. When false,\n * the column AND the controls-header toggle disappear — leaving a\n * clean 2-col scene + controls layout. Default `true`. Reactive.\n */\n smartEnabled?: boolean;\n /**\n * When `smartEnabled`, whether the slot drawer starts open. Reactive\n * — flips re-fire the editor's `setSmartOpen` so the panel slides\n * in/out. Default `true`.\n */\n smartOpen?: boolean;\n\n className?: string;\n style?: CSSProperties;\n apiRef?: Ref<LightingEditorApi | null>;\n\n onChange?: (cfg: LightingConfig) => void;\n onGenerate?: (cfg: LightingConfig) => void;\n /** Fires when the user clicks × or the Smart mode header toggle. */\n onSmartOpenChange?: (open: boolean) => void;\n}\n\nexport function LightingEditor(props: LightingEditorProps) {\n const hostRef = useRef<HTMLDivElement | null>(null);\n const editorRef = useRef<CoreLightingEditor | null>(null);\n // smart slot DOM node — set once the editor mounts; gates the portal.\n const [slot, setSlot] = useState<HTMLElement | null>(null);\n\n // Stable closure for callbacks the core only ever subscribes to once.\n const cbRef = useRef(props);\n cbRef.current = props;\n\n useEffect(() => {\n const host = hostRef.current;\n if (!host) return;\n const editor = CoreLightingEditor.create({\n container: host,\n subjectImageUrl: cbRef.current.subjectImageUrl,\n config: cbRef.current.defaultConfig,\n view: cbRef.current.defaultView,\n smartEnabled: cbRef.current.smartEnabled,\n smartOpen: cbRef.current.smartOpen,\n theme: cbRef.current.theme,\n locale: cbRef.current.locale,\n onChange: (cfg) => cbRef.current.onChange?.(cfg),\n onGenerate: (cfg) => cbRef.current.onGenerate?.(cfg),\n onSmartOpenChange: (open) =>\n cbRef.current.onSmartOpenChange?.(open),\n });\n editorRef.current = editor;\n setSlot(editor.smartSlot);\n return () => {\n editor.destroy();\n editorRef.current = null;\n setSlot(null);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Reactive prop mirror — theme + locale + subject only. Config /\n // view are owned by the editor instance after mount; host should\n // use `apiRef.current?.setConfig(…)` to push them.\n useEffect(() => {\n if (props.theme) editorRef.current?.setTheme(props.theme);\n }, [props.theme]);\n useEffect(() => {\n // Always push — `undefined` is the \"reset to English defaults\"\n // signal. Previously the `if (props.locale)` guard meant that\n // toggling ZH→EN in the host kept the ZH labels in place.\n editorRef.current?.setLocale(props.locale ?? {});\n }, [props.locale]);\n useEffect(() => {\n if (props.subjectImageUrl)\n editorRef.current?.setSubjectImage(props.subjectImageUrl);\n }, [props.subjectImageUrl]);\n\n useEffect(() => {\n if (props.smartEnabled !== undefined)\n editorRef.current?.setSmartEnabled(props.smartEnabled);\n }, [props.smartEnabled]);\n useEffect(() => {\n if (props.smartOpen !== undefined)\n editorRef.current?.setSmartOpen(props.smartOpen);\n }, [props.smartOpen]);\n\n // Keyed on `slot` for the same reason VideoEditor's apiRef is —\n // useImperativeHandle's factory runs in the commit phase BEFORE\n // useEffect, so with `[]` deps the ref locks to null forever.\n useImperativeHandle<LightingEditorApi | null, LightingEditorApi | null>(\n props.apiRef,\n () => {\n const ed = editorRef.current;\n if (!ed) return null;\n return {\n setConfig: (p) => ed.setConfig(p),\n getConfig: () => ed.getConfig(),\n setSubjectImage: (url) => ed.setSubjectImage(url),\n setView: (v) => ed.setView(v),\n getView: () => ed.getView(),\n setSmartEnabled: (en) => ed.setSmartEnabled(en),\n isSmartEnabled: () => ed.isSmartEnabled(),\n setSmartOpen: (open) => ed.setSmartOpen(open),\n isSmartOpen: () => ed.isSmartOpen(),\n requestGenerate: () => ed.requestGenerate(),\n };\n },\n [slot],\n );\n\n return (\n <div\n ref={hostRef}\n className={props.className}\n style={props.style}\n data-aicut-lighting-host=\"\"\n >\n {slot && props.smartPanel != null\n ? createPortal(props.smartPanel, slot)\n : null}\n </div>\n );\n}\n"],"mappings":"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","names":["CoreLightingEditor","import_lighting"]}
@@ -0,0 +1,63 @@
1
+ import * as react from 'react';
2
+ import { ReactNode, CSSProperties, Ref } from 'react';
3
+ import { LightingConfig, LightingView, LightingEditorOptions } from '@aicut/core/lighting';
4
+ export { DEFAULT_LIGHTING_CONFIG, KeyPreset, LightingConfig, LightingEditorOptions, LightingLocale, LightingView, PRESET_DIRECTIONS, lightingLocaleEn, lightingLocaleZh, mergeLightingLocale, snapToPreset } from '@aicut/core/lighting';
5
+ import { Theme } from '@aicut/core';
6
+
7
+ /**
8
+ * Imperative handle. Mirrors the core class's mutating surface, plus
9
+ * a `requestGenerate()` shortcut so host buttons inside the smart slot
10
+ * don't need to thread the api ref to fire onGenerate.
11
+ */
12
+ interface LightingEditorApi {
13
+ setConfig(partial: Partial<LightingConfig>): void;
14
+ getConfig(): LightingConfig;
15
+ setSubjectImage(url: string): void;
16
+ setView(v: LightingView): void;
17
+ getView(): LightingView;
18
+ setSmartEnabled(enabled: boolean): void;
19
+ isSmartEnabled(): boolean;
20
+ setSmartOpen(open: boolean): void;
21
+ isSmartOpen(): boolean;
22
+ requestGenerate(): void;
23
+ }
24
+ interface LightingEditorProps {
25
+ /** Initial subject image (URL or data URI). */
26
+ subjectImageUrl?: string;
27
+ /** Initial config. */
28
+ defaultConfig?: Partial<LightingConfig>;
29
+ /** Initial view. Default `"perspective"`. */
30
+ defaultView?: LightingView;
31
+ /** Theme — reactive (calls editor.setTheme). */
32
+ theme?: Theme;
33
+ /** Locale partial — reactive (calls editor.setLocale). */
34
+ locale?: LightingEditorOptions["locale"];
35
+ /**
36
+ * Any React node — portaled into the editor's smart slot. Host uses
37
+ * this for prompt textarea, preset grid, generate button, anything.
38
+ * The library renders nothing into the slot until you populate it.
39
+ */
40
+ smartPanel?: ReactNode;
41
+ /**
42
+ * Whether the Smart mode feature is wired in at all. When false,
43
+ * the column AND the controls-header toggle disappear — leaving a
44
+ * clean 2-col scene + controls layout. Default `true`. Reactive.
45
+ */
46
+ smartEnabled?: boolean;
47
+ /**
48
+ * When `smartEnabled`, whether the slot drawer starts open. Reactive
49
+ * — flips re-fire the editor's `setSmartOpen` so the panel slides
50
+ * in/out. Default `true`.
51
+ */
52
+ smartOpen?: boolean;
53
+ className?: string;
54
+ style?: CSSProperties;
55
+ apiRef?: Ref<LightingEditorApi | null>;
56
+ onChange?: (cfg: LightingConfig) => void;
57
+ onGenerate?: (cfg: LightingConfig) => void;
58
+ /** Fires when the user clicks × or the Smart mode header toggle. */
59
+ onSmartOpenChange?: (open: boolean) => void;
60
+ }
61
+ declare function LightingEditor(props: LightingEditorProps): react.JSX.Element;
62
+
63
+ export { LightingEditor, type LightingEditorApi, type LightingEditorProps };
@@ -0,0 +1,63 @@
1
+ import * as react from 'react';
2
+ import { ReactNode, CSSProperties, Ref } from 'react';
3
+ import { LightingConfig, LightingView, LightingEditorOptions } from '@aicut/core/lighting';
4
+ export { DEFAULT_LIGHTING_CONFIG, KeyPreset, LightingConfig, LightingEditorOptions, LightingLocale, LightingView, PRESET_DIRECTIONS, lightingLocaleEn, lightingLocaleZh, mergeLightingLocale, snapToPreset } from '@aicut/core/lighting';
5
+ import { Theme } from '@aicut/core';
6
+
7
+ /**
8
+ * Imperative handle. Mirrors the core class's mutating surface, plus
9
+ * a `requestGenerate()` shortcut so host buttons inside the smart slot
10
+ * don't need to thread the api ref to fire onGenerate.
11
+ */
12
+ interface LightingEditorApi {
13
+ setConfig(partial: Partial<LightingConfig>): void;
14
+ getConfig(): LightingConfig;
15
+ setSubjectImage(url: string): void;
16
+ setView(v: LightingView): void;
17
+ getView(): LightingView;
18
+ setSmartEnabled(enabled: boolean): void;
19
+ isSmartEnabled(): boolean;
20
+ setSmartOpen(open: boolean): void;
21
+ isSmartOpen(): boolean;
22
+ requestGenerate(): void;
23
+ }
24
+ interface LightingEditorProps {
25
+ /** Initial subject image (URL or data URI). */
26
+ subjectImageUrl?: string;
27
+ /** Initial config. */
28
+ defaultConfig?: Partial<LightingConfig>;
29
+ /** Initial view. Default `"perspective"`. */
30
+ defaultView?: LightingView;
31
+ /** Theme — reactive (calls editor.setTheme). */
32
+ theme?: Theme;
33
+ /** Locale partial — reactive (calls editor.setLocale). */
34
+ locale?: LightingEditorOptions["locale"];
35
+ /**
36
+ * Any React node — portaled into the editor's smart slot. Host uses
37
+ * this for prompt textarea, preset grid, generate button, anything.
38
+ * The library renders nothing into the slot until you populate it.
39
+ */
40
+ smartPanel?: ReactNode;
41
+ /**
42
+ * Whether the Smart mode feature is wired in at all. When false,
43
+ * the column AND the controls-header toggle disappear — leaving a
44
+ * clean 2-col scene + controls layout. Default `true`. Reactive.
45
+ */
46
+ smartEnabled?: boolean;
47
+ /**
48
+ * When `smartEnabled`, whether the slot drawer starts open. Reactive
49
+ * — flips re-fire the editor's `setSmartOpen` so the panel slides
50
+ * in/out. Default `true`.
51
+ */
52
+ smartOpen?: boolean;
53
+ className?: string;
54
+ style?: CSSProperties;
55
+ apiRef?: Ref<LightingEditorApi | null>;
56
+ onChange?: (cfg: LightingConfig) => void;
57
+ onGenerate?: (cfg: LightingConfig) => void;
58
+ /** Fires when the user clicks × or the Smart mode header toggle. */
59
+ onSmartOpenChange?: (open: boolean) => void;
60
+ }
61
+ declare function LightingEditor(props: LightingEditorProps): react.JSX.Element;
62
+
63
+ export { LightingEditor, type LightingEditorApi, type LightingEditorProps };
@@ -0,0 +1,111 @@
1
+ // src/LightingEditor.tsx
2
+ import {
3
+ useEffect,
4
+ useImperativeHandle,
5
+ useRef,
6
+ useState
7
+ } from "react";
8
+ import { createPortal } from "react-dom";
9
+ import {
10
+ LightingEditor as CoreLightingEditor
11
+ } from "@aicut/core/lighting";
12
+ import { jsx } from "react/jsx-runtime";
13
+ function LightingEditor(props) {
14
+ const hostRef = useRef(null);
15
+ const editorRef = useRef(null);
16
+ const [slot, setSlot] = 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 = CoreLightingEditor.create({
23
+ container: host,
24
+ subjectImageUrl: cbRef.current.subjectImageUrl,
25
+ config: cbRef.current.defaultConfig,
26
+ view: cbRef.current.defaultView,
27
+ smartEnabled: cbRef.current.smartEnabled,
28
+ smartOpen: cbRef.current.smartOpen,
29
+ theme: cbRef.current.theme,
30
+ locale: cbRef.current.locale,
31
+ onChange: (cfg) => cbRef.current.onChange?.(cfg),
32
+ onGenerate: (cfg) => cbRef.current.onGenerate?.(cfg),
33
+ onSmartOpenChange: (open) => cbRef.current.onSmartOpenChange?.(open)
34
+ });
35
+ editorRef.current = editor;
36
+ setSlot(editor.smartSlot);
37
+ return () => {
38
+ editor.destroy();
39
+ editorRef.current = null;
40
+ setSlot(null);
41
+ };
42
+ }, []);
43
+ useEffect(() => {
44
+ if (props.theme) editorRef.current?.setTheme(props.theme);
45
+ }, [props.theme]);
46
+ useEffect(() => {
47
+ editorRef.current?.setLocale(props.locale ?? {});
48
+ }, [props.locale]);
49
+ useEffect(() => {
50
+ if (props.subjectImageUrl)
51
+ editorRef.current?.setSubjectImage(props.subjectImageUrl);
52
+ }, [props.subjectImageUrl]);
53
+ useEffect(() => {
54
+ if (props.smartEnabled !== void 0)
55
+ editorRef.current?.setSmartEnabled(props.smartEnabled);
56
+ }, [props.smartEnabled]);
57
+ useEffect(() => {
58
+ if (props.smartOpen !== void 0)
59
+ editorRef.current?.setSmartOpen(props.smartOpen);
60
+ }, [props.smartOpen]);
61
+ useImperativeHandle(
62
+ props.apiRef,
63
+ () => {
64
+ const ed = editorRef.current;
65
+ if (!ed) return null;
66
+ return {
67
+ setConfig: (p) => ed.setConfig(p),
68
+ getConfig: () => ed.getConfig(),
69
+ setSubjectImage: (url) => ed.setSubjectImage(url),
70
+ setView: (v) => ed.setView(v),
71
+ getView: () => ed.getView(),
72
+ setSmartEnabled: (en) => ed.setSmartEnabled(en),
73
+ isSmartEnabled: () => ed.isSmartEnabled(),
74
+ setSmartOpen: (open) => ed.setSmartOpen(open),
75
+ isSmartOpen: () => ed.isSmartOpen(),
76
+ requestGenerate: () => ed.requestGenerate()
77
+ };
78
+ },
79
+ [slot]
80
+ );
81
+ return /* @__PURE__ */ jsx(
82
+ "div",
83
+ {
84
+ ref: hostRef,
85
+ className: props.className,
86
+ style: props.style,
87
+ "data-aicut-lighting-host": "",
88
+ children: slot && props.smartPanel != null ? createPortal(props.smartPanel, slot) : null
89
+ }
90
+ );
91
+ }
92
+
93
+ // src/lighting.ts
94
+ import {
95
+ DEFAULT_LIGHTING_CONFIG,
96
+ PRESET_DIRECTIONS,
97
+ lightingLocaleEn,
98
+ lightingLocaleZh,
99
+ mergeLightingLocale,
100
+ snapToPreset
101
+ } from "@aicut/core/lighting";
102
+ export {
103
+ DEFAULT_LIGHTING_CONFIG,
104
+ LightingEditor,
105
+ PRESET_DIRECTIONS,
106
+ lightingLocaleEn,
107
+ lightingLocaleZh,
108
+ mergeLightingLocale,
109
+ snapToPreset
110
+ };
111
+ //# sourceMappingURL=lighting.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/LightingEditor.tsx","../src/lighting.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 LightingEditor as CoreLightingEditor,\n type LightingConfig,\n type LightingEditorOptions,\n type LightingView,\n} from \"@aicut/core/lighting\";\nimport type { Theme } from \"@aicut/core\";\n\n/**\n * Imperative handle. Mirrors the core class's mutating surface, plus\n * a `requestGenerate()` shortcut so host buttons inside the smart slot\n * don't need to thread the api ref to fire onGenerate.\n */\nexport interface LightingEditorApi {\n setConfig(partial: Partial<LightingConfig>): void;\n getConfig(): LightingConfig;\n setSubjectImage(url: string): void;\n setView(v: LightingView): void;\n getView(): LightingView;\n setSmartEnabled(enabled: boolean): void;\n isSmartEnabled(): boolean;\n setSmartOpen(open: boolean): void;\n isSmartOpen(): boolean;\n requestGenerate(): void;\n}\n\nexport interface LightingEditorProps {\n /** Initial subject image (URL or data URI). */\n subjectImageUrl?: string;\n /** Initial config. */\n defaultConfig?: Partial<LightingConfig>;\n /** Initial view. Default `\"perspective\"`. */\n defaultView?: LightingView;\n /** Theme — reactive (calls editor.setTheme). */\n theme?: Theme;\n /** Locale partial — reactive (calls editor.setLocale). */\n locale?: LightingEditorOptions[\"locale\"];\n\n /**\n * Any React node — portaled into the editor's smart slot. Host uses\n * this for prompt textarea, preset grid, generate button, anything.\n * The library renders nothing into the slot until you populate it.\n */\n smartPanel?: ReactNode;\n /**\n * Whether the Smart mode feature is wired in at all. When false,\n * the column AND the controls-header toggle disappear — leaving a\n * clean 2-col scene + controls layout. Default `true`. Reactive.\n */\n smartEnabled?: boolean;\n /**\n * When `smartEnabled`, whether the slot drawer starts open. Reactive\n * — flips re-fire the editor's `setSmartOpen` so the panel slides\n * in/out. Default `true`.\n */\n smartOpen?: boolean;\n\n className?: string;\n style?: CSSProperties;\n apiRef?: Ref<LightingEditorApi | null>;\n\n onChange?: (cfg: LightingConfig) => void;\n onGenerate?: (cfg: LightingConfig) => void;\n /** Fires when the user clicks × or the Smart mode header toggle. */\n onSmartOpenChange?: (open: boolean) => void;\n}\n\nexport function LightingEditor(props: LightingEditorProps) {\n const hostRef = useRef<HTMLDivElement | null>(null);\n const editorRef = useRef<CoreLightingEditor | null>(null);\n // smart slot DOM node — set once the editor mounts; gates the portal.\n const [slot, setSlot] = useState<HTMLElement | null>(null);\n\n // Stable closure for callbacks the core only ever subscribes to once.\n const cbRef = useRef(props);\n cbRef.current = props;\n\n useEffect(() => {\n const host = hostRef.current;\n if (!host) return;\n const editor = CoreLightingEditor.create({\n container: host,\n subjectImageUrl: cbRef.current.subjectImageUrl,\n config: cbRef.current.defaultConfig,\n view: cbRef.current.defaultView,\n smartEnabled: cbRef.current.smartEnabled,\n smartOpen: cbRef.current.smartOpen,\n theme: cbRef.current.theme,\n locale: cbRef.current.locale,\n onChange: (cfg) => cbRef.current.onChange?.(cfg),\n onGenerate: (cfg) => cbRef.current.onGenerate?.(cfg),\n onSmartOpenChange: (open) =>\n cbRef.current.onSmartOpenChange?.(open),\n });\n editorRef.current = editor;\n setSlot(editor.smartSlot);\n return () => {\n editor.destroy();\n editorRef.current = null;\n setSlot(null);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Reactive prop mirror — theme + locale + subject only. Config /\n // view are owned by the editor instance after mount; host should\n // use `apiRef.current?.setConfig(…)` to push them.\n useEffect(() => {\n if (props.theme) editorRef.current?.setTheme(props.theme);\n }, [props.theme]);\n useEffect(() => {\n // Always push — `undefined` is the \"reset to English defaults\"\n // signal. Previously the `if (props.locale)` guard meant that\n // toggling ZH→EN in the host kept the ZH labels in place.\n editorRef.current?.setLocale(props.locale ?? {});\n }, [props.locale]);\n useEffect(() => {\n if (props.subjectImageUrl)\n editorRef.current?.setSubjectImage(props.subjectImageUrl);\n }, [props.subjectImageUrl]);\n\n useEffect(() => {\n if (props.smartEnabled !== undefined)\n editorRef.current?.setSmartEnabled(props.smartEnabled);\n }, [props.smartEnabled]);\n useEffect(() => {\n if (props.smartOpen !== undefined)\n editorRef.current?.setSmartOpen(props.smartOpen);\n }, [props.smartOpen]);\n\n // Keyed on `slot` for the same reason VideoEditor's apiRef is —\n // useImperativeHandle's factory runs in the commit phase BEFORE\n // useEffect, so with `[]` deps the ref locks to null forever.\n useImperativeHandle<LightingEditorApi | null, LightingEditorApi | null>(\n props.apiRef,\n () => {\n const ed = editorRef.current;\n if (!ed) return null;\n return {\n setConfig: (p) => ed.setConfig(p),\n getConfig: () => ed.getConfig(),\n setSubjectImage: (url) => ed.setSubjectImage(url),\n setView: (v) => ed.setView(v),\n getView: () => ed.getView(),\n setSmartEnabled: (en) => ed.setSmartEnabled(en),\n isSmartEnabled: () => ed.isSmartEnabled(),\n setSmartOpen: (open) => ed.setSmartOpen(open),\n isSmartOpen: () => ed.isSmartOpen(),\n requestGenerate: () => ed.requestGenerate(),\n };\n },\n [slot],\n );\n\n return (\n <div\n ref={hostRef}\n className={props.className}\n style={props.style}\n data-aicut-lighting-host=\"\"\n >\n {slot && props.smartPanel != null\n ? createPortal(props.smartPanel, slot)\n : null}\n </div>\n );\n}\n","/**\n * @aicut/react/lighting — separate entry that pulls three.js. Users\n * who never import this path don't pay the three.js bundle cost.\n */\nexport { LightingEditor } from \"./LightingEditor.js\";\nexport type {\n LightingEditorProps,\n LightingEditorApi,\n} from \"./LightingEditor.js\";\n\n// Re-export the data + locale exports from the core sub-entry so\n// hosts only need a single import line for everything lighting-related.\nexport {\n DEFAULT_LIGHTING_CONFIG,\n PRESET_DIRECTIONS,\n lightingLocaleEn,\n lightingLocaleZh,\n mergeLightingLocale,\n snapToPreset,\n} from \"@aicut/core/lighting\";\nexport type {\n KeyPreset,\n LightingConfig,\n LightingEditorOptions,\n LightingLocale,\n LightingView,\n} from \"@aicut/core/lighting\";\n"],"mappings":";AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AACP,SAAS,oBAAoB;AAC7B;AAAA,EACE,kBAAkB;AAAA,OAIb;AAsJH;AAxFG,SAAS,eAAe,OAA4B;AACzD,QAAM,UAAU,OAA8B,IAAI;AAClD,QAAM,YAAY,OAAkC,IAAI;AAExD,QAAM,CAAC,MAAM,OAAO,IAAI,SAA6B,IAAI;AAGzD,QAAM,QAAQ,OAAO,KAAK;AAC1B,QAAM,UAAU;AAEhB,YAAU,MAAM;AACd,UAAM,OAAO,QAAQ;AACrB,QAAI,CAAC,KAAM;AACX,UAAM,SAAS,mBAAmB,OAAO;AAAA,MACvC,WAAW;AAAA,MACX,iBAAiB,MAAM,QAAQ;AAAA,MAC/B,QAAQ,MAAM,QAAQ;AAAA,MACtB,MAAM,MAAM,QAAQ;AAAA,MACpB,cAAc,MAAM,QAAQ;AAAA,MAC5B,WAAW,MAAM,QAAQ;AAAA,MACzB,OAAO,MAAM,QAAQ;AAAA,MACrB,QAAQ,MAAM,QAAQ;AAAA,MACtB,UAAU,CAAC,QAAQ,MAAM,QAAQ,WAAW,GAAG;AAAA,MAC/C,YAAY,CAAC,QAAQ,MAAM,QAAQ,aAAa,GAAG;AAAA,MACnD,mBAAmB,CAAC,SAClB,MAAM,QAAQ,oBAAoB,IAAI;AAAA,IAC1C,CAAC;AACD,cAAU,UAAU;AACpB,YAAQ,OAAO,SAAS;AACxB,WAAO,MAAM;AACX,aAAO,QAAQ;AACf,gBAAU,UAAU;AACpB,cAAQ,IAAI;AAAA,IACd;AAAA,EAEF,GAAG,CAAC,CAAC;AAKL,YAAU,MAAM;AACd,QAAI,MAAM,MAAO,WAAU,SAAS,SAAS,MAAM,KAAK;AAAA,EAC1D,GAAG,CAAC,MAAM,KAAK,CAAC;AAChB,YAAU,MAAM;AAId,cAAU,SAAS,UAAU,MAAM,UAAU,CAAC,CAAC;AAAA,EACjD,GAAG,CAAC,MAAM,MAAM,CAAC;AACjB,YAAU,MAAM;AACd,QAAI,MAAM;AACR,gBAAU,SAAS,gBAAgB,MAAM,eAAe;AAAA,EAC5D,GAAG,CAAC,MAAM,eAAe,CAAC;AAE1B,YAAU,MAAM;AACd,QAAI,MAAM,iBAAiB;AACzB,gBAAU,SAAS,gBAAgB,MAAM,YAAY;AAAA,EACzD,GAAG,CAAC,MAAM,YAAY,CAAC;AACvB,YAAU,MAAM;AACd,QAAI,MAAM,cAAc;AACtB,gBAAU,SAAS,aAAa,MAAM,SAAS;AAAA,EACnD,GAAG,CAAC,MAAM,SAAS,CAAC;AAKpB;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AACJ,YAAM,KAAK,UAAU;AACrB,UAAI,CAAC,GAAI,QAAO;AAChB,aAAO;AAAA,QACL,WAAW,CAAC,MAAM,GAAG,UAAU,CAAC;AAAA,QAChC,WAAW,MAAM,GAAG,UAAU;AAAA,QAC9B,iBAAiB,CAAC,QAAQ,GAAG,gBAAgB,GAAG;AAAA,QAChD,SAAS,CAAC,MAAM,GAAG,QAAQ,CAAC;AAAA,QAC5B,SAAS,MAAM,GAAG,QAAQ;AAAA,QAC1B,iBAAiB,CAAC,OAAO,GAAG,gBAAgB,EAAE;AAAA,QAC9C,gBAAgB,MAAM,GAAG,eAAe;AAAA,QACxC,cAAc,CAAC,SAAS,GAAG,aAAa,IAAI;AAAA,QAC5C,aAAa,MAAM,GAAG,YAAY;AAAA,QAClC,iBAAiB,MAAM,GAAG,gBAAgB;AAAA,MAC5C;AAAA,IACF;AAAA,IACA,CAAC,IAAI;AAAA,EACP;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,WAAW,MAAM;AAAA,MACjB,OAAO,MAAM;AAAA,MACb,4BAAyB;AAAA,MAExB,kBAAQ,MAAM,cAAc,OACzB,aAAa,MAAM,YAAY,IAAI,IACnC;AAAA;AAAA,EACN;AAEJ;;;ACpKA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;","names":[]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@aicut/react",
3
- "version": "0.1.1",
4
- "description": "React wrapper for the AiCut video editor — thin declarative shell over @aicut/core.",
3
+ "version": "0.2.0",
4
+ "description": "React wrapper for the AiCut video editor + lighting picker — thin declarative shells over @aicut/core.",
5
5
  "license": "MIT",
6
6
  "author": "ziqiang <ziqiangytu@gmail.com>",
7
7
  "homepage": "https://github.com/ziqiangai/AiCut#readme",
@@ -34,7 +34,9 @@
34
34
  "davinci",
35
35
  "imovie",
36
36
  "veed",
37
- "filmora"
37
+ "filmora",
38
+ "lighting",
39
+ "three.js"
38
40
  ],
39
41
  "type": "module",
40
42
  "main": "./dist/index.cjs",
@@ -45,6 +47,11 @@
45
47
  "types": "./dist/index.d.ts",
46
48
  "import": "./dist/index.js",
47
49
  "require": "./dist/index.cjs"
50
+ },
51
+ "./lighting": {
52
+ "types": "./dist/lighting.d.ts",
53
+ "import": "./dist/lighting.js",
54
+ "require": "./dist/lighting.cjs"
48
55
  }
49
56
  },
50
57
  "files": [
@@ -52,7 +59,7 @@
52
59
  "README.md"
53
60
  ],
54
61
  "dependencies": {
55
- "@aicut/core": "0.1.1"
62
+ "@aicut/core": "0.2.0"
56
63
  },
57
64
  "peerDependencies": {
58
65
  "react": "^18.0.0 || ^19.0.0",