@aicut/react 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/lighting.cjs CHANGED
@@ -32,13 +32,12 @@ module.exports = __toCommonJS(lighting_exports);
32
32
 
33
33
  // src/LightingEditor.tsx
34
34
  var import_react = require("react");
35
- var import_react_dom = require("react-dom");
36
35
  var import_lighting = require("@aicut/core/lighting");
37
36
  var import_jsx_runtime = require("react/jsx-runtime");
38
37
  function LightingEditor(props) {
39
38
  const hostRef = (0, import_react.useRef)(null);
40
39
  const editorRef = (0, import_react.useRef)(null);
41
- const [slot, setSlot] = (0, import_react.useState)(null);
40
+ const [ready, setReady] = (0, import_react.useState)(false);
42
41
  const cbRef = (0, import_react.useRef)(props);
43
42
  cbRef.current = props;
44
43
  (0, import_react.useEffect)(() => {
@@ -49,20 +48,16 @@ function LightingEditor(props) {
49
48
  subjectImageUrl: cbRef.current.subjectImageUrl,
50
49
  config: cbRef.current.defaultConfig,
51
50
  view: cbRef.current.defaultView,
52
- smartEnabled: cbRef.current.smartEnabled,
53
- smartOpen: cbRef.current.smartOpen,
54
51
  theme: cbRef.current.theme,
55
52
  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)
53
+ onChange: (cfg) => cbRef.current.onChange?.(cfg)
59
54
  });
60
55
  editorRef.current = editor;
61
- setSlot(editor.smartSlot);
56
+ setReady(true);
62
57
  return () => {
63
58
  editor.destroy();
64
59
  editorRef.current = null;
65
- setSlot(null);
60
+ setReady(false);
66
61
  };
67
62
  }, []);
68
63
  (0, import_react.useEffect)(() => {
@@ -75,14 +70,6 @@ function LightingEditor(props) {
75
70
  if (props.subjectImageUrl)
76
71
  editorRef.current?.setSubjectImage(props.subjectImageUrl);
77
72
  }, [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
73
  (0, import_react.useImperativeHandle)(
87
74
  props.apiRef,
88
75
  () => {
@@ -93,15 +80,10 @@ function LightingEditor(props) {
93
80
  getConfig: () => ed.getConfig(),
94
81
  setSubjectImage: (url) => ed.setSubjectImage(url),
95
82
  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()
83
+ getView: () => ed.getView()
102
84
  };
103
85
  },
104
- [slot]
86
+ [ready]
105
87
  );
106
88
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
107
89
  "div",
@@ -109,8 +91,7 @@ function LightingEditor(props) {
109
91
  ref: hostRef,
110
92
  className: props.className,
111
93
  style: props.style,
112
- "data-aicut-lighting-host": "",
113
- children: slot && props.smartPanel != null ? (0, import_react_dom.createPortal)(props.smartPanel, slot) : null
94
+ "data-aicut-lighting-host": ""
114
95
  }
115
96
  );
116
97
  }
@@ -1 +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"]}
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 Ref,\n} from \"react\";\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\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}\n\nexport interface LightingEditorProps {\n /** Initial subject image (URL or data URI). Reactive. */\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 className?: string;\n style?: CSSProperties;\n apiRef?: Ref<LightingEditorApi | null>;\n\n onChange?: (cfg: LightingConfig) => void;\n}\n\n/**\n * React shell for the 3D lighting picker. Renders scene + controls;\n * nothing else. Host code lays out their own surrounding UI (smart\n * mode panel, generate button, etc.) alongside this component in\n * whatever flex/grid the host prefers.\n */\nexport function LightingEditor(props: LightingEditorProps) {\n const hostRef = useRef<HTMLDivElement | null>(null);\n const editorRef = useRef<CoreLightingEditor | null>(null);\n // Triggers a re-render the moment the editor is created, so the\n // useImperativeHandle factory below can return the real instance\n // instead of locking at null forever (same trick as VideoEditor).\n const [ready, setReady] = useState(false);\n\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 theme: cbRef.current.theme,\n locale: cbRef.current.locale,\n onChange: (cfg) => cbRef.current.onChange?.(cfg),\n });\n editorRef.current = editor;\n setReady(true);\n return () => {\n editor.destroy();\n editorRef.current = null;\n setReady(false);\n };\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 useEffect(() => {\n // Always push — `undefined` is the \"reset to English defaults\"\n // signal. Without this, toggling ZH→EN in the host kept ZH labels.\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 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 };\n },\n [ready],\n );\n\n return (\n <div\n ref={hostRef}\n className={props.className}\n style={props.style}\n data-aicut-lighting-host=\"\"\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAOO;AACP,sBAKO;AAmGH;AA/DG,SAAS,eAAe,OAA4B;AACzD,QAAM,cAAU,qBAA8B,IAAI;AAClD,QAAM,gBAAY,qBAAkC,IAAI;AAIxD,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAS,KAAK;AAExC,QAAM,YAAQ,qBAAO,KAAK;AAC1B,QAAM,UAAU;AAEhB,8BAAU,MAAM;AACd,UAAM,OAAO,QAAQ;AACrB,QAAI,CAAC,KAAM;AACX,UAAM,SAAS,gBAAAA,eAAmB,OAAO;AAAA,MACvC,WAAW;AAAA,MACX,iBAAiB,MAAM,QAAQ;AAAA,MAC/B,QAAQ,MAAM,QAAQ;AAAA,MACtB,MAAM,MAAM,QAAQ;AAAA,MACpB,OAAO,MAAM,QAAQ;AAAA,MACrB,QAAQ,MAAM,QAAQ;AAAA,MACtB,UAAU,CAAC,QAAQ,MAAM,QAAQ,WAAW,GAAG;AAAA,IACjD,CAAC;AACD,cAAU,UAAU;AACpB,aAAS,IAAI;AACb,WAAO,MAAM;AACX,aAAO,QAAQ;AACf,gBAAU,UAAU;AACpB,eAAS,KAAK;AAAA,IAChB;AAAA,EAEF,GAAG,CAAC,CAAC;AAEL,8BAAU,MAAM;AACd,QAAI,MAAM,MAAO,WAAU,SAAS,SAAS,MAAM,KAAK;AAAA,EAC1D,GAAG,CAAC,MAAM,KAAK,CAAC;AAChB,8BAAU,MAAM;AAGd,cAAU,SAAS,UAAU,MAAM,UAAU,CAAC,CAAC;AAAA,EACjD,GAAG,CAAC,MAAM,MAAM,CAAC;AACjB,8BAAU,MAAM;AACd,QAAI,MAAM;AACR,gBAAU,SAAS,gBAAgB,MAAM,eAAe;AAAA,EAC5D,GAAG,CAAC,MAAM,eAAe,CAAC;AAE1B;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,MAC5B;AAAA,IACF;AAAA,IACA,CAAC,KAAK;AAAA,EACR;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,WAAW,MAAM;AAAA,MACjB,OAAO,MAAM;AAAA,MACb,4BAAyB;AAAA;AAAA,EAC3B;AAEJ;;;AD3GA,IAAAC,mBAOO;","names":["CoreLightingEditor","import_lighting"]}
@@ -1,28 +1,18 @@
1
1
  import * as react from 'react';
2
- import { ReactNode, CSSProperties, Ref } from 'react';
2
+ import { CSSProperties, Ref } from 'react';
3
3
  import { LightingConfig, LightingView, LightingEditorOptions } from '@aicut/core/lighting';
4
4
  export { DEFAULT_LIGHTING_CONFIG, KeyPreset, LightingConfig, LightingEditorOptions, LightingLocale, LightingView, PRESET_DIRECTIONS, lightingLocaleEn, lightingLocaleZh, mergeLightingLocale, snapToPreset } from '@aicut/core/lighting';
5
5
  import { Theme } from '@aicut/core';
6
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
7
  interface LightingEditorApi {
13
8
  setConfig(partial: Partial<LightingConfig>): void;
14
9
  getConfig(): LightingConfig;
15
10
  setSubjectImage(url: string): void;
16
11
  setView(v: LightingView): void;
17
12
  getView(): LightingView;
18
- setSmartEnabled(enabled: boolean): void;
19
- isSmartEnabled(): boolean;
20
- setSmartOpen(open: boolean): void;
21
- isSmartOpen(): boolean;
22
- requestGenerate(): void;
23
13
  }
24
14
  interface LightingEditorProps {
25
- /** Initial subject image (URL or data URI). */
15
+ /** Initial subject image (URL or data URI). Reactive. */
26
16
  subjectImageUrl?: string;
27
17
  /** Initial config. */
28
18
  defaultConfig?: Partial<LightingConfig>;
@@ -32,32 +22,17 @@ interface LightingEditorProps {
32
22
  theme?: Theme;
33
23
  /** Locale partial — reactive (calls editor.setLocale). */
34
24
  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
25
  className?: string;
54
26
  style?: CSSProperties;
55
27
  apiRef?: Ref<LightingEditorApi | null>;
56
28
  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
29
  }
30
+ /**
31
+ * React shell for the 3D lighting picker. Renders scene + controls;
32
+ * nothing else. Host code lays out their own surrounding UI (smart
33
+ * mode panel, generate button, etc.) alongside this component in
34
+ * whatever flex/grid the host prefers.
35
+ */
61
36
  declare function LightingEditor(props: LightingEditorProps): react.JSX.Element;
62
37
 
63
38
  export { LightingEditor, type LightingEditorApi, type LightingEditorProps };
@@ -1,28 +1,18 @@
1
1
  import * as react from 'react';
2
- import { ReactNode, CSSProperties, Ref } from 'react';
2
+ import { CSSProperties, Ref } from 'react';
3
3
  import { LightingConfig, LightingView, LightingEditorOptions } from '@aicut/core/lighting';
4
4
  export { DEFAULT_LIGHTING_CONFIG, KeyPreset, LightingConfig, LightingEditorOptions, LightingLocale, LightingView, PRESET_DIRECTIONS, lightingLocaleEn, lightingLocaleZh, mergeLightingLocale, snapToPreset } from '@aicut/core/lighting';
5
5
  import { Theme } from '@aicut/core';
6
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
7
  interface LightingEditorApi {
13
8
  setConfig(partial: Partial<LightingConfig>): void;
14
9
  getConfig(): LightingConfig;
15
10
  setSubjectImage(url: string): void;
16
11
  setView(v: LightingView): void;
17
12
  getView(): LightingView;
18
- setSmartEnabled(enabled: boolean): void;
19
- isSmartEnabled(): boolean;
20
- setSmartOpen(open: boolean): void;
21
- isSmartOpen(): boolean;
22
- requestGenerate(): void;
23
13
  }
24
14
  interface LightingEditorProps {
25
- /** Initial subject image (URL or data URI). */
15
+ /** Initial subject image (URL or data URI). Reactive. */
26
16
  subjectImageUrl?: string;
27
17
  /** Initial config. */
28
18
  defaultConfig?: Partial<LightingConfig>;
@@ -32,32 +22,17 @@ interface LightingEditorProps {
32
22
  theme?: Theme;
33
23
  /** Locale partial — reactive (calls editor.setLocale). */
34
24
  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
25
  className?: string;
54
26
  style?: CSSProperties;
55
27
  apiRef?: Ref<LightingEditorApi | null>;
56
28
  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
29
  }
30
+ /**
31
+ * React shell for the 3D lighting picker. Renders scene + controls;
32
+ * nothing else. Host code lays out their own surrounding UI (smart
33
+ * mode panel, generate button, etc.) alongside this component in
34
+ * whatever flex/grid the host prefers.
35
+ */
61
36
  declare function LightingEditor(props: LightingEditorProps): react.JSX.Element;
62
37
 
63
38
  export { LightingEditor, type LightingEditorApi, type LightingEditorProps };
package/dist/lighting.js CHANGED
@@ -5,7 +5,6 @@ import {
5
5
  useRef,
6
6
  useState
7
7
  } from "react";
8
- import { createPortal } from "react-dom";
9
8
  import {
10
9
  LightingEditor as CoreLightingEditor
11
10
  } from "@aicut/core/lighting";
@@ -13,7 +12,7 @@ import { jsx } from "react/jsx-runtime";
13
12
  function LightingEditor(props) {
14
13
  const hostRef = useRef(null);
15
14
  const editorRef = useRef(null);
16
- const [slot, setSlot] = useState(null);
15
+ const [ready, setReady] = useState(false);
17
16
  const cbRef = useRef(props);
18
17
  cbRef.current = props;
19
18
  useEffect(() => {
@@ -24,20 +23,16 @@ function LightingEditor(props) {
24
23
  subjectImageUrl: cbRef.current.subjectImageUrl,
25
24
  config: cbRef.current.defaultConfig,
26
25
  view: cbRef.current.defaultView,
27
- smartEnabled: cbRef.current.smartEnabled,
28
- smartOpen: cbRef.current.smartOpen,
29
26
  theme: cbRef.current.theme,
30
27
  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)
28
+ onChange: (cfg) => cbRef.current.onChange?.(cfg)
34
29
  });
35
30
  editorRef.current = editor;
36
- setSlot(editor.smartSlot);
31
+ setReady(true);
37
32
  return () => {
38
33
  editor.destroy();
39
34
  editorRef.current = null;
40
- setSlot(null);
35
+ setReady(false);
41
36
  };
42
37
  }, []);
43
38
  useEffect(() => {
@@ -50,14 +45,6 @@ function LightingEditor(props) {
50
45
  if (props.subjectImageUrl)
51
46
  editorRef.current?.setSubjectImage(props.subjectImageUrl);
52
47
  }, [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
48
  useImperativeHandle(
62
49
  props.apiRef,
63
50
  () => {
@@ -68,15 +55,10 @@ function LightingEditor(props) {
68
55
  getConfig: () => ed.getConfig(),
69
56
  setSubjectImage: (url) => ed.setSubjectImage(url),
70
57
  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()
58
+ getView: () => ed.getView()
77
59
  };
78
60
  },
79
- [slot]
61
+ [ready]
80
62
  );
81
63
  return /* @__PURE__ */ jsx(
82
64
  "div",
@@ -84,8 +66,7 @@ function LightingEditor(props) {
84
66
  ref: hostRef,
85
67
  className: props.className,
86
68
  style: props.style,
87
- "data-aicut-lighting-host": "",
88
- children: slot && props.smartPanel != null ? createPortal(props.smartPanel, slot) : null
69
+ "data-aicut-lighting-host": ""
89
70
  }
90
71
  );
91
72
  }
@@ -1 +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":"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","names":[]}
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 Ref,\n} from \"react\";\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\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}\n\nexport interface LightingEditorProps {\n /** Initial subject image (URL or data URI). Reactive. */\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 className?: string;\n style?: CSSProperties;\n apiRef?: Ref<LightingEditorApi | null>;\n\n onChange?: (cfg: LightingConfig) => void;\n}\n\n/**\n * React shell for the 3D lighting picker. Renders scene + controls;\n * nothing else. Host code lays out their own surrounding UI (smart\n * mode panel, generate button, etc.) alongside this component in\n * whatever flex/grid the host prefers.\n */\nexport function LightingEditor(props: LightingEditorProps) {\n const hostRef = useRef<HTMLDivElement | null>(null);\n const editorRef = useRef<CoreLightingEditor | null>(null);\n // Triggers a re-render the moment the editor is created, so the\n // useImperativeHandle factory below can return the real instance\n // instead of locking at null forever (same trick as VideoEditor).\n const [ready, setReady] = useState(false);\n\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 theme: cbRef.current.theme,\n locale: cbRef.current.locale,\n onChange: (cfg) => cbRef.current.onChange?.(cfg),\n });\n editorRef.current = editor;\n setReady(true);\n return () => {\n editor.destroy();\n editorRef.current = null;\n setReady(false);\n };\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 useEffect(() => {\n // Always push — `undefined` is the \"reset to English defaults\"\n // signal. Without this, toggling ZH→EN in the host kept ZH labels.\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 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 };\n },\n [ready],\n );\n\n return (\n <div\n ref={hostRef}\n className={props.className}\n style={props.style}\n data-aicut-lighting-host=\"\"\n />\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,OAGK;AACP;AAAA,EACE,kBAAkB;AAAA,OAIb;AAmGH;AA/DG,SAAS,eAAe,OAA4B;AACzD,QAAM,UAAU,OAA8B,IAAI;AAClD,QAAM,YAAY,OAAkC,IAAI;AAIxD,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,KAAK;AAExC,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,OAAO,MAAM,QAAQ;AAAA,MACrB,QAAQ,MAAM,QAAQ;AAAA,MACtB,UAAU,CAAC,QAAQ,MAAM,QAAQ,WAAW,GAAG;AAAA,IACjD,CAAC;AACD,cAAU,UAAU;AACpB,aAAS,IAAI;AACb,WAAO,MAAM;AACX,aAAO,QAAQ;AACf,gBAAU,UAAU;AACpB,eAAS,KAAK;AAAA,IAChB;AAAA,EAEF,GAAG,CAAC,CAAC;AAEL,YAAU,MAAM;AACd,QAAI,MAAM,MAAO,WAAU,SAAS,SAAS,MAAM,KAAK;AAAA,EAC1D,GAAG,CAAC,MAAM,KAAK,CAAC;AAChB,YAAU,MAAM;AAGd,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;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,MAC5B;AAAA,IACF;AAAA,IACA,CAAC,KAAK;AAAA,EACR;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,WAAW,MAAM;AAAA,MACjB,OAAO,MAAM;AAAA,MACb,4BAAyB;AAAA;AAAA,EAC3B;AAEJ;;;AC3GA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aicut/react",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
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>",
@@ -59,7 +59,7 @@
59
59
  "README.md"
60
60
  ],
61
61
  "dependencies": {
62
- "@aicut/core": "0.2.0"
62
+ "@aicut/core": "0.3.0"
63
63
  },
64
64
  "peerDependencies": {
65
65
  "react": "^18.0.0 || ^19.0.0",