@aicut/core 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 CHANGED
@@ -144,7 +144,9 @@ editor.toolbarRight.appendChild(exportBtn);
144
144
 
145
145
  ## Lighting picker (opt-in sub-entry)
146
146
 
147
- A separate component for AI relighting workflows — drag a light dot around a 3D sphere wrapping a subject frame, control brightness / color / direction. Three.js is bundled only on this sub-entry, so consumers of the video editor pay zero bytes for it.
147
+ A separate component for AI-relighting workflows — drag a light dot around a 3D sphere wrapping a subject frame, control brightness / color / direction. Three.js is bundled only on this sub-entry, so consumers of the video editor pay zero bytes for it.
148
+
149
+ The library renders **only the picker** (scene + controls). Smart-mode UI (prompt textarea, preset thumbnails, Generate button, close handling) is host code laid out beside `<LightingEditor>`.
148
150
 
149
151
  ```ts
150
152
  import { LightingEditor } from "@aicut/core/lighting";
@@ -153,23 +155,22 @@ import "@aicut/core/styles.css";
153
155
  const ed = LightingEditor.create({
154
156
  container: document.getElementById("light")!,
155
157
  subjectImageUrl: "/frames/subject.jpg",
156
- smartEnabled: true, // default; false → no slot column at all
157
- smartOpen: true, // default; user can toggle via × / header pill
158
158
  onChange: (cfg) => console.log(cfg),
159
- onGenerate: (cfg) => fetch("/relight", { method: "POST", body: JSON.stringify(cfg) }),
160
159
  });
161
160
 
162
- // Host appends UI into the smart slot (prompt textarea, presets, generate btn, …).
163
- ed.smartSlot.appendChild(myAiUI);
164
-
165
- // Runtime control:
166
- ed.setSmartOpen(false);
167
- ed.setSmartEnabled(false);
161
+ // Runtime control
168
162
  ed.setView("front");
169
163
  ed.setConfig({ brightness: 0.8, color: "#ffaa3a" });
164
+ ed.setSubjectImage("/frames/another-subject.jpg");
165
+
166
+ // Host snapshot for "Generate" — call from your own button
167
+ function onGenerate() {
168
+ const cfg = ed.getConfig();
169
+ fetch("/relight", { method: "POST", body: JSON.stringify(cfg) });
170
+ }
170
171
  ```
171
172
 
172
- Locale extension `LightingLocale` (separate from the video editor's `Locale`) is also exported with `lightingLocaleEn` / `lightingLocaleZh`.
173
+ Locale extension `LightingLocale` (separate from the video editor's `Locale`) is exported with `lightingLocaleEn` / `lightingLocaleZh`.
173
174
 
174
175
  ## Standalone Timeline
175
176
 
@@ -90,6 +90,13 @@ var LightingControls = class {
90
90
  /** Exposed so the editor can append host-level chrome (e.g. the
91
91
  * Smart Mode toggle) without LightingControls knowing about it. */
92
92
  headerSlot;
93
+ /**
94
+ * Footer slot — sits where the built-in Reset used to live. Host
95
+ * appends any action buttons (Reset, Generate, Save preset, etc.)
96
+ * via the React wrapper's `controlsFooter` prop / Vue's
97
+ * `<slot name="controlsFooter">`. Library renders nothing here.
98
+ */
99
+ footerSlot;
93
100
  brightnessInput;
94
101
  colorInput;
95
102
  dirButtons;
@@ -102,7 +109,6 @@ var LightingControls = class {
102
109
  colorLabelEl;
103
110
  keyLabelEl;
104
111
  rimLabelEl;
105
- resetBtn;
106
112
  locale;
107
113
  lastConfig = null;
108
114
  constructor(locale, cb) {
@@ -192,15 +198,12 @@ var LightingControls = class {
192
198
  });
193
199
  rimSection.appendChild(this.rimToggle);
194
200
  this.root.appendChild(rimSection);
195
- const footer = mkDiv("aicut-lighting-section aicut-lighting-section-row");
196
- this.resetBtn = document.createElement("button");
197
- this.resetBtn.type = "button";
198
- this.resetBtn.className = "aicut-lighting-reset";
199
- this.resetBtn.textContent = locale.lightingResetParams;
200
- this.resetBtn.setAttribute("data-testid", "aicut-lighting-reset");
201
- this.resetBtn.addEventListener("click", () => cb.onReset());
202
- footer.appendChild(this.resetBtn);
203
- this.root.appendChild(footer);
201
+ this.footerSlot = mkDiv("aicut-lighting-controls-footer");
202
+ this.footerSlot.setAttribute(
203
+ "data-testid",
204
+ "aicut-lighting-controls-footer"
205
+ );
206
+ this.root.appendChild(this.footerSlot);
204
207
  }
205
208
  /** Idempotent — mirror the given config into all visible controls. */
206
209
  render(cfg) {
@@ -229,7 +232,6 @@ var LightingControls = class {
229
232
  this.colorLabelEl.textContent = locale.lightingColor;
230
233
  this.keyLabelEl.textContent = locale.lightingKeyTitle;
231
234
  this.rimLabelEl.textContent = locale.lightingRim;
232
- this.resetBtn.textContent = locale.lightingResetParams;
233
235
  const dirLabels = {
234
236
  left: "lightingDirLeft",
235
237
  right: "lightingDirRight",
@@ -265,9 +267,6 @@ var lightingLocaleEn = {
265
267
  lightingRim: "Rim light",
266
268
  lightingViewPerspective: "Perspective",
267
269
  lightingViewFront: "Front",
268
- lightingResetParams: "Reset",
269
- lightingSmartClose: "Close Smart mode",
270
- lightingSmartToggle: "Smart mode",
271
270
  lightingDirLeft: "Left",
272
271
  lightingDirRight: "Right",
273
272
  lightingDirTop: "Top",
@@ -284,9 +283,6 @@ var lightingLocaleZh = {
284
283
  lightingRim: "\u8F6E\u5ED3\u5149",
285
284
  lightingViewPerspective: "\u900F\u89C6",
286
285
  lightingViewFront: "\u6B63\u9762",
287
- lightingResetParams: "\u91CD\u7F6E\u53C2\u6570",
288
- lightingSmartClose: "\u5173\u95ED\u667A\u80FD\u6A21\u5F0F",
289
- lightingSmartToggle: "\u667A\u80FD\u6A21\u5F0F",
290
286
  lightingDirLeft: "\u5DE6\u4FA7",
291
287
  lightingDirRight: "\u53F3\u4FA7",
292
288
  lightingDirTop: "\u9876\u90E8",
@@ -19041,22 +19037,14 @@ var LightingEditor = class _LightingEditor {
19041
19037
  config;
19042
19038
  view;
19043
19039
  locale;
19044
- smartEnabled;
19045
- smartOpen;
19046
19040
  scene;
19047
19041
  controls;
19048
19042
  sceneViewport;
19049
19043
  viewToggleEl;
19050
- body;
19051
- smartWrapper = null;
19052
- smartCloseBtn = null;
19053
- smartToggleEl = null;
19054
- smartToggleThumb = null;
19055
- /** Host slot the React/Vue wrapper portals/teleports into. Stable
19056
- * reference across smartOpen toggles — only the wrapper around it
19057
- * collapses/expands. Always present even when `smartEnabled: false`
19058
- * so portals don't blow up; just detached from the visible tree. */
19059
- smartSlot;
19044
+ /** Footer slot in the controls column. Host appends Reset/Generate/
19045
+ * preset-save/etc. buttons here. The library renders nothing into
19046
+ * it it's the same convention as the video editor's toolbar slots. */
19047
+ controlsFooter;
19060
19048
  resizeObs = null;
19061
19049
  destroyed = false;
19062
19050
  static create(opts) {
@@ -19067,8 +19055,6 @@ var LightingEditor = class _LightingEditor {
19067
19055
  this.root = opts.container;
19068
19056
  this.config = { ...DEFAULT_LIGHTING_CONFIG, ...opts.config };
19069
19057
  this.view = opts.view ?? "perspective";
19070
- this.smartEnabled = opts.smartEnabled !== false;
19071
- this.smartOpen = opts.smartOpen !== false;
19072
19058
  this.locale = {
19073
19059
  ...mergeLocale(opts.locale),
19074
19060
  ...mergeLightingLocale(opts.locale)
@@ -19077,9 +19063,9 @@ var LightingEditor = class _LightingEditor {
19077
19063
  this.root.innerHTML = "";
19078
19064
  if (!this.root.style.position) this.root.style.position = "relative";
19079
19065
  applyTheme(this.root, opts.theme);
19080
- this.body = document.createElement("div");
19081
- this.body.className = "aicut-lighting-body";
19082
- this.root.appendChild(this.body);
19066
+ const body = document.createElement("div");
19067
+ body.className = "aicut-lighting-body";
19068
+ this.root.appendChild(body);
19083
19069
  const sceneCol = document.createElement("div");
19084
19070
  sceneCol.className = "aicut-lighting-scene-col";
19085
19071
  this.viewToggleEl = this.buildViewToggle();
@@ -19088,7 +19074,7 @@ var LightingEditor = class _LightingEditor {
19088
19074
  this.sceneViewport.className = "aicut-lighting-scene-viewport";
19089
19075
  this.sceneViewport.setAttribute("data-testid", "aicut-lighting-scene");
19090
19076
  sceneCol.appendChild(this.sceneViewport);
19091
- this.body.appendChild(sceneCol);
19077
+ body.appendChild(sceneCol);
19092
19078
  this.controls = new LightingControls(this.locale, {
19093
19079
  onBrightnessChange: (level) => this.applyMutation({ brightness: level }),
19094
19080
  onColorChange: (hex) => this.applyMutation({ color: hex }),
@@ -19096,20 +19082,10 @@ var LightingEditor = class _LightingEditor {
19096
19082
  keyDirection: PRESET_DIRECTIONS[preset],
19097
19083
  keyPreset: preset
19098
19084
  }),
19099
- onRimToggle: (on) => this.applyMutation({ rim: on }),
19100
- onReset: () => this.setConfig(DEFAULT_LIGHTING_CONFIG, "reset")
19085
+ onRimToggle: (on) => this.applyMutation({ rim: on })
19101
19086
  });
19102
- this.body.appendChild(this.controls.root);
19103
- this.smartSlot = document.createElement("div");
19104
- this.smartSlot.className = "aicut-lighting-smart-slot";
19105
- this.smartSlot.setAttribute("data-testid", "aicut-lighting-smart");
19106
- if (this.smartEnabled) {
19107
- this.smartWrapper = this.buildSmartWrapper();
19108
- this.body.appendChild(this.smartWrapper);
19109
- this.smartToggleEl = this.buildSmartToggle();
19110
- this.controls.headerSlot.appendChild(this.smartToggleEl);
19111
- }
19112
- this.syncSmartState();
19087
+ body.appendChild(this.controls.root);
19088
+ this.controlsFooter = this.controls.footerSlot;
19113
19089
  this.scene = new LightingScene(this.sceneViewport, this.view);
19114
19090
  this.scene.setLightDirection(this.config.keyDirection);
19115
19091
  this.scene.setBrightness(this.config.brightness);
@@ -19153,6 +19129,11 @@ var LightingEditor = class _LightingEditor {
19153
19129
  this.opts.subjectImageUrl = url;
19154
19130
  this.scene.setSubjectImage(url);
19155
19131
  }
19132
+ /** Restore config to the safe defaults. Convenience for host's
19133
+ * "Reset" button — equivalent to `setConfig(DEFAULT_LIGHTING_CONFIG)`. */
19134
+ reset() {
19135
+ this.setConfig(DEFAULT_LIGHTING_CONFIG, "reset");
19136
+ }
19156
19137
  setView(v) {
19157
19138
  if (v === this.view) return;
19158
19139
  this.view = v;
@@ -19162,42 +19143,6 @@ var LightingEditor = class _LightingEditor {
19162
19143
  getView() {
19163
19144
  return this.view;
19164
19145
  }
19165
- /** Enable/disable the entire Smart Mode feature at runtime. */
19166
- setSmartEnabled(enabled) {
19167
- if (enabled === this.smartEnabled) return;
19168
- this.smartEnabled = enabled;
19169
- if (enabled) {
19170
- if (!this.smartWrapper) {
19171
- this.smartWrapper = this.buildSmartWrapper();
19172
- this.body.appendChild(this.smartWrapper);
19173
- }
19174
- if (!this.smartToggleEl) {
19175
- this.smartToggleEl = this.buildSmartToggle();
19176
- this.controls.headerSlot.appendChild(this.smartToggleEl);
19177
- }
19178
- } else {
19179
- this.smartWrapper?.remove();
19180
- this.smartWrapper = null;
19181
- this.smartCloseBtn = null;
19182
- this.smartToggleEl?.remove();
19183
- this.smartToggleEl = null;
19184
- this.smartToggleThumb = null;
19185
- }
19186
- this.syncSmartState();
19187
- }
19188
- isSmartEnabled() {
19189
- return this.smartEnabled;
19190
- }
19191
- /** Open/close the smart slot drawer when enabled. No-op when disabled. */
19192
- setSmartOpen(open) {
19193
- if (!this.smartEnabled || open === this.smartOpen) return;
19194
- this.smartOpen = open;
19195
- this.syncSmartState();
19196
- this.opts.onSmartOpenChange?.(open);
19197
- }
19198
- isSmartOpen() {
19199
- return this.smartOpen;
19200
- }
19201
19146
  setTheme(theme) {
19202
19147
  applyTheme(this.root, theme);
19203
19148
  }
@@ -19208,10 +19153,6 @@ var LightingEditor = class _LightingEditor {
19208
19153
  };
19209
19154
  this.controls.setLocale(this.locale);
19210
19155
  this.syncViewToggle();
19211
- this.syncSmartLocale();
19212
- }
19213
- requestGenerate() {
19214
- this.opts.onGenerate?.(this.getConfig());
19215
19156
  }
19216
19157
  destroy() {
19217
19158
  if (this.destroyed) return;
@@ -19220,8 +19161,6 @@ var LightingEditor = class _LightingEditor {
19220
19161
  this.scene.destroy();
19221
19162
  this.root.innerHTML = "";
19222
19163
  this.root.classList.remove("aicut-root", "aicut-lighting-editor");
19223
- this.root.removeAttribute("data-smart-enabled");
19224
- this.root.removeAttribute("data-smart-open");
19225
19164
  }
19226
19165
  // ---- Internal ------------------------------------------------------
19227
19166
  applyMutation(partial) {
@@ -19271,86 +19210,6 @@ var LightingEditor = class _LightingEditor {
19271
19210
  if (v === "front") btn.textContent = this.locale.lightingViewFront;
19272
19211
  }
19273
19212
  }
19274
- /**
19275
- * Build the smart slot column wrapper: × close button + the actual
19276
- * host slot. Wrapper handles the drawer animation; slot reference
19277
- * stays stable so portals don't have to relocate.
19278
- */
19279
- buildSmartWrapper() {
19280
- const wrap = document.createElement("div");
19281
- wrap.className = "aicut-lighting-smart-wrapper";
19282
- this.smartCloseBtn = document.createElement("button");
19283
- this.smartCloseBtn.type = "button";
19284
- this.smartCloseBtn.className = "aicut-lighting-smart-close";
19285
- this.smartCloseBtn.title = this.locale.lightingSmartClose;
19286
- this.smartCloseBtn.setAttribute("aria-label", this.locale.lightingSmartClose);
19287
- this.smartCloseBtn.setAttribute("data-testid", "aicut-lighting-smart-close");
19288
- this.smartCloseBtn.innerHTML = "&times;";
19289
- this.smartCloseBtn.addEventListener("click", () => this.setSmartOpen(false));
19290
- wrap.appendChild(this.smartCloseBtn);
19291
- wrap.appendChild(this.smartSlot);
19292
- return wrap;
19293
- }
19294
- /** Pill-style toggle that lives in the controls header. Re-opens
19295
- * the smart drawer when the host has closed it via ×. */
19296
- buildSmartToggle() {
19297
- const row = document.createElement("div");
19298
- row.className = "aicut-lighting-smart-toggle-row";
19299
- const label = document.createElement("span");
19300
- label.className = "aicut-lighting-smart-toggle-label";
19301
- label.textContent = this.locale.lightingSmartToggle;
19302
- row.appendChild(label);
19303
- const toggle = document.createElement("div");
19304
- toggle.className = "aicut-lighting-toggle aicut-lighting-smart-toggle";
19305
- toggle.setAttribute("role", "switch");
19306
- toggle.setAttribute("tabindex", "0");
19307
- toggle.setAttribute("data-testid", "aicut-lighting-smart-toggle");
19308
- toggle.title = this.locale.lightingSmartToggle;
19309
- this.smartToggleThumb = document.createElement("div");
19310
- this.smartToggleThumb.className = "aicut-lighting-toggle-thumb";
19311
- toggle.appendChild(this.smartToggleThumb);
19312
- toggle.addEventListener("click", () => this.setSmartOpen(!this.smartOpen));
19313
- row.appendChild(toggle);
19314
- return row;
19315
- }
19316
- /** Re-translate smart-related labels after a locale swap. */
19317
- syncSmartLocale() {
19318
- if (this.smartCloseBtn) {
19319
- this.smartCloseBtn.title = this.locale.lightingSmartClose;
19320
- this.smartCloseBtn.setAttribute("aria-label", this.locale.lightingSmartClose);
19321
- }
19322
- if (this.smartToggleEl) {
19323
- const label = this.smartToggleEl.querySelector(
19324
- ".aicut-lighting-smart-toggle-label"
19325
- );
19326
- if (label) label.textContent = this.locale.lightingSmartToggle;
19327
- const toggle = this.smartToggleEl.querySelector(
19328
- ".aicut-lighting-smart-toggle"
19329
- );
19330
- if (toggle) toggle.title = this.locale.lightingSmartToggle;
19331
- }
19332
- }
19333
- /** Mirror smart state to data-* attrs + toggle thumb position.
19334
- * Drives the CSS that collapses the column when closed. */
19335
- syncSmartState() {
19336
- this.root.setAttribute(
19337
- "data-smart-enabled",
19338
- this.smartEnabled ? "true" : "false"
19339
- );
19340
- this.root.setAttribute(
19341
- "data-smart-open",
19342
- this.smartOpen ? "true" : "false"
19343
- );
19344
- if (this.smartToggleEl) {
19345
- const toggle = this.smartToggleEl.querySelector(
19346
- ".aicut-lighting-smart-toggle"
19347
- );
19348
- if (toggle) {
19349
- toggle.classList.toggle("active", this.smartOpen);
19350
- toggle.setAttribute("aria-checked", this.smartOpen ? "true" : "false");
19351
- }
19352
- }
19353
- }
19354
19213
  };
19355
19214
  /*! Bundled license information:
19356
19215