@aicut/react 0.3.0 → 0.4.1
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 +30 -21
- package/dist/lighting.cjs +10 -6
- package/dist/lighting.cjs.map +1 -1
- package/dist/lighting.d.cts +12 -4
- package/dist/lighting.d.ts +12 -4
- package/dist/lighting.js +10 -6
- package/dist/lighting.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -160,32 +160,41 @@ 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
|
-
</button>
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
+
// Reset / Generate / save-preset / etc. buttons go into the
|
|
178
|
+
// controls column's footer slot — the only host-supplied
|
|
179
|
+
// surface the library reserves space for.
|
|
180
|
+
controlsFooter={
|
|
181
|
+
<button onClick={() => apiRef.current?.reset()}>Reset</button>
|
|
182
|
+
}
|
|
183
|
+
/>
|
|
184
|
+
<aside>
|
|
185
|
+
<textarea placeholder="Describe the mood…" />
|
|
186
|
+
<button onClick={onGenerate}>Generate</button>
|
|
187
|
+
</aside>
|
|
188
|
+
</div>
|
|
184
189
|
);
|
|
185
190
|
}
|
|
186
191
|
```
|
|
187
192
|
|
|
188
|
-
Props: `subjectImageUrl`, `defaultConfig`, `defaultView`, `theme`, `locale`, `
|
|
193
|
+
Props: `subjectImageUrl`, `defaultConfig`, `defaultView`, `theme`, `locale`, `controlsFooter`, `onChange`.
|
|
194
|
+
|
|
195
|
+
Imperative API (`apiRef.current`): `setConfig`, `getConfig`, `setSubjectImage`, `setView`, `getView`, `reset`.
|
|
196
|
+
|
|
197
|
+
The library is intentionally focused on the picker — Smart mode UI, Generate buttons, close handling, layout all live in host code.
|
|
189
198
|
|
|
190
199
|
## Standalone `<Timeline>`
|
|
191
200
|
|
package/dist/lighting.cjs
CHANGED
|
@@ -32,12 +32,13 @@ 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");
|
|
35
36
|
var import_lighting = require("@aicut/core/lighting");
|
|
36
37
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
37
38
|
function LightingEditor(props) {
|
|
38
39
|
const hostRef = (0, import_react.useRef)(null);
|
|
39
40
|
const editorRef = (0, import_react.useRef)(null);
|
|
40
|
-
const [
|
|
41
|
+
const [footerSlot, setFooterSlot] = (0, import_react.useState)(null);
|
|
41
42
|
const cbRef = (0, import_react.useRef)(props);
|
|
42
43
|
cbRef.current = props;
|
|
43
44
|
(0, import_react.useEffect)(() => {
|
|
@@ -53,11 +54,11 @@ function LightingEditor(props) {
|
|
|
53
54
|
onChange: (cfg) => cbRef.current.onChange?.(cfg)
|
|
54
55
|
});
|
|
55
56
|
editorRef.current = editor;
|
|
56
|
-
|
|
57
|
+
setFooterSlot(editor.controlsFooter);
|
|
57
58
|
return () => {
|
|
58
59
|
editor.destroy();
|
|
59
60
|
editorRef.current = null;
|
|
60
|
-
|
|
61
|
+
setFooterSlot(null);
|
|
61
62
|
};
|
|
62
63
|
}, []);
|
|
63
64
|
(0, import_react.useEffect)(() => {
|
|
@@ -80,10 +81,12 @@ function LightingEditor(props) {
|
|
|
80
81
|
getConfig: () => ed.getConfig(),
|
|
81
82
|
setSubjectImage: (url) => ed.setSubjectImage(url),
|
|
82
83
|
setView: (v) => ed.setView(v),
|
|
83
|
-
getView: () => ed.getView()
|
|
84
|
+
getView: () => ed.getView(),
|
|
85
|
+
reset: () => ed.reset()
|
|
84
86
|
};
|
|
85
87
|
},
|
|
86
|
-
|
|
88
|
+
// Keyed on footerSlot — same null-lock fix the VideoEditor uses.
|
|
89
|
+
[footerSlot]
|
|
87
90
|
);
|
|
88
91
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
89
92
|
"div",
|
|
@@ -91,7 +94,8 @@ function LightingEditor(props) {
|
|
|
91
94
|
ref: hostRef,
|
|
92
95
|
className: props.className,
|
|
93
96
|
style: props.style,
|
|
94
|
-
"data-aicut-lighting-host": ""
|
|
97
|
+
"data-aicut-lighting-host": "",
|
|
98
|
+
children: footerSlot && props.controlsFooter != null ? (0, import_react_dom.createPortal)(props.controlsFooter, footerSlot) : null
|
|
95
99
|
}
|
|
96
100
|
);
|
|
97
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 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 *
|
|
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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import { CSSProperties, Ref } from 'react';
|
|
2
|
+
import { ReactNode, 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';
|
|
@@ -10,6 +10,8 @@ interface LightingEditorApi {
|
|
|
10
10
|
setSubjectImage(url: string): void;
|
|
11
11
|
setView(v: LightingView): void;
|
|
12
12
|
getView(): LightingView;
|
|
13
|
+
/** Convenience — restores config to safe defaults. */
|
|
14
|
+
reset(): void;
|
|
13
15
|
}
|
|
14
16
|
interface LightingEditorProps {
|
|
15
17
|
/** Initial subject image (URL or data URI). Reactive. */
|
|
@@ -22,6 +24,13 @@ interface LightingEditorProps {
|
|
|
22
24
|
theme?: Theme;
|
|
23
25
|
/** Locale partial — reactive (calls editor.setLocale). */
|
|
24
26
|
locale?: LightingEditorOptions["locale"];
|
|
27
|
+
/**
|
|
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.
|
|
32
|
+
*/
|
|
33
|
+
controlsFooter?: ReactNode;
|
|
25
34
|
className?: string;
|
|
26
35
|
style?: CSSProperties;
|
|
27
36
|
apiRef?: Ref<LightingEditorApi | null>;
|
|
@@ -29,9 +38,8 @@ interface LightingEditorProps {
|
|
|
29
38
|
}
|
|
30
39
|
/**
|
|
31
40
|
* React shell for the 3D lighting picker. Renders scene + controls;
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* whatever flex/grid the host prefers.
|
|
41
|
+
* the host owns everything else (smart panel beside, action buttons
|
|
42
|
+
* in the controlsFooter slot, layout, theming the surrounding page).
|
|
35
43
|
*/
|
|
36
44
|
declare function LightingEditor(props: LightingEditorProps): react.JSX.Element;
|
|
37
45
|
|
package/dist/lighting.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import { CSSProperties, Ref } from 'react';
|
|
2
|
+
import { ReactNode, 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';
|
|
@@ -10,6 +10,8 @@ interface LightingEditorApi {
|
|
|
10
10
|
setSubjectImage(url: string): void;
|
|
11
11
|
setView(v: LightingView): void;
|
|
12
12
|
getView(): LightingView;
|
|
13
|
+
/** Convenience — restores config to safe defaults. */
|
|
14
|
+
reset(): void;
|
|
13
15
|
}
|
|
14
16
|
interface LightingEditorProps {
|
|
15
17
|
/** Initial subject image (URL or data URI). Reactive. */
|
|
@@ -22,6 +24,13 @@ interface LightingEditorProps {
|
|
|
22
24
|
theme?: Theme;
|
|
23
25
|
/** Locale partial — reactive (calls editor.setLocale). */
|
|
24
26
|
locale?: LightingEditorOptions["locale"];
|
|
27
|
+
/**
|
|
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.
|
|
32
|
+
*/
|
|
33
|
+
controlsFooter?: ReactNode;
|
|
25
34
|
className?: string;
|
|
26
35
|
style?: CSSProperties;
|
|
27
36
|
apiRef?: Ref<LightingEditorApi | null>;
|
|
@@ -29,9 +38,8 @@ interface LightingEditorProps {
|
|
|
29
38
|
}
|
|
30
39
|
/**
|
|
31
40
|
* React shell for the 3D lighting picker. Renders scene + controls;
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* whatever flex/grid the host prefers.
|
|
41
|
+
* the host owns everything else (smart panel beside, action buttons
|
|
42
|
+
* in the controlsFooter slot, layout, theming the surrounding page).
|
|
35
43
|
*/
|
|
36
44
|
declare function LightingEditor(props: LightingEditorProps): react.JSX.Element;
|
|
37
45
|
|
package/dist/lighting.js
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
useRef,
|
|
6
6
|
useState
|
|
7
7
|
} from "react";
|
|
8
|
+
import { createPortal } from "react-dom";
|
|
8
9
|
import {
|
|
9
10
|
LightingEditor as CoreLightingEditor
|
|
10
11
|
} from "@aicut/core/lighting";
|
|
@@ -12,7 +13,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
12
13
|
function LightingEditor(props) {
|
|
13
14
|
const hostRef = useRef(null);
|
|
14
15
|
const editorRef = useRef(null);
|
|
15
|
-
const [
|
|
16
|
+
const [footerSlot, setFooterSlot] = useState(null);
|
|
16
17
|
const cbRef = useRef(props);
|
|
17
18
|
cbRef.current = props;
|
|
18
19
|
useEffect(() => {
|
|
@@ -28,11 +29,11 @@ function LightingEditor(props) {
|
|
|
28
29
|
onChange: (cfg) => cbRef.current.onChange?.(cfg)
|
|
29
30
|
});
|
|
30
31
|
editorRef.current = editor;
|
|
31
|
-
|
|
32
|
+
setFooterSlot(editor.controlsFooter);
|
|
32
33
|
return () => {
|
|
33
34
|
editor.destroy();
|
|
34
35
|
editorRef.current = null;
|
|
35
|
-
|
|
36
|
+
setFooterSlot(null);
|
|
36
37
|
};
|
|
37
38
|
}, []);
|
|
38
39
|
useEffect(() => {
|
|
@@ -55,10 +56,12 @@ function LightingEditor(props) {
|
|
|
55
56
|
getConfig: () => ed.getConfig(),
|
|
56
57
|
setSubjectImage: (url) => ed.setSubjectImage(url),
|
|
57
58
|
setView: (v) => ed.setView(v),
|
|
58
|
-
getView: () => ed.getView()
|
|
59
|
+
getView: () => ed.getView(),
|
|
60
|
+
reset: () => ed.reset()
|
|
59
61
|
};
|
|
60
62
|
},
|
|
61
|
-
|
|
63
|
+
// Keyed on footerSlot — same null-lock fix the VideoEditor uses.
|
|
64
|
+
[footerSlot]
|
|
62
65
|
);
|
|
63
66
|
return /* @__PURE__ */ jsx(
|
|
64
67
|
"div",
|
|
@@ -66,7 +69,8 @@ function LightingEditor(props) {
|
|
|
66
69
|
ref: hostRef,
|
|
67
70
|
className: props.className,
|
|
68
71
|
style: props.style,
|
|
69
|
-
"data-aicut-lighting-host": ""
|
|
72
|
+
"data-aicut-lighting-host": "",
|
|
73
|
+
children: footerSlot && props.controlsFooter != null ? createPortal(props.controlsFooter, footerSlot) : null
|
|
70
74
|
}
|
|
71
75
|
);
|
|
72
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 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 *
|
|
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.1",
|
|
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.1"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
65
|
"react": "^18.0.0 || ^19.0.0",
|