@aicut/react 0.2.0 → 0.4.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 +20 -21
- package/dist/lighting.cjs +8 -23
- package/dist/lighting.cjs.map +1 -1
- package/dist/lighting.d.cts +13 -30
- package/dist/lighting.d.ts +13 -30
- package/dist/lighting.js +8 -23
- package/dist/lighting.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -160,32 +160,31 @@ import "@aicut/core/styles.css";
|
|
|
160
160
|
|
|
161
161
|
function Relight() {
|
|
162
162
|
const apiRef = useRef<LightingEditorApi | null>(null);
|
|
163
|
+
|
|
164
|
+
const onGenerate = (): void => {
|
|
165
|
+
const cfg = apiRef.current?.getConfig();
|
|
166
|
+
if (cfg) fetch("/relight", { method: "POST", body: JSON.stringify(cfg) });
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// Library renders ONLY the picker (scene + controls). Host lays out
|
|
170
|
+
// the Smart mode panel beside it in their own flex/grid.
|
|
163
171
|
return (
|
|
164
|
-
<
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
/>
|
|
172
|
+
<div style={{ display: "flex", gap: 16 }}>
|
|
173
|
+
<LightingEditor
|
|
174
|
+
apiRef={apiRef}
|
|
175
|
+
subjectImageUrl="/frames/subject.jpg"
|
|
176
|
+
onChange={(cfg: LightingConfig) => console.log(cfg)}
|
|
177
|
+
/>
|
|
178
|
+
<aside>
|
|
179
|
+
<textarea placeholder="Describe the mood…" />
|
|
180
|
+
<button onClick={onGenerate}>Generate</button>
|
|
181
|
+
</aside>
|
|
182
|
+
</div>
|
|
184
183
|
);
|
|
185
184
|
}
|
|
186
185
|
```
|
|
187
186
|
|
|
188
|
-
Props: `subjectImageUrl`, `defaultConfig`, `defaultView`, `theme`, `locale`, `
|
|
187
|
+
Props: `subjectImageUrl`, `defaultConfig`, `defaultView`, `theme`, `locale`, `onChange`. The library is intentionally focused on the picker — Smart mode UI, Generate buttons, close handling, layout all live in host code.
|
|
189
188
|
|
|
190
189
|
## Standalone `<Timeline>`
|
|
191
190
|
|
package/dist/lighting.cjs
CHANGED
|
@@ -38,7 +38,7 @@ var import_jsx_runtime = require("react/jsx-runtime");
|
|
|
38
38
|
function LightingEditor(props) {
|
|
39
39
|
const hostRef = (0, import_react.useRef)(null);
|
|
40
40
|
const editorRef = (0, import_react.useRef)(null);
|
|
41
|
-
const [
|
|
41
|
+
const [footerSlot, setFooterSlot] = (0, import_react.useState)(null);
|
|
42
42
|
const cbRef = (0, import_react.useRef)(props);
|
|
43
43
|
cbRef.current = props;
|
|
44
44
|
(0, import_react.useEffect)(() => {
|
|
@@ -49,20 +49,16 @@ function LightingEditor(props) {
|
|
|
49
49
|
subjectImageUrl: cbRef.current.subjectImageUrl,
|
|
50
50
|
config: cbRef.current.defaultConfig,
|
|
51
51
|
view: cbRef.current.defaultView,
|
|
52
|
-
smartEnabled: cbRef.current.smartEnabled,
|
|
53
|
-
smartOpen: cbRef.current.smartOpen,
|
|
54
52
|
theme: cbRef.current.theme,
|
|
55
53
|
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)
|
|
54
|
+
onChange: (cfg) => cbRef.current.onChange?.(cfg)
|
|
59
55
|
});
|
|
60
56
|
editorRef.current = editor;
|
|
61
|
-
|
|
57
|
+
setFooterSlot(editor.controlsFooter);
|
|
62
58
|
return () => {
|
|
63
59
|
editor.destroy();
|
|
64
60
|
editorRef.current = null;
|
|
65
|
-
|
|
61
|
+
setFooterSlot(null);
|
|
66
62
|
};
|
|
67
63
|
}, []);
|
|
68
64
|
(0, import_react.useEffect)(() => {
|
|
@@ -75,14 +71,6 @@ function LightingEditor(props) {
|
|
|
75
71
|
if (props.subjectImageUrl)
|
|
76
72
|
editorRef.current?.setSubjectImage(props.subjectImageUrl);
|
|
77
73
|
}, [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
74
|
(0, import_react.useImperativeHandle)(
|
|
87
75
|
props.apiRef,
|
|
88
76
|
() => {
|
|
@@ -94,14 +82,11 @@ function LightingEditor(props) {
|
|
|
94
82
|
setSubjectImage: (url) => ed.setSubjectImage(url),
|
|
95
83
|
setView: (v) => ed.setView(v),
|
|
96
84
|
getView: () => ed.getView(),
|
|
97
|
-
|
|
98
|
-
isSmartEnabled: () => ed.isSmartEnabled(),
|
|
99
|
-
setSmartOpen: (open) => ed.setSmartOpen(open),
|
|
100
|
-
isSmartOpen: () => ed.isSmartOpen(),
|
|
101
|
-
requestGenerate: () => ed.requestGenerate()
|
|
85
|
+
reset: () => ed.reset()
|
|
102
86
|
};
|
|
103
87
|
},
|
|
104
|
-
|
|
88
|
+
// Keyed on footerSlot — same null-lock fix the VideoEditor uses.
|
|
89
|
+
[footerSlot]
|
|
105
90
|
);
|
|
106
91
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
107
92
|
"div",
|
|
@@ -110,7 +95,7 @@ function LightingEditor(props) {
|
|
|
110
95
|
className: props.className,
|
|
111
96
|
style: props.style,
|
|
112
97
|
"data-aicut-lighting-host": "",
|
|
113
|
-
children:
|
|
98
|
+
children: footerSlot && props.controlsFooter != null ? (0, import_react_dom.createPortal)(props.controlsFooter, footerSlot) : null
|
|
114
99
|
}
|
|
115
100
|
);
|
|
116
101
|
}
|
package/dist/lighting.cjs.map
CHANGED
|
@@ -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\
|
|
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\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 /** Convenience — restores config to safe defaults. */\n reset(): void;\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 /**\n * Any React node — portaled into the editor's controls footer slot\n * (where the built-in Reset button used to live). Hosts put their\n * Reset / Generate / save-preset / etc. buttons here. Empty until\n * populated; the library renders nothing into the slot.\n */\n controlsFooter?: ReactNode;\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 * the host owns everything else (smart panel beside, action buttons\n * in the controlsFooter slot, layout, theming the surrounding page).\n */\nexport function LightingEditor(props: LightingEditorProps) {\n const hostRef = useRef<HTMLDivElement | null>(null);\n const editorRef = useRef<CoreLightingEditor | null>(null);\n // Hold the footer slot DOM node in state so the portal mounts after\n // editor creation. Same lifecycle dance VideoEditor does for its\n // toolbar slots.\n const [footerSlot, setFooterSlot] = useState<HTMLElement | null>(null);\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 setFooterSlot(editor.controlsFooter);\n return () => {\n editor.destroy();\n editorRef.current = null;\n setFooterSlot(null);\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 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 reset: () => ed.reset(),\n };\n },\n // Keyed on footerSlot — same null-lock fix the VideoEditor uses.\n [footerSlot],\n );\n\n return (\n <div\n ref={hostRef}\n className={props.className}\n style={props.style}\n data-aicut-lighting-host=\"\"\n >\n {footerSlot && props.controlsFooter != null\n ? createPortal(props.controlsFooter, footerSlot)\n : null}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAQO;AACP,uBAA6B;AAC7B,sBAKO;AA4GH;AA/DG,SAAS,eAAe,OAA4B;AACzD,QAAM,cAAU,qBAA8B,IAAI;AAClD,QAAM,gBAAY,qBAAkC,IAAI;AAIxD,QAAM,CAAC,YAAY,aAAa,QAAI,uBAA6B,IAAI;AAErE,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,kBAAc,OAAO,cAAc;AACnC,WAAO,MAAM;AACX,aAAO,QAAQ;AACf,gBAAU,UAAU;AACpB,oBAAc,IAAI;AAAA,IACpB;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;AACd,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,QAC1B,OAAO,MAAM,GAAG,MAAM;AAAA,MACxB;AAAA,IACF;AAAA;AAAA,IAEA,CAAC,UAAU;AAAA,EACb;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,WAAW,MAAM;AAAA,MACjB,OAAO,MAAM;AAAA,MACb,4BAAyB;AAAA,MAExB,wBAAc,MAAM,kBAAkB,WACnC,+BAAa,MAAM,gBAAgB,UAAU,IAC7C;AAAA;AAAA,EACN;AAEJ;;;AD1HA,IAAAC,mBAOO;","names":["CoreLightingEditor","import_lighting"]}
|
package/dist/lighting.d.cts
CHANGED
|
@@ -4,25 +4,17 @@ import { LightingConfig, LightingView, LightingEditorOptions } from '@aicut/core
|
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
setSmartOpen(open: boolean): void;
|
|
21
|
-
isSmartOpen(): boolean;
|
|
22
|
-
requestGenerate(): void;
|
|
13
|
+
/** Convenience — restores config to safe defaults. */
|
|
14
|
+
reset(): void;
|
|
23
15
|
}
|
|
24
16
|
interface LightingEditorProps {
|
|
25
|
-
/** Initial subject image (URL or data URI). */
|
|
17
|
+
/** Initial subject image (URL or data URI). Reactive. */
|
|
26
18
|
subjectImageUrl?: string;
|
|
27
19
|
/** Initial config. */
|
|
28
20
|
defaultConfig?: Partial<LightingConfig>;
|
|
@@ -33,31 +25,22 @@ interface LightingEditorProps {
|
|
|
33
25
|
/** Locale partial — reactive (calls editor.setLocale). */
|
|
34
26
|
locale?: LightingEditorOptions["locale"];
|
|
35
27
|
/**
|
|
36
|
-
* Any React node — portaled into the editor's
|
|
37
|
-
*
|
|
38
|
-
*
|
|
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.
|
|
28
|
+
* Any React node — portaled into the editor's controls footer slot
|
|
29
|
+
* (where the built-in Reset button used to live). Hosts put their
|
|
30
|
+
* Reset / Generate / save-preset / etc. buttons here. Empty until
|
|
31
|
+
* populated; the library renders nothing into the slot.
|
|
45
32
|
*/
|
|
46
|
-
|
|
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;
|
|
33
|
+
controlsFooter?: ReactNode;
|
|
53
34
|
className?: string;
|
|
54
35
|
style?: CSSProperties;
|
|
55
36
|
apiRef?: Ref<LightingEditorApi | null>;
|
|
56
37
|
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
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* React shell for the 3D lighting picker. Renders scene + controls;
|
|
41
|
+
* the host owns everything else (smart panel beside, action buttons
|
|
42
|
+
* in the controlsFooter slot, layout, theming the surrounding page).
|
|
43
|
+
*/
|
|
61
44
|
declare function LightingEditor(props: LightingEditorProps): react.JSX.Element;
|
|
62
45
|
|
|
63
46
|
export { LightingEditor, type LightingEditorApi, type LightingEditorProps };
|
package/dist/lighting.d.ts
CHANGED
|
@@ -4,25 +4,17 @@ import { LightingConfig, LightingView, LightingEditorOptions } from '@aicut/core
|
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
setSmartOpen(open: boolean): void;
|
|
21
|
-
isSmartOpen(): boolean;
|
|
22
|
-
requestGenerate(): void;
|
|
13
|
+
/** Convenience — restores config to safe defaults. */
|
|
14
|
+
reset(): void;
|
|
23
15
|
}
|
|
24
16
|
interface LightingEditorProps {
|
|
25
|
-
/** Initial subject image (URL or data URI). */
|
|
17
|
+
/** Initial subject image (URL or data URI). Reactive. */
|
|
26
18
|
subjectImageUrl?: string;
|
|
27
19
|
/** Initial config. */
|
|
28
20
|
defaultConfig?: Partial<LightingConfig>;
|
|
@@ -33,31 +25,22 @@ interface LightingEditorProps {
|
|
|
33
25
|
/** Locale partial — reactive (calls editor.setLocale). */
|
|
34
26
|
locale?: LightingEditorOptions["locale"];
|
|
35
27
|
/**
|
|
36
|
-
* Any React node — portaled into the editor's
|
|
37
|
-
*
|
|
38
|
-
*
|
|
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.
|
|
28
|
+
* Any React node — portaled into the editor's controls footer slot
|
|
29
|
+
* (where the built-in Reset button used to live). Hosts put their
|
|
30
|
+
* Reset / Generate / save-preset / etc. buttons here. Empty until
|
|
31
|
+
* populated; the library renders nothing into the slot.
|
|
45
32
|
*/
|
|
46
|
-
|
|
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;
|
|
33
|
+
controlsFooter?: ReactNode;
|
|
53
34
|
className?: string;
|
|
54
35
|
style?: CSSProperties;
|
|
55
36
|
apiRef?: Ref<LightingEditorApi | null>;
|
|
56
37
|
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
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* React shell for the 3D lighting picker. Renders scene + controls;
|
|
41
|
+
* the host owns everything else (smart panel beside, action buttons
|
|
42
|
+
* in the controlsFooter slot, layout, theming the surrounding page).
|
|
43
|
+
*/
|
|
61
44
|
declare function LightingEditor(props: LightingEditorProps): react.JSX.Element;
|
|
62
45
|
|
|
63
46
|
export { LightingEditor, type LightingEditorApi, type LightingEditorProps };
|
package/dist/lighting.js
CHANGED
|
@@ -13,7 +13,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
13
13
|
function LightingEditor(props) {
|
|
14
14
|
const hostRef = useRef(null);
|
|
15
15
|
const editorRef = useRef(null);
|
|
16
|
-
const [
|
|
16
|
+
const [footerSlot, setFooterSlot] = useState(null);
|
|
17
17
|
const cbRef = useRef(props);
|
|
18
18
|
cbRef.current = props;
|
|
19
19
|
useEffect(() => {
|
|
@@ -24,20 +24,16 @@ function LightingEditor(props) {
|
|
|
24
24
|
subjectImageUrl: cbRef.current.subjectImageUrl,
|
|
25
25
|
config: cbRef.current.defaultConfig,
|
|
26
26
|
view: cbRef.current.defaultView,
|
|
27
|
-
smartEnabled: cbRef.current.smartEnabled,
|
|
28
|
-
smartOpen: cbRef.current.smartOpen,
|
|
29
27
|
theme: cbRef.current.theme,
|
|
30
28
|
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)
|
|
29
|
+
onChange: (cfg) => cbRef.current.onChange?.(cfg)
|
|
34
30
|
});
|
|
35
31
|
editorRef.current = editor;
|
|
36
|
-
|
|
32
|
+
setFooterSlot(editor.controlsFooter);
|
|
37
33
|
return () => {
|
|
38
34
|
editor.destroy();
|
|
39
35
|
editorRef.current = null;
|
|
40
|
-
|
|
36
|
+
setFooterSlot(null);
|
|
41
37
|
};
|
|
42
38
|
}, []);
|
|
43
39
|
useEffect(() => {
|
|
@@ -50,14 +46,6 @@ function LightingEditor(props) {
|
|
|
50
46
|
if (props.subjectImageUrl)
|
|
51
47
|
editorRef.current?.setSubjectImage(props.subjectImageUrl);
|
|
52
48
|
}, [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
49
|
useImperativeHandle(
|
|
62
50
|
props.apiRef,
|
|
63
51
|
() => {
|
|
@@ -69,14 +57,11 @@ function LightingEditor(props) {
|
|
|
69
57
|
setSubjectImage: (url) => ed.setSubjectImage(url),
|
|
70
58
|
setView: (v) => ed.setView(v),
|
|
71
59
|
getView: () => ed.getView(),
|
|
72
|
-
|
|
73
|
-
isSmartEnabled: () => ed.isSmartEnabled(),
|
|
74
|
-
setSmartOpen: (open) => ed.setSmartOpen(open),
|
|
75
|
-
isSmartOpen: () => ed.isSmartOpen(),
|
|
76
|
-
requestGenerate: () => ed.requestGenerate()
|
|
60
|
+
reset: () => ed.reset()
|
|
77
61
|
};
|
|
78
62
|
},
|
|
79
|
-
|
|
63
|
+
// Keyed on footerSlot — same null-lock fix the VideoEditor uses.
|
|
64
|
+
[footerSlot]
|
|
80
65
|
);
|
|
81
66
|
return /* @__PURE__ */ jsx(
|
|
82
67
|
"div",
|
|
@@ -85,7 +70,7 @@ function LightingEditor(props) {
|
|
|
85
70
|
className: props.className,
|
|
86
71
|
style: props.style,
|
|
87
72
|
"data-aicut-lighting-host": "",
|
|
88
|
-
children:
|
|
73
|
+
children: footerSlot && props.controlsFooter != null ? createPortal(props.controlsFooter, footerSlot) : null
|
|
89
74
|
}
|
|
90
75
|
);
|
|
91
76
|
}
|
package/dist/lighting.js.map
CHANGED
|
@@ -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\
|
|
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\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 /** Convenience — restores config to safe defaults. */\n reset(): void;\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 /**\n * Any React node — portaled into the editor's controls footer slot\n * (where the built-in Reset button used to live). Hosts put their\n * Reset / Generate / save-preset / etc. buttons here. Empty until\n * populated; the library renders nothing into the slot.\n */\n controlsFooter?: ReactNode;\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 * the host owns everything else (smart panel beside, action buttons\n * in the controlsFooter slot, layout, theming the surrounding page).\n */\nexport function LightingEditor(props: LightingEditorProps) {\n const hostRef = useRef<HTMLDivElement | null>(null);\n const editorRef = useRef<CoreLightingEditor | null>(null);\n // Hold the footer slot DOM node in state so the portal mounts after\n // editor creation. Same lifecycle dance VideoEditor does for its\n // toolbar slots.\n const [footerSlot, setFooterSlot] = useState<HTMLElement | null>(null);\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 setFooterSlot(editor.controlsFooter);\n return () => {\n editor.destroy();\n editorRef.current = null;\n setFooterSlot(null);\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 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 reset: () => ed.reset(),\n };\n },\n // Keyed on footerSlot — same null-lock fix the VideoEditor uses.\n [footerSlot],\n );\n\n return (\n <div\n ref={hostRef}\n className={props.className}\n style={props.style}\n data-aicut-lighting-host=\"\"\n >\n {footerSlot && props.controlsFooter != null\n ? createPortal(props.controlsFooter, footerSlot)\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;AA4GH;AA/DG,SAAS,eAAe,OAA4B;AACzD,QAAM,UAAU,OAA8B,IAAI;AAClD,QAAM,YAAY,OAAkC,IAAI;AAIxD,QAAM,CAAC,YAAY,aAAa,IAAI,SAA6B,IAAI;AAErE,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,kBAAc,OAAO,cAAc;AACnC,WAAO,MAAM;AACX,aAAO,QAAQ;AACf,gBAAU,UAAU;AACpB,oBAAc,IAAI;AAAA,IACpB;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;AACd,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,QAC1B,OAAO,MAAM,GAAG,MAAM;AAAA,MACxB;AAAA,IACF;AAAA;AAAA,IAEA,CAAC,UAAU;AAAA,EACb;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,WAAW,MAAM;AAAA,MACjB,OAAO,MAAM;AAAA,MACb,4BAAyB;AAAA,MAExB,wBAAc,MAAM,kBAAkB,OACnC,aAAa,MAAM,gBAAgB,UAAU,IAC7C;AAAA;AAAA,EACN;AAEJ;;;AC1HA;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.
|
|
3
|
+
"version": "0.4.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.
|
|
62
|
+
"@aicut/core": "0.4.0"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
65
|
"react": "^18.0.0 || ^19.0.0",
|