@codingfactory/mediables-vue 2.18.3 → 2.19.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.
Files changed (55) hide show
  1. package/dist/{PixiFrameExporter-YrJf3-l9.js → PixiFrameExporter-64httBgJ.js} +2 -2
  2. package/dist/{PixiFrameExporter-YrJf3-l9.js.map → PixiFrameExporter-64httBgJ.js.map} +1 -1
  3. package/dist/{PixiFrameExporter-DhAJGzIK.cjs → PixiFrameExporter-DkXTPJWn.cjs} +2 -2
  4. package/dist/{PixiFrameExporter-DhAJGzIK.cjs.map → PixiFrameExporter-DkXTPJWn.cjs.map} +1 -1
  5. package/dist/components/MediaAlbumPickerModal.vue.d.ts +6 -0
  6. package/dist/components/MediaFilterChips.vue.d.ts +10 -0
  7. package/dist/components/MediaInspectorPanel.vue.d.ts +13 -0
  8. package/dist/components/MediaLibraryShell.vue.d.ts +26 -0
  9. package/dist/components/MediaManagementView.vue.d.ts +1 -0
  10. package/dist/components/MediaTrashWorkspace.vue.d.ts +5 -0
  11. package/dist/composables/useMediaDeletion.d.ts +20 -0
  12. package/dist/composables/useMediaLibraryVisibility.d.ts +37 -0
  13. package/dist/composables/useMediaMetadata.d.ts +24 -0
  14. package/dist/editor-CiTXlIVO.js +4327 -0
  15. package/dist/editor-CiTXlIVO.js.map +1 -0
  16. package/dist/editor-P9MyuiTc.cjs +2 -0
  17. package/dist/editor-P9MyuiTc.cjs.map +1 -0
  18. package/dist/imageEditorState-BNQEZoCF.js +81 -0
  19. package/dist/imageEditorState-BNQEZoCF.js.map +1 -0
  20. package/dist/imageEditorState-PrcqbsfM.cjs +2 -0
  21. package/dist/imageEditorState-PrcqbsfM.cjs.map +1 -0
  22. package/dist/index-2jP5K__o.cjs +357 -0
  23. package/dist/index-2jP5K__o.cjs.map +1 -0
  24. package/dist/index-Dae8SHT7.js +6557 -0
  25. package/dist/index-Dae8SHT7.js.map +1 -0
  26. package/dist/{index-Dc-M8oj6.js → index-Dinl1Puu.js} +17147 -15638
  27. package/dist/index-Dinl1Puu.js.map +1 -0
  28. package/dist/index-QOKC8XA_.cjs +2 -0
  29. package/dist/index-QOKC8XA_.cjs.map +1 -0
  30. package/dist/index.d.ts +5 -1
  31. package/dist/mediables-primitives.cjs +2 -0
  32. package/dist/mediables-primitives.cjs.map +1 -0
  33. package/dist/mediables-primitives.mjs +21 -0
  34. package/dist/mediables-primitives.mjs.map +1 -0
  35. package/dist/mediables-vanilla.cjs +1 -1
  36. package/dist/mediables-vanilla.mjs +8 -7
  37. package/dist/mediables-vanilla.mjs.map +1 -1
  38. package/dist/mediables-vue.cjs +1 -1
  39. package/dist/mediables-vue.mjs +74 -70
  40. package/dist/mediables-vue.mjs.map +1 -1
  41. package/dist/primitives-exports.d.ts +37 -0
  42. package/dist/style.css +1 -1
  43. package/dist/types/media.d.ts +2 -0
  44. package/dist/types/mediaLibraryPicker.d.ts +5 -0
  45. package/dist/utils/focus.d.ts +4 -0
  46. package/dist/vanilla-editor/core/EventEmitter.d.ts +8 -8
  47. package/dist/vanilla-editor/core/State.d.ts +441 -95
  48. package/package.json +8 -2
  49. package/dist/editor-D85y_zTO.cjs +0 -2
  50. package/dist/editor-D85y_zTO.cjs.map +0 -1
  51. package/dist/editor-DAJxTvzM.js +0 -10873
  52. package/dist/editor-DAJxTvzM.js.map +0 -1
  53. package/dist/index-BFrh53TT.cjs +0 -357
  54. package/dist/index-BFrh53TT.cjs.map +0 -1
  55. package/dist/index-Dc-M8oj6.js.map +0 -1
@@ -0,0 +1,4327 @@
1
+ var Me = Object.defineProperty;
2
+ var Fe = (g, e, t) => e in g ? Me(g, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : g[e] = t;
3
+ var oe = (g, e, t) => Fe(g, typeof e != "symbol" ? e + "" : e, t);
4
+ import { E as re, d as Se } from "./index-Dae8SHT7.js";
5
+ const Re = Object.freeze(["normal", "multiply", "screen", "overlay"]), E = Object.freeze({
6
+ stroke: Object.freeze({ enabled: !1, color: "#000000", width: 0 }),
7
+ shadow: Object.freeze({ enabled: !1, color: "#000000", alpha: 0.5, blur: 0, distance: 0, angle: 45 }),
8
+ glow: Object.freeze({ enabled: !1, color: "#ffffff", alpha: 0.5, blur: 0 }),
9
+ backdrop: Object.freeze({ enabled: !1, color: "#000000", opacity: 0.5, padding: 0, radius: 0, blur: 0 })
10
+ });
11
+ class Te extends re {
12
+ constructor() {
13
+ super(), this.app = null, this.sprite = null, this.originalTexture = null, this.baseTexture = null, this.fitScale = 1, this.zoom = 1, this._container = null, this._lastExportDimensions = null, this._mountPromise = null, this._mountToken = 0, this._destroyed = !1, this._layerDocument = null, this._layerDisplayObjects = /* @__PURE__ */ new Map(), this._renderableLayerIds = [], this._loadedTextureUrl = null, this._layerRenderToken = 0, this._layerFilterFactory = null;
14
+ }
15
+ /**
16
+ * Whether the renderer is fully initialized and ready for use.
17
+ * @returns {boolean}
18
+ */
19
+ get isReady() {
20
+ var e;
21
+ return !!((e = this.app) != null && e.renderer);
22
+ }
23
+ /**
24
+ * Get CSS size of the canvas (for proper coordinate mapping).
25
+ * Accesses renderer.canvas directly to avoid PIXI Application's
26
+ * canvas getter which throws when renderer is undefined.
27
+ */
28
+ _getCssSize() {
29
+ var s, r, o, n, a, l;
30
+ const e = (r = (s = this.app) == null ? void 0 : s.renderer) == null ? void 0 : r.canvas, t = (e == null ? void 0 : e.clientWidth) ?? ((n = (o = this.app) == null ? void 0 : o.screen) == null ? void 0 : n.width) ?? 0, i = (e == null ? void 0 : e.clientHeight) ?? ((l = (a = this.app) == null ? void 0 : a.screen) == null ? void 0 : l.height) ?? 0;
31
+ return { w: t, h: i };
32
+ }
33
+ /**
34
+ * Force render the stage
35
+ */
36
+ render() {
37
+ var e, t;
38
+ try {
39
+ (e = this.app) != null && e.renderer && ((t = this.app) != null && t.stage) && this.app.renderer.render(this.app.stage);
40
+ } catch {
41
+ }
42
+ }
43
+ /**
44
+ * Wait for the next animation frame, with a timeout fallback for environments
45
+ * where the frame callback never arrives while the editor is open.
46
+ * @param {number} timeoutMs
47
+ * @returns {Promise<void>}
48
+ */
49
+ _waitForNextFrame(e = 32) {
50
+ return new Promise((t) => {
51
+ if (typeof window > "u") {
52
+ t();
53
+ return;
54
+ }
55
+ let i = !1, s = 0;
56
+ const r = () => {
57
+ i || (i = !0, window.clearTimeout(s), t());
58
+ };
59
+ if (s = window.setTimeout(r, e), typeof window.requestAnimationFrame == "function") {
60
+ window.requestAnimationFrame(() => r());
61
+ return;
62
+ }
63
+ r();
64
+ });
65
+ }
66
+ /**
67
+ * Calculate fit scale for a texture
68
+ * @param {PIXI.Texture} tex
69
+ * @returns {number}
70
+ */
71
+ getFitScaleFor(e) {
72
+ if (!this.app || !e) return 1;
73
+ const { w: t, h: i } = this._getCssSize();
74
+ if (t <= 1 || i <= 1) return 1;
75
+ let s = Math.min(t / e.width, i / e.height) * 0.9;
76
+ return (!Number.isFinite(s) || s <= 0) && (s = 1), s;
77
+ }
78
+ /**
79
+ * Apply view transform (position and scale) to sprite
80
+ */
81
+ applyViewTransform(e = {}) {
82
+ if (!this.app || !this.sprite || !this.originalTexture) return;
83
+ const t = this.fitScale * this.zoom, { w: i, h: s } = this._getCssSize(), r = e.keepCenter ? { x: this.sprite.x + this.sprite.width / 2, y: this.sprite.y + this.sprite.height / 2 } : e.center ?? { x: i / 2, y: s / 2 };
84
+ this.sprite.width = this.originalTexture.width * t, this.sprite.height = this.originalTexture.height * t, this.sprite.x = r.x - this.sprite.width / 2, this.sprite.y = r.y - this.sprite.height / 2, this._syncLayerDisplayObjects(), this.render();
85
+ }
86
+ /**
87
+ * Set zoom level
88
+ * @param {number} z - Zoom level (0.1 to 8)
89
+ * @param {Object} opts - Options
90
+ */
91
+ setZoom(e, t = {}) {
92
+ this.zoom = Math.max(0.1, Math.min(8, e)), this.applyViewTransform({ keepCenter: t.keepCenter ?? !0 }), this.emit("zoomChange", this.zoom);
93
+ }
94
+ /**
95
+ * Fit image to screen
96
+ */
97
+ fitToScreen() {
98
+ this.originalTexture && (this.fitScale = this.getFitScaleFor(this.originalTexture), this.setZoom(1, { keepCenter: !1 }));
99
+ }
100
+ /**
101
+ * Mount PIXI application to container
102
+ * @param {HTMLElement} container
103
+ * @param {number} backgroundColor
104
+ * @param {Object} [options]
105
+ * @param {number} [options.backgroundAlpha=1] - 0 makes the canvas transparent
106
+ * so a host-rendered background (e.g. CSS checkerboard) shows through.
107
+ */
108
+ async mount(e, t = 16777215, i = {}) {
109
+ if (!e) return;
110
+ this._destroyed = !1, this._container = e;
111
+ const s = ++this._mountToken, r = async () => {
112
+ var p;
113
+ const o = window.PIXI;
114
+ if (!o)
115
+ throw new Error("PIXI.js not found. Please ensure PIXI is loaded globally.");
116
+ (p = o.Assets) != null && p.setPreferences && o.Assets.setPreferences({ crossOrigin: "anonymous" });
117
+ const n = e.clientWidth || 600, a = e.clientHeight || 400, l = typeof i.backgroundAlpha == "number" ? i.backgroundAlpha : 1, h = new o.Application();
118
+ if (this.app = h, await h.init({
119
+ width: n,
120
+ height: a,
121
+ backgroundColor: t,
122
+ backgroundAlpha: l,
123
+ antialias: !0,
124
+ autoDensity: !0,
125
+ resolution: window.devicePixelRatio || 1
126
+ }), this._destroyed || this._mountToken !== s || this.app !== h) {
127
+ try {
128
+ h.destroy(!0, { children: !0, texture: !0 });
129
+ } catch {
130
+ }
131
+ return !1;
132
+ }
133
+ e.appendChild(h.canvas);
134
+ const c = h.canvas;
135
+ return c.classList.add("pixi-canvas"), c.style.width = "100%", c.style.height = "100%", c.style.transform = "translateZ(0)", c.style.willChange = "transform", c.setAttribute("role", "img"), c.setAttribute("aria-label", "Image editor canvas — use toolbar controls to edit the image"), this.emit("mounted", { width: n, height: a }), !0;
136
+ };
137
+ return this._mountPromise = r(), await this._mountPromise;
138
+ }
139
+ /**
140
+ * Load an image texture
141
+ * @param {string} imageUrl - URL, data URL, or blob URL of the image
142
+ * @param {Object} [options]
143
+ * @param {() => boolean} [options.isCurrent] - Returns false when this load
144
+ * has been superseded and must not mutate the renderer.
145
+ */
146
+ async loadTexture(e, t = {}) {
147
+ var h, c, p;
148
+ const i = () => typeof t.isCurrent != "function" || t.isCurrent() !== !1;
149
+ if (this._mountPromise && await this._mountPromise, !((h = this.app) != null && h.renderer) || !i()) return !1;
150
+ const s = window.PIXI, r = await this._loadImageElement(e);
151
+ if (!r || !((c = this.app) != null && c.renderer) || !i() || (await this._waitForNextFrame(), !((p = this.app) != null && p.renderer) || !i())) return !1;
152
+ const o = s.Texture.from(r);
153
+ if (!o)
154
+ return !1;
155
+ this.originalTexture = o, this.baseTexture = o, this._loadedTextureUrl = typeof e == "string" ? e : null, this.sprite && (this.app.stage.removeChild(this.sprite), this.sprite.destroy()), this.sprite = new s.Sprite(o), this.app.stage.addChild(this.sprite), this.fitScale = this.getFitScaleFor(o), this.zoom = 1;
156
+ const { w: n, h: a } = this._getCssSize();
157
+ this.applyViewTransform({ center: { x: n / 2, y: a / 2 } });
158
+ const l = this.getFitScaleFor(o);
159
+ return Math.abs(l - this.fitScale) / Math.max(1e-6, l) > 0.02 && (this.fitScale = l, this.applyViewTransform({ center: { x: n / 2, y: a / 2 } })), this.render(), this.emit("textureLoaded", { width: o.width, height: o.height }), !0;
160
+ }
161
+ /**
162
+ * Render a v2 layer document in preview order. The existing subject sprite
163
+ * remains the filter/crop target; auxiliary layers are synced to its preview
164
+ * geometry until arbitrary per-layer transforms are introduced.
165
+ * @param {Object|null} document
166
+ * @returns {Promise<boolean>}
167
+ */
168
+ async renderLayerDocument(e) {
169
+ var o;
170
+ if (!((o = this.app) != null && o.stage) || !this.sprite || !e || !Array.isArray(e.layers))
171
+ return !1;
172
+ const t = window.PIXI;
173
+ if (!t) return !1;
174
+ const i = ++this._layerRenderToken, s = () => i === this._layerRenderToken;
175
+ this._destroyLayerDisplayObjects(), this._layerDocument = JSON.parse(JSON.stringify(e)), this._renderableLayerIds = [];
176
+ try {
177
+ this.app.stage.removeChild(this.sprite);
178
+ } catch {
179
+ }
180
+ let r = !1;
181
+ for (const n of this._layerDocument.layers) {
182
+ if (!n || n.visible === !1) {
183
+ (n == null ? void 0 : n.role) === "subject" && (this.sprite.visible = !1, this.sprite.__layerId = n.id, r = !0);
184
+ continue;
185
+ }
186
+ if (this._isSubjectLayer(n)) {
187
+ const l = await this._createSubjectDisplayObjectForLayer(t, n);
188
+ if (!s())
189
+ return this._destroyTransientDisplayObject(l), !1;
190
+ if (l) {
191
+ this.sprite.visible = !1, l.__layerId = n.id, l.visible = !0, l.alpha = this._layerOpacity(n), this._layerDisplayObjects.set(n.id, { layer: n, displayObject: l }), this._syncLayerDisplayObject(n, l), this.app.stage.addChild(l), this._renderableLayerIds.push(n.id), r = !0;
192
+ continue;
193
+ }
194
+ this.sprite.__layerId = n.id, this.sprite.visible = !0, this.sprite.alpha = this._layerOpacity(n), this._applyLayerFilterInstances(this.sprite, n), this.app.stage.addChild(this.sprite), this._renderableLayerIds.push(n.id), r = !0;
195
+ continue;
196
+ }
197
+ const a = await this._createDisplayObjectForLayer(t, n);
198
+ if (!s())
199
+ return this._destroyTransientDisplayObject(a), !1;
200
+ a && (a.__layerId = n.id, a.visible = !0, a.alpha = this._layerOpacity(n), this._layerDisplayObjects.set(n.id, { layer: n, displayObject: a }), this._syncLayerDisplayObject(n, a), this.app.stage.addChild(a), this._renderableLayerIds.push(n.id));
201
+ }
202
+ return r || (this.sprite.visible = !1), s() ? (this.render(), !0) : !1;
203
+ }
204
+ getRenderableLayerIds() {
205
+ return [...this._renderableLayerIds];
206
+ }
207
+ setLayerFilterFactory(e) {
208
+ this._layerFilterFactory = typeof e == "function" ? e : null;
209
+ }
210
+ applyLayerFilters(e = this._layerDocument) {
211
+ var t;
212
+ if (!e || !Array.isArray(e.layers)) return !1;
213
+ this._layerDocument = JSON.parse(JSON.stringify(e));
214
+ for (const i of this._layerDocument.layers) {
215
+ if (!i) continue;
216
+ const s = this._layerDisplayObjects.get(i.id) || null;
217
+ s && (s.layer = i);
218
+ let r = (s == null ? void 0 : s.displayObject) || null;
219
+ !r && this._isSubjectLayer(i) && ((t = this.sprite) == null ? void 0 : t.__layerId) === i.id && (r = this.sprite), r && this._applyLayerFilterInstances(r, i);
220
+ }
221
+ return this.render(), !0;
222
+ }
223
+ _isSubjectLayer(e) {
224
+ return (e == null ? void 0 : e.role) === "subject" || (e == null ? void 0 : e.id) === "subject-layer";
225
+ }
226
+ _layerSupportsImageFilters(e) {
227
+ var t;
228
+ return !e || typeof e != "object" ? !1 : e.type === "image" ? !0 : e.type === "background" || e.role === "background" ? ((t = e.fill) == null ? void 0 : t.kind) === "media" : !1;
229
+ }
230
+ _layerOpacity(e) {
231
+ const t = Number(e == null ? void 0 : e.opacity);
232
+ return Number.isFinite(t) ? Math.max(0, Math.min(1, t)) : 1;
233
+ }
234
+ _applyLayerFilterInstances(e, t, i = {}) {
235
+ if (!e || !this._layerSupportsImageFilters(t)) return;
236
+ let s = [];
237
+ if (this._layerFilterFactory)
238
+ try {
239
+ s = this._layerFilterFactory(t, {
240
+ displayObject: e,
241
+ forExport: i.forExport === !0,
242
+ previewToNativeScale: i.previewToNativeScale || 1,
243
+ sourceWidth: i.sourceWidth,
244
+ sourceHeight: i.sourceHeight
245
+ }) || [];
246
+ } catch {
247
+ s = [];
248
+ }
249
+ const r = Array.isArray(s) ? s.filter(Boolean) : [];
250
+ for (const a of r)
251
+ if (!(!a || typeof a != "object" && typeof a != "function"))
252
+ try {
253
+ Object.defineProperty(a, "__mediablesLayerImageFilter", {
254
+ value: !0,
255
+ configurable: !0
256
+ });
257
+ } catch {
258
+ try {
259
+ a.__mediablesLayerImageFilter = !0;
260
+ } catch {
261
+ }
262
+ }
263
+ const n = [...Array.isArray(e.filters) ? e.filters.filter((a) => (a == null ? void 0 : a.__mediablesLayerImageFilter) !== !0) : [], ...r];
264
+ e.filters = n.length > 0 ? n : null;
265
+ }
266
+ async _createDisplayObjectForLayer(e, t) {
267
+ var o, n;
268
+ const i = this._createTextDisplayObjectForLayer(e, t);
269
+ if (i) return i;
270
+ const s = await this._createImageDisplayObjectForLayer(e, t);
271
+ if (s) return s;
272
+ const r = this._backgroundFillForLayer(t);
273
+ if ((r == null ? void 0 : r.kind) === "media") {
274
+ const a = await this._textureForLayerSource(e, r.source);
275
+ if (!a) return null;
276
+ const l = this._createBackgroundSprite(e, a, r.fit);
277
+ return l.__sourceWidth = Number((o = r.source) == null ? void 0 : o.originalWidth) || a.width || 1, l.__sourceHeight = Number((n = r.source) == null ? void 0 : n.originalHeight) || a.height || 1, this._applyLayerEffects(e, l, t), this._applyLayerFilterInstances(l, t), l;
278
+ }
279
+ if ((r == null ? void 0 : r.kind) === "gradient") {
280
+ const a = this._createGradientTexture(e, r, 512, 512);
281
+ if (!a || typeof e.Sprite != "function") return null;
282
+ const l = new e.Sprite(a);
283
+ return l.__ownsTexture = !0, l.__sourceWidth = 512, l.__sourceHeight = 512, this._applyLayerEffects(e, l, t), l;
284
+ }
285
+ if (this._backgroundColorForLayer(t) && typeof e.Graphics == "function") {
286
+ const a = new e.Graphics();
287
+ return this._applyLayerEffects(e, a, t), a;
288
+ }
289
+ return null;
290
+ }
291
+ _isTextLayer(e) {
292
+ return (e == null ? void 0 : e.type) === "text" || (e == null ? void 0 : e.role) === "text";
293
+ }
294
+ _normalizeTextBox(e) {
295
+ var i;
296
+ const t = (i = e == null ? void 0 : e.text) != null && i.box && typeof e.text.box == "object" ? e.text.box : {};
297
+ return {
298
+ x: this._clampNumber(t.x, 0, 1, 0.2),
299
+ y: this._clampNumber(t.y, 0, 1, 0.2),
300
+ width: this._clampNumber(t.width, 0.05, 1, 0.6),
301
+ height: this._clampNumber(t.height, 0.05, 1, 0.18)
302
+ };
303
+ }
304
+ _clampNumber(e, t, i, s) {
305
+ const r = Number(e);
306
+ return Number.isFinite(r) ? Math.max(t, Math.min(i, r)) : s;
307
+ }
308
+ _supportedTextFontFamily(e) {
309
+ const t = ["Inter", "Arial", "Helvetica", "Georgia", "Times New Roman", "Courier New", "Verdana"], i = typeof e == "string" ? e.trim() : "";
310
+ return t.includes(i) ? i : "Inter";
311
+ }
312
+ _supportedTextColor(e, t = "#000000") {
313
+ return typeof e == "string" && /^#[0-9a-f]{6}$/i.test(e) ? e : t;
314
+ }
315
+ _textBlendMode(e) {
316
+ const t = e == null ? void 0 : e.blendMode;
317
+ return Re.includes(t) ? t : "normal";
318
+ }
319
+ _textContentForLayer(e) {
320
+ var i;
321
+ const t = (i = e == null ? void 0 : e.text) == null ? void 0 : i.content;
322
+ return t == null ? "" : String(t);
323
+ }
324
+ _textStyleForLayer(e, t = 1, i = 1, s = {}) {
325
+ var m;
326
+ const r = (m = e == null ? void 0 : e.text) != null && m.style && typeof e.text.style == "object" ? e.text.style : {}, o = this._textEffectsForLayer(e), n = this._clampNumber(r.fontSize, 8, 400, 64), a = this._clampNumber(r.lineHeight, 0.8, 3, 1.2), l = this._clampNumber(r.letterSpacing, -100, 100, 0), h = this._normalizeTextFontWeight(r.fontWeight), c = ["normal", "italic"].includes(r.fontStyle) ? r.fontStyle : "normal", p = ["left", "center", "right"].includes(r.align) ? r.align : "center", u = s.fill || (typeof r.fill == "string" && /^#[0-9a-f]{6}$/i.test(r.fill) ? r.fill : "#ffffff"), f = {
327
+ fontFamily: this._supportedTextFontFamily(r.fontFamily),
328
+ fontSize: n * t,
329
+ fontWeight: h,
330
+ fontStyle: c,
331
+ fill: u,
332
+ align: p,
333
+ lineHeight: n * a * t,
334
+ letterSpacing: l * t,
335
+ wordWrap: !0,
336
+ wordWrapWidth: Math.max(1, i),
337
+ breakWords: !0
338
+ };
339
+ return s.stroke !== !1 && o.stroke.enabled && o.stroke.width > 0 && (f.stroke = {
340
+ color: o.stroke.color,
341
+ width: o.stroke.width * t
342
+ }), f;
343
+ }
344
+ _normalizeTextFontWeight(e) {
345
+ if (e === "normal") return 400;
346
+ if (e === "bold") return 700;
347
+ const t = Number(e);
348
+ return !Number.isFinite(t) || t < 100 || t > 900 || t % 100 !== 0 ? 700 : t;
349
+ }
350
+ _textEffectsForLayer(e) {
351
+ var n;
352
+ const t = (n = e == null ? void 0 : e.text) != null && n.effects && typeof e.text.effects == "object" ? e.text.effects : {}, i = t.stroke && typeof t.stroke == "object" ? t.stroke : {}, s = t.shadow && typeof t.shadow == "object" ? t.shadow : {}, r = t.glow && typeof t.glow == "object" ? t.glow : {}, o = t.backdrop && typeof t.backdrop == "object" ? t.backdrop : {};
353
+ return {
354
+ stroke: {
355
+ enabled: i.enabled === !0,
356
+ color: this._supportedTextColor(i.color, E.stroke.color),
357
+ width: this._clampNumber(i.width, 0, 80, E.stroke.width)
358
+ },
359
+ shadow: {
360
+ enabled: s.enabled === !0,
361
+ color: this._supportedTextColor(s.color, E.shadow.color),
362
+ alpha: this._clampNumber(s.alpha, 0, 1, E.shadow.alpha),
363
+ blur: this._clampNumber(s.blur, 0, 120, E.shadow.blur),
364
+ distance: this._clampNumber(s.distance, 0, 200, E.shadow.distance),
365
+ angle: this._normalizeDegrees(s.angle ?? E.shadow.angle)
366
+ },
367
+ glow: {
368
+ enabled: r.enabled === !0,
369
+ color: this._supportedTextColor(r.color, E.glow.color),
370
+ alpha: this._clampNumber(r.alpha, 0, 1, E.glow.alpha),
371
+ blur: this._clampNumber(r.blur, 0, 120, E.glow.blur)
372
+ },
373
+ backdrop: {
374
+ enabled: o.enabled === !0,
375
+ color: this._supportedTextColor(o.color, E.backdrop.color),
376
+ opacity: this._clampNumber(o.opacity, 0, 1, E.backdrop.opacity),
377
+ padding: this._clampNumber(o.padding, 0, 200, E.backdrop.padding),
378
+ radius: this._clampNumber(o.radius, 0, 200, E.backdrop.radius),
379
+ blur: this._clampNumber(o.blur, 0, 80, E.backdrop.blur)
380
+ }
381
+ };
382
+ }
383
+ _createTextBlurFilter(e, t) {
384
+ if (t <= 0 || typeof (e == null ? void 0 : e.BlurFilter) != "function") return null;
385
+ try {
386
+ let i;
387
+ try {
388
+ i = new e.BlurFilter({ strength: t, quality: 4 });
389
+ } catch {
390
+ i = new e.BlurFilter(t);
391
+ }
392
+ return typeof i.blur == "number" && (i.blur = t), typeof i.strength == "number" && (i.strength = t), i.padding = Math.max(Number(i.padding) || 0, Math.ceil(t * 2)), i;
393
+ } catch {
394
+ return null;
395
+ }
396
+ }
397
+ _textEffectPadding(e, t) {
398
+ const i = e.stroke.enabled ? e.stroke.width * t : 0, s = e.shadow.enabled ? (e.shadow.distance + e.shadow.blur * 2) * t : 0, r = e.glow.enabled ? e.glow.blur * 2 * t : 0, o = e.backdrop.enabled ? (e.backdrop.padding + e.backdrop.blur * 2) * t : 0;
399
+ return Math.ceil(Math.max(i, s, r, o, 0));
400
+ }
401
+ _createTextDisplayObjectForLayer(e, t) {
402
+ if (!this._isTextLayer(t) || typeof (e == null ? void 0 : e.Text) != "function") return null;
403
+ const i = this._createTextPrimitive(e, t);
404
+ if (!i) return null;
405
+ if (typeof e.Container != "function" || typeof e.Graphics != "function")
406
+ return i;
407
+ const s = new e.Container(), r = new e.Graphics(), o = new e.Graphics(), n = new e.Graphics(), a = this._createTextPrimitive(e, t), l = this._createTextPrimitive(e, t);
408
+ return s.__isTextLayerContainer = !0, s.__textObject = i, s.__textMask = r, s.__textEffectMask = o, s.__textBackdrop = n, s.__textShadow = a, s.__textGlow = l, n.visible = !1, a && (a.visible = !1), l && (l.visible = !1), i.mask = r, s.addChild(n), a && s.addChild(a), l && s.addChild(l), s.addChild(i), s.addChild(o), s.addChild(r), s;
409
+ }
410
+ _createTextPrimitive(e, t) {
411
+ try {
412
+ return new e.Text({
413
+ text: this._textContentForLayer(t),
414
+ style: this._textStyleForLayer(t)
415
+ });
416
+ } catch {
417
+ try {
418
+ return new e.Text(this._textContentForLayer(t), this._textStyleForLayer(t));
419
+ } catch {
420
+ return null;
421
+ }
422
+ }
423
+ }
424
+ async _createSubjectDisplayObjectForLayer(e, t) {
425
+ const i = this._sourceUrlForLayerSource(t == null ? void 0 : t.source);
426
+ return !i || i === this._loadedTextureUrl ? null : this._createImageDisplayObjectForLayer(e, t);
427
+ }
428
+ async _createImageDisplayObjectForLayer(e, t) {
429
+ var r, o;
430
+ if ((t == null ? void 0 : t.type) !== "image") return null;
431
+ const i = await this._textureForLayerSource(e, t.source);
432
+ if (!i || typeof e.Sprite != "function") return null;
433
+ const s = new e.Sprite(i);
434
+ return s.__sourceWidth = Number((r = t.source) == null ? void 0 : r.originalWidth) || i.width || 1, s.__sourceHeight = Number((o = t.source) == null ? void 0 : o.originalHeight) || i.height || 1, this._applyLayerEffects(e, s, t), this._applyLayerFilterInstances(s, t), s;
435
+ }
436
+ _backgroundFillForLayer(e) {
437
+ const t = e == null ? void 0 : e.fill;
438
+ if (t && typeof t == "object") return t;
439
+ const i = e == null ? void 0 : e.source;
440
+ return (i == null ? void 0 : i.kind) === "color" && typeof i.color == "string" ? { kind: "color", value: i.color } : null;
441
+ }
442
+ _backgroundColorForLayer(e) {
443
+ const t = e == null ? void 0 : e.source;
444
+ if ((t == null ? void 0 : t.kind) === "color" && typeof t.color == "string")
445
+ return t.color;
446
+ const i = e == null ? void 0 : e.fill;
447
+ return (i == null ? void 0 : i.kind) === "color" && typeof i.value == "string" ? i.value : null;
448
+ }
449
+ _sourceUrlForLayerSource(e) {
450
+ if (!e || typeof e != "object") return null;
451
+ for (const t of ["previewUrl", "sourceUrl", "url"]) {
452
+ const i = e[t];
453
+ if (typeof i == "string" && i.trim() !== "")
454
+ return i;
455
+ }
456
+ return null;
457
+ }
458
+ async _textureForLayerSource(e, t) {
459
+ const i = this._sourceUrlForLayerSource(t);
460
+ if (!i) return null;
461
+ const s = await this._loadImageElement(i);
462
+ return s ? e.Texture.from(s) : null;
463
+ }
464
+ _createBackgroundSprite(e, t, i = "cover") {
465
+ if (i === "tile" && typeof e.TilingSprite == "function")
466
+ try {
467
+ const s = new e.TilingSprite({ texture: t, width: 1, height: 1 });
468
+ return s.__isTilingBackground = !0, s;
469
+ } catch {
470
+ try {
471
+ const r = new e.TilingSprite(t, 1, 1);
472
+ return r.__isTilingBackground = !0, r;
473
+ } catch {
474
+ }
475
+ }
476
+ return new e.Sprite(t);
477
+ }
478
+ _createGradientTexture(e, t, i, s) {
479
+ var l;
480
+ if (typeof document > "u" || !((l = e == null ? void 0 : e.Texture) != null && l.from)) return null;
481
+ const r = document.createElement("canvas");
482
+ r.width = Math.max(1, Math.round(i)), r.height = Math.max(1, Math.round(s));
483
+ const o = r.getContext("2d");
484
+ if (!o) return null;
485
+ const n = Array.isArray(t == null ? void 0 : t.stops) && t.stops.length > 0 ? t.stops : [
486
+ { offset: 0, color: "#111827" },
487
+ { offset: 1, color: "#f59e0b" }
488
+ ];
489
+ let a;
490
+ if ((t == null ? void 0 : t.gradientType) === "radial") {
491
+ const h = Math.max(r.width, r.height) / 2;
492
+ a = o.createRadialGradient(
493
+ r.width / 2,
494
+ r.height / 2,
495
+ 0,
496
+ r.width / 2,
497
+ r.height / 2,
498
+ h
499
+ );
500
+ } else {
501
+ const h = (Number(t == null ? void 0 : t.angle) || 0) * Math.PI / 180, c = r.width / 2, p = r.height / 2, u = Math.sqrt(r.width ** 2 + r.height ** 2) / 2, f = Math.cos(h) * u, m = Math.sin(h) * u;
502
+ a = o.createLinearGradient(c - f, p - m, c + f, p + m);
503
+ }
504
+ for (const h of n) {
505
+ if (!h || typeof h.color != "string") continue;
506
+ const c = Number.isFinite(Number(h.offset)) ? Math.max(0, Math.min(1, Number(h.offset))) : 0;
507
+ a.addColorStop(c, h.color);
508
+ }
509
+ return o.fillStyle = a, o.fillRect(0, 0, r.width, r.height), e.Texture.from(r);
510
+ }
511
+ _backgroundBlurAmount(e) {
512
+ var r;
513
+ const i = (Array.isArray(e == null ? void 0 : e.effects) ? e.effects : []).find((o) => (o == null ? void 0 : o.id) === "background-blur" || (o == null ? void 0 : o.type) === "blur"), s = Number((r = i == null ? void 0 : i.params) == null ? void 0 : r.amount);
514
+ return Number.isFinite(s) ? Math.max(0, s) : 0;
515
+ }
516
+ _applyLayerEffects(e, t, i) {
517
+ const s = this._backgroundBlurAmount(i);
518
+ if (!(s <= 0 || typeof (e == null ? void 0 : e.BlurFilter) != "function"))
519
+ try {
520
+ let r;
521
+ try {
522
+ r = new e.BlurFilter({ strength: s, quality: 4 });
523
+ } catch {
524
+ r = new e.BlurFilter(s);
525
+ }
526
+ typeof r.blur == "number" && (r.blur = s), typeof r.strength == "number" && (r.strength = s), r.padding = Math.max(Number(r.padding) || 0, Math.ceil(s * 2)), t.filters = Array.isArray(t.filters) ? [...t.filters, r] : [r];
527
+ } catch {
528
+ }
529
+ }
530
+ _syncLayerDisplayObjects() {
531
+ if (this.sprite)
532
+ for (const { layer: e, displayObject: t } of this._layerDisplayObjects.values())
533
+ this._syncLayerDisplayObject(e, t);
534
+ }
535
+ _syncLayerDisplayObject(e, t) {
536
+ if (!this.sprite || !t) return;
537
+ if (this._isTextLayer(e)) {
538
+ this._fitTextDisplayObjectToRect(t, e, {
539
+ x: this.sprite.x,
540
+ y: this.sprite.y,
541
+ width: this.sprite.width,
542
+ height: this.sprite.height
543
+ });
544
+ return;
545
+ }
546
+ const i = this._backgroundColorForLayer(e);
547
+ if (i && typeof t.clear == "function") {
548
+ t.clear(), t.rect(this.sprite.x, this.sprite.y, this.sprite.width, this.sprite.height).fill(i);
549
+ return;
550
+ }
551
+ this._fitDisplayObjectToRect(t, e, {
552
+ x: this.sprite.x,
553
+ y: this.sprite.y,
554
+ width: this.sprite.width,
555
+ height: this.sprite.height
556
+ });
557
+ }
558
+ _fillTextEffectGraphics(e, t, i = 1) {
559
+ if (!(!e || typeof e.fill != "function"))
560
+ try {
561
+ e.fill({ color: t, alpha: i });
562
+ } catch {
563
+ e.fill(t), e.alpha = i;
564
+ }
565
+ }
566
+ _syncTextEffectPrimitive(e, t, i, s, r, o, n, a) {
567
+ if (!t) return;
568
+ const l = s[r];
569
+ if (t.visible = (l == null ? void 0 : l.enabled) === !0, t.filters = null, t.mask = t.visible ? a : null, !!t.visible) {
570
+ if (t.text = this._textContentForLayer(i), t.rotation = 0, r === "shadow") {
571
+ const h = l.angle * Math.PI / 180;
572
+ t.x = Math.cos(h) * l.distance * o, t.y = Math.sin(h) * l.distance * o, t.alpha = l.alpha, t.style = this._textStyleForLayer(i, o, n, {
573
+ fill: l.color,
574
+ stroke: !1
575
+ });
576
+ const c = this._createTextBlurFilter(e, l.blur * o);
577
+ t.filters = c ? [c] : null;
578
+ return;
579
+ }
580
+ if (r === "glow") {
581
+ t.x = 0, t.y = 0, t.alpha = l.alpha, t.style = this._textStyleForLayer(i, o, n, {
582
+ fill: l.color,
583
+ stroke: !1
584
+ });
585
+ const h = this._createTextBlurFilter(e, l.blur * o);
586
+ t.filters = h ? [h] : null;
587
+ }
588
+ }
589
+ }
590
+ _syncTextBackdrop(e, t, i, s, r, o, n) {
591
+ if (!t) return;
592
+ const a = i.backdrop;
593
+ if (t.visible = a.enabled === !0, t.filters = null, t.mask = t.visible ? n : null, t.alpha = 1, typeof t.clear == "function" && t.clear(), !t.visible) return;
594
+ const l = a.padding * s, h = a.radius * s, c = -l, p = -l, u = r + l * 2, f = o + l * 2;
595
+ h > 0 && typeof t.roundRect == "function" ? t.roundRect(c, p, u, f, h) : typeof t.rect == "function" && t.rect(c, p, u, f), this._fillTextEffectGraphics(t, a.color, a.opacity);
596
+ const m = this._createTextBlurFilter(e, a.blur * s);
597
+ t.filters = m ? [m] : null;
598
+ }
599
+ _fitTextDisplayObjectToRect(e, t, i) {
600
+ var m, b;
601
+ const s = this._normalizeTextBox(t), r = i.x + s.x * i.width, o = i.y + s.y * i.height, n = s.width * i.width, a = s.height * i.height, l = Number((m = this.originalTexture) == null ? void 0 : m.width) || i.width, h = l > 0 ? i.width / l : 1, c = this._textEffectsForLayer(t);
602
+ e.x = r, e.y = o, e.rotation = this._normalizeDegrees(Number((b = t == null ? void 0 : t.transform) == null ? void 0 : b.rotation) || 0) * Math.PI / 180, e.blendMode = this._textBlendMode(t), e.__textBox = s, e.__textLayout = { x: r, y: o, width: n, height: a, scale: h };
603
+ const p = e.__textObject || e, u = e.__textMask || null, f = e.__textEffectMask || null;
604
+ if (p.text = this._textContentForLayer(t), p.style = this._textStyleForLayer(t, h, n), p.x = 0, p.y = 0, p.rotation = 0, u && typeof u.clear == "function" && typeof u.rect == "function" && typeof u.fill == "function" && (u.clear(), u.rect(0, 0, n, a).fill(16777215), p.mask = u), f && typeof f.clear == "function" && typeof f.rect == "function" && typeof f.fill == "function") {
605
+ const C = this._textEffectPadding(c, h);
606
+ f.clear(), f.rect(-C, -C, n + C * 2, a + C * 2).fill(16777215);
607
+ }
608
+ this._syncTextBackdrop(window.PIXI, e.__textBackdrop, c, h, n, a, f), this._syncTextEffectPrimitive(window.PIXI, e.__textShadow, t, c, "shadow", h, n, f), this._syncTextEffectPrimitive(window.PIXI, e.__textGlow, t, c, "glow", h, n, f);
609
+ }
610
+ _fitDisplayObjectToRect(e, t, i) {
611
+ var c, p, u, f, m, b, C, x;
612
+ const s = this._backgroundFillForLayer(t), r = (s == null ? void 0 : s.fit) || "cover";
613
+ if (e.__isTilingBackground) {
614
+ e.x = i.x, e.y = i.y, e.width = i.width, e.height = i.height, (p = (c = e.tilePosition) == null ? void 0 : c.set) == null || p.call(c, 0, 0), (f = (u = e.tileScale) == null ? void 0 : u.set) == null || f.call(u, 1, 1);
615
+ return;
616
+ }
617
+ if (r === "stretch" || (s == null ? void 0 : s.kind) === "gradient" || (s == null ? void 0 : s.kind) === "color") {
618
+ e.x = i.x, e.y = i.y, e.width = i.width, e.height = i.height;
619
+ return;
620
+ }
621
+ const o = Number(e.__sourceWidth || ((m = s == null ? void 0 : s.source) == null ? void 0 : m.originalWidth) || ((b = e.texture) == null ? void 0 : b.width) || i.width), n = Number(e.__sourceHeight || ((C = s == null ? void 0 : s.source) == null ? void 0 : C.originalHeight) || ((x = e.texture) == null ? void 0 : x.height) || i.height);
622
+ if (!Number.isFinite(o) || !Number.isFinite(n) || o <= 0 || n <= 0) {
623
+ e.x = i.x, e.y = i.y, e.width = i.width, e.height = i.height;
624
+ return;
625
+ }
626
+ const a = r === "contain" ? Math.min(i.width / o, i.height / n) : Math.max(i.width / o, i.height / n), l = o * a, h = n * a;
627
+ e.x = i.x + (i.width - l) / 2, e.y = i.y + (i.height - h) / 2, e.width = l, e.height = h;
628
+ }
629
+ _destroyLayerDisplayObjects() {
630
+ var e, t, i, s, r;
631
+ if ((e = this._layerDisplayObjects) != null && e.size) {
632
+ for (const { displayObject: o } of this._layerDisplayObjects.values()) {
633
+ try {
634
+ (s = (i = (t = this.app) == null ? void 0 : t.stage) == null ? void 0 : i.removeChild) == null || s.call(i, o);
635
+ } catch {
636
+ }
637
+ const n = (o == null ? void 0 : o.__ownsTexture) === !0;
638
+ (r = o == null ? void 0 : o.destroy) == null || r.call(o, { children: !0, texture: n, textureSource: n });
639
+ }
640
+ this._layerDisplayObjects.clear(), this._renderableLayerIds = [];
641
+ }
642
+ }
643
+ _destroyTransientDisplayObject(e) {
644
+ var i, s, r, o;
645
+ if (!e) return;
646
+ try {
647
+ (r = (s = (i = this.app) == null ? void 0 : i.stage) == null ? void 0 : s.removeChild) == null || r.call(s, e);
648
+ } catch {
649
+ }
650
+ const t = (e == null ? void 0 : e.__ownsTexture) === !0;
651
+ (o = e == null ? void 0 : e.destroy) == null || o.call(e, { children: !0, texture: t, textureSource: t });
652
+ }
653
+ /**
654
+ * Normalize degrees to the 0..360 range.
655
+ * @param {number} degrees
656
+ * @returns {number}
657
+ */
658
+ _normalizeDegrees(e) {
659
+ if (!Number.isFinite(e)) return 0;
660
+ const t = e % 360;
661
+ return t < 0 ? t + 360 : t;
662
+ }
663
+ /**
664
+ * Calculate the axis-aligned bounds for a rotated rectangle.
665
+ * @param {number} width
666
+ * @param {number} height
667
+ * @param {number} degrees
668
+ * @returns {{ width: number, height: number }}
669
+ */
670
+ getRotatedBounds(e, t, i) {
671
+ const s = this._normalizeDegrees(i);
672
+ if (s === 0)
673
+ return {
674
+ width: Math.max(1, Math.round(e)),
675
+ height: Math.max(1, Math.round(t))
676
+ };
677
+ const r = s * Math.PI / 180, o = Math.abs(Math.cos(r)), n = Math.abs(Math.sin(r)), a = o < 1e-10 ? 0 : o, l = n < 1e-10 ? 0 : n;
678
+ return {
679
+ width: Math.max(1, Math.ceil(e * a + t * l)),
680
+ height: Math.max(1, Math.ceil(e * l + t * a))
681
+ };
682
+ }
683
+ /**
684
+ * Permanently rotate the working texture. This keeps crop math simple:
685
+ * after rotation, sprite coordinates and texture dimensions both describe
686
+ * the visible image exactly.
687
+ *
688
+ * @param {number} degrees Clockwise degrees to add to the working texture.
689
+ * @returns {boolean}
690
+ */
691
+ rotateBy(e) {
692
+ var m, b, C;
693
+ const t = this._normalizeDegrees(e);
694
+ if (t === 0) return !0;
695
+ if (!((m = this.app) != null && m.renderer) || !this.originalTexture || !this.sprite)
696
+ return !1;
697
+ const i = window.PIXI, s = Math.round(this.originalTexture.width), r = Math.round(this.originalTexture.height);
698
+ if (s <= 0 || r <= 0) return !1;
699
+ const { width: o, height: n } = this.getRotatedBounds(s, r, t), a = t * Math.PI / 180, l = new i.Container(), h = new i.Sprite(this.originalTexture);
700
+ (C = (b = h.anchor) == null ? void 0 : b.set) == null || C.call(b, 0.5), h.x = o / 2, h.y = n / 2, h.rotation = a, l.addChild(h);
701
+ const c = i.RenderTexture.create({ width: o, height: n, resolution: 1 });
702
+ try {
703
+ this.app.renderer.render({
704
+ container: l,
705
+ target: c,
706
+ clear: !0,
707
+ clearColor: [0, 0, 0, 0]
708
+ });
709
+ } catch {
710
+ return c.destroy(!0), !1;
711
+ } finally {
712
+ l.removeChildren(), h.destroy({ children: !1, texture: !1, textureSource: !1 }), l.destroy({ children: !1 });
713
+ }
714
+ const p = this.originalTexture, u = Array.isArray(this.sprite.filters) ? [...this.sprite.filters] : null;
715
+ p && p !== this.baseTexture && p.destroy(!0), this.originalTexture = c, this._lastExportDimensions = null, this.app.stage.removeChild(this.sprite), this.sprite.destroy({ children: !1, texture: !1, textureSource: !1 });
716
+ const f = new i.Sprite(c);
717
+ return u != null && u.length && (f.filters = u), this.app.stage.addChild(f), this.sprite = f, this.fitScale = this.getFitScaleFor(c), this.zoom = 1, this.applyViewTransform({ keepCenter: !1 }), this.render(), this.emit("textureRotated", { width: o, height: n, degrees: t }), !0;
718
+ }
719
+ /**
720
+ * Export the current image with filters applied.
721
+ *
722
+ * @param {string} format - 'png' or 'jpeg'
723
+ * @param {number} quality - Quality for jpeg (0-1)
724
+ * @param {number} maxEdge - Maximum edge size in pixels (0 for original)
725
+ * @param {boolean} dontUpscale - Don't upscale smaller images
726
+ * @param {number} [maxPixels=0] - Max total pixel count (width*height). 0 disables.
727
+ * Backend API often limits payload size (blowglass caps at 8M pixels); set this
728
+ * to keep the export under that cap while preserving aspect ratio.
729
+ * @returns {string|null} Data URL
730
+ */
731
+ exportImage(e = "png", t = 0.92, i = 0, s = !0, r = 0) {
732
+ var F;
733
+ if (!((F = this.app) != null && F.renderer) || !this.originalTexture || !this.sprite)
734
+ return null;
735
+ const o = window.PIXI, n = Math.round(this.originalTexture.width), a = Math.round(this.originalTexture.height);
736
+ if (n <= 0 || a <= 0)
737
+ return null;
738
+ const l = this.sprite.width, h = this.sprite.height;
739
+ if (l <= 0 || h <= 0)
740
+ return null;
741
+ const c = l / n, p = c > 0 ? 1 / c : 1, u = this._layerDocument, f = u && Array.isArray(u.layers), m = [];
742
+ if (!f && Array.isArray(this.sprite.filters)) {
743
+ for (const y of this.sprite.filters)
744
+ if (y)
745
+ if (typeof y.createExportFilter == "function") {
746
+ const w = y.createExportFilter({ previewToNativeScale: p });
747
+ w && m.push(w);
748
+ } else
749
+ m.push(y);
750
+ }
751
+ let b = 0;
752
+ for (const y of m) {
753
+ const w = typeof y.getExportPadding == "function" ? Number(y.getExportPadding()) || 0 : typeof y._exportPadding == "number" ? y._exportPadding : typeof y.padding == "number" ? y.padding : 0;
754
+ w > 0 && (b += w);
755
+ }
756
+ const C = Math.max(Math.ceil(b), 64), x = (y) => {
757
+ var Q, X, L, ee;
758
+ const w = Math.ceil(n + 2 * y), S = Math.ceil(a + 2 * y), k = new o.Container(), N = [], P = (_ = null) => {
759
+ const v = new o.Sprite(this.originalTexture);
760
+ v.x = y, v.y = y, v.scale.set(1, 1), v.alpha = _ ? this._layerOpacity(_) : 1, _ && this._applyLayerFilterInstances(v, _, {
761
+ forExport: !0,
762
+ previewToNativeScale: p,
763
+ sourceWidth: n,
764
+ sourceHeight: a
765
+ }), m.length > 0 && (v.filters = m, v.filterArea = new o.Rectangle(0, 0, n, a)), k.addChild(v), N.push(v);
766
+ }, M = (_) => {
767
+ var R, K, Y;
768
+ const v = (R = this._layerDisplayObjects.get(_.id)) == null ? void 0 : R.displayObject, I = (v == null ? void 0 : v.texture) || (this._isSubjectLayer(_) ? this.originalTexture : null);
769
+ if (!I || typeof o.Sprite != "function") return !1;
770
+ const D = new o.Sprite(I);
771
+ return D.__sourceWidth = Number(((K = _.source) == null ? void 0 : K.originalWidth) || (v == null ? void 0 : v.__sourceWidth) || I.width || n), D.__sourceHeight = Number(((Y = _.source) == null ? void 0 : Y.originalHeight) || (v == null ? void 0 : v.__sourceHeight) || I.height || a), this._fitDisplayObjectToRect(D, _, { x: y, y, width: n, height: a }), D.alpha = this._layerOpacity(_), !f && this._isSubjectLayer(_) && m.length > 0 && (D.filters = m, D.filterArea = new o.Rectangle(0, 0, n, a)), this._applyLayerEffects(o, D, _), this._applyLayerFilterInstances(D, _, {
772
+ forExport: !0,
773
+ previewToNativeScale: p,
774
+ sourceWidth: n,
775
+ sourceHeight: a
776
+ }), k.addChild(D), N.push(D), !0;
777
+ }, T = (_) => {
778
+ var K, Y, pe;
779
+ const v = this._backgroundFillForLayer(_), I = this._backgroundColorForLayer(_), D = { x: y, y, width: n, height: a };
780
+ let R = null;
781
+ if (I && typeof o.Graphics == "function")
782
+ R = new o.Graphics(), R.rect(y, y, n, a).fill(I);
783
+ else if ((v == null ? void 0 : v.kind) === "gradient" && typeof o.Sprite == "function") {
784
+ const H = this._createGradientTexture(o, v, n, a);
785
+ if (!H) return !1;
786
+ R = new o.Sprite(H), R.__ownsTexture = !0, R.__sourceWidth = n, R.__sourceHeight = a, this._fitDisplayObjectToRect(R, _, D);
787
+ } else if ((v == null ? void 0 : v.kind) === "media") {
788
+ const H = (K = this._layerDisplayObjects.get(_.id)) == null ? void 0 : K.displayObject, te = H == null ? void 0 : H.texture;
789
+ if (!te) return !1;
790
+ R = this._createBackgroundSprite(o, te, v.fit), R.__sourceWidth = Number(((Y = v.source) == null ? void 0 : Y.originalWidth) || H.__sourceWidth || te.width || 1), R.__sourceHeight = Number(((pe = v.source) == null ? void 0 : pe.originalHeight) || H.__sourceHeight || te.height || 1), this._fitDisplayObjectToRect(R, _, D);
791
+ }
792
+ return R ? (R.alpha = this._layerOpacity(_), this._applyLayerEffects(o, R, _), this._applyLayerFilterInstances(R, _, {
793
+ forExport: !0,
794
+ previewToNativeScale: p,
795
+ sourceWidth: n,
796
+ sourceHeight: a
797
+ }), k.addChild(R), N.push(R), !0) : !1;
798
+ }, z = (_) => {
799
+ const v = this._createTextDisplayObjectForLayer(o, _);
800
+ return v ? (this._fitTextDisplayObjectToRect(v, _, { x: y, y, width: n, height: a }), v.alpha = this._layerOpacity(_), k.addChild(v), N.push(v), !0) : !1;
801
+ }, V = u, q = f;
802
+ if (q) {
803
+ for (const _ of V.layers)
804
+ if (!(!_ || _.visible === !1)) {
805
+ if (this._isTextLayer(_)) {
806
+ z(_);
807
+ continue;
808
+ }
809
+ if (_.type === "image") {
810
+ !M(_) && this._isSubjectLayer(_) && P(_);
811
+ continue;
812
+ }
813
+ if (this._isSubjectLayer(_)) {
814
+ M(_) || P(_);
815
+ continue;
816
+ }
817
+ T(_);
818
+ }
819
+ } else
820
+ P();
821
+ !q && ((Q = k.children) == null ? void 0 : Q.length) === 0 && P();
822
+ const G = o.RenderTexture.create({
823
+ width: w,
824
+ height: S,
825
+ resolution: 1
826
+ }), ne = ((X = k.children) == null ? void 0 : X.length) ?? 0;
827
+ try {
828
+ this.app.renderer.render({
829
+ container: k,
830
+ target: G,
831
+ clear: !0,
832
+ clearColor: [0, 0, 0, 0]
833
+ });
834
+ const _ = (ee = (L = this.app.renderer.extract) == null ? void 0 : L.canvas) == null ? void 0 : ee.call(L, G);
835
+ return _ ? { canvas: _, width: w, height: S, margin: y, childCount: ne } : null;
836
+ } finally {
837
+ for (const _ of N)
838
+ _.filters = null;
839
+ k.removeChildren();
840
+ for (const _ of N) {
841
+ const v = (_ == null ? void 0 : _.__ownsTexture) === !0;
842
+ _.destroy({ children: !1, texture: v, textureSource: v });
843
+ }
844
+ k.destroy({ children: !1 }), G.destroy(!0);
845
+ }
846
+ }, B = (y, w, S) => {
847
+ const k = typeof y.getContext == "function" ? y.getContext("2d") : null;
848
+ if (!k) return null;
849
+ let N;
850
+ try {
851
+ N = k.getImageData(0, 0, w, S).data;
852
+ } catch {
853
+ return null;
854
+ }
855
+ const P = 1;
856
+ let M = w, T = S, z = -1, V = -1;
857
+ const q = 4, G = w * q;
858
+ for (let L = 0; L < S; L++) {
859
+ const ee = L * G;
860
+ for (let _ = 0; _ < w; _++)
861
+ N[ee + _ * q + 3] >= P && (_ < M && (M = _), _ > z && (z = _), L < T && (T = L), L > V && (V = L));
862
+ }
863
+ if (z < 0) return null;
864
+ const ne = z - M + 1, Q = V - T + 1, X = M === 0 || T === 0 || z === w - 1 || V === S - 1;
865
+ return { x: M, y: T, width: ne, height: Q, touchesEdge: X };
866
+ };
867
+ let A = null;
868
+ try {
869
+ let y = x(C);
870
+ if (!y)
871
+ return null;
872
+ let w = B(y.canvas, y.width, y.height);
873
+ if (!w)
874
+ if (f && y.childCount === 0)
875
+ w = {
876
+ x: y.margin,
877
+ y: y.margin,
878
+ width: n,
879
+ height: a,
880
+ touchesEdge: !1
881
+ };
882
+ else
883
+ return null;
884
+ if (w.touchesEdge && C < n && C < a) {
885
+ const M = Math.max(C * 4, 256), T = x(M);
886
+ if (T) {
887
+ const z = B(T.canvas, T.width, T.height);
888
+ z && !z.touchesEdge ? (y = T, w = z) : (y = T, w = z ?? w);
889
+ }
890
+ }
891
+ let S = w.width, k = w.height;
892
+ if (i > 0) {
893
+ const M = Math.max(S, k);
894
+ let T = i / M;
895
+ s && (T = Math.min(1, T)), S = Math.max(1, Math.round(S * T)), k = Math.max(1, Math.round(k * T));
896
+ }
897
+ if (r > 0 && S * k > r) {
898
+ const M = Math.sqrt(r / (S * k));
899
+ S = Math.max(1, Math.floor(S * M)), k = Math.max(1, Math.floor(k * M));
900
+ }
901
+ const N = document.createElement("canvas");
902
+ N.width = S, N.height = k;
903
+ const P = N.getContext("2d");
904
+ return P ? (P.drawImage(
905
+ y.canvas,
906
+ w.x,
907
+ w.y,
908
+ w.width,
909
+ w.height,
910
+ 0,
911
+ 0,
912
+ S,
913
+ k
914
+ ), A = N.toDataURL(`image/${e}`, t), this._lastExportDimensions = { width: S, height: k }, A || null) : null;
915
+ } catch {
916
+ return null;
917
+ }
918
+ }
919
+ /**
920
+ * Get the actual export dimensions at the current filter state.
921
+ *
922
+ * When an export has been performed, returns the exact trimmed dimensions
923
+ * from that export. Otherwise falls back to source texture size plus a
924
+ * rough filter-padding estimate — the real dimensions are only known for
925
+ * certain after exportImage() runs the bbox scan.
926
+ *
927
+ * @returns {{ width: number, height: number }}
928
+ */
929
+ getExportDimensions() {
930
+ var o;
931
+ if (this._lastExportDimensions)
932
+ return { ...this._lastExportDimensions };
933
+ const e = this.originalTexture, t = Math.round((e == null ? void 0 : e.width) || 0), i = Math.round((e == null ? void 0 : e.height) || 0);
934
+ let s = 0;
935
+ const r = (o = this.sprite) == null ? void 0 : o.filters;
936
+ if (Array.isArray(r))
937
+ for (const n of r) {
938
+ if (!n) continue;
939
+ const a = typeof n._exportPadding == "number" ? n._exportPadding : typeof n.padding == "number" ? n.padding : 0;
940
+ a > s && (s = a);
941
+ }
942
+ return {
943
+ width: t + 2 * s,
944
+ height: i + 2 * s
945
+ };
946
+ }
947
+ /**
948
+ * Export the current image as a Blob with correct dimensions.
949
+ * @param {string} format - 'png' or 'jpeg'
950
+ * @param {number} quality - Quality for jpeg (0-1)
951
+ * @param {{ maxPixels?: number, maxEdge?: number, dontUpscale?: boolean }} [options]
952
+ * @returns {Promise<{ blob: Blob, width: number, height: number } | null>}
953
+ */
954
+ async exportBlob(e = "png", t = 0.92, i = {}) {
955
+ const s = this.exportImage(
956
+ e,
957
+ t,
958
+ i.maxEdge ?? 0,
959
+ i.dontUpscale !== !1,
960
+ i.maxPixels ?? 0
961
+ );
962
+ if (!s) return null;
963
+ const o = await (await fetch(s)).blob(), n = this.getExportDimensions();
964
+ return { blob: o, width: n.width, height: n.height };
965
+ }
966
+ /**
967
+ * Resize renderer to container
968
+ * @param {HTMLElement} container
969
+ */
970
+ resizeTo(e) {
971
+ var s;
972
+ if (!((s = this.app) != null && s.renderer) || !e) return;
973
+ const t = e.clientWidth, i = e.clientHeight;
974
+ if (!(t === Math.round(this.app.screen.width) && i === Math.round(this.app.screen.height)) && !(t <= 0 || i <= 0) && (this.app.renderer.resize(t, i), this.originalTexture && this.sprite)) {
975
+ const r = {
976
+ x: this.sprite.x + this.sprite.width / 2,
977
+ y: this.sprite.y + this.sprite.height / 2
978
+ };
979
+ this.fitScale = this.getFitScaleFor(this.originalTexture), this.applyViewTransform({ center: r });
980
+ }
981
+ }
982
+ /**
983
+ * Set background color
984
+ * @param {number} color - Hex color
985
+ */
986
+ setBackgroundColor(e) {
987
+ var t, i;
988
+ (i = (t = this.app) == null ? void 0 : t.renderer) != null && i.background && (this.app.renderer.background.color = e);
989
+ }
990
+ /**
991
+ * Load an image URL into an HTMLImageElement.
992
+ * Handles blob:, data:, and http(s): URLs.
993
+ * @param {string} url
994
+ * @returns {Promise<HTMLImageElement|null>}
995
+ */
996
+ _loadImageElement(e) {
997
+ return new Promise((t) => {
998
+ const i = new Image();
999
+ typeof e == "string" && /^https?:\/\//.test(e) && (i.crossOrigin = "anonymous"), i.onload = () => t(i), i.onerror = () => {
1000
+ t(null);
1001
+ }, i.src = e;
1002
+ });
1003
+ }
1004
+ /**
1005
+ * Clean up and destroy
1006
+ */
1007
+ destroy() {
1008
+ if (this._destroyed = !0, this._mountToken += 1, this._mountPromise = null, this._destroyLayerDisplayObjects(), this.app) {
1009
+ try {
1010
+ this.app.destroy(!0, { children: !0, texture: !0 });
1011
+ } catch {
1012
+ }
1013
+ this.app = null;
1014
+ }
1015
+ this.sprite = null, this.originalTexture = null, this.baseTexture = null, this._container = null, this.removeAllListeners();
1016
+ }
1017
+ }
1018
+ const _e = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="48" d="M328 112L184 256l144 144"/></svg>', me = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="48" d="M184 112l144 144-144 144"/></svg>', gt = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M221.09 64a157.09 157.09 0 10157.09 157.09A157.1 157.1 0 00221.09 64z"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M338.29 338.29L448 448M256 184v74m-37-37h74"/></svg>', _t = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M221.09 64a157.09 157.09 0 10157.09 157.09A157.1 157.1 0 00221.09 64z"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M338.29 338.29L448 448M184 221h74"/></svg>', mt = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M432 320v112H320M80 192V80h112M320 80h112v112M192 432H80V320"/></svg>', Ae = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M262.29 192.31a64 64 0 1057.4 57.4 64.13 64.13 0 00-57.4-57.4z"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M416.39 256a154.34 154.34 0 01-1.53 20.79l45.21 35.46a10.81 10.81 0 012.45 13.75l-42.77 74a10.81 10.81 0 01-13.14 4.59l-44.9-18.08a16.11 16.11 0 00-15.17 1.75A164.48 164.48 0 01325 400.8a15.94 15.94 0 00-8.82 12.14l-6.73 47.89a11.08 11.08 0 01-10.68 9.17h-85.54a11.11 11.11 0 01-10.69-8.87l-6.72-47.82a16.07 16.07 0 00-9-12.22 155.3 155.3 0 01-21.46-12.57 16 16 0 00-15.11-1.71l-44.89 18.07a10.81 10.81 0 01-13.14-4.58l-42.77-74a10.8 10.8 0 012.45-13.75l38.21-30a16.05 16.05 0 006-14.08c-.36-4.17-.58-8.33-.58-12.5s.21-8.27.58-12.35a16 16 0 00-6.07-13.94l-38.19-30A10.81 10.81 0 0149.48 186l42.77-74a10.81 10.81 0 0113.14-4.59l44.9 18.08a16.11 16.11 0 0015.17-1.75A164.48 164.48 0 01187 111.2a15.94 15.94 0 008.82-12.14l6.73-47.89A11.08 11.08 0 01213.23 42h85.54a11.11 11.11 0 0110.69 8.87l6.72 47.82a16.07 16.07 0 009 12.22 155.3 155.3 0 0121.46 12.57 16 16 0 0015.11 1.71l44.89-18.07a10.81 10.81 0 0113.14 4.58l42.77 74a10.8 10.8 0 01-2.45 13.75l-38.21 30a16.05 16.05 0 00-6.05 14.08c.33 4.14.55 8.3.55 12.47z"/></svg>', Ne = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M400 320c0 88.37-55.63 144-144 144s-144-55.63-144-144c0-94.83 103.23-222.85 134.89-259.88a12 12 0 0118.23 0C296.77 97.15 400 225.17 400 320z"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M344 328a72 72 0 01-72 72"/></svg>', Be = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32" d="M430.11 347.9c-6.6-6.1-16.3-7.6-24.6-9-11.5-1.9-15.9-4-22.6-10-14.3-12.7-14.3-31.1 0-43.8l30.3-26.9c46.4-41 46.4-108.2 0-149.2-34.2-30.1-80.1-45-127.8-45-55.7 0-113.9 20.3-158.8 60.1-83.5 73.8-83.5 194.7 0 268.5 41.5 36.7 97.5 55 152.9 55.4h1.7c55.4 0 110-17.9 148.8-52.4 14.4-12.7 11.99-36.6.1-47.7z"/><circle cx="144" cy="208" r="32"/><circle cx="152" cy="311" r="32"/><circle cx="224" cy="144" r="32"/><circle cx="256" cy="367" r="32"/><circle cx="328" cy="144" r="32"/></svg>', Ee = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M259.92 262.91L216.4 149.77a9 9 0 00-16.8 0l-43.52 113.14a9 9 0 01-5.17 5.17L37.77 311.6a9 9 0 000 16.8l113.14 43.52a9 9 0 015.17 5.17l43.52 113.14a9 9 0 0016.8 0l43.52-113.14a9 9 0 015.17-5.17l113.14-43.52a9 9 0 000-16.8l-113.14-43.52a9 9 0 01-5.17-5.17zM108 68L88 16 68 68 16 88l52 20 20 52 20-52 52-20-52-20zM426.67 117.33L400 48l-26.67 69.33L304 144l69.33 26.67L400 240l26.67-69.33L496 144l-69.33-26.67z"/></svg>', Le = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M176 112l80-80 80 80M255.98 32l.02 448M176 400l80 80 80-80M400 176l80 80-80 80M112 176l-80 80 80 80M32 256h448"/></svg>', De = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M452.37 59.63h0a40.49 40.49 0 00-57.26 0L184 294.74c23.08 4.7 46.12 27.29 49.26 49.26l219.11-227.11a40.49 40.49 0 000-57.26z"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M138 336c-29.88 0-54 24.5-54 54.86 0 23.95-20.67 36.57-34 44.78 15.08 8.08 32.23 12.36 50 12.36 48.49 0 88-38.89 88-88 0-30.36-24.12-54-50-54z"/></svg>', je = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M315.27 33L96 304h128l-31.51 173.23a2.36 2.36 0 002.33 2.77h0a2.36 2.36 0 001.89-.95L416 208H288l31.66-173.25a2.45 2.45 0 00-2.44-2.75h0a2.42 2.42 0 00-1.95 1z"/></svg>', be = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M144 48v272a48 48 0 0048 48h272"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M368 464V192a48 48 0 00-48-48H48"/></svg>', Oe = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M380.93 57.37A32 32 0 00358.3 48H94.22A46.21 46.21 0 0048 94.22v323.56A46.21 46.21 0 0094.22 464h323.56A46.36 46.36 0 00464 417.78V153.7a32 32 0 00-9.37-22.63zM256 416a64 64 0 1164-64 63.92 63.92 0 01-64 64zm48-224H112a16 16 0 01-16-16v-64a16 16 0 0116-16h192a16 16 0 0116 16v64a16 16 0 01-16 16z"/></svg>', de = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M368 368L144 144M368 144L144 368"/></svg>', ye = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M320 146s24.36-12-64-12a160 160 0 10160 160"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M256 58l80 80-80 80"/></svg>', ze = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M176 112l-64 64 64 64"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M112 176h176a112 112 0 110 224H160"/></svg>', Pe = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M336 112l64 64-64 64"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M400 176H224a112 112 0 100 224h128"/></svg>', He = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M112 112l20 320c.95 18.49 14.4 32 32 32h184c17.67 0 30.87-13.51 32-32l20-320"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32" d="M80 112h352"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M192 112V72h0a23.93 23.93 0 0124-24h80a23.93 23.93 0 0124 24h0v40M256 176v224M184 176l8 224M328 176l-8 224"/></svg>', Ie = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M64 192v-72a40 40 0 0140-40h75.89a40 40 0 0122.19 6.72l27.84 18.56a40 40 0 0022.19 6.72H408a40 40 0 0140 40v40"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M479.9 226.55L463.68 392a40 40 0 01-39.93 40H88.25a40 40 0 01-39.93-40L32.1 226.55A32 32 0 0164 192h384.1a32 32 0 0131.8 34.55z"/></svg>', he = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M416 128L192 384l-96-96"/></svg>', Ue = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32" d="M48 256l208 112 208-112-208-112L48 256z"/><path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32" d="M48 336l208 112 208-112M48 176L256 64l208 112"/></svg>', bt = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32" d="M48 256s80-144 208-144 208 144 208 144-80 144-208 144S48 256 48 256z"/><circle cx="256" cy="256" r="64" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/></svg>', yt = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M432 448L80 64M160 160c-67.2 36.8-112 96-112 96s80 144 208 144c35.8 0 68.4-11.3 96-28.1M224.4 113.8A199.6 199.6 0 01256 112c128 0 208 144 208 144a334.2 334.2 0 01-65.1 78.6"/><path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32" d="M213.3 213.3A64 64 0 10298.7 298.7"/></svg>', vt = '<svg viewBox="0 0 512 512" width="20" height="20"><rect x="96" y="208" width="320" height="240" rx="32" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M160 208v-64a96 96 0 01192 0v64"/></svg>', wt = '<svg viewBox="0 0 512 512" width="20" height="20"><rect x="96" y="208" width="320" height="240" rx="32" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M160 208v-64a96 96 0 01168-63.7"/></svg>', xt = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32" d="M96 352l-32 96 96-32L421.5 154.5a45.3 45.3 0 000-64h0a45.3 45.3 0 00-64 0L96 352z"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M320 128l64 64"/></svg>', Ct = '<svg viewBox="0 0 512 512" width="20" height="20"><rect x="144" y="144" width="288" height="288" rx="32" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/><path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32" d="M80 368V112a32 32 0 0132-32h256"/></svg>', Ve = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M96 112V80h320v32M256 80v352M184 432h144"/></svg>', We = '<svg viewBox="0 0 512 512" width="20" height="20"><rect x="64" y="64" width="384" height="384" rx="48" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/></svg>', $e = '<svg viewBox="0 0 512 512" width="20" height="20"><circle cx="256" cy="256" r="208" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>', Ze = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M80 96h64l64 320h64l64-160h96"/></svg>', fe = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32" d="M256 48v48M256 416v48M403.08 108.92l-33.94 33.94M142.86 369.14l-33.94 33.94M464 256h-48M96 256H48M403.08 403.08l-33.94-33.94M142.86 142.86l-33.94-33.94"/><circle cx="256" cy="256" r="80" fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32"/></svg>', ge = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M160 136c0-30.62 4.51-61.61 16-88C99.57 81.27 48 159.32 48 248c0 119.29 96.71 216 216 216 88.68 0 166.73-51.57 200-128-26.39 11.49-57.38 16-88 16-119.29 0-216-96.71-216-216z"/></svg>', qe = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M436 80H76a44.05 44.05 0 00-44 44v264a44.05 44.05 0 0044 44h360a44.05 44.05 0 0044-44V124a44.05 44.05 0 00-44-44z"/><circle cx="256" cy="256" r="80" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M220 80v56M292 80v56M220 376v56M292 376v56M80 144h56M80 224h56M80 304h56M376 144h56M376 224h56M376 304h56"/></svg>', Ge = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M344 144c-3.92 52.87-44 96-88 96s-84.15-43.12-88-96c-4-55 35-96 88-96s92 42 88 96z"/><path fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32" d="M256 304c-87 0-175.3 48-191.64 138.6C62.39 453.52 68.57 464 80 464h352c11.44 0 17.62-10.48 15.65-21.4C431.3 352 343 304 256 304z"/></svg>', ue = [
1019
+ {
1020
+ id: "adjust",
1021
+ name: "Adjust",
1022
+ icon: Ae,
1023
+ registryCategories: ["adjust", "advanced"]
1024
+ // adjustment, adjustmentAdvanced, alpha, colorMatrix
1025
+ },
1026
+ {
1027
+ id: "blur",
1028
+ name: "Blur",
1029
+ icon: Ne,
1030
+ registryCategories: ["blur"]
1031
+ },
1032
+ {
1033
+ id: "color",
1034
+ name: "Color",
1035
+ icon: Be,
1036
+ registryCategories: ["color"]
1037
+ },
1038
+ {
1039
+ id: "effects",
1040
+ name: "Effects",
1041
+ icon: Ee,
1042
+ registryCategories: ["effects"]
1043
+ },
1044
+ {
1045
+ id: "distortion",
1046
+ name: "Distortion",
1047
+ icon: Le,
1048
+ registryCategories: ["distortion"]
1049
+ },
1050
+ {
1051
+ id: "light",
1052
+ name: "Light",
1053
+ icon: je,
1054
+ registryCategories: ["light"]
1055
+ },
1056
+ {
1057
+ id: "stylize",
1058
+ name: "Stylize",
1059
+ icon: qe,
1060
+ registryCategories: ["stylize"]
1061
+ },
1062
+ {
1063
+ id: "text",
1064
+ name: "Text",
1065
+ icon: Ve
1066
+ },
1067
+ {
1068
+ id: "background",
1069
+ name: "Background",
1070
+ icon: De
1071
+ },
1072
+ {
1073
+ id: "layers",
1074
+ name: "Layers",
1075
+ icon: Ue
1076
+ },
1077
+ {
1078
+ id: "crop",
1079
+ name: "Crop",
1080
+ icon: be
1081
+ }
1082
+ ], Ke = Object.fromEntries(
1083
+ ue.filter((g) => Array.isArray(g.registryCategories) && g.registryCategories.length > 0).map((g) => [g.id, g.registryCategories])
1084
+ ), Ye = Object.fromEntries(
1085
+ ue.filter((g) => Array.isArray(g.registryCategories)).flatMap((g) => g.registryCategories.map((e) => [e, g.id]))
1086
+ );
1087
+ function Je(g) {
1088
+ return Ke[g] || [g];
1089
+ }
1090
+ class Qe extends re {
1091
+ constructor(e, t) {
1092
+ super(), this.state = e, this.renderer = t, this.instances = {}, this._filterRegistry = null;
1093
+ }
1094
+ /**
1095
+ * Set the filter registry (from mediables/filters)
1096
+ * @param {Object} registry - { getFilter, getAllFilters, getFiltersByCategory }
1097
+ */
1098
+ setRegistry(e) {
1099
+ var t, i;
1100
+ this._filterRegistry = e, (i = (t = this.renderer) == null ? void 0 : t.setLayerFilterFactory) == null || i.call(t, (s, r) => this.createFiltersForLayer(s, r));
1101
+ }
1102
+ /**
1103
+ * Get filter definition by ID
1104
+ * @param {string} filterId
1105
+ * @returns {Object|null}
1106
+ */
1107
+ getFilterDef(e) {
1108
+ return this._filterRegistry ? this._filterRegistry.getFilter(e) : null;
1109
+ }
1110
+ /**
1111
+ * Get all filters
1112
+ * @returns {Array}
1113
+ */
1114
+ getAllFilters() {
1115
+ var e;
1116
+ return ((e = this._filterRegistry) == null ? void 0 : e.getAllFilters()) || [];
1117
+ }
1118
+ /**
1119
+ * Get filters by category
1120
+ * Maps UI category names to registry category names
1121
+ * @param {string} category - UI category ID
1122
+ * @returns {Array}
1123
+ */
1124
+ getFiltersByCategory(e) {
1125
+ if (!this._filterRegistry) return [];
1126
+ const t = Je(e), i = [], s = /* @__PURE__ */ new Set();
1127
+ for (const r of t) {
1128
+ const o = this._filterRegistry.getFiltersByCategory(r) || [];
1129
+ for (const n of o)
1130
+ s.has(n.id) || (s.add(n.id), i.push(n));
1131
+ }
1132
+ return i;
1133
+ }
1134
+ /**
1135
+ * Normalize UI values from various input types
1136
+ * @param {*} value
1137
+ * @returns {*}
1138
+ */
1139
+ _normalizeValue(e) {
1140
+ return Array.isArray(e) ? e.length > 0 && typeof e[0] == "object" ? e : Number(e[0] ?? 0) : typeof e == "string" ? e.startsWith("#") ? e : e.trim() === "" ? 0 : Number(e) : e;
1141
+ }
1142
+ /**
1143
+ * Get a deep property from an object.
1144
+ * Returns undefined if any segment along the path is missing.
1145
+ * @param {Object} target
1146
+ * @param {string} path
1147
+ * @returns {*}
1148
+ */
1149
+ _getDeepProp(e, t) {
1150
+ if (!e || !t) return;
1151
+ if (!t.includes(".") && !t.includes("["))
1152
+ return e[t];
1153
+ const i = t.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
1154
+ let s = e;
1155
+ for (const r of i) {
1156
+ if (s == null) return;
1157
+ s = s[r];
1158
+ }
1159
+ return s;
1160
+ }
1161
+ /**
1162
+ * Set a deep property on an object
1163
+ * @param {Object} target
1164
+ * @param {string} path
1165
+ * @param {*} value
1166
+ */
1167
+ _setDeepProp(e, t, i) {
1168
+ if (!e || !t) return;
1169
+ if (!t.includes(".") && !t.includes("[")) {
1170
+ e[t] = i;
1171
+ return;
1172
+ }
1173
+ const s = t.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
1174
+ let r = e;
1175
+ for (let n = 0; n < s.length - 1; n++) {
1176
+ const a = s[n];
1177
+ if (!(a in r) || (r = r[a], r == null)) return;
1178
+ }
1179
+ const o = s[s.length - 1];
1180
+ r[o] = i;
1181
+ }
1182
+ /**
1183
+ * Initialize filter values from definition defaults.
1184
+ * Seeds BOTH defaultParams (e.g. colorStops) and control defaults so that
1185
+ * filters with dynamic parameters not backed by a static control still have
1186
+ * their state populated.
1187
+ * @param {string} filterId
1188
+ */
1189
+ initializeValues(e, t = void 0) {
1190
+ const i = this.getFilterDef(e);
1191
+ if (!i) return;
1192
+ const s = this.state.getFilterValues(e), r = new Set((i.controls || []).map((o) => o.id));
1193
+ if (i.defaultParams)
1194
+ for (const [o, n] of Object.entries(i.defaultParams))
1195
+ !r.has(o) && !(o in s) && this.state.setFilterValue(e, o, n, t);
1196
+ i.controls && i.controls.forEach((o) => {
1197
+ o.id in s || this.state.setFilterValue(e, o.id, o.default, t);
1198
+ });
1199
+ }
1200
+ /**
1201
+ * Reset all filter values to their defaults (unconditionally).
1202
+ * Resets BOTH defaultParams and control defaults.
1203
+ * @param {string} filterId
1204
+ */
1205
+ resetValues(e, t = void 0) {
1206
+ const i = this.getFilterDef(e);
1207
+ if (!i) return;
1208
+ const s = new Set((i.controls || []).map((r) => r.id));
1209
+ if (i.defaultParams)
1210
+ for (const [r, o] of Object.entries(i.defaultParams))
1211
+ s.has(r) || this.state.setFilterValue(e, r, o, t);
1212
+ i.controls && i.controls.forEach((r) => {
1213
+ this.state.setFilterValue(e, r.id, r.default, t);
1214
+ });
1215
+ }
1216
+ /**
1217
+ * Toggle a filter on/off
1218
+ * @param {string} filterId
1219
+ * @param {boolean} enabled
1220
+ */
1221
+ toggle(e, t, i = void 0) {
1222
+ return this.state.toggleFilter(e, t, i) === !1 ? !1 : (t ? this.initializeValues(e, i) : delete this.instances[e], this.applyFilters(), this.emit("filterToggled", { filterId: e, enabled: t }), !0);
1223
+ }
1224
+ /**
1225
+ * Update a filter control value.
1226
+ *
1227
+ * The `controlId` may be either a static control id (from def.controls) or a
1228
+ * dynamic path (e.g. 'colorStops[0].color') produced by a filter's
1229
+ * getDynamicControls(). Static control ids are stored in state keyed by the
1230
+ * control id. Dynamic paths are NOT stored as flat path keys — instead, we
1231
+ * apply them to the live instance and then sync the filter's serializable
1232
+ * params back into state, so recreation via applyFilters() rebuilds from
1233
+ * the canonical shape (e.g. a colorStops array, not 'colorStops[0].color').
1234
+ *
1235
+ * Returns true when the filter was updated in-place. Returns false when the
1236
+ * caller should invoke applyFilters() to recreate the filter from state —
1237
+ * this happens when the property target is a method (which would be
1238
+ * silently corrupted by a direct write) or when the property doesn't exist.
1239
+ *
1240
+ * @param {string} filterId
1241
+ * @param {string} controlId - Static control id or dynamic property path
1242
+ * @param {*} value
1243
+ * @returns {boolean} True if updated in-place, false if caller must recreate
1244
+ */
1245
+ updateValue(e, t, i, s = void 0) {
1246
+ const r = this._normalizeValue(i);
1247
+ if (!this._matchesExpectedFilterLayer(s)) return !1;
1248
+ const o = this.getFilterDef(e), n = o == null ? void 0 : o.controls.find((p) => p.id === t), a = !n && (t.includes("[") || t.includes("."));
1249
+ if (!a && this.state.setFilterValue(e, t, r, s) === !1)
1250
+ return !1;
1251
+ const l = this.instances[e];
1252
+ if (!l) return !1;
1253
+ const h = (n == null ? void 0 : n.property) || t;
1254
+ if (typeof l.updateUIParam == "function") {
1255
+ if (l.updateUIParam(h, r), a && typeof l.getSerializableParams == "function") {
1256
+ const p = l.getSerializableParams();
1257
+ for (const [u, f] of Object.entries(p))
1258
+ this.state.setFilterValue(e, u, f, s);
1259
+ }
1260
+ return !0;
1261
+ }
1262
+ const c = this._getDeepProp(l, h);
1263
+ return typeof c == "function" ? !1 : l.uniforms && h in l.uniforms ? (l.uniforms[h] = r, !0) : c !== void 0 ? (this._setDeepProp(l, h, r), !0) : !1;
1264
+ }
1265
+ /**
1266
+ * Perform a filter action (e.g. addColorStop, removeColorStop) and sync
1267
+ * any resulting state changes back into State.js before recreating the
1268
+ * filter. Without this sync, recreation via applyFilters() would rebuild
1269
+ * from stale state and undo the action.
1270
+ *
1271
+ * @param {string} filterId
1272
+ * @param {string} action - Action name understood by filter.updateUIParam
1273
+ */
1274
+ performFilterAction(e, t, i = void 0) {
1275
+ if (!this._matchesExpectedFilterLayer(i)) return !1;
1276
+ const s = this.instances[e];
1277
+ if (!s || typeof s.updateUIParam != "function") return !1;
1278
+ if (s.updateUIParam(t, !0), typeof s.getSerializableParams == "function") {
1279
+ const r = s.getSerializableParams();
1280
+ for (const [o, n] of Object.entries(r))
1281
+ this.state.setFilterValue(e, o, n, i);
1282
+ }
1283
+ return this.applyFilters(), this.emit("filterActionPerformed", { filterId: e, action: t }), !0;
1284
+ }
1285
+ _matchesExpectedFilterLayer(e) {
1286
+ if (e === void 0 || !this._layerDocument()) return !0;
1287
+ if (e === null)
1288
+ return typeof this.state.getActiveFilterLayerId == "function" ? this.state.getActiveFilterLayerId() === null : this.state.get("activeLayerId") === null;
1289
+ const i = typeof e == "string" ? e.trim() : "";
1290
+ return i === "" ? !1 : typeof this.state.getActiveFilterLayerId == "function" ? this.state.getActiveFilterLayerId() === i : this.state.get("activeLayerId") === i;
1291
+ }
1292
+ _layerDocument() {
1293
+ var t, i;
1294
+ const e = (i = (t = this.state).getSerializableDocument) == null ? void 0 : i.call(t);
1295
+ return e && e.version === 2 && Array.isArray(e.layers) ? e : null;
1296
+ }
1297
+ _sourceDimensionsForLayer(e, t = {}) {
1298
+ var s, r, o, n, a, l, h, c, p;
1299
+ const i = (e == null ? void 0 : e.source) || ((s = e == null ? void 0 : e.fill) == null ? void 0 : s.source) || null;
1300
+ return {
1301
+ width: Math.round(Number(t.sourceWidth || (i == null ? void 0 : i.originalWidth) || ((o = (r = this.renderer) == null ? void 0 : r.originalTexture) == null ? void 0 : o.width) || ((a = (n = this.renderer) == null ? void 0 : n.sprite) == null ? void 0 : a.width) || 0)),
1302
+ height: Math.round(Number(t.sourceHeight || (i == null ? void 0 : i.originalHeight) || ((h = (l = this.renderer) == null ? void 0 : l.originalTexture) == null ? void 0 : h.height) || ((p = (c = this.renderer) == null ? void 0 : c.sprite) == null ? void 0 : p.height) || 0))
1303
+ };
1304
+ }
1305
+ _paramsForFilterState(e, t, i) {
1306
+ const s = t != null && t.values && typeof t.values == "object" ? t.values : {}, r = e.defaultParams ? { ...e.defaultParams } : {};
1307
+ e.controls && Array.isArray(e.controls) && e.controls.forEach((n) => {
1308
+ const a = n.property || n.id;
1309
+ r[a] = s[n.id] ?? n.default;
1310
+ });
1311
+ const o = new Set((e.controls || []).map((n) => n.id));
1312
+ for (const [n, a] of Object.entries(s))
1313
+ o.has(n) || (r[n] = a);
1314
+ return r._sourceWidth = i.width, r._sourceHeight = i.height, r;
1315
+ }
1316
+ createFiltersForLayer(e, t = {}) {
1317
+ if (!Array.isArray(e == null ? void 0 : e.filters) || e.filters.length === 0) return [];
1318
+ const i = [], s = this.state.get("activeLayerId"), r = e.id === s && t.forExport !== !0, o = this._sourceDimensionsForLayer(e, t);
1319
+ for (const n of e.filters)
1320
+ if (!(!n || n.enabled === !1 || typeof n.id != "string"))
1321
+ try {
1322
+ const a = this.getFilterDef(n.id);
1323
+ if (!(a != null && a.createFilter) || typeof a.createFilter != "function") continue;
1324
+ const l = a.createFilter(this._paramsForFilterState(a, n, o));
1325
+ if (!l) continue;
1326
+ let h = l;
1327
+ t.forExport === !0 && typeof l.createExportFilter == "function" && (h = l.createExportFilter({
1328
+ previewToNativeScale: t.previewToNativeScale || 1
1329
+ }) || l), i.push(h), r && (this.instances[n.id] = l);
1330
+ } catch {
1331
+ }
1332
+ return i;
1333
+ }
1334
+ /**
1335
+ * Apply all active filters to the sprite
1336
+ *
1337
+ * This method recreates all filter instances on each call to ensure
1338
+ * PIXI v8's filter pipeline correctly processes changes.
1339
+ */
1340
+ applyFilters() {
1341
+ var o, n, a, l, h, c, p;
1342
+ const e = this._layerDocument();
1343
+ if (e) {
1344
+ for (const f in this.instances)
1345
+ delete this.instances[f];
1346
+ (n = (o = this.renderer) == null ? void 0 : o.setLayerFilterFactory) == null || n.call(o, (f, m) => this.createFiltersForLayer(f, m));
1347
+ const u = (l = (a = this.renderer) == null ? void 0 : a.applyLayerFilters) == null ? void 0 : l.call(a, e);
1348
+ this.emit("filtersApplied", {
1349
+ count: ((h = this.state.get("activeFilters")) == null ? void 0 : h.size) ?? 0,
1350
+ failed: [],
1351
+ scopedToLayer: !0
1352
+ }), u || (p = (c = this.renderer) == null ? void 0 : c.render) == null || p.call(c);
1353
+ return;
1354
+ }
1355
+ const t = this.renderer.sprite;
1356
+ if (!t) return;
1357
+ for (const u in this.instances)
1358
+ delete this.instances[u];
1359
+ const i = [], s = [];
1360
+ this.state.get("activeFilters").forEach((u) => {
1361
+ try {
1362
+ const f = this.getFilterDef(u);
1363
+ if (!f || !f.createFilter || typeof f.createFilter != "function")
1364
+ return;
1365
+ const m = this.state.getFilterValues(u), b = f.defaultParams ? { ...f.defaultParams } : {};
1366
+ f.controls && Array.isArray(f.controls) && f.controls.forEach((F) => {
1367
+ const J = F.property || F.id;
1368
+ b[J] = m[F.id] ?? F.default;
1369
+ });
1370
+ const C = new Set((f.controls || []).map((F) => F.id));
1371
+ for (const [F, J] of Object.entries(m))
1372
+ C.has(F) || (b[F] = J);
1373
+ const x = this.renderer.sprite, B = this.renderer.originalTexture;
1374
+ b._sourceWidth = Math.round((B == null ? void 0 : B.width) || (x == null ? void 0 : x.width) || 0), b._sourceHeight = Math.round((B == null ? void 0 : B.height) || (x == null ? void 0 : x.height) || 0);
1375
+ const A = f.createFilter(b);
1376
+ A && (i.push(A), this.instances[u] = A);
1377
+ } catch {
1378
+ s.push(u);
1379
+ }
1380
+ });
1381
+ try {
1382
+ t.filters = null, t.filters = i.length ? i : null, this.renderer.render(), this.emit("filtersApplied", { count: i.length, failed: s });
1383
+ } catch (u) {
1384
+ try {
1385
+ t.filters = null, this.renderer.render();
1386
+ } catch {
1387
+ }
1388
+ this.emit("filtersError", { error: u, failedFilters: s });
1389
+ }
1390
+ s.length > 0;
1391
+ }
1392
+ /**
1393
+ * Reset all filters.
1394
+ * IMPORTANT: Only clears filter instances and sprite.filters.
1395
+ * Does NOT touch baseTexture/originalTexture/sprite — that would
1396
+ * destroy any applied crop, which is a separate concern.
1397
+ */
1398
+ resetAll(e = void 0) {
1399
+ for (const s in this.instances)
1400
+ delete this.instances[s];
1401
+ return this.state.resetFilters(e) === !1 ? !1 : this._layerDocument() ? (this.applyFilters(), this.emit("filtersReset"), !0) : (this.renderer.sprite && (this.renderer.sprite.filters = null), this.renderer.render(), this.emit("filtersReset"), !0);
1402
+ }
1403
+ /**
1404
+ * Get a filter instance
1405
+ * @param {string} filterId
1406
+ * @returns {Object|null}
1407
+ */
1408
+ getInstance(e) {
1409
+ return this.instances[e] || null;
1410
+ }
1411
+ }
1412
+ const U = class U extends re {
1413
+ constructor(e, t) {
1414
+ super(), this.state = e, this.renderer = t, this._overlayCanvas = null, this._isDragging = !1, this._dragStart = null, this._dragMode = null, this._startRect = null, this._hoverMode = null, this._lastAutoZoomCheck = 0, this.HANDLE_SIZE = 14, this.EDGE_HIT_PAD = 10, this._onPointerDown = this._handlePointerDown.bind(this), this._onPointerMove = this._handlePointerMove.bind(this), this._onPointerUp = this._handlePointerUp.bind(this);
1415
+ }
1416
+ /**
1417
+ * Set the overlay canvas element
1418
+ * @param {HTMLCanvasElement} canvas
1419
+ */
1420
+ setOverlayCanvas(e) {
1421
+ this._overlayCanvas = e;
1422
+ }
1423
+ /**
1424
+ * Get aspect ratio value from string
1425
+ * @param {string} aspect
1426
+ * @returns {number|null}
1427
+ */
1428
+ _getAspectRatio(e) {
1429
+ if (!e || e === "free") return null;
1430
+ const t = {
1431
+ "1:1": 1,
1432
+ "4:3": 4 / 3,
1433
+ "16:9": 16 / 9,
1434
+ "3:2": 3 / 2,
1435
+ "2:3": 2 / 3
1436
+ };
1437
+ if (t[e] !== void 0) return t[e];
1438
+ const i = e.split(":");
1439
+ if (i.length === 2) {
1440
+ const s = parseFloat(i[0]), r = parseFloat(i[1]);
1441
+ if (Number.isFinite(s) && Number.isFinite(r) && r > 0)
1442
+ return s / r;
1443
+ }
1444
+ return null;
1445
+ }
1446
+ /**
1447
+ * Return the numeric aspect ratio that the crop rect must obey, accounting
1448
+ * for both the shape (circle/square force 1:1) and the explicit aspect
1449
+ * setting. Returns null when the crop is free-form.
1450
+ */
1451
+ _getEffectiveTargetAspect() {
1452
+ const e = this.state.get("crop.shape");
1453
+ if (e === "circle" || e === "square") return 1;
1454
+ const t = this.state.get("crop.aspect");
1455
+ return !t || t === "free" ? null : this._getAspectRatio(t);
1456
+ }
1457
+ /**
1458
+ * Apply aspect ratio constraint to crop rect. Honours shape (circle/square
1459
+ * force 1:1) so the post-drag correction agrees with the effective ratio
1460
+ * the edge-handle drag code targets.
1461
+ */
1462
+ applyAspectRatio() {
1463
+ const e = this.state.get("crop.rect");
1464
+ if (!e) return;
1465
+ const t = this._getEffectiveTargetAspect();
1466
+ if (!t) return;
1467
+ const i = e.width / e.height;
1468
+ Math.abs(i - t) < 0.01 || (i > t ? e.width = e.height * t : e.height = e.width / t, this.state.set("crop.rect", e));
1469
+ }
1470
+ /**
1471
+ * Constrain crop rect to bounds
1472
+ */
1473
+ constrainCropRect() {
1474
+ const e = this.state.get("crop.rect");
1475
+ if (!e) return;
1476
+ const t = this.renderer.sprite, i = this.renderer.app;
1477
+ if (!t || !i) return;
1478
+ const s = this.state.get("crop.shape"), r = this._getEffectiveTargetAspect(), o = r !== null ? { x: t.x, y: t.y, w: t.width, h: t.height } : { x: 0, y: 0, w: i.screen.width, h: i.screen.height };
1479
+ if (r !== null) {
1480
+ const n = o.h * r, a = Math.min(e.width, o.w, n);
1481
+ e.width = a, e.height = a / r;
1482
+ } else
1483
+ e.width = Math.min(e.width, o.w), e.height = Math.min(e.height, o.h);
1484
+ if (e.x = Math.max(o.x, Math.min(e.x, o.x + o.w - e.width)), e.y = Math.max(o.y, Math.min(e.y, o.y + o.h - e.height)), s !== "free" || this.state.get("crop.aspect") === "1:1") {
1485
+ const n = Math.min(e.width, e.height);
1486
+ e.width = n, e.height = n;
1487
+ }
1488
+ this.state.set("crop.rect", e);
1489
+ }
1490
+ /**
1491
+ * Calculate the target zoom level for auto-zoom when crop exceeds image bounds.
1492
+ * Pure function — returns the new zoom level, or null if no adjustment is needed.
1493
+ *
1494
+ * @param {{ width: number, height: number }} cropRect - Current crop dimensions
1495
+ * @param {number} spriteWidth - Current sprite width on screen
1496
+ * @param {number} spriteHeight - Current sprite height on screen
1497
+ * @param {number} texWidth - Original texture width (pixels)
1498
+ * @param {number} texHeight - Original texture height (pixels)
1499
+ * @param {number} fitScale - Current fit-to-screen scale
1500
+ * @param {number} currentZoom - Current zoom level
1501
+ * @returns {number|null} Target zoom level, or null if no zoom needed
1502
+ */
1503
+ static calcAutoZoom(e, t, i, s, r, o, n) {
1504
+ if (!e || !s || !r || !o) return null;
1505
+ const a = e.width > t, l = e.height > i;
1506
+ if (!a && !l) return null;
1507
+ const h = U.AUTO_ZOOM_PADDING;
1508
+ let c = n;
1509
+ if (a) {
1510
+ const p = e.width / (h * s * o);
1511
+ c = Math.min(c, p);
1512
+ }
1513
+ if (l) {
1514
+ const p = e.height / (h * r * o);
1515
+ c = Math.min(c, p);
1516
+ }
1517
+ return c = Math.max(0.1, c), c >= n - 0.01 ? null : c;
1518
+ }
1519
+ /**
1520
+ * Check if auto-zoom is needed and apply it (throttled).
1521
+ * Called during crop resize drags when autoZoomOnCropOverflow is enabled.
1522
+ */
1523
+ _checkAutoZoom() {
1524
+ if (!this.state.get("autoZoomOnCropOverflow")) return;
1525
+ const e = Date.now();
1526
+ if (e - this._lastAutoZoomCheck < U.AUTO_ZOOM_THROTTLE_MS) return;
1527
+ this._lastAutoZoomCheck = e;
1528
+ const t = this.state.get("crop.rect"), i = this.renderer.sprite, s = this.renderer.originalTexture;
1529
+ if (!t || !i || !s) return;
1530
+ const r = U.calcAutoZoom(
1531
+ t,
1532
+ i.width,
1533
+ i.height,
1534
+ s.width,
1535
+ s.height,
1536
+ this.renderer.fitScale,
1537
+ this.renderer.zoom
1538
+ );
1539
+ r !== null && this.renderer.setZoom(r, { keepCenter: !0 });
1540
+ }
1541
+ /**
1542
+ * Draw crop overlay on canvas
1543
+ */
1544
+ drawOverlay() {
1545
+ const e = this._overlayCanvas;
1546
+ if (!e) return;
1547
+ const t = this.renderer.app;
1548
+ if (!t) return;
1549
+ const i = t.canvas, s = i.clientWidth, r = i.clientHeight, o = window.devicePixelRatio || 1;
1550
+ (e.width !== Math.max(1, Math.floor(s * o)) || e.height !== Math.max(1, Math.floor(r * o))) && (e.width = Math.max(1, Math.floor(s * o)), e.height = Math.max(1, Math.floor(r * o)), e.style.width = s + "px", e.style.height = r + "px");
1551
+ const n = e.getContext("2d");
1552
+ if (!n) return;
1553
+ n.setTransform(o, 0, 0, o, 0, 0), n.clearRect(0, 0, s, r), n.fillStyle = "rgba(0, 0, 0, 0.5)", n.fillRect(0, 0, s, r);
1554
+ const a = this.state.get("crop.rect");
1555
+ if (!a) return;
1556
+ const l = this.state.get("crop.shape");
1557
+ if (n.save(), l === "circle") {
1558
+ const b = a.x + a.width / 2, C = a.y + a.height / 2, x = Math.min(a.width, a.height) / 2;
1559
+ n.beginPath(), n.arc(b, C, x, 0, Math.PI * 2), n.clip();
1560
+ } else
1561
+ n.beginPath(), n.rect(a.x, a.y, a.width, a.height), n.clip();
1562
+ n.clearRect(a.x, a.y, a.width, a.height), n.restore(), n.strokeStyle = "#ffffff", n.lineWidth = 2, n.setLineDash([5, 5]), l === "circle" ? (n.beginPath(), n.arc(
1563
+ a.x + a.width / 2,
1564
+ a.y + a.height / 2,
1565
+ Math.min(a.width, a.height) / 2,
1566
+ 0,
1567
+ Math.PI * 2
1568
+ ), n.stroke()) : n.strokeRect(a.x, a.y, a.width, a.height), n.setLineDash([]), n.strokeStyle = "rgba(255,255,255,0.3)", n.lineWidth = 1;
1569
+ const h = a.width / 3, c = a.height / 3;
1570
+ for (let b = 1; b <= 2; b++)
1571
+ n.beginPath(), n.moveTo(a.x + h * b, a.y), n.lineTo(a.x + h * b, a.y + a.height), n.stroke(), n.beginPath(), n.moveTo(a.x, a.y + c * b), n.lineTo(a.x + a.width, a.y + c * b), n.stroke();
1572
+ const p = this.HANDLE_SIZE, u = [
1573
+ { x: a.x, y: a.y, m: "resize-nw" },
1574
+ { x: a.x + a.width, y: a.y, m: "resize-ne" },
1575
+ { x: a.x, y: a.y + a.height, m: "resize-sw" },
1576
+ { x: a.x + a.width, y: a.y + a.height, m: "resize-se" }
1577
+ ], f = [
1578
+ { x: a.x + a.width / 2, y: a.y, m: "n" },
1579
+ { x: a.x + a.width / 2, y: a.y + a.height, m: "s" },
1580
+ { x: a.x, y: a.y + a.height / 2, m: "w" },
1581
+ { x: a.x + a.width, y: a.y + a.height / 2, m: "e" }
1582
+ ], m = [...u, ...f];
1583
+ for (const b of m) {
1584
+ const C = this._hoverMode === b.m, x = C ? p + 4 : p;
1585
+ n.beginPath(), n.rect(b.x - x / 2, b.y - x / 2, x, x), n.fillStyle = C ? "#4da3ff" : "#ffffff", n.strokeStyle = "rgba(0,0,0,0.6)", n.lineWidth = 1, n.fill(), n.stroke();
1586
+ }
1587
+ }
1588
+ /**
1589
+ * Hit test for handles
1590
+ * @param {number} px
1591
+ * @param {number} py
1592
+ * @returns {string|null}
1593
+ */
1594
+ _hitHandle(e, t) {
1595
+ const i = this.state.get("crop.rect");
1596
+ if (!i) return null;
1597
+ const s = (r, o, n, a, l) => Math.abs(r - n) <= l && Math.abs(o - a) <= l;
1598
+ return s(e, t, i.x, i.y, this.HANDLE_SIZE) ? "resize-nw" : s(e, t, i.x + i.width, i.y, this.HANDLE_SIZE) ? "resize-ne" : s(e, t, i.x, i.y + i.height, this.HANDLE_SIZE) ? "resize-sw" : s(e, t, i.x + i.width, i.y + i.height, this.HANDLE_SIZE) ? "resize-se" : Math.abs(t - i.y) <= this.EDGE_HIT_PAD && e >= i.x && e <= i.x + i.width ? "n" : Math.abs(t - (i.y + i.height)) <= this.EDGE_HIT_PAD && e >= i.x && e <= i.x + i.width ? "s" : Math.abs(e - i.x) <= this.EDGE_HIT_PAD && t >= i.y && t <= i.y + i.height ? "w" : Math.abs(e - (i.x + i.width)) <= this.EDGE_HIT_PAD && t >= i.y && t <= i.y + i.height ? "e" : e >= i.x && e <= i.x + i.width && t >= i.y && t <= i.y + i.height ? "move" : null;
1599
+ }
1600
+ /**
1601
+ * Handle pointer down event
1602
+ */
1603
+ _handlePointerDown(e) {
1604
+ const t = this.state.get("crop.rect");
1605
+ if (!t) return;
1606
+ const i = e.global;
1607
+ this._dragMode = this._hitHandle(i.x, i.y), this._dragMode && (this._isDragging = !0, this._dragStart = { x: i.x, y: i.y }, this._startRect = { ...t }, this._lastAutoZoomCheck = 0);
1608
+ }
1609
+ /**
1610
+ * Handle pointer move event
1611
+ */
1612
+ _handlePointerMove(e) {
1613
+ var l, h, c, p;
1614
+ const t = this.renderer.app;
1615
+ if (!t) return;
1616
+ const i = e.global;
1617
+ if (!this._isDragging || !this._dragStart || !this._startRect) {
1618
+ this._hoverMode = this._hitHandle(i.x, i.y), t.stage.cursor = this._hoverMode === "move" ? "move" : this._hoverMode === "n" || this._hoverMode === "s" ? "ns-resize" : this._hoverMode === "e" || this._hoverMode === "w" ? "ew-resize" : (l = this._hoverMode) != null && l.endsWith("nw") || (h = this._hoverMode) != null && h.endsWith("se") ? "nwse-resize" : (c = this._hoverMode) != null && c.endsWith("ne") || (p = this._hoverMode) != null && p.endsWith("sw") ? "nesw-resize" : "crosshair", this.drawOverlay();
1619
+ return;
1620
+ }
1621
+ const s = this.state.get("crop.rect");
1622
+ if (!s) return;
1623
+ const r = i.x - this._dragStart.x, o = i.y - this._dragStart.y, n = this._getEffectiveTargetAspect();
1624
+ switch (this._dragMode) {
1625
+ case "move":
1626
+ s.x = this._startRect.x + r, s.y = this._startRect.y + o;
1627
+ break;
1628
+ case "n": {
1629
+ const u = this._startRect.height - o;
1630
+ if (s.y = this._startRect.y + o, s.height = u, n) {
1631
+ const f = u * n;
1632
+ s.x = this._startRect.x + (this._startRect.width - f) / 2, s.width = f;
1633
+ }
1634
+ break;
1635
+ }
1636
+ case "s": {
1637
+ const u = this._startRect.height + o;
1638
+ if (s.height = u, n) {
1639
+ const f = u * n;
1640
+ s.x = this._startRect.x + (this._startRect.width - f) / 2, s.width = f;
1641
+ }
1642
+ break;
1643
+ }
1644
+ case "w": {
1645
+ const u = this._startRect.width - r;
1646
+ if (s.x = this._startRect.x + r, s.width = u, n) {
1647
+ const f = u / n;
1648
+ s.y = this._startRect.y + (this._startRect.height - f) / 2, s.height = f;
1649
+ }
1650
+ break;
1651
+ }
1652
+ case "e": {
1653
+ const u = this._startRect.width + r;
1654
+ if (s.width = u, n) {
1655
+ const f = u / n;
1656
+ s.y = this._startRect.y + (this._startRect.height - f) / 2, s.height = f;
1657
+ }
1658
+ break;
1659
+ }
1660
+ case "resize-nw":
1661
+ s.x = this._startRect.x + r, s.y = this._startRect.y + o, s.width = this._startRect.width - r, s.height = this._startRect.height - o;
1662
+ break;
1663
+ case "resize-ne":
1664
+ s.y = this._startRect.y + o, s.width = this._startRect.width + r, s.height = this._startRect.height - o;
1665
+ break;
1666
+ case "resize-sw":
1667
+ s.x = this._startRect.x + r, s.width = this._startRect.width - r, s.height = this._startRect.height + o;
1668
+ break;
1669
+ case "resize-se":
1670
+ s.width = this._startRect.width + r, s.height = this._startRect.height + o;
1671
+ break;
1672
+ }
1673
+ s.width = Math.max(50, s.width), s.height = Math.max(50, s.height), this.state.set("crop.rect", s), this.state.set("crop.dirty", !0), this.state.get("crop.shape") === "circle" && this.state.get("crop.aspect") !== "1:1" && this.state.set("crop.aspect", "1:1"), this.applyAspectRatio(), this._dragMode !== "move" && this._checkAutoZoom(), this.constrainCropRect(), this.drawOverlay();
1674
+ }
1675
+ /**
1676
+ * Handle pointer up event
1677
+ */
1678
+ _handlePointerUp() {
1679
+ this._isDragging = !1, this._dragMode = null, this._dragStart = null, this._startRect = null;
1680
+ }
1681
+ /**
1682
+ * Enable crop mode
1683
+ */
1684
+ enable() {
1685
+ const e = this.renderer.app, t = this.renderer.sprite;
1686
+ if (!e || !t) return;
1687
+ let i = this.state.get("crop.rect");
1688
+ if (!i) {
1689
+ this.state.get("crop.shape") === "circle" && this.state.set("crop.aspect", "1:1");
1690
+ const o = this.state.get("crop.aspect"), n = this._getAspectRatio(o);
1691
+ let a, l;
1692
+ if (n) {
1693
+ const p = t.width * 0.9, u = t.height * 0.9;
1694
+ p / u > n ? (l = u, a = l * n) : (a = p, l = a / n);
1695
+ } else {
1696
+ const p = Math.min(e.screen.width, e.screen.height) * 0.7;
1697
+ a = p, l = p;
1698
+ }
1699
+ const h = t.x + (t.width - a) / 2, c = t.y + (t.height - l) / 2;
1700
+ i = { x: h, y: c, width: a, height: l }, this.state.set("crop.rect", i), o !== "free" && this.constrainCropRect();
1701
+ }
1702
+ this.state.set("crop.dirty", !1);
1703
+ const s = e.stage;
1704
+ s.eventMode = "static", s.hitArea = e.screen, s.cursor = "crosshair", s.on("pointerdown", this._onPointerDown), s.on("pointermove", this._onPointerMove), s.on("pointerup", this._onPointerUp), s.on("pointerupoutside", this._onPointerUp), this.state.set("mode", "crop"), this.drawOverlay(), this.emit("enabled");
1705
+ }
1706
+ /**
1707
+ * Disable crop mode
1708
+ */
1709
+ disable() {
1710
+ const e = this.renderer.app;
1711
+ if (!e) return;
1712
+ const t = e.stage;
1713
+ if (t.off("pointerdown", this._onPointerDown), t.off("pointermove", this._onPointerMove), t.off("pointerup", this._onPointerUp), t.off("pointerupoutside", this._onPointerUp), t.eventMode = "auto", t.cursor = "default", this.state.set("crop.rect", null), this.state.set("crop.dirty", !1), this._overlayCanvas) {
1714
+ const i = this._overlayCanvas.getContext("2d");
1715
+ i == null || i.clearRect(0, 0, this._overlayCanvas.width, this._overlayCanvas.height);
1716
+ }
1717
+ this.state.set("mode", "filters"), this.emit("disabled");
1718
+ }
1719
+ /**
1720
+ * Apply the crop
1721
+ * @returns {{ texture: PIXI.Texture, preservedZoom: number }|null}
1722
+ */
1723
+ apply() {
1724
+ const e = this.renderer.app, t = this.renderer.sprite, i = this.renderer.originalTexture, s = this.state.get("crop.rect");
1725
+ if (!s || !t || !e || !i) return null;
1726
+ const r = window.PIXI, o = this.renderer.zoom, n = i.width / t.width, a = i.height / t.height, l = (s.x - t.x) * n, h = (s.y - t.y) * a;
1727
+ let c = Math.round(Math.max(1, s.width * n)), p = Math.round(Math.max(1, s.height * a)), u = Math.round(l), f = Math.round(h);
1728
+ if (c <= 0 || p <= 0) return null;
1729
+ const m = new r.Container(), b = new r.Sprite(i);
1730
+ if (this.state.get("crop.shape") === "circle") {
1731
+ const k = Math.round(Math.max(c, p)), N = u + c / 2, P = f + p / 2;
1732
+ u = Math.round(N - k / 2), f = Math.round(P - k / 2), c = p = k;
1733
+ const M = new r.Graphics();
1734
+ typeof M.circle == "function" && typeof M.fill == "function" ? M.circle(c / 2, p / 2, c / 2).fill(16777215) : (M.beginFill(16777215, 1), M.drawCircle(c / 2, p / 2, c / 2), M.endFill()), b.mask = M, m.addChild(M);
1735
+ }
1736
+ b.x = -u, b.y = -f, m.addChild(b);
1737
+ const x = r.RenderTexture.create({ width: c, height: p });
1738
+ e.renderer.render({
1739
+ container: m,
1740
+ target: x,
1741
+ clear: !0
1742
+ }), m.destroy({ children: !0 });
1743
+ const B = this.renderer.originalTexture;
1744
+ B && B !== this.renderer.baseTexture && B.destroy(!0), this.renderer.originalTexture = x, e.stage.removeChild(t), t.destroy();
1745
+ const A = new r.Sprite(x);
1746
+ e.stage.addChild(A), this.renderer.sprite = A, this.renderer.fitScale = this.renderer.getFitScaleFor(x), this.renderer.setZoom(o, { keepCenter: !1 }), this.renderer.applyViewTransform(), this.renderer.render();
1747
+ const F = this.state.get("crop.appliedRect"), y = F && Number.isFinite(F.x) && Number.isFinite(F.y) && Number.isFinite(F.width) && Number.isFinite(F.height) ? {
1748
+ x: Math.round(F.x + u),
1749
+ y: Math.round(F.y + f),
1750
+ width: c,
1751
+ height: p
1752
+ } : { x: u, y: f, width: c, height: p }, w = this.state.get("crop.shape") || "free", S = this.state.get("crop.aspect") || "free";
1753
+ return this.disable(), this.state.set("crop.appliedRect", y), this.state.set("crop.appliedShape", w), this.state.set("crop.appliedAspect", S), this.state.set("crop.dirty", !1), this.emit("applied", { width: c, height: p }), { texture: x, preservedZoom: o };
1754
+ }
1755
+ /**
1756
+ * Apply a crop from saved texture-pixel coordinates (for state rehydration).
1757
+ * Unlike apply(), this does not read from crop.rect or require the interactive overlay.
1758
+ * @param {{ x: number, y: number, width: number, height: number }} pixelRect
1759
+ * @param {'free'|'circle'} shape
1760
+ * @returns {{ texture: Object, preservedZoom: number }|null}
1761
+ */
1762
+ applyFromPixelRect(e, t = "free") {
1763
+ const i = this.renderer.app, s = this.renderer.sprite, r = this.renderer.originalTexture;
1764
+ if (!e || !s || !i || !r) return null;
1765
+ const o = window.PIXI, n = this.renderer.zoom;
1766
+ let a = Math.round(Math.max(1, e.width)), l = Math.round(Math.max(1, e.height)), h = Math.round(e.x), c = Math.round(e.y);
1767
+ if (a <= 0 || l <= 0) return null;
1768
+ const p = new o.Container(), u = new o.Sprite(r);
1769
+ if (t === "circle") {
1770
+ const C = Math.round(Math.max(a, l)), x = h + a / 2, B = c + l / 2;
1771
+ h = Math.round(x - C / 2), c = Math.round(B - C / 2), a = l = C;
1772
+ const A = new o.Graphics();
1773
+ typeof A.circle == "function" && typeof A.fill == "function" ? A.circle(a / 2, l / 2, a / 2).fill(16777215) : (A.beginFill(16777215, 1), A.drawCircle(a / 2, l / 2, a / 2), A.endFill()), u.mask = A, p.addChild(A);
1774
+ }
1775
+ u.x = -h, u.y = -c, p.addChild(u);
1776
+ const f = o.RenderTexture.create({ width: a, height: l });
1777
+ i.renderer.render({ container: p, target: f, clear: !0 }), p.destroy({ children: !0 });
1778
+ const m = this.renderer.originalTexture;
1779
+ m && m !== this.renderer.baseTexture && m.destroy(!0), this.renderer.originalTexture = f, i.stage.removeChild(s), s.destroy();
1780
+ const b = new o.Sprite(f);
1781
+ return i.stage.addChild(b), this.renderer.sprite = b, this.renderer.fitScale = this.renderer.getFitScaleFor(f), this.renderer.setZoom(n, { keepCenter: !1 }), this.renderer.applyViewTransform(), this.renderer.render(), { texture: f, preservedZoom: n };
1782
+ }
1783
+ /**
1784
+ * Cancel crop
1785
+ */
1786
+ cancel() {
1787
+ this.disable(), this.emit("cancelled");
1788
+ }
1789
+ /**
1790
+ * Set crop shape
1791
+ * @param {'free'|'square'|'circle'} shape
1792
+ */
1793
+ setShape(e) {
1794
+ this.state.get("lockCropShape") || (this.state.set("crop.shape", e), this.state.set("crop.dirty", !0), (e === "circle" || e === "square") && this.state.set("crop.aspect", "1:1"), this.applyAspectRatio(), this.constrainCropRect(), this.drawOverlay());
1795
+ }
1796
+ /**
1797
+ * Set aspect ratio
1798
+ * @param {'free'|'1:1'|'4:3'|'16:9'|'3:2'|'2:3'|string} aspect
1799
+ */
1800
+ setAspect(e) {
1801
+ if (this.state.get("lockAspectRatio")) return;
1802
+ if (this.state.get("crop.shape") !== "free") {
1803
+ if (this.state.get("lockCropShape")) return;
1804
+ this.state.set("crop.shape", "free");
1805
+ }
1806
+ this.state.set("crop.aspect", e), this.state.set("crop.dirty", !0), this.applyAspectRatio(), this.constrainCropRect(), this.drawOverlay();
1807
+ }
1808
+ };
1809
+ /**
1810
+ * Padding factor for auto-zoom: image will be ~91% of crop size (1/1.1).
1811
+ * Higher values = more aggressive zoom-out, more padding around image.
1812
+ */
1813
+ oe(U, "AUTO_ZOOM_PADDING", 1.1), /**
1814
+ * Minimum interval (ms) between auto-zoom adjustments during drag.
1815
+ */
1816
+ oe(U, "AUTO_ZOOM_THROTTLE_MS", 100);
1817
+ let ce = U;
1818
+ class Xe {
1819
+ /**
1820
+ * Create a new RemoveBgManager
1821
+ * @param {Object} options
1822
+ * @param {string} [options.endpoint] - API endpoint for uploaded preview background removal
1823
+ * @param {string} [options.optionsEndpoint] - API endpoint for background-removal capability/options
1824
+ * @param {string} [options.savedEndpoint] - API endpoint template for saved-media removal
1825
+ * @param {string} [options.fallbackEndpoint] - Fallback endpoint if primary fails
1826
+ */
1827
+ constructor(e = {}) {
1828
+ this._endpoint = e.endpoint || "/api/v1/media/background-removal/preview", this._optionsEndpoint = e.optionsEndpoint || "/api/v1/media/background-removal/options", this._savedEndpoint = e.savedEndpoint || "/api/v1/media/{media}/background-removal", this._fallbackEndpoint = e.fallbackEndpoint || null, this._activeControllers = /* @__PURE__ */ new Set();
1829
+ }
1830
+ _createAbortController() {
1831
+ if (typeof AbortController > "u")
1832
+ return null;
1833
+ const e = new AbortController();
1834
+ return this._activeControllers.add(e), e;
1835
+ }
1836
+ _releaseAbortController(e) {
1837
+ e && this._activeControllers.delete(e);
1838
+ }
1839
+ cancelActiveRequests(e = "cancelled") {
1840
+ const t = Array.from(this._activeControllers);
1841
+ this._activeControllers.clear();
1842
+ for (const i of t)
1843
+ i.signal.aborted || i.abort(e);
1844
+ return t.length;
1845
+ }
1846
+ /**
1847
+ * Build a stable preview request so callers can stale-guard the response.
1848
+ * @param {string|Blob} imageData
1849
+ * @param {Object} options
1850
+ * @returns {Promise<Object>}
1851
+ */
1852
+ async preparePreviewRequest(e, t = {}) {
1853
+ const i = typeof e == "string" ? await this._dataUrlToBlob(e) : e;
1854
+ return {
1855
+ ...t,
1856
+ blob: i,
1857
+ operationId: t.operationId || this._uuid(),
1858
+ sessionKey: t.sessionKey || this._uuid(),
1859
+ sourceHash: t.sourceHash || await this._sha256Hex(i),
1860
+ targetRef: t.targetRef || null,
1861
+ tier: t.tier || "balanced"
1862
+ };
1863
+ }
1864
+ /**
1865
+ * @param {string|Blob} imageData
1866
+ * @returns {Promise<string>}
1867
+ */
1868
+ async computeImageDataHash(e) {
1869
+ const t = typeof e == "string" ? await this._dataUrlToBlob(e) : e;
1870
+ return this._sha256Hex(t);
1871
+ }
1872
+ /**
1873
+ * Remove background from an image
1874
+ * @param {string} imageData - Data URL or Blob of the image
1875
+ * @param {Object} options
1876
+ * @param {string} [options.tier='balanced'] - Quality tier: 'fast', 'balanced', 'best'
1877
+ * @param {string} [options.model] - Explicit model name (overrides tier)
1878
+ * @param {boolean} [options.alpha_matting=false] - Enable alpha matting for edge refinement
1879
+ * @param {number} [options.alpha_f=10] - Alpha matting foreground threshold
1880
+ * @param {number} [options.alpha_fr=15] - Alpha matting foreground radius
1881
+ * @param {number} [options.alpha_erode_size=10] - Alpha matting erode size
1882
+ * @returns {Promise<{dataUrl: string, model: string, processMs: string}>}
1883
+ */
1884
+ async removeBackground(e, t = {}) {
1885
+ const i = typeof Blob < "u" && t.blob instanceof Blob ? t : await this.preparePreviewRequest(e, t), s = i.blob, r = i.operationId, o = i.sessionKey, n = i.sourceHash, a = new FormData();
1886
+ a.append("image_file", s, "image.png"), a.append("operation_id", r), a.append("session_key", o), a.append("source_hash", n), a.append("tier", i.tier || "balanced"), i.targetRef && a.append("target_ref", i.targetRef), i.model && a.append("model", i.model), i.alpha_matting && (a.append("alpha_matting", "true"), a.append("alpha_f", String(i.alpha_f ?? 10)), a.append("alpha_fr", String(i.alpha_fr ?? 15)), a.append("alpha_erode_size", String(i.alpha_erode_size ?? 10))), i.max_megapixels !== void 0 && a.append("max_megapixels", String(i.max_megapixels));
1887
+ let l, h;
1888
+ const c = this._createAbortController(), p = (u) => c ? { ...u, signal: c.signal } : u;
1889
+ try {
1890
+ try {
1891
+ l = await fetch(this._endpoint, p({
1892
+ method: "POST",
1893
+ body: a,
1894
+ credentials: "include"
1895
+ // For Sanctum auth
1896
+ }));
1897
+ } catch (m) {
1898
+ if (c != null && c.signal.aborted)
1899
+ throw m;
1900
+ h = m;
1901
+ }
1902
+ if ((!l || !l.ok) && this._fallbackEndpoint)
1903
+ try {
1904
+ l = await fetch(this._fallbackEndpoint, p({
1905
+ method: "POST",
1906
+ body: a
1907
+ }));
1908
+ } catch (m) {
1909
+ if (c != null && c.signal.aborted)
1910
+ throw m;
1911
+ h || (h = m);
1912
+ }
1913
+ if (!l)
1914
+ throw h || new Error("Network error: Unable to connect to background removal service");
1915
+ if (!l.ok) {
1916
+ let m = `Background removal failed (HTTP ${l.status})`;
1917
+ try {
1918
+ const b = await l.text();
1919
+ b && (m += `: ${b}`);
1920
+ } catch {
1921
+ }
1922
+ throw new Error(m);
1923
+ }
1924
+ const u = await l.blob();
1925
+ return {
1926
+ dataUrl: await this._blobToDataUrl(u),
1927
+ model: l.headers.get("X-Model-Used") || "unknown",
1928
+ processMs: l.headers.get("X-Process-Ms") || "0",
1929
+ operationId: l.headers.get("X-Operation-Id") || r,
1930
+ sessionKey: l.headers.get("X-Session-Key") || o,
1931
+ sourceHash: l.headers.get("X-Source-Hash") || n,
1932
+ targetRef: l.headers.get("X-Target-Ref") || i.targetRef || null
1933
+ };
1934
+ } finally {
1935
+ this._releaseAbortController(c);
1936
+ }
1937
+ }
1938
+ /**
1939
+ * Remove the background from a persisted editor-document layer.
1940
+ * @param {Object} options
1941
+ * @param {string} options.targetMediaUuid
1942
+ * @param {string} options.operationId
1943
+ * @param {string} options.documentId
1944
+ * @param {string} options.documentRevisionId
1945
+ * @param {string} options.targetLayerId
1946
+ * @param {string} options.expectedSourceHash
1947
+ * @param {string} [options.tier]
1948
+ * @returns {Promise<Object>}
1949
+ */
1950
+ async removeSavedMediaBackground(e) {
1951
+ const t = this._savedEndpoint.replace("{media}", encodeURIComponent(e.targetMediaUuid)), i = {
1952
+ operation_id: e.operationId,
1953
+ document_id: e.documentId,
1954
+ document_revision_id: e.documentRevisionId,
1955
+ target_layer_id: e.targetLayerId,
1956
+ expected_source_hash: e.expectedSourceHash,
1957
+ tier: e.tier || "balanced"
1958
+ };
1959
+ e.model && (i.model = e.model), e.alpha_matting !== void 0 && (i.alpha_matting = !!e.alpha_matting), e.max_megapixels !== void 0 && (i.max_megapixels = Number(e.max_megapixels));
1960
+ const s = this._createAbortController();
1961
+ let r;
1962
+ try {
1963
+ if (r = await fetch(t, {
1964
+ method: "POST",
1965
+ body: JSON.stringify(i),
1966
+ credentials: "include",
1967
+ headers: {
1968
+ Accept: "application/json",
1969
+ "Content-Type": "application/json"
1970
+ },
1971
+ ...s ? { signal: s.signal } : {}
1972
+ }), !r.ok) {
1973
+ let o = `Background removal failed (HTTP ${r.status})`;
1974
+ try {
1975
+ const n = await r.json();
1976
+ n != null && n.message && (o += `: ${n.message}`);
1977
+ } catch {
1978
+ }
1979
+ throw new Error(o);
1980
+ }
1981
+ return await r.json();
1982
+ } finally {
1983
+ this._releaseAbortController(s);
1984
+ }
1985
+ }
1986
+ /**
1987
+ * Convert a data URL to a Blob
1988
+ * @param {string} dataUrl
1989
+ * @returns {Promise<Blob>}
1990
+ */
1991
+ async _dataUrlToBlob(e) {
1992
+ if (e.startsWith("data:")) {
1993
+ const [i, s] = e.split(","), r = i.match(/:(.*?);/), o = r ? r[1] : "image/png", n = atob(s), a = new Uint8Array(n.length);
1994
+ for (let l = 0; l < n.length; l++)
1995
+ a[l] = n.charCodeAt(l);
1996
+ return new Blob([a], { type: o });
1997
+ }
1998
+ return (await fetch(e)).blob();
1999
+ }
2000
+ /**
2001
+ * Convert a Blob to a data URL
2002
+ * @param {Blob} blob
2003
+ * @returns {Promise<string>}
2004
+ */
2005
+ _blobToDataUrl(e) {
2006
+ return new Promise((t, i) => {
2007
+ const s = new FileReader();
2008
+ s.onload = () => t(s.result), s.onerror = () => i(new Error("Failed to read blob as data URL")), s.readAsDataURL(e);
2009
+ });
2010
+ }
2011
+ /**
2012
+ * Check if the background removal service is available
2013
+ * @returns {Promise<boolean>}
2014
+ */
2015
+ async isAvailable() {
2016
+ var t;
2017
+ const e = await this.getOptions();
2018
+ return e.enabled === !0 && ((t = e.service) == null ? void 0 : t.available) === !0;
2019
+ }
2020
+ /**
2021
+ * @returns {Promise<Object>}
2022
+ */
2023
+ async getOptions() {
2024
+ try {
2025
+ const e = await fetch(this._optionsEndpoint, {
2026
+ method: "GET",
2027
+ credentials: "include"
2028
+ });
2029
+ if (!e.ok)
2030
+ return { enabled: !1, service: { available: !1 } };
2031
+ const t = await e.json();
2032
+ return t && typeof t == "object" ? t : { enabled: !1, service: { available: !1 } };
2033
+ } catch {
2034
+ if (this._fallbackEndpoint)
2035
+ try {
2036
+ const t = await fetch(this._fallbackEndpoint, {
2037
+ method: "OPTIONS"
2038
+ });
2039
+ return {
2040
+ enabled: t.ok,
2041
+ service: { available: t.ok }
2042
+ };
2043
+ } catch {
2044
+ }
2045
+ return { enabled: !1, service: { available: !1 } };
2046
+ }
2047
+ }
2048
+ _uuid() {
2049
+ var e;
2050
+ return (e = globalThis.crypto) != null && e.randomUUID ? globalThis.crypto.randomUUID() : "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (t) => {
2051
+ const i = Math.floor(Math.random() * 16);
2052
+ return (t === "x" ? i : i & 3 | 8).toString(16);
2053
+ });
2054
+ }
2055
+ async _sha256Hex(e) {
2056
+ const t = await e.arrayBuffer(), i = await globalThis.crypto.subtle.digest("SHA-256", t);
2057
+ return [...new Uint8Array(i)].map((s) => s.toString(16).padStart(2, "0")).join("");
2058
+ }
2059
+ }
2060
+ function d(g, e = {}, ...t) {
2061
+ const i = document.createElement(g);
2062
+ for (const [s, r] of Object.entries(e))
2063
+ if (r != null)
2064
+ if (s === "className")
2065
+ i.className = r;
2066
+ else if (s === "style" && typeof r == "object")
2067
+ Object.assign(i.style, r);
2068
+ else if (s.startsWith("on") && typeof r == "function") {
2069
+ const o = s.slice(2).toLowerCase();
2070
+ i.addEventListener(o, r);
2071
+ } else s === "dataset" && typeof r == "object" ? Object.assign(i.dataset, r) : i.setAttribute(s, r);
2072
+ for (const s of t)
2073
+ typeof s == "string" ? i.appendChild(document.createTextNode(s)) : s instanceof HTMLElement && i.appendChild(s);
2074
+ return i;
2075
+ }
2076
+ function ve({ id: g, label: e, min: t = 0, max: i = 1, step: s = 0.01, value: r = 0.5, onChange: o }) {
2077
+ const n = g.includes("-") ? g.split("-").slice(1).join("-") : g, a = d("div", {
2078
+ className: "slider-control slider-wrapper",
2079
+ "data-control": n,
2080
+ "data-testid": `slider-${n}`
2081
+ }), l = d(
2082
+ "div",
2083
+ { className: "slider-header" },
2084
+ d("label", { for: g, className: "slider-label" }, e),
2085
+ d("span", { className: "slider-value", id: `${g}-value` }, ae(r))
2086
+ ), h = d("input", {
2087
+ type: "range",
2088
+ id: g,
2089
+ className: "slider-input",
2090
+ min: String(t),
2091
+ max: String(i),
2092
+ step: String(s),
2093
+ value: String(r),
2094
+ onInput: (c) => {
2095
+ const p = parseFloat(c.target.value), u = a.querySelector(".slider-value");
2096
+ u && (u.textContent = ae(p)), o == null || o(p);
2097
+ }
2098
+ });
2099
+ return a.appendChild(l), a.appendChild(h), a.setValue = (c) => {
2100
+ h.value = String(c);
2101
+ const p = a.querySelector(".slider-value");
2102
+ p && (p.textContent = ae(c));
2103
+ }, a;
2104
+ }
2105
+ function ae(g) {
2106
+ return Number.isInteger(g) ? String(g) : g.toFixed(2);
2107
+ }
2108
+ function we({ id: g, label: e, checked: t = !1, onChange: i }) {
2109
+ const s = d("div", { className: "toggle-control" }), r = d("label", { className: "toggle-label", for: g }, e), o = d("input", {
2110
+ type: "checkbox",
2111
+ id: g,
2112
+ className: "toggle-input",
2113
+ checked: t ? "checked" : void 0,
2114
+ onChange: (l) => i == null ? void 0 : i(l.target.checked)
2115
+ }), n = d("div", {
2116
+ className: "toggle-switch",
2117
+ onClick: (l) => {
2118
+ l.target !== o && (o.checked = !o.checked, i == null || i(o.checked));
2119
+ }
2120
+ }), a = d("span", { className: "toggle-slider" });
2121
+ return n.appendChild(o), n.appendChild(a), s.appendChild(r), s.appendChild(n), s.setChecked = (l) => {
2122
+ o.checked = l;
2123
+ }, s;
2124
+ }
2125
+ function xe({ id: g, label: e, value: t = "#000000", onChange: i }) {
2126
+ const s = d("div", { className: "color-control" }), r = d("label", { className: "color-label", for: g }, e), o = d("input", {
2127
+ type: "color",
2128
+ id: g,
2129
+ className: "color-input",
2130
+ value: t,
2131
+ onInput: (n) => i == null ? void 0 : i(n.target.value)
2132
+ });
2133
+ return s.appendChild(r), s.appendChild(o), s.setValue = (n) => {
2134
+ o.value = n;
2135
+ }, s;
2136
+ }
2137
+ function Ce({ id: g, label: e, value: t = "", placeholder: i = "", onCommit: s }) {
2138
+ const r = d("div", {
2139
+ className: "text-control",
2140
+ "data-control": g,
2141
+ "data-testid": `text-${g}`
2142
+ }), o = d("label", { className: "text-label", for: g }, e);
2143
+ let n = t;
2144
+ const a = d("input", {
2145
+ type: "text",
2146
+ id: g,
2147
+ className: "text-input",
2148
+ value: t,
2149
+ placeholder: i,
2150
+ onKeyDown: (h) => {
2151
+ h.key === "Enter" && (h.preventDefault(), l(), a.blur());
2152
+ },
2153
+ onBlur: () => l()
2154
+ });
2155
+ function l() {
2156
+ const h = a.value;
2157
+ h !== n && (n = h, s == null || s(h));
2158
+ }
2159
+ return r.appendChild(o), r.appendChild(a), r.setValue = (h) => {
2160
+ const c = h == null ? "" : String(h);
2161
+ a.value = c, n = c;
2162
+ }, r;
2163
+ }
2164
+ function ke({ id: g, label: e, options: t = [], value: i, onChange: s }) {
2165
+ const r = d("div", { className: "select-control" }), o = d("label", { className: "select-label", for: g }, e), n = d("select", {
2166
+ id: g,
2167
+ className: "select-input",
2168
+ onChange: (a) => s == null ? void 0 : s(a.target.value)
2169
+ });
2170
+ for (const a of t) {
2171
+ const l = d("option", { value: a.value }, a.label);
2172
+ a.value === i && (l.selected = !0), n.appendChild(l);
2173
+ }
2174
+ return r.appendChild(o), r.appendChild(n), r.setValue = (a) => {
2175
+ n.value = a;
2176
+ }, r;
2177
+ }
2178
+ function j({ label: g, className: e = "", onClick: t, icon: i = null, disabled: s = !1 }) {
2179
+ const r = d("button", {
2180
+ type: "button",
2181
+ className: `btn ${e}`.trim(),
2182
+ onClick: t,
2183
+ disabled: s ? "disabled" : void 0
2184
+ });
2185
+ if (i) {
2186
+ const o = d("span", { className: "btn-icon" });
2187
+ o.innerHTML = i;
2188
+ const n = o.querySelector("svg");
2189
+ n && n.setAttribute("aria-hidden", "true"), r.appendChild(o);
2190
+ }
2191
+ return g && r.appendChild(document.createTextNode(g)), r;
2192
+ }
2193
+ function O({ icon: g, title: e, className: t = "", onClick: i, disabled: s = !1, testId: r = null, ariaLabel: o = null }) {
2194
+ const n = {
2195
+ type: "button",
2196
+ className: `icon-btn ${t}`.trim(),
2197
+ title: e,
2198
+ "aria-label": o || e,
2199
+ onClick: i,
2200
+ disabled: s ? "disabled" : void 0
2201
+ };
2202
+ r && (n.dataset = { testid: r });
2203
+ const a = d("button", n);
2204
+ a.innerHTML = g;
2205
+ const l = a.querySelector("svg");
2206
+ return l && l.setAttribute("aria-hidden", "true"), a;
2207
+ }
2208
+ function se({ label: g, icon: e, active: t = !1, onClick: i }) {
2209
+ const s = d("button", {
2210
+ type: "button",
2211
+ className: `chip ${t ? "active" : ""}`.trim(),
2212
+ onClick: i
2213
+ });
2214
+ if (e) {
2215
+ const r = d("span", { className: "chip-icon" });
2216
+ r.innerHTML = e;
2217
+ const o = r.querySelector("svg");
2218
+ o && o.setAttribute("aria-hidden", "true"), s.appendChild(r);
2219
+ }
2220
+ return s.appendChild(d("span", { className: "chip-label" }, g)), s.setActive = (r) => {
2221
+ s.classList.toggle("active", r);
2222
+ }, s;
2223
+ }
2224
+ class et {
2225
+ constructor(e, t) {
2226
+ this.state = e, this.editor = t, this.element = null, this._removeBgBtn = null, this._removeBgTierSelect = null, this._removeBgStatus = null, this._removeBgTier = "balanced", this._unsubscribers = [];
2227
+ }
2228
+ /**
2229
+ * Create and render the toolbar
2230
+ * @returns {HTMLElement}
2231
+ */
2232
+ render() {
2233
+ this.element = d("div", { className: "editor-toolbar" });
2234
+ const e = d("div", { className: "toolbar-section toolbar-left" }), t = O({
2235
+ icon: Ie,
2236
+ title: "Open Image",
2237
+ className: "toolbar-btn",
2238
+ testId: "btn-open-image",
2239
+ ariaLabel: "Open image file",
2240
+ onClick: () => this.editor.openFilePicker()
2241
+ });
2242
+ e.appendChild(t);
2243
+ const i = d("div", { className: "toolbar-section toolbar-right" });
2244
+ this._themeBtn = O({
2245
+ icon: this.state.get("isDarkMode") ? fe : ge,
2246
+ title: "Toggle Theme",
2247
+ className: "toolbar-btn toolbar-btn-theme",
2248
+ testId: "btn-toggle-theme",
2249
+ ariaLabel: "Toggle theme",
2250
+ onClick: () => this.editor.toggleTheme()
2251
+ }), this._cropBtn = O({
2252
+ icon: be,
2253
+ title: "Crop",
2254
+ className: "toolbar-btn toolbar-btn-crop",
2255
+ testId: "btn-crop",
2256
+ ariaLabel: "Crop image",
2257
+ onClick: () => {
2258
+ this.state.get("mode") === "crop" ? this.editor.setMode("filters") : this.editor.setMode("crop");
2259
+ }
2260
+ });
2261
+ const s = d("div", { className: "remove-bg-control" });
2262
+ this._removeBgTierSelect = d("select", {
2263
+ className: "remove-bg-tier-select",
2264
+ title: "Background removal quality",
2265
+ "aria-label": "Background removal quality",
2266
+ dataset: { testid: "select-remove-background-tier" },
2267
+ onChange: (h) => {
2268
+ this._removeBgTier = h.target.value;
2269
+ }
2270
+ });
2271
+ for (const h of [
2272
+ ["fast", "Fast"],
2273
+ ["balanced", "Balanced"],
2274
+ ["best", "Best"]
2275
+ ]) {
2276
+ const c = d("option", { value: h[0] }, h[1]);
2277
+ h[0] === this._removeBgTier && (c.selected = !0), this._removeBgTierSelect.appendChild(c);
2278
+ }
2279
+ this._removeBgBtn = O({
2280
+ icon: Ge,
2281
+ title: "Remove Background",
2282
+ className: "toolbar-btn toolbar-btn-remove-bg",
2283
+ testId: "btn-remove-background",
2284
+ ariaLabel: "Remove background",
2285
+ disabled: !this._canRemoveBackground(),
2286
+ onClick: async () => {
2287
+ if (this._canRemoveBackground()) {
2288
+ this._setRemoveBgStatus("Removing background...");
2289
+ try {
2290
+ await this.editor.removeBackground({ tier: this._removeBgTier }), this._setRemoveBgStatus("Background removed");
2291
+ } catch {
2292
+ this._setRemoveBgStatus("Background removal failed");
2293
+ } finally {
2294
+ this._updateRemoveBgButton();
2295
+ }
2296
+ }
2297
+ }
2298
+ }), this._removeBgStatus = d("span", {
2299
+ className: "sr-only",
2300
+ role: "status",
2301
+ "aria-live": "polite",
2302
+ "aria-atomic": "true"
2303
+ }), s.appendChild(this._removeBgTierSelect), s.appendChild(this._removeBgBtn), s.appendChild(this._removeBgStatus);
2304
+ const r = O({
2305
+ icon: ye,
2306
+ title: "Reset All",
2307
+ className: "toolbar-btn",
2308
+ testId: "btn-reset-all",
2309
+ ariaLabel: "Reset all changes",
2310
+ onClick: () => this.editor.resetAll()
2311
+ }), o = d("button", {
2312
+ type: "button",
2313
+ className: "icon-btn toolbar-btn toolbar-btn-primary toolbar-save-labeled",
2314
+ title: "Save Image",
2315
+ "aria-label": "Save image",
2316
+ dataset: { testid: "btn-save-edit" },
2317
+ onClick: () => this.editor.save()
2318
+ }), n = d("span", { className: "btn-icon" });
2319
+ n.innerHTML = Oe;
2320
+ const a = n.querySelector("svg");
2321
+ a && a.setAttribute("aria-hidden", "true"), o.appendChild(n), o.appendChild(d("span", { className: "toolbar-save-label" }, "Save"));
2322
+ const l = O({
2323
+ icon: de,
2324
+ title: "Close",
2325
+ className: "toolbar-btn",
2326
+ testId: "btn-cancel-edit",
2327
+ ariaLabel: "Close editor",
2328
+ onClick: () => this.editor.close()
2329
+ });
2330
+ return i.appendChild(this._themeBtn), i.appendChild(this._cropBtn), i.appendChild(s), i.appendChild(r), i.appendChild(o), i.appendChild(l), this.element.appendChild(e), this.element.appendChild(i), this._subscribeToState(), this.element;
2331
+ }
2332
+ /**
2333
+ * Subscribe to state changes
2334
+ */
2335
+ _subscribeToState() {
2336
+ const e = this.state.on("change:isDarkMode", ({ value: l }) => {
2337
+ this._themeBtn.innerHTML = l ? fe : ge;
2338
+ });
2339
+ this._unsubscribers.push(e);
2340
+ const t = this.state.on("change:mode", ({ value: l }) => {
2341
+ this._cropBtn && this._cropBtn.classList.toggle("toolbar-btn-primary", l === "crop");
2342
+ });
2343
+ this._unsubscribers.push(t);
2344
+ const i = this.state.on("change:hasImage", () => this._updateRemoveBgButton());
2345
+ this._unsubscribers.push(i);
2346
+ const s = this.state.on("change:isProcessing", () => this._updateRemoveBgButton());
2347
+ this._unsubscribers.push(s);
2348
+ const r = this.state.on("change:backgroundRemovalAvailable", () => this._updateRemoveBgButton());
2349
+ this._unsubscribers.push(r);
2350
+ const o = this.state.on("change:editorDocument", () => this._updateRemoveBgButton());
2351
+ this._unsubscribers.push(o);
2352
+ const n = this.state.on("change:layers", () => this._updateRemoveBgButton());
2353
+ this._unsubscribers.push(n);
2354
+ const a = this.state.on("change:activeLayerId", () => this._updateRemoveBgButton());
2355
+ this._unsubscribers.push(a);
2356
+ }
2357
+ _canRemoveBackground() {
2358
+ return typeof this.editor.canRemoveBackground == "function" ? this.editor.canRemoveBackground() : !!this.state.get("hasImage") && typeof this.editor.removeBackground == "function";
2359
+ }
2360
+ _updateRemoveBgButton() {
2361
+ if (!this._removeBgBtn) return;
2362
+ const e = this.state.get("isProcessing") === !0, t = !this._canRemoveBackground() || e;
2363
+ this._removeBgBtn.disabled = t, this._removeBgBtn.setAttribute("aria-disabled", t ? "true" : "false"), this._removeBgBtn.setAttribute("aria-busy", e ? "true" : "false"), this._removeBgTierSelect && (this._removeBgTierSelect.disabled = t, this._removeBgTierSelect.setAttribute("aria-disabled", t ? "true" : "false"));
2364
+ }
2365
+ _setRemoveBgStatus(e) {
2366
+ this._removeBgStatus && (this._removeBgStatus.textContent = e);
2367
+ }
2368
+ /**
2369
+ * Clean up
2370
+ */
2371
+ destroy() {
2372
+ var e;
2373
+ this._unsubscribers.forEach((t) => t()), this._unsubscribers = [], (e = this.element) == null || e.remove(), this.element = null;
2374
+ }
2375
+ }
2376
+ const ie = ue;
2377
+ class tt {
2378
+ constructor(e, t) {
2379
+ this.state = e, this.editor = t, this.element = null, this._chips = /* @__PURE__ */ new Map(), this._scrollIndex = 0, this._unsubscribers = [];
2380
+ }
2381
+ /**
2382
+ * Create and render the category carousel
2383
+ * @param {Function} onSelect - Callback when category is selected
2384
+ * @returns {HTMLElement}
2385
+ */
2386
+ render(e) {
2387
+ this._onSelect = e, this.element = d("div", {
2388
+ className: "category-carousel-container",
2389
+ "data-testid": "category-carousel"
2390
+ }), this._leftBtn = O({
2391
+ icon: _e,
2392
+ title: "Previous categories",
2393
+ className: "carousel-nav carousel-nav-left",
2394
+ onClick: () => this._scrollLeft()
2395
+ }), this._carousel = d("div", { className: "category-carousel" }), ie.forEach((i) => {
2396
+ const s = se({
2397
+ label: i.name,
2398
+ icon: i.icon,
2399
+ active: this.state.get("selectedCategory") === i.id,
2400
+ onClick: () => this._selectCategory(i.id)
2401
+ });
2402
+ s.dataset.categoryId = i.id, s.dataset.category = i.id, s.dataset.testid = `category-${i.id}`, this._chips.set(i.id, s), this._carousel.appendChild(s);
2403
+ }), this._rightBtn = O({
2404
+ icon: me,
2405
+ title: "Next categories",
2406
+ className: "carousel-nav carousel-nav-right",
2407
+ onClick: () => this._scrollRight()
2408
+ }), this._pagination = d("div", {
2409
+ className: "carousel-pagination",
2410
+ role: "tablist",
2411
+ "aria-label": "Category pages"
2412
+ });
2413
+ const t = Math.ceil(ie.length / 3);
2414
+ for (let i = 0; i < t; i++) {
2415
+ const s = d("button", {
2416
+ type: "button",
2417
+ className: `pagination-dot ${i === 0 ? "active" : ""}`,
2418
+ role: "tab",
2419
+ "aria-label": `Page ${i + 1} of ${t}`,
2420
+ "aria-selected": i === 0 ? "true" : "false",
2421
+ onClick: () => this._scrollToPage(i)
2422
+ });
2423
+ this._pagination.appendChild(s);
2424
+ }
2425
+ return this.element.appendChild(this._leftBtn), this.element.appendChild(this._carousel), this.element.appendChild(this._rightBtn), this.element.appendChild(this._pagination), this._subscribeToState(), this._updateNavButtons(), this.element;
2426
+ }
2427
+ /**
2428
+ * Subscribe to state changes
2429
+ */
2430
+ _subscribeToState() {
2431
+ const e = this.state.on("change:selectedCategory", ({ value: t }) => {
2432
+ this._chips.forEach((i, s) => {
2433
+ i.setActive(s === t);
2434
+ });
2435
+ });
2436
+ this._unsubscribers.push(e);
2437
+ }
2438
+ /**
2439
+ * Select a category
2440
+ * @param {string} categoryId
2441
+ */
2442
+ _selectCategory(e) {
2443
+ var s;
2444
+ this.state.set("selectedCategory", e);
2445
+ const t = this.state.get("mode");
2446
+ e === "crop" ? t !== "crop" && this.editor.setMode("crop") : t !== "filters" && this.editor.setMode("filters");
2447
+ const i = this._chips.get(e);
2448
+ i && this._carousel && i.scrollIntoView({ behavior: "smooth", inline: "center", block: "nearest" }), (s = this._onSelect) == null || s.call(this, e);
2449
+ }
2450
+ /**
2451
+ * Scroll carousel left
2452
+ */
2453
+ _scrollLeft() {
2454
+ this._scrollIndex > 0 && (this._scrollIndex--, this._scrollCarousel());
2455
+ }
2456
+ /**
2457
+ * Scroll carousel right
2458
+ */
2459
+ _scrollRight() {
2460
+ this._scrollIndex < ie.length - 3 && (this._scrollIndex++, this._scrollCarousel());
2461
+ }
2462
+ /**
2463
+ * Scroll to a specific page
2464
+ * @param {number} pageIndex
2465
+ */
2466
+ _scrollToPage(e) {
2467
+ this._scrollIndex = e * 3, this._scrollCarousel();
2468
+ }
2469
+ /**
2470
+ * Animate carousel scroll
2471
+ */
2472
+ _scrollCarousel() {
2473
+ if (this._carousel) {
2474
+ const e = this._carousel.querySelector(".chip"), t = (e == null ? void 0 : e.clientWidth) || 100;
2475
+ this._carousel.scrollTo({
2476
+ left: this._scrollIndex * (t + 8),
2477
+ // 8px gap
2478
+ behavior: "smooth"
2479
+ });
2480
+ }
2481
+ this._updateNavButtons(), this._updatePagination();
2482
+ }
2483
+ /**
2484
+ * Update navigation button states
2485
+ */
2486
+ _updateNavButtons() {
2487
+ this._leftBtn && (this._leftBtn.disabled = this._scrollIndex === 0), this._rightBtn && (this._rightBtn.disabled = this._scrollIndex >= ie.length - 3);
2488
+ }
2489
+ /**
2490
+ * Update pagination dot states
2491
+ */
2492
+ _updatePagination() {
2493
+ if (this._pagination) {
2494
+ const e = this._pagination.querySelectorAll(".pagination-dot"), t = Math.floor(this._scrollIndex / 3);
2495
+ e.forEach((i, s) => {
2496
+ const r = s === t;
2497
+ i.classList.toggle("active", r), i.setAttribute("aria-selected", r ? "true" : "false");
2498
+ });
2499
+ }
2500
+ }
2501
+ /**
2502
+ * Set the selected category
2503
+ * @param {string} categoryId
2504
+ */
2505
+ setSelected(e) {
2506
+ this._selectCategory(e);
2507
+ }
2508
+ /**
2509
+ * Clean up
2510
+ */
2511
+ destroy() {
2512
+ var e;
2513
+ this._unsubscribers.forEach((t) => t()), this._unsubscribers = [], this._chips.clear(), (e = this.element) == null || e.remove(), this.element = null;
2514
+ }
2515
+ }
2516
+ class it {
2517
+ constructor(e, t) {
2518
+ this.state = e, this.filterManager = t, this.element = null, this._filterCards = /* @__PURE__ */ new Map(), this._scrollIndex = 0, this._unsubscribers = [], this._onToggle = null, this._onSelect = null;
2519
+ }
2520
+ /**
2521
+ * Create and render the filter carousel
2522
+ * @param {Object} options - { onToggle, onSelect }
2523
+ * @returns {HTMLElement}
2524
+ */
2525
+ render({ onToggle: e, onSelect: t }) {
2526
+ return this._onToggle = e, this._onSelect = t, this.element = d("div", { className: "filter-carousel-container" }), this._leftBtn = O({
2527
+ icon: _e,
2528
+ title: "Previous filters",
2529
+ className: "carousel-nav carousel-nav-left",
2530
+ onClick: () => this._scrollLeft()
2531
+ }), this._carousel = d("div", { className: "filter-carousel" }), this._rightBtn = O({
2532
+ icon: me,
2533
+ title: "Next filters",
2534
+ className: "carousel-nav carousel-nav-right",
2535
+ onClick: () => this._scrollRight()
2536
+ }), this.element.appendChild(this._leftBtn), this.element.appendChild(this._carousel), this.element.appendChild(this._rightBtn), this._subscribeToState(), this._renderFilters(), this.element;
2537
+ }
2538
+ /**
2539
+ * Subscribe to state changes
2540
+ */
2541
+ _subscribeToState() {
2542
+ const e = this.state.on("change:selectedCategory", () => {
2543
+ this._renderFilters();
2544
+ });
2545
+ this._unsubscribers.push(e);
2546
+ const t = this.state.on("change:activeFilters", () => {
2547
+ this._updateActiveStates();
2548
+ });
2549
+ this._unsubscribers.push(t);
2550
+ const i = this.state.on("change:selectedFilter", () => {
2551
+ this._updateSelectedState();
2552
+ });
2553
+ this._unsubscribers.push(i);
2554
+ }
2555
+ /**
2556
+ * Render filters for current category
2557
+ */
2558
+ _renderFilters() {
2559
+ this._carousel.innerHTML = "", this._filterCards.clear(), this._scrollIndex = 0;
2560
+ const e = this.state.get("selectedCategory");
2561
+ if (e === "crop") return;
2562
+ const t = this.filterManager.getFiltersByCategory(e), i = this.state.get("activeFilters"), s = this.state.get("selectedFilter");
2563
+ t.forEach((r) => {
2564
+ const o = this._createFilterCard(r, {
2565
+ isActive: i.has(r.id),
2566
+ isSelected: s === r.id
2567
+ });
2568
+ this._filterCards.set(r.id, o), this._carousel.appendChild(o);
2569
+ }), this._updateNavButtons();
2570
+ }
2571
+ /**
2572
+ * Create a filter card
2573
+ * @param {Object} filter
2574
+ * @param {Object} options
2575
+ * @returns {HTMLElement}
2576
+ */
2577
+ _createFilterCard(e, { isActive: t, isSelected: i }) {
2578
+ const s = d("div", {
2579
+ className: `filter-card ${t ? "active" : ""} ${i ? "selected" : ""}`,
2580
+ "data-filter": e.id,
2581
+ "data-testid": `filter-${e.id}`,
2582
+ onClick: () => this._handleCardClick(e.id)
2583
+ }), r = d("div", { className: "filter-preview" }), o = d("span", { className: "filter-preview-text" }, e.name.charAt(0));
2584
+ r.appendChild(o);
2585
+ const n = d("span", { className: "filter-name", title: e.name }, e.name), a = d("button", {
2586
+ className: `filter-toggle ${t ? "active" : ""}`,
2587
+ onClick: (l) => {
2588
+ var p;
2589
+ l.stopPropagation();
2590
+ const c = !this.state.get("activeFilters").has(e.id);
2591
+ this._handleToggle(e.id, c), c && (this.state.set("selectedFilter", e.id), (p = this._onSelect) == null || p.call(this, e.id));
2592
+ }
2593
+ });
2594
+ return a.innerHTML = t ? he : "", s.appendChild(r), s.appendChild(n), s.appendChild(a), s._toggle = a, s._isActive = t, s;
2595
+ }
2596
+ /**
2597
+ * Handle filter card click (select)
2598
+ * @param {string} filterId
2599
+ */
2600
+ _handleCardClick(e) {
2601
+ var i;
2602
+ this.state.get("activeFilters").has(e) || this._handleToggle(e, !0), this.state.set("selectedFilter", e), (i = this._onSelect) == null || i.call(this, e);
2603
+ }
2604
+ /**
2605
+ * Handle filter toggle
2606
+ * @param {string} filterId
2607
+ * @param {boolean} enabled
2608
+ */
2609
+ _handleToggle(e, t) {
2610
+ var i;
2611
+ (i = this._onToggle) == null || i.call(this, e, t);
2612
+ }
2613
+ /**
2614
+ * Update active states based on state
2615
+ */
2616
+ _updateActiveStates() {
2617
+ const e = this.state.get("activeFilters");
2618
+ this._filterCards.forEach((t, i) => {
2619
+ const s = e.has(i);
2620
+ t.classList.toggle("active", s), t._toggle.classList.toggle("active", s), t._toggle.innerHTML = s ? he : "", t._isActive = s;
2621
+ });
2622
+ }
2623
+ /**
2624
+ * Update selected state based on state
2625
+ */
2626
+ _updateSelectedState() {
2627
+ const e = this.state.get("selectedFilter");
2628
+ this._filterCards.forEach((t, i) => {
2629
+ t.classList.toggle("selected", i === e);
2630
+ });
2631
+ }
2632
+ /**
2633
+ * Scroll carousel left
2634
+ */
2635
+ _scrollLeft() {
2636
+ this._scrollIndex > 0 && (this._scrollIndex--, this._scrollCarousel());
2637
+ }
2638
+ /**
2639
+ * Scroll carousel right
2640
+ */
2641
+ _scrollRight() {
2642
+ const e = Math.max(0, this._filterCards.size - 3);
2643
+ this._scrollIndex < e && (this._scrollIndex++, this._scrollCarousel());
2644
+ }
2645
+ /**
2646
+ * Animate carousel scroll
2647
+ */
2648
+ _scrollCarousel() {
2649
+ if (this._carousel) {
2650
+ const e = this._carousel.querySelector(".filter-card"), t = (e == null ? void 0 : e.clientWidth) || 120;
2651
+ this._carousel.scrollTo({
2652
+ left: this._scrollIndex * (t + 8),
2653
+ behavior: "smooth"
2654
+ });
2655
+ }
2656
+ this._updateNavButtons();
2657
+ }
2658
+ /**
2659
+ * Update navigation button states
2660
+ */
2661
+ _updateNavButtons() {
2662
+ const e = Math.max(0, this._filterCards.size - 3);
2663
+ this._leftBtn && (this._leftBtn.disabled = this._scrollIndex === 0), this._rightBtn && (this._rightBtn.disabled = this._scrollIndex >= e);
2664
+ }
2665
+ /**
2666
+ * Clean up
2667
+ */
2668
+ destroy() {
2669
+ var e;
2670
+ this._unsubscribers.forEach((t) => t()), this._unsubscribers = [], this._filterCards.clear(), (e = this.element) == null || e.remove(), this.element = null;
2671
+ }
2672
+ }
2673
+ class st {
2674
+ constructor(e, t) {
2675
+ this.state = e, this.filterManager = t, this.element = null, this._controls = /* @__PURE__ */ new Map(), this._onChange = null, this._onReset = null, this._onAction = null, this._unsubscribers = [];
2676
+ }
2677
+ /**
2678
+ * Create and render the filter adjustments panel
2679
+ * @param {Object} options - { onChange, onReset, onAction }
2680
+ * @returns {HTMLElement}
2681
+ */
2682
+ render({ onChange: e, onReset: t, onAction: i }) {
2683
+ return this._onChange = e, this._onReset = t, this._onAction = i, this.element = d("div", { className: "filter-adjustments" }), this._subscribeToState(), this._renderControls(), this.element;
2684
+ }
2685
+ /**
2686
+ * Subscribe to state changes
2687
+ */
2688
+ _subscribeToState() {
2689
+ const e = this.state.on("change:selectedFilter", () => {
2690
+ this._renderControls();
2691
+ });
2692
+ this._unsubscribers.push(e);
2693
+ const t = this.state.on("change:filterValues", () => {
2694
+ this._updateValues();
2695
+ });
2696
+ this._unsubscribers.push(t);
2697
+ }
2698
+ /**
2699
+ * Render controls for the selected filter.
2700
+ *
2701
+ * Renders both the static controls from def.controls and any dynamic
2702
+ * controls the filter instance exposes via getDynamicControls() (e.g.
2703
+ * per-stop color/offset/alpha rows for Color Gradient).
2704
+ */
2705
+ _renderControls() {
2706
+ this.element.innerHTML = "", this._controls.clear();
2707
+ const e = this.state.get("selectedFilter");
2708
+ if (!e) {
2709
+ this.element.appendChild(
2710
+ d("div", { className: "no-filter-selected" }, "Select a filter to adjust")
2711
+ );
2712
+ return;
2713
+ }
2714
+ const t = this.filterManager.getFilterDef(e);
2715
+ if (!t) {
2716
+ this.element.appendChild(
2717
+ d("div", { className: "no-filter-selected" }, "Filter not found")
2718
+ );
2719
+ return;
2720
+ }
2721
+ const i = d("div", { className: "adjustments-header" });
2722
+ i.appendChild(d("span", { className: "adjustments-title" }, t.name)), i.appendChild(j({
2723
+ label: "Reset",
2724
+ className: "btn-text",
2725
+ onClick: () => this._handleReset(e)
2726
+ })), this.element.appendChild(i);
2727
+ const s = d("div", { className: "adjustments-grid" }), r = this.state.getFilterValues(e);
2728
+ (t.controls || []).forEach((n) => {
2729
+ const a = r[n.id] ?? n.default, l = this._createControl(e, n, a);
2730
+ l && (this._controls.set(n.id, l), s.appendChild(l));
2731
+ });
2732
+ const o = this.filterManager.getInstance(e);
2733
+ if (o && typeof o.getDynamicControls == "function") {
2734
+ let n = [];
2735
+ try {
2736
+ n = o.getDynamicControls() || [];
2737
+ } catch {
2738
+ }
2739
+ n.forEach((a) => {
2740
+ const l = a.property || a.id, h = r[l] ?? a.default, c = this._createControl(e, a, h);
2741
+ c && (this._controls.set(a.id, c), s.appendChild(c));
2742
+ });
2743
+ }
2744
+ this.element.appendChild(s);
2745
+ }
2746
+ /**
2747
+ * Create a control element based on type.
2748
+ *
2749
+ * The update key passed to _handleChange is `ctl.property || ctl.id`.
2750
+ * Dynamic controls (e.g. from getDynamicControls) use their property
2751
+ * path (e.g. 'colorStops[0].color') so FilterManager.updateValue can
2752
+ * route the write correctly. Static controls without a property fall
2753
+ * back to the control id, matching the legacy contract.
2754
+ *
2755
+ * @param {string} filterId
2756
+ * @param {Object} ctl - Control definition
2757
+ * @param {*} value - Current value
2758
+ * @returns {HTMLElement|null}
2759
+ */
2760
+ _createControl(e, t, i) {
2761
+ if (t.hidden) return null;
2762
+ const s = t.label || t.id, r = this._normalizeControlType(t.type), o = t.property || t.id;
2763
+ switch (r) {
2764
+ case "slider":
2765
+ return ve({
2766
+ id: `${e}-${t.id}`,
2767
+ label: s,
2768
+ min: t.min ?? 0,
2769
+ max: t.max ?? 1,
2770
+ step: t.step ?? 0.01,
2771
+ value: typeof i == "number" ? i : t.default ?? 0,
2772
+ onChange: (l) => this._handleChange(e, o, l)
2773
+ });
2774
+ case "toggle":
2775
+ return we({
2776
+ id: `${e}-${t.id}`,
2777
+ label: s,
2778
+ checked: !!i,
2779
+ onChange: (l) => this._handleChange(e, o, l)
2780
+ });
2781
+ case "color":
2782
+ return xe({
2783
+ id: `${e}-${t.id}`,
2784
+ label: s,
2785
+ value: typeof i == "string" && i.startsWith("#") ? i : t.default || "#000000",
2786
+ onChange: (l) => this._handleChange(e, o, l)
2787
+ });
2788
+ case "select":
2789
+ const n = this._normalizeOptions(t.options);
2790
+ return ke({
2791
+ id: `${e}-${t.id}`,
2792
+ label: s,
2793
+ options: n,
2794
+ value: i ?? t.default,
2795
+ onChange: (l) => this._handleChange(e, o, l)
2796
+ });
2797
+ case "text":
2798
+ return Ce({
2799
+ id: `${e}-${t.id}`,
2800
+ label: s,
2801
+ value: typeof i == "string" ? i : t.default ?? "",
2802
+ placeholder: t.placeholder || "",
2803
+ onCommit: (l) => this._handleChange(e, o, l)
2804
+ });
2805
+ case "button":
2806
+ const a = d("div", { className: "button-control" });
2807
+ return a.appendChild(j({
2808
+ label: s,
2809
+ className: "btn-secondary",
2810
+ onClick: () => this._handleAction(e, t.action || t.id)
2811
+ })), a;
2812
+ default:
2813
+ return null;
2814
+ }
2815
+ }
2816
+ /**
2817
+ * Normalize control type (handle aliases)
2818
+ * @param {string} type
2819
+ * @returns {string}
2820
+ */
2821
+ _normalizeControlType(e) {
2822
+ return {
2823
+ slider: "slider",
2824
+ range: "slider",
2825
+ toggle: "toggle",
2826
+ checkbox: "toggle",
2827
+ color: "color",
2828
+ select: "select",
2829
+ dropdown: "select",
2830
+ button: "button",
2831
+ text: "text"
2832
+ }[e] || e;
2833
+ }
2834
+ /**
2835
+ * Normalize options array format
2836
+ * @param {Array} options
2837
+ * @returns {Array}
2838
+ */
2839
+ _normalizeOptions(e) {
2840
+ return !e || !Array.isArray(e) ? [] : e.map((t) => typeof t == "object" && t.value !== void 0 ? { value: t.value, label: t.label || String(t.value) } : typeof t == "string" ? { value: t, label: t } : typeof t == "number" ? { value: t, label: String(t) } : { value: t, label: String(t) });
2841
+ }
2842
+ /**
2843
+ * Handle control value change
2844
+ * @param {string} filterId
2845
+ * @param {string} controlId
2846
+ * @param {*} value
2847
+ */
2848
+ _handleChange(e, t, i) {
2849
+ var s;
2850
+ (s = this._onChange) == null || s.call(this, e, t, i);
2851
+ }
2852
+ /**
2853
+ * Handle reset button
2854
+ * @param {string} filterId
2855
+ */
2856
+ _handleReset(e) {
2857
+ var t;
2858
+ (t = this._onReset) == null || t.call(this, e), this._renderControls();
2859
+ }
2860
+ /**
2861
+ * Handle button action. Dynamic controls (e.g. color stop rows) depend on
2862
+ * filter state that can change as a result of the action (add/remove stop),
2863
+ * so re-render after the action completes.
2864
+ *
2865
+ * @param {string} filterId
2866
+ * @param {string} action
2867
+ */
2868
+ _handleAction(e, t) {
2869
+ var i;
2870
+ (i = this._onAction) == null || i.call(this, e, t), this._renderControls();
2871
+ }
2872
+ /**
2873
+ * Update control values from state
2874
+ */
2875
+ _updateValues() {
2876
+ const e = this.state.get("selectedFilter");
2877
+ if (!e) return;
2878
+ const t = this.state.getFilterValues(e), i = this.filterManager.getFilterDef(e);
2879
+ i && i.controls.forEach((s) => {
2880
+ const r = this._controls.get(s.id);
2881
+ if (r && typeof r.setValue == "function") {
2882
+ const o = t[s.id] ?? s.default;
2883
+ r.setValue(o);
2884
+ }
2885
+ });
2886
+ }
2887
+ /**
2888
+ * Show panel
2889
+ */
2890
+ show() {
2891
+ this.element && (this.element.style.display = "");
2892
+ }
2893
+ /**
2894
+ * Hide panel
2895
+ */
2896
+ hide() {
2897
+ this.element && (this.element.style.display = "none");
2898
+ }
2899
+ /**
2900
+ * Clean up
2901
+ */
2902
+ destroy() {
2903
+ var e;
2904
+ this._unsubscribers.forEach((t) => t()), this._unsubscribers = [], this._controls.clear(), (e = this.element) == null || e.remove(), this.element = null;
2905
+ }
2906
+ }
2907
+ class rt {
2908
+ constructor(e, t) {
2909
+ this.state = e, this.filterManager = t, this._drawer = null, this._body = null, this._titleEl = null, this._isOpen = !1, this._currentFilterId = null, this._controls = /* @__PURE__ */ new Map(), this._onChange = null, this._onReset = null, this._onRemove = null, this._onAction = null, this._container = null;
2910
+ }
2911
+ /**
2912
+ * Build the drawer DOM and append to container (should be the controls section)
2913
+ * @param {HTMLElement} container - Parent element (controls-section)
2914
+ * @param {Object} callbacks - { onChange, onReset, onRemove, onAction }
2915
+ */
2916
+ build(e, { onChange: t, onReset: i, onRemove: s, onAction: r }) {
2917
+ this._onChange = t, this._onReset = i, this._onRemove = s, this._onAction = r, this._container = e, this._drawer = d("div", { className: "mobile-filter-drawer" });
2918
+ const o = d("div", { className: "drawer-header" });
2919
+ this._titleEl = d("span", { className: "drawer-title" }, "Filter");
2920
+ const n = d("div", { className: "drawer-header-actions" }), a = j({
2921
+ label: "Reset",
2922
+ className: "btn-text",
2923
+ onClick: () => {
2924
+ var c;
2925
+ this._currentFilterId && ((c = this._onReset) == null || c.call(this, this._currentFilterId), this._renderControls());
2926
+ }
2927
+ }), l = j({
2928
+ label: "Remove",
2929
+ className: "btn-text btn-danger",
2930
+ onClick: () => {
2931
+ var c;
2932
+ this._currentFilterId && ((c = this._onRemove) == null || c.call(this, this._currentFilterId), this.close());
2933
+ }
2934
+ }), h = O({
2935
+ icon: de,
2936
+ title: "Close",
2937
+ className: "btn-icon-sm",
2938
+ ariaLabel: "Close drawer",
2939
+ onClick: () => this.close()
2940
+ });
2941
+ n.appendChild(a), n.appendChild(l), n.appendChild(h), o.appendChild(this._titleEl), o.appendChild(n), this._drawer.appendChild(o), this._body = d("div", { className: "drawer-body" }), this._drawer.appendChild(this._body), e.appendChild(this._drawer);
2942
+ }
2943
+ /**
2944
+ * Open the drawer for a specific filter
2945
+ * @param {string} filterId
2946
+ */
2947
+ open(e) {
2948
+ this._isOpen && this._currentFilterId === e || (this._currentFilterId = e, this._renderControls(), this._isOpen || (this._isOpen = !0, this._sizeToContainer(), this._animateOpen()));
2949
+ }
2950
+ /**
2951
+ * Close the drawer
2952
+ */
2953
+ close() {
2954
+ this._isOpen && (this._isOpen = !1, this._animateClose(), this._currentFilterId = null);
2955
+ }
2956
+ /**
2957
+ * Check if drawer is open
2958
+ * @returns {boolean}
2959
+ */
2960
+ get isOpen() {
2961
+ return this._isOpen;
2962
+ }
2963
+ /**
2964
+ * Size the drawer to match the container (controls section) height
2965
+ */
2966
+ _sizeToContainer() {
2967
+ if (!this._container || !this._drawer) return;
2968
+ const e = this._container.offsetHeight;
2969
+ e > 0 && (this._drawer.style.height = e + "px");
2970
+ }
2971
+ /**
2972
+ * Animate drawer open (CSS transition)
2973
+ */
2974
+ _animateOpen() {
2975
+ this._drawer.style.transition = "none", this._drawer.style.transform = "translateY(100%)", this._drawer.offsetHeight, this._drawer.style.transition = "transform 0.25s cubic-bezier(0.33, 1, 0.68, 1)", this._drawer.style.transform = "translateY(0)";
2976
+ }
2977
+ /**
2978
+ * Animate drawer close (CSS transition)
2979
+ */
2980
+ _animateClose() {
2981
+ this._drawer.style.transition = "transform 0.2s cubic-bezier(0.32, 0, 0.67, 0)", this._drawer.style.transform = "translateY(100%)";
2982
+ }
2983
+ /**
2984
+ * Render controls for the current filter.
2985
+ * Renders both static (def.controls) and dynamic (instance.getDynamicControls)
2986
+ * controls so filters like Color Gradient show per-stop rows.
2987
+ */
2988
+ _renderControls() {
2989
+ if (this._body.innerHTML = "", this._controls.clear(), !this._currentFilterId) return;
2990
+ const e = this.filterManager.getFilterDef(this._currentFilterId);
2991
+ if (!e) return;
2992
+ this._titleEl.textContent = e.name;
2993
+ const t = d("div", { className: "adjustments-grid" }), i = this.state.getFilterValues(this._currentFilterId);
2994
+ (e.controls || []).forEach((r) => {
2995
+ if (r.hidden) return;
2996
+ const o = this._createControl(this._currentFilterId, r, i[r.id] ?? r.default);
2997
+ o && (this._controls.set(r.id, o), t.appendChild(o));
2998
+ });
2999
+ const s = this.filterManager.getInstance(this._currentFilterId);
3000
+ if (s && typeof s.getDynamicControls == "function") {
3001
+ let r = [];
3002
+ try {
3003
+ r = s.getDynamicControls() || [];
3004
+ } catch {
3005
+ }
3006
+ r.forEach((o) => {
3007
+ const n = o.property || o.id, a = i[n] ?? o.default, l = this._createControl(this._currentFilterId, o, a);
3008
+ l && (this._controls.set(o.id, l), t.appendChild(l));
3009
+ });
3010
+ }
3011
+ this._body.appendChild(t);
3012
+ }
3013
+ /**
3014
+ * Create a control element based on type. See FilterAdjustments._createControl
3015
+ * for the update-key contract (property path for dynamic controls, id for static).
3016
+ * @param {string} filterId
3017
+ * @param {Object} ctl
3018
+ * @param {*} value
3019
+ * @returns {HTMLElement|null}
3020
+ */
3021
+ _createControl(e, t, i) {
3022
+ if (t.hidden) return null;
3023
+ const s = t.label || t.id, r = this._normalizeControlType(t.type), o = t.property || t.id;
3024
+ switch (r) {
3025
+ case "slider":
3026
+ return ve({
3027
+ id: `drawer-${e}-${t.id}`,
3028
+ label: s,
3029
+ min: t.min ?? 0,
3030
+ max: t.max ?? 1,
3031
+ step: t.step ?? 0.01,
3032
+ value: typeof i == "number" ? i : t.default ?? 0,
3033
+ onChange: (n) => {
3034
+ var a;
3035
+ return (a = this._onChange) == null ? void 0 : a.call(this, e, o, n);
3036
+ }
3037
+ });
3038
+ case "toggle":
3039
+ return we({
3040
+ id: `drawer-${e}-${t.id}`,
3041
+ label: s,
3042
+ checked: !!i,
3043
+ onChange: (n) => {
3044
+ var a;
3045
+ return (a = this._onChange) == null ? void 0 : a.call(this, e, o, n);
3046
+ }
3047
+ });
3048
+ case "color":
3049
+ return xe({
3050
+ id: `drawer-${e}-${t.id}`,
3051
+ label: s,
3052
+ value: typeof i == "string" && i.startsWith("#") ? i : t.default || "#000000",
3053
+ onChange: (n) => {
3054
+ var a;
3055
+ return (a = this._onChange) == null ? void 0 : a.call(this, e, o, n);
3056
+ }
3057
+ });
3058
+ case "select": {
3059
+ const n = this._normalizeOptions(t.options);
3060
+ return ke({
3061
+ id: `drawer-${e}-${t.id}`,
3062
+ label: s,
3063
+ options: n,
3064
+ value: i ?? t.default,
3065
+ onChange: (a) => {
3066
+ var l;
3067
+ return (l = this._onChange) == null ? void 0 : l.call(this, e, o, a);
3068
+ }
3069
+ });
3070
+ }
3071
+ case "text":
3072
+ return Ce({
3073
+ id: `drawer-${e}-${t.id}`,
3074
+ label: s,
3075
+ value: typeof i == "string" ? i : t.default ?? "",
3076
+ placeholder: t.placeholder || "",
3077
+ onCommit: (n) => {
3078
+ var a;
3079
+ return (a = this._onChange) == null ? void 0 : a.call(this, e, o, n);
3080
+ }
3081
+ });
3082
+ case "button": {
3083
+ const n = d("div", { className: "button-control" });
3084
+ return n.appendChild(j({
3085
+ label: s,
3086
+ className: "btn-secondary",
3087
+ onClick: () => {
3088
+ var a;
3089
+ (a = this._onAction) == null || a.call(this, e, t.action || t.id), this._renderControls();
3090
+ }
3091
+ })), n;
3092
+ }
3093
+ default:
3094
+ return null;
3095
+ }
3096
+ }
3097
+ /**
3098
+ * Normalize control type
3099
+ * @param {string} type
3100
+ * @returns {string}
3101
+ */
3102
+ _normalizeControlType(e) {
3103
+ return {
3104
+ slider: "slider",
3105
+ range: "slider",
3106
+ toggle: "toggle",
3107
+ checkbox: "toggle",
3108
+ color: "color",
3109
+ select: "select",
3110
+ dropdown: "select",
3111
+ button: "button",
3112
+ text: "text"
3113
+ }[e] || e;
3114
+ }
3115
+ /**
3116
+ * Normalize options array
3117
+ * @param {Array} options
3118
+ * @returns {Array}
3119
+ */
3120
+ _normalizeOptions(e) {
3121
+ return !e || !Array.isArray(e) ? [] : e.map((t) => typeof t == "object" && t.value !== void 0 ? { value: t.value, label: t.label || String(t.value) } : typeof t == "string" ? { value: t, label: t } : typeof t == "number" ? { value: t, label: String(t) } : { value: t, label: String(t) });
3122
+ }
3123
+ /**
3124
+ * Clean up
3125
+ */
3126
+ destroy() {
3127
+ var e;
3128
+ this._controls.clear(), (e = this._drawer) == null || e.remove(), this._drawer = null, this._isOpen = !1, this._currentFilterId = null, this._container = null;
3129
+ }
3130
+ }
3131
+ class nt {
3132
+ constructor(e, t) {
3133
+ this.state = e, this.filterManager = t, this.element = null, this._scrollContainer = null, this._chips = /* @__PURE__ */ new Map(), this._unsubscribers = [], this._onToggle = null, this._onSelect = null;
3134
+ }
3135
+ /**
3136
+ * @param {Object} callbacks - { onToggle(filterId, enabled), onSelect(filterId) }
3137
+ * @returns {HTMLElement}
3138
+ */
3139
+ render({ onToggle: e, onSelect: t }) {
3140
+ return this._onToggle = e, this._onSelect = t, this.element = d("div", {
3141
+ className: "mobile-active-filters",
3142
+ "data-testid": "mobile-active-filters"
3143
+ }), this._scrollContainer = d("div", { className: "mobile-active-filters-scroll" }), this.element.appendChild(this._scrollContainer), this._subscribeToState(), this._renderChips(), this.element;
3144
+ }
3145
+ _subscribeToState() {
3146
+ const e = this.state.on("change:activeFilters", () => {
3147
+ this._renderChips();
3148
+ });
3149
+ this._unsubscribers.push(e);
3150
+ const t = this.state.on("change:selectedFilter", () => {
3151
+ this._updateSelectedState();
3152
+ });
3153
+ this._unsubscribers.push(t);
3154
+ }
3155
+ _renderChips() {
3156
+ this._scrollContainer.innerHTML = "", this._chips.clear();
3157
+ const e = this.state.get("activeFilters");
3158
+ if (!e || e.size === 0) {
3159
+ const t = d("div", {
3160
+ className: "active-filter-placeholder"
3161
+ }, "No active filters");
3162
+ this._scrollContainer.appendChild(t);
3163
+ return;
3164
+ }
3165
+ e.forEach((t) => {
3166
+ const i = this.filterManager.getFilterDef(t);
3167
+ if (!i) return;
3168
+ const s = this._createChip(t, i);
3169
+ this._chips.set(t, s), this._scrollContainer.appendChild(s);
3170
+ }), this._updateSelectedState();
3171
+ }
3172
+ _createChip(e, t) {
3173
+ const i = d("div", {
3174
+ className: "active-filter-chip",
3175
+ "data-filter-id": e,
3176
+ "data-testid": `active-chip-${e}`
3177
+ }), s = d("span", {
3178
+ className: "active-filter-chip-label",
3179
+ onClick: (o) => {
3180
+ var n;
3181
+ o.stopPropagation(), (n = this._onSelect) == null || n.call(this, e);
3182
+ }
3183
+ }, t.name), r = d("button", {
3184
+ className: "active-filter-chip-check",
3185
+ "aria-label": `Remove ${t.name} filter`,
3186
+ onClick: (o) => {
3187
+ var n;
3188
+ o.stopPropagation(), (n = this._onToggle) == null || n.call(this, e, !1);
3189
+ }
3190
+ });
3191
+ return r.innerHTML = '<svg width="10" height="10" viewBox="0 0 12 12"><path d="M2 6l3 3 5-5" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>', i.appendChild(s), i.appendChild(r), i;
3192
+ }
3193
+ _updateSelectedState() {
3194
+ const e = this.state.get("selectedFilter");
3195
+ this._chips.forEach((t, i) => {
3196
+ t.classList.toggle("selected", i === e);
3197
+ });
3198
+ }
3199
+ destroy() {
3200
+ var e;
3201
+ this._unsubscribers.forEach((t) => t()), this._unsubscribers = [], this._chips.clear(), (e = this.element) == null || e.remove(), this.element = null;
3202
+ }
3203
+ }
3204
+ const ot = [
3205
+ { id: "free", name: "Free", icon: Ze },
3206
+ { id: "square", name: "Square", icon: We },
3207
+ { id: "circle", name: "Circle", icon: $e }
3208
+ ], at = [
3209
+ { id: "free", name: "Free" },
3210
+ { id: "1:1", name: "1:1" },
3211
+ { id: "4:3", name: "4:3" },
3212
+ { id: "16:9", name: "16:9" },
3213
+ { id: "3:2", name: "3:2" },
3214
+ { id: "2:3", name: "2:3" }
3215
+ ], lt = [
3216
+ { id: "0", label: "0°", angle: 0 },
3217
+ { id: "90", label: "90°", angle: 90 },
3218
+ { id: "180", label: "180°", angle: 180 },
3219
+ { id: "270", label: "270°", angle: 270 }
3220
+ ];
3221
+ function W(g) {
3222
+ if (!Number.isFinite(Number(g))) return 0;
3223
+ const e = Number(g) % 360;
3224
+ return e < 0 ? e + 360 : e;
3225
+ }
3226
+ class ht {
3227
+ constructor(e, t, i = null) {
3228
+ this.state = e, this.cropManager = t, this.editor = i, this.element = null, this._shapeChips = /* @__PURE__ */ new Map(), this._aspectChips = /* @__PURE__ */ new Map(), this._rotationPresetChips = /* @__PURE__ */ new Map(), this._rotationRange = null, this._rotationNumber = null, this._rotationValue = null, this._unsubscribers = [];
3229
+ }
3230
+ /**
3231
+ * Apply crop and return the editor UI to filter mode.
3232
+ * If crop application cannot complete, force-disable crop mode to avoid
3233
+ * leaving the editor stuck in crop controls.
3234
+ */
3235
+ _applyCropAndReturnToFilters() {
3236
+ this.cropManager.apply() === null && typeof this.cropManager.disable == "function" && this.cropManager.disable(), this.state.set("selectedCategory", "adjust");
3237
+ }
3238
+ /**
3239
+ * Create and render the crop controls
3240
+ * @returns {HTMLElement}
3241
+ */
3242
+ render() {
3243
+ this.element = d("div", {
3244
+ className: "crop-controls",
3245
+ "data-testid": "crop-controls"
3246
+ }), this.element.appendChild(this._renderRotationSection());
3247
+ const e = d("div", { className: "crop-section" });
3248
+ e.appendChild(d("label", { className: "section-label" }, "Shape"));
3249
+ const t = d("div", { className: "chip-row" }), i = this.state.get("crop.shape");
3250
+ ot.forEach((l) => {
3251
+ const h = se({
3252
+ label: l.name,
3253
+ icon: l.icon,
3254
+ active: i === l.id,
3255
+ onClick: () => this._selectShape(l.id)
3256
+ });
3257
+ h.dataset.shape = l.id, h.dataset.testid = `crop-shape-${l.id}`, this._shapeChips.set(l.id, h), t.appendChild(h);
3258
+ }), e.appendChild(t), this.element.appendChild(e), this.state.get("lockCropShape") && (e.style.display = "none"), this._shapeSection = e, this._aspectSection = d("div", { className: "crop-section" }), this._aspectSection.appendChild(d("label", { className: "section-label" }, "Aspect Ratio"));
3259
+ const s = d("div", { className: "chip-row aspect-row" }), r = this.state.get("crop.aspect");
3260
+ at.forEach((l) => {
3261
+ const h = se({
3262
+ label: l.name,
3263
+ active: r === l.id,
3264
+ onClick: () => this._selectAspect(l.id)
3265
+ });
3266
+ h.dataset.ratio = l.id, h.dataset.testid = `crop-ratio-${l.id}`, this._aspectChips.set(l.id, h), s.appendChild(h);
3267
+ }), this._aspectSection.appendChild(s), this.element.appendChild(this._aspectSection), this._updateAspectVisibility();
3268
+ const o = d("div", { className: "crop-actions" }), n = j({
3269
+ label: "Cancel",
3270
+ className: "btn-secondary crop-cancel-btn",
3271
+ icon: de,
3272
+ onClick: () => this.cropManager.cancel()
3273
+ });
3274
+ n.dataset.testid = "cancel-crop";
3275
+ const a = j({
3276
+ label: "Apply Crop",
3277
+ className: "btn-primary crop-apply-btn",
3278
+ icon: he,
3279
+ onClick: () => this._applyCropAndReturnToFilters()
3280
+ });
3281
+ return a.dataset.testid = "apply-crop", o.appendChild(n), o.appendChild(a), this.element.appendChild(o), this._subscribeToState(), this.element;
3282
+ }
3283
+ _renderRotationSection() {
3284
+ const e = d("div", { className: "crop-section rotation-section" });
3285
+ e.appendChild(d("label", { className: "section-label" }, "Rotate"));
3286
+ const t = d("div", { className: "rotation-action-row" }), i = j({
3287
+ label: "Left 90°",
3288
+ className: "btn-secondary rotation-action-btn",
3289
+ icon: ze,
3290
+ onClick: () => this._rotateBy(-90)
3291
+ });
3292
+ i.dataset.testid = "rotate-left-90";
3293
+ const s = j({
3294
+ label: "Right 90°",
3295
+ className: "btn-secondary rotation-action-btn",
3296
+ icon: Pe,
3297
+ onClick: () => this._rotateBy(90)
3298
+ });
3299
+ s.dataset.testid = "rotate-right-90";
3300
+ const r = j({
3301
+ label: "Rotate 180°",
3302
+ className: "btn-secondary rotation-action-btn",
3303
+ onClick: () => this._rotateBy(180)
3304
+ });
3305
+ r.dataset.testid = "rotate-180";
3306
+ const o = j({
3307
+ label: "Rotate 270°",
3308
+ className: "btn-secondary rotation-action-btn",
3309
+ onClick: () => this._rotateBy(270)
3310
+ });
3311
+ o.dataset.testid = "rotate-270", t.appendChild(i), t.appendChild(s), t.appendChild(r), t.appendChild(o), e.appendChild(t);
3312
+ const n = d("div", { className: "chip-row rotation-preset-row" }), a = W(this.state.get("transform.rotation") ?? 0);
3313
+ lt.forEach((u) => {
3314
+ const f = se({
3315
+ label: u.label,
3316
+ active: Math.round(a) === u.angle,
3317
+ onClick: () => this._setRotation(u.angle)
3318
+ });
3319
+ f.dataset.angle = u.id, f.dataset.testid = `rotation-preset-${u.id}`, this._rotationPresetChips.set(u.id, f), n.appendChild(f);
3320
+ }), e.appendChild(n);
3321
+ const l = d("div", { className: "rotation-arbitrary" }), h = d(
3322
+ "div",
3323
+ { className: "rotation-angle-header" },
3324
+ d("label", { className: "rotation-angle-label", for: "image-editor-rotation-angle" }, "Angle"),
3325
+ d("span", { className: "rotation-angle-value" }, this._formatAngle(a))
3326
+ );
3327
+ this._rotationValue = h.querySelector(".rotation-angle-value"), this._rotationRange = d("input", {
3328
+ id: "image-editor-rotation-angle",
3329
+ type: "range",
3330
+ className: "slider-input rotation-range",
3331
+ min: "0",
3332
+ max: "360",
3333
+ step: "1",
3334
+ value: String(Math.round(a)),
3335
+ "aria-label": "Rotation angle",
3336
+ onInput: (u) => this._previewAngleValue(u.target.value),
3337
+ onChange: (u) => this._setRotation(Number(u.target.value))
3338
+ }), this._rotationNumber = d("input", {
3339
+ type: "number",
3340
+ className: "text-input rotation-number-input",
3341
+ min: "-360",
3342
+ max: "360",
3343
+ step: "1",
3344
+ value: this._formatPlainAngle(a),
3345
+ "aria-label": "Rotation angle in degrees",
3346
+ onKeyDown: (u) => {
3347
+ u.key === "Enter" && (u.preventDefault(), this._setRotation(Number(u.target.value)), u.target.blur());
3348
+ },
3349
+ onBlur: (u) => this._setRotation(Number(u.target.value))
3350
+ });
3351
+ const c = d(
3352
+ "div",
3353
+ { className: "rotation-angle-row" },
3354
+ this._rotationRange,
3355
+ this._rotationNumber
3356
+ );
3357
+ l.appendChild(h), l.appendChild(c);
3358
+ const p = j({
3359
+ label: "Reset rotation",
3360
+ className: "btn-text rotation-reset-btn",
3361
+ onClick: () => this._setRotation(0)
3362
+ });
3363
+ return p.dataset.testid = "reset-rotation", l.appendChild(p), e.appendChild(l), e;
3364
+ }
3365
+ /**
3366
+ * Subscribe to state changes
3367
+ */
3368
+ _subscribeToState() {
3369
+ const e = this.state.on("change:crop.shape", ({ value: s }) => {
3370
+ this._shapeChips.forEach((r, o) => {
3371
+ r.setActive(o === s);
3372
+ }), this._updateAspectVisibility();
3373
+ });
3374
+ this._unsubscribers.push(e);
3375
+ const t = this.state.on("change:crop.aspect", ({ value: s }) => {
3376
+ this._aspectChips.forEach((r, o) => {
3377
+ r.setActive(o === s);
3378
+ });
3379
+ });
3380
+ this._unsubscribers.push(t);
3381
+ const i = this.state.on("change:transform.rotation", ({ value: s }) => {
3382
+ this._syncRotationControls(s);
3383
+ });
3384
+ this._unsubscribers.push(i);
3385
+ }
3386
+ _rotateBy(e) {
3387
+ var i, s;
3388
+ ((s = (i = this.editor) == null ? void 0 : i.rotateBy) == null ? void 0 : s.call(i, e)) !== !1 && this._syncRotationControls(this.state.get("transform.rotation") ?? 0);
3389
+ }
3390
+ _setRotation(e) {
3391
+ var s, r;
3392
+ const t = W(e);
3393
+ if (((r = (s = this.editor) == null ? void 0 : s.setRotationAngle) == null ? void 0 : r.call(s, t)) === !1) {
3394
+ this._syncRotationControls(this.state.get("transform.rotation") ?? 0);
3395
+ return;
3396
+ }
3397
+ this._syncRotationControls(t);
3398
+ }
3399
+ _previewAngleValue(e) {
3400
+ const t = W(Number(e));
3401
+ this._rotationValue && (this._rotationValue.textContent = this._formatAngle(t)), this._rotationNumber && (this._rotationNumber.value = this._formatPlainAngle(t)), this._refreshPresetChips(t);
3402
+ }
3403
+ _syncRotationControls(e) {
3404
+ const t = W(e);
3405
+ this._rotationValue && (this._rotationValue.textContent = this._formatAngle(t)), this._rotationRange && (this._rotationRange.value = String(Math.round(t))), this._rotationNumber && (this._rotationNumber.value = this._formatPlainAngle(t)), this._refreshPresetChips(t);
3406
+ }
3407
+ _refreshPresetChips(e) {
3408
+ const t = Math.round(W(e)) % 360;
3409
+ this._rotationPresetChips.forEach((i, s) => {
3410
+ i.setActive(Number(s) === t);
3411
+ });
3412
+ }
3413
+ _formatAngle(e) {
3414
+ return `${this._formatPlainAngle(e)}°`;
3415
+ }
3416
+ _formatPlainAngle(e) {
3417
+ const t = W(e);
3418
+ return Number.isInteger(t) ? String(t) : t.toFixed(1);
3419
+ }
3420
+ /**
3421
+ * Select a crop shape
3422
+ * @param {string} shapeId
3423
+ */
3424
+ _selectShape(e) {
3425
+ this.cropManager.setShape(e);
3426
+ }
3427
+ /**
3428
+ * Select an aspect ratio
3429
+ * @param {string} aspectId
3430
+ */
3431
+ _selectAspect(e) {
3432
+ this.cropManager.setAspect(e);
3433
+ }
3434
+ /**
3435
+ * Update aspect ratio section visibility
3436
+ */
3437
+ _updateAspectVisibility() {
3438
+ const e = this.state.get("crop.shape"), t = this.state.get("lockAspectRatio");
3439
+ this._aspectSection && (this._aspectSection.style.display = e === "free" && !t ? "" : "none");
3440
+ }
3441
+ /**
3442
+ * Show controls
3443
+ */
3444
+ show() {
3445
+ this.element && (this.element.style.display = "");
3446
+ }
3447
+ /**
3448
+ * Hide controls
3449
+ */
3450
+ hide() {
3451
+ this.element && (this.element.style.display = "none");
3452
+ }
3453
+ /**
3454
+ * Clean up
3455
+ */
3456
+ destroy() {
3457
+ var e;
3458
+ this._unsubscribers.forEach((t) => t()), this._unsubscribers = [], this._shapeChips.clear(), this._aspectChips.clear(), this._rotationPresetChips.clear(), (e = this.element) == null || e.remove(), this.element = null;
3459
+ }
3460
+ }
3461
+ class ct {
3462
+ constructor(e, t) {
3463
+ this.state = e, this.filterManager = t, this.element = null, this._filterItems = /* @__PURE__ */ new Map(), this._unsubscribers = [], this._onRemove = null, this._onReset = null, this._onClearAll = null, this._onUpdateValue = null, this._onSelect = null;
3464
+ }
3465
+ /**
3466
+ * Create and render the active filters panel
3467
+ * @param {Object} options - { onRemove, onReset, onClearAll, onUpdateValue, onSelect }
3468
+ * @returns {HTMLElement}
3469
+ */
3470
+ render({ onRemove: e, onReset: t, onClearAll: i, onUpdateValue: s, onSelect: r }) {
3471
+ this._onRemove = e, this._onReset = t, this._onClearAll = i, this._onUpdateValue = s, this._onSelect = r, this.element = d("div", {
3472
+ className: "active-filters-panel",
3473
+ "data-testid": "active-filters-panel"
3474
+ });
3475
+ const o = d("div", { className: "panel-header" });
3476
+ o.appendChild(d("h3", { className: "panel-title" }, "Active Filters"));
3477
+ const n = j({
3478
+ label: "Clear All",
3479
+ className: "btn-text btn-danger",
3480
+ onClick: () => this._handleClearAll()
3481
+ });
3482
+ return n.dataset.testid = "clear-all-filters", o.appendChild(n), this.element.appendChild(o), this._listContainer = d("div", { className: "active-filters-list" }), this.element.appendChild(this._listContainer), this._subscribeToState(), this._renderFilterList(), this.element;
3483
+ }
3484
+ /**
3485
+ * Subscribe to state changes
3486
+ */
3487
+ _subscribeToState() {
3488
+ const e = this.state.on("change:activeFilters", () => {
3489
+ this._renderFilterList();
3490
+ });
3491
+ this._unsubscribers.push(e);
3492
+ const t = this.state.on("change:filterValues", () => {
3493
+ this._updateValuesDisplay();
3494
+ });
3495
+ this._unsubscribers.push(t);
3496
+ const i = this.state.on("change:selectedFilter", () => {
3497
+ this._updateSelectedState();
3498
+ });
3499
+ this._unsubscribers.push(i);
3500
+ }
3501
+ /**
3502
+ * Update selected state for all items
3503
+ */
3504
+ _updateSelectedState() {
3505
+ const e = this.state.get("selectedFilter");
3506
+ this._filterItems.forEach((t, i) => {
3507
+ t.classList.toggle("selected", i === e);
3508
+ });
3509
+ }
3510
+ /**
3511
+ * Render the filter list
3512
+ */
3513
+ _renderFilterList() {
3514
+ this._listContainer.innerHTML = "", this._filterItems.clear();
3515
+ const e = this.state.get("activeFilters");
3516
+ if (e.size === 0) {
3517
+ this._listContainer.appendChild(
3518
+ d("div", { className: "no-filters-message" }, "No filters active")
3519
+ );
3520
+ return;
3521
+ }
3522
+ e.forEach((t) => {
3523
+ const i = this.filterManager.getFilterDef(t);
3524
+ if (!i) return;
3525
+ const s = this._createFilterItem(t, i);
3526
+ this._filterItems.set(t, s), this._listContainer.appendChild(s);
3527
+ });
3528
+ }
3529
+ /**
3530
+ * Create a filter item
3531
+ * @param {string} filterId
3532
+ * @param {Object} def - Filter definition
3533
+ * @returns {HTMLElement}
3534
+ */
3535
+ _createFilterItem(e, t) {
3536
+ const s = this.state.get("selectedFilter") === e, r = d("div", {
3537
+ className: `active-filter-item ${s ? "selected" : ""}`,
3538
+ "data-active-filter": e,
3539
+ "data-testid": `active-filter-${e}`,
3540
+ onClick: (u) => {
3541
+ u.target.closest(".filter-item-actions") || this._handleSelect(e);
3542
+ }
3543
+ }), o = d("div", { className: "filter-item-header" }), n = d("span", { className: "filter-item-name" }, t.name), a = d("div", { className: "filter-item-actions" }), l = O({
3544
+ icon: ye,
3545
+ title: "Reset filter",
3546
+ className: "btn-icon-sm",
3547
+ onClick: () => this._handleReset(e)
3548
+ }), h = O({
3549
+ icon: He,
3550
+ title: "Remove filter",
3551
+ className: "btn-icon-sm btn-danger",
3552
+ onClick: () => this._handleRemove(e)
3553
+ });
3554
+ a.appendChild(l), a.appendChild(h), o.appendChild(n), o.appendChild(a), r.appendChild(o);
3555
+ const c = this.state.getFilterValues(e), p = d("div", { className: "filter-item-summary" });
3556
+ return p.textContent = this._getValuesSummary(t, c), r.appendChild(p), r._summaryEl = p, r;
3557
+ }
3558
+ /**
3559
+ * Normalize control type (handle aliases)
3560
+ * @param {string} type
3561
+ * @returns {string}
3562
+ */
3563
+ _normalizeControlType(e) {
3564
+ return {
3565
+ slider: "slider",
3566
+ range: "slider",
3567
+ toggle: "toggle",
3568
+ checkbox: "toggle",
3569
+ color: "color",
3570
+ select: "select",
3571
+ dropdown: "select",
3572
+ button: "button"
3573
+ }[e] || e;
3574
+ }
3575
+ /**
3576
+ * Get a summary of filter values
3577
+ * @param {Object} def - Filter definition
3578
+ * @param {Object} values - Current values
3579
+ * @returns {string}
3580
+ */
3581
+ _getValuesSummary(e, t) {
3582
+ if (!e.controls || !Array.isArray(e.controls))
3583
+ return "Default values";
3584
+ const i = [];
3585
+ return e.controls.forEach((s) => {
3586
+ const r = this._normalizeControlType(s.type);
3587
+ if (r === "button") return;
3588
+ const o = s.label || s.id, n = t[s.id] ?? s.default;
3589
+ n !== s.default && (r === "slider" ? i.push(`${o}: ${this._formatValue(n)}`) : r === "toggle" ? n && i.push(o) : r === "color" ? i.push(`${o}: ${n}`) : r === "select" && i.push(`${o}: ${n}`));
3590
+ }), i.length > 0 ? i.join(", ") : "Default values";
3591
+ }
3592
+ /**
3593
+ * Format a numeric value for display
3594
+ * @param {number} value
3595
+ * @returns {string}
3596
+ */
3597
+ _formatValue(e) {
3598
+ return typeof e != "number" || Number.isInteger(e) ? String(e) : e.toFixed(2);
3599
+ }
3600
+ /**
3601
+ * Update values display for all items
3602
+ */
3603
+ _updateValuesDisplay() {
3604
+ this._filterItems.forEach((e, t) => {
3605
+ const i = this.filterManager.getFilterDef(t);
3606
+ if (!i || !e._summaryEl) return;
3607
+ const s = this.state.getFilterValues(t);
3608
+ e._summaryEl.textContent = this._getValuesSummary(i, s);
3609
+ });
3610
+ }
3611
+ /**
3612
+ * Handle filter removal
3613
+ * @param {string} filterId
3614
+ */
3615
+ _handleRemove(e) {
3616
+ var t;
3617
+ (t = this._onRemove) == null || t.call(this, e);
3618
+ }
3619
+ /**
3620
+ * Handle filter reset
3621
+ * @param {string} filterId
3622
+ */
3623
+ _handleReset(e) {
3624
+ var t;
3625
+ (t = this._onReset) == null || t.call(this, e);
3626
+ }
3627
+ /**
3628
+ * Handle clear all
3629
+ */
3630
+ _handleClearAll() {
3631
+ var e;
3632
+ (e = this._onClearAll) == null || e.call(this);
3633
+ }
3634
+ /**
3635
+ * Handle filter selection
3636
+ * @param {string} filterId
3637
+ */
3638
+ _handleSelect(e) {
3639
+ var t;
3640
+ this.state.set("selectedFilter", e), (t = this._onSelect) == null || t.call(this, e);
3641
+ }
3642
+ /**
3643
+ * Show panel
3644
+ */
3645
+ show() {
3646
+ this.element && (this.element.style.display = "");
3647
+ }
3648
+ /**
3649
+ * Hide panel
3650
+ */
3651
+ hide() {
3652
+ this.element && (this.element.style.display = "none");
3653
+ }
3654
+ /**
3655
+ * Clean up
3656
+ */
3657
+ destroy() {
3658
+ var e;
3659
+ this._unsubscribers.forEach((t) => t()), this._unsubscribers = [], this._filterItems.clear(), (e = this.element) == null || e.remove(), this.element = null;
3660
+ }
3661
+ }
3662
+ const $ = {
3663
+ name: "free",
3664
+ initialMode: "filters",
3665
+ cropShape: "free",
3666
+ aspectRatio: "free",
3667
+ autoZoomOnCropOverflow: !1,
3668
+ lockCropShape: !1,
3669
+ lockAspectRatio: !1,
3670
+ showFilters: !0,
3671
+ showCropControls: !0,
3672
+ maxExportWidth: void 0,
3673
+ maxExportHeight: void 0
3674
+ }, le = {
3675
+ free: {
3676
+ ...$
3677
+ },
3678
+ avatar: {
3679
+ ...$,
3680
+ name: "avatar",
3681
+ initialMode: "crop",
3682
+ cropShape: "circle",
3683
+ aspectRatio: "1:1",
3684
+ autoZoomOnCropOverflow: !0,
3685
+ lockCropShape: !0,
3686
+ lockAspectRatio: !0
3687
+ },
3688
+ banner: {
3689
+ ...$,
3690
+ name: "banner",
3691
+ initialMode: "crop",
3692
+ cropShape: "square",
3693
+ aspectRatio: "16:9",
3694
+ autoZoomOnCropOverflow: !1,
3695
+ lockCropShape: !1,
3696
+ lockAspectRatio: !0
3697
+ },
3698
+ cover: {
3699
+ ...$,
3700
+ name: "cover",
3701
+ initialMode: "crop",
3702
+ cropShape: "free",
3703
+ aspectRatio: "24:5",
3704
+ autoZoomOnCropOverflow: !0,
3705
+ lockCropShape: !0,
3706
+ lockAspectRatio: !0
3707
+ },
3708
+ product: {
3709
+ ...$,
3710
+ name: "product",
3711
+ initialMode: "filters",
3712
+ cropShape: "square",
3713
+ aspectRatio: "1:1",
3714
+ autoZoomOnCropOverflow: !1,
3715
+ lockCropShape: !1,
3716
+ lockAspectRatio: !1
3717
+ }
3718
+ };
3719
+ function dt(g) {
3720
+ if (!g)
3721
+ return { ...le.free };
3722
+ if (typeof g == "object" && g !== null)
3723
+ return { ...$, ...g };
3724
+ const e = le[g];
3725
+ return e ? { ...e } : { ...le.free };
3726
+ }
3727
+ function Z(g) {
3728
+ if (!Number.isFinite(Number(g))) return 0;
3729
+ const e = Number(g) % 360;
3730
+ return e < 0 ? e + 360 : e;
3731
+ }
3732
+ function ut(g, e) {
3733
+ const t = Z(g);
3734
+ return (Z(e) - t + 540) % 360 - 180;
3735
+ }
3736
+ class kt extends re {
3737
+ /**
3738
+ * Create a new VanillaImageEditor
3739
+ * @param {HTMLElement} container - Container element to mount the editor
3740
+ * @param {Object} options - Configuration options
3741
+ */
3742
+ constructor(e, t = {}) {
3743
+ var r, o, n, a;
3744
+ if (super(), !e)
3745
+ throw new Error("VanillaImageEditor: container element is required");
3746
+ this._container = e, this._destroyed = !1, this._loadVersion = 0, this._objectUrls = /* @__PURE__ */ new Set(), this._activeObjectUrl = null;
3747
+ const i = t.preset ? dt(t.preset) : null, s = i ? {
3748
+ initialMode: i.initialMode === "crop" ? "crop" : "adjust",
3749
+ cropShape: i.cropShape,
3750
+ initialAspectRatio: i.aspectRatio
3751
+ } : {};
3752
+ this._options = {
3753
+ theme: "auto",
3754
+ initialImage: null,
3755
+ initialMode: "adjust",
3756
+ cropShape: "free",
3757
+ initialAspectRatio: "free",
3758
+ backgroundRemoval: {
3759
+ enabled: !0,
3760
+ endpoint: "/api/v1/media/background-removal/preview",
3761
+ optionsEndpoint: "/api/v1/media/background-removal/options",
3762
+ fallbackEndpoint: null
3763
+ },
3764
+ ...s,
3765
+ ...t
3766
+ }, this._preset = i, this._state = Se(), this._state.set("theme", this._options.theme), this._state.set("crop.shape", this._options.cropShape), this._state.set("crop.aspect", this._options.initialAspectRatio), this._preset && (this._state.set("lockCropShape", !!this._preset.lockCropShape), this._state.set("lockAspectRatio", !!this._preset.lockAspectRatio), this._state.set("showFilters", this._preset.showFilters !== !1), this._state.set("showCropControls", this._preset.showCropControls !== !1), this._state.set("autoZoomOnCropOverflow", !!this._preset.autoZoomOnCropOverflow)), this._renderer = new Te(), this._filterManager = new Qe(this._state, this._renderer), this._cropManager = new ce(this._state, this._renderer), this._removeBgManager = null, this._backgroundRemovalAvailable = !1, ((r = this._options.backgroundRemoval) == null ? void 0 : r.enabled) !== !1 && (this._removeBgManager = new Xe({
3767
+ endpoint: (o = this._options.backgroundRemoval) == null ? void 0 : o.endpoint,
3768
+ optionsEndpoint: (n = this._options.backgroundRemoval) == null ? void 0 : n.optionsEndpoint,
3769
+ fallbackEndpoint: (a = this._options.backgroundRemoval) == null ? void 0 : a.fallbackEndpoint
3770
+ })), this._loadingOverlay = null, this._isMobile = typeof window < "u" ? window.innerWidth <= 768 : !1, this._toolbar = null, this._categoryCarousel = null, this._filterCarousel = null, this._filterAdjustments = null, this._mobileFilterDrawer = null, this._mobileActiveFilters = null, this._cropControls = null, this._activeFiltersPanel = null, this._editorEl = null, this._canvasContainer = null, this._cropOverlay = null, this._controlsSection = null, this._filterControlsEl = null, this._fileInput = null, this._resizeObserver = null, this._initPromise = Promise.resolve(this._init()).catch((l) => {
3771
+ this._destroyed || this.emit("error", l instanceof Error ? l : new Error(String(l)));
3772
+ });
3773
+ }
3774
+ /**
3775
+ * Initialize the editor
3776
+ */
3777
+ async _init() {
3778
+ this._state.detectTheme(), this._buildDOM(), !(!await this._renderer.mount(
3779
+ this._canvasContainer,
3780
+ this._state.get("isDarkMode") ? 657930 : 16777215
3781
+ ) || this._destroyed) && (this._cropManager.setOverlayCanvas(this._cropOverlay), this._initUI(), this._setupResizeObserver(), this._setupWindowResize(), this._subscribeToState(), this._refreshBackgroundRemovalAvailability(), this._options.initialImage && await this.loadImage(this._options.initialImage), this.emit("ready"));
3782
+ }
3783
+ /**
3784
+ * Build DOM structure
3785
+ */
3786
+ _buildDOM() {
3787
+ this._container.innerHTML = "", this._editorEl = d("div", {
3788
+ className: `vanilla-image-editor ${this._state.get("isDarkMode") ? "dark" : "light"}`
3789
+ });
3790
+ const e = d("div", { className: "editor-toolbar-container" });
3791
+ this._editorEl.appendChild(e), this._toolbarContainer = e;
3792
+ const t = d("div", { className: "editor-content" }), i = d("div", { className: "canvas-section" });
3793
+ this._canvasContainer = d("div", { className: "canvas-container" }), this._cropOverlay = d("canvas", { className: "crop-overlay", "aria-hidden": "true" }), i.appendChild(this._canvasContainer), i.appendChild(this._cropOverlay), t.appendChild(i), this._controlsSection = d("div", { className: "controls-section" }), t.appendChild(this._controlsSection), this._editorEl.appendChild(t), this._fileInput = d("input", {
3794
+ type: "file",
3795
+ accept: "image/*",
3796
+ className: "hidden-file-input",
3797
+ "aria-label": "Choose image file to edit",
3798
+ onChange: (s) => this._handleFileSelect(s)
3799
+ }), this._editorEl.appendChild(this._fileInput), this._container.appendChild(this._editorEl);
3800
+ }
3801
+ /**
3802
+ * Initialize UI components
3803
+ */
3804
+ _initUI() {
3805
+ this._toolbar = new et(this._state, this), this._toolbarContainer.appendChild(this._toolbar.render()), this._filterControlsEl = d("div", { className: "filter-controls-container" }), this._categoryCarousel = new tt(this._state, this), this._filterControlsEl.appendChild(this._categoryCarousel.render((s) => {
3806
+ }));
3807
+ const e = d("div", { className: "filter-layout" }), t = d("div", { className: "filter-left-column" });
3808
+ this._filterCarousel = new it(this._state, this._filterManager), t.appendChild(this._filterCarousel.render({
3809
+ onToggle: (s, r) => this._handleFilterToggle(s, r),
3810
+ onSelect: (s) => {
3811
+ this._handleFilterSelect(s), this._isMobile && this._mobileFilterDrawer && this._mobileFilterDrawer.open(s);
3812
+ }
3813
+ })), this._filterAdjustments = new st(this._state, this._filterManager), t.appendChild(this._filterAdjustments.render({
3814
+ onChange: (s, r, o) => this._handleFilterChange(s, r, o),
3815
+ onReset: (s) => this._handleFilterReset(s),
3816
+ onAction: (s, r) => this._handleFilterAction(s, r)
3817
+ })), e.appendChild(t);
3818
+ const i = d("div", { className: "filter-right-column" });
3819
+ this._activeFiltersPanel = new ct(this._state, this._filterManager), i.appendChild(this._activeFiltersPanel.render({
3820
+ onRemove: (s) => this._handleFilterToggle(s, !1),
3821
+ onReset: (s) => this._handleFilterReset(s),
3822
+ onClearAll: () => this.resetAll(),
3823
+ onSelect: (s) => this._handleFilterSelect(s)
3824
+ })), e.appendChild(i), this._filterControlsEl.appendChild(e), this._mobileActiveFilters = new nt(this._state, this._filterManager), this._filterControlsEl.appendChild(this._mobileActiveFilters.render({
3825
+ onToggle: (s, r) => this._handleFilterToggle(s, r),
3826
+ onSelect: (s) => {
3827
+ this._handleFilterSelect(s), this._isMobile && this._mobileFilterDrawer && this._mobileFilterDrawer.open(s);
3828
+ }
3829
+ })), this._cropControls = new ht(this._state, this._cropManager, this), this._cropControlsEl = this._cropControls.render(), this._cropControlsEl.style.display = "none", this._controlsSection.appendChild(this._filterControlsEl), this._controlsSection.appendChild(this._cropControlsEl), this._mobileFilterDrawer = new rt(this._state, this._filterManager), this._mobileFilterDrawer.build(this._controlsSection, {
3830
+ onChange: (s, r, o) => this._handleFilterChange(s, r, o),
3831
+ onReset: (s) => this._handleFilterReset(s),
3832
+ onRemove: (s) => {
3833
+ this._handleFilterToggle(s, !1), this._state.set("selectedFilter", null);
3834
+ },
3835
+ onAction: (s, r) => this._handleFilterAction(s, r)
3836
+ });
3837
+ }
3838
+ /**
3839
+ * Set up resize observer with debounce to prevent infinite loops.
3840
+ * Without debounce, PIXI renderer.resize() changes the canvas buffer
3841
+ * which triggers another ResizeObserver notification, causing a loop
3842
+ * that floods the console with "ResizeObserver loop completed" errors
3843
+ * and prevents the image from rendering (gray canvas).
3844
+ */
3845
+ _setupResizeObserver() {
3846
+ let e = null;
3847
+ this._resizeObserver = new ResizeObserver(() => {
3848
+ clearTimeout(e), e = setTimeout(() => {
3849
+ this._canvasContainer && (this._renderer.resizeTo(this._canvasContainer), this._state.get("mode") === "crop" && this._cropManager.drawOverlay());
3850
+ }, 50);
3851
+ }), this._resizeObserver.observe(this._canvasContainer);
3852
+ }
3853
+ /**
3854
+ * Set up window resize listener for mobile detection
3855
+ */
3856
+ _setupWindowResize() {
3857
+ this._handleWindowResize = () => {
3858
+ var e;
3859
+ this._isMobile = window.innerWidth <= 768, !this._isMobile && ((e = this._mobileFilterDrawer) != null && e.isOpen) && this._mobileFilterDrawer.close();
3860
+ }, window.addEventListener("resize", this._handleWindowResize);
3861
+ }
3862
+ /**
3863
+ * Subscribe to state changes
3864
+ */
3865
+ _subscribeToState() {
3866
+ this._state.on("change:mode", ({ value: e }) => {
3867
+ this._updateModeUI(e), e === "filters" && this._filterManager.applyFilters();
3868
+ }), this._state.on("change:isDarkMode", ({ value: e }) => {
3869
+ this._editorEl.classList.toggle("dark", e), this._editorEl.classList.toggle("light", !e), this._renderer.setBackgroundColor(e ? 657930 : 16777215);
3870
+ }), this._state.on("change:hasImage", ({ value: e }) => {
3871
+ this._editorEl.classList.toggle("has-image", e);
3872
+ }), this._renderer.on("zoomChange", (e) => {
3873
+ this._state.set("zoom", e);
3874
+ });
3875
+ }
3876
+ /**
3877
+ * Update UI based on mode
3878
+ * @param {string} mode
3879
+ */
3880
+ _updateModeUI(e) {
3881
+ var t;
3882
+ e === "crop" ? (this._filterControlsEl.style.display = "none", this._cropControlsEl.style.display = "", (t = this._mobileFilterDrawer) != null && t.isOpen && this._mobileFilterDrawer.close()) : (this._filterControlsEl.style.display = "", this._cropControlsEl.style.display = "none");
3883
+ }
3884
+ /**
3885
+ * Handle file selection
3886
+ * @param {Event} event
3887
+ */
3888
+ async _handleFileSelect(e) {
3889
+ var i;
3890
+ const t = (i = e.target.files) == null ? void 0 : i[0];
3891
+ t && await this.loadImage(t), this._fileInput.value = "";
3892
+ }
3893
+ /**
3894
+ * Handle filter toggle
3895
+ * @param {string} filterId
3896
+ * @param {boolean} enabled
3897
+ */
3898
+ _handleFilterToggle(e, t) {
3899
+ this._filterManager.toggle(e, t);
3900
+ }
3901
+ /**
3902
+ * Handle filter select
3903
+ * @param {string} filterId
3904
+ */
3905
+ _handleFilterSelect(e) {
3906
+ this._state.set("selectedFilter", e);
3907
+ }
3908
+ /**
3909
+ * Handle filter value change
3910
+ * @param {string} filterId
3911
+ * @param {string} controlId
3912
+ * @param {*} value
3913
+ */
3914
+ _handleFilterChange(e, t, i) {
3915
+ this._filterManager.updateValue(e, t, i) || this._filterManager.applyFilters(), this._renderer.render();
3916
+ }
3917
+ /**
3918
+ * Handle filter reset
3919
+ * @param {string} filterId
3920
+ */
3921
+ _handleFilterReset(e) {
3922
+ this._filterManager.resetValues(e), this._filterManager.applyFilters();
3923
+ }
3924
+ /**
3925
+ * Handle filter action (button click).
3926
+ * Delegates to FilterManager.performFilterAction which syncs state after
3927
+ * the action so the result survives filter recreation.
3928
+ * @param {string} filterId
3929
+ * @param {string} action
3930
+ */
3931
+ _handleFilterAction(e, t) {
3932
+ this._filterManager.performFilterAction(e, t);
3933
+ }
3934
+ _revokeObjectUrl(e) {
3935
+ !e || !this._objectUrls.has(e) || (URL.revokeObjectURL(e), this._objectUrls.delete(e), this._activeObjectUrl === e && (this._activeObjectUrl = null));
3936
+ }
3937
+ _replaceActiveObjectUrl(e) {
3938
+ const t = this._activeObjectUrl;
3939
+ this._activeObjectUrl = e, this._objectUrls.add(e), t && t !== e && this._revokeObjectUrl(t);
3940
+ }
3941
+ _clearActiveObjectUrl() {
3942
+ this._activeObjectUrl && this._revokeObjectUrl(this._activeObjectUrl);
3943
+ }
3944
+ // ==================== Public API ====================
3945
+ /**
3946
+ * Set the filter registry (from mediables/filters)
3947
+ * @param {Object} registry - { getFilter, getAllFilters, getFiltersByCategory }
3948
+ */
3949
+ setFilterRegistry(e) {
3950
+ this._filterManager.setRegistry(e);
3951
+ }
3952
+ /**
3953
+ * Load an image into the editor
3954
+ * @param {string|Blob|File} imageSource - URL, data URL, Blob, or File object
3955
+ * @param {Object} [options] - Load options
3956
+ * @param {Object|null} [options.state] - ImageEditorSessionState to restore
3957
+ * @param {string|number} [options.sessionKey] - Session boundary identifier
3958
+ */
3959
+ async loadImage(e, t = {}) {
3960
+ var n, a;
3961
+ if (this._destroyed) return;
3962
+ (a = (n = this._removeBgManager) == null ? void 0 : n.cancelActiveRequests) == null || a.call(n, "image-load");
3963
+ const i = ++this._loadVersion;
3964
+ let s = e, r = null;
3965
+ this._resetEditorState(), this._state.set("hasImage", !1), this._state.set("imageUrl", null), e instanceof Blob && (s = URL.createObjectURL(e), r = s, this._objectUrls.add(s));
3966
+ const o = await this._renderer.loadTexture(s, {
3967
+ isCurrent: () => !this._destroyed && this._loadVersion === i
3968
+ });
3969
+ if (this._destroyed || this._loadVersion !== i) {
3970
+ this._revokeObjectUrl(r);
3971
+ return;
3972
+ }
3973
+ if (!o) {
3974
+ this._revokeObjectUrl(r), this.emit("error", new Error("Failed to load image"));
3975
+ return;
3976
+ }
3977
+ r ? this._replaceActiveObjectUrl(r) : this._clearActiveObjectUrl(), this._state.set("hasImage", !0), this._state.set("imageUrl", s), t.state && this._hydrateState(t.state), this._options.initialMode === "crop" && this.setMode("crop"), this.emit("imageLoaded", { url: s });
3978
+ }
3979
+ /**
3980
+ * Reset all editor state (filters + crop) to a clean baseline.
3981
+ * Called at the start of loadImage() to prevent stale state bleed between sessions.
3982
+ */
3983
+ _resetEditorState() {
3984
+ this._filterManager.resetAll(), this._state.set("crop.rect", null), this._state.set("crop.appliedRect", null), this._state.set("crop.appliedShape", null), this._state.set("crop.appliedAspect", null), this._state.set("crop.dirty", !1), this._state.set("crop.shape", this._options.cropShape || "free"), this._state.set("crop.aspect", this._options.initialAspectRatio || "free"), this._state.set("transform.rotation", 0), this._state.get("mode") === "crop" && (this._cropManager.disable(), this._state.set("mode", "filters"));
3985
+ }
3986
+ /**
3987
+ * Hydrate editor state from a canonical ImageEditorSessionState object.
3988
+ * Restores filter stack + values and crop metadata (shape, aspect, rect).
3989
+ * @param {Object} state - ImageEditorSessionState
3990
+ */
3991
+ _hydrateState(e) {
3992
+ var i;
3993
+ if (!e || e.version !== 1) return;
3994
+ const t = Z(((i = e.transform) == null ? void 0 : i.rotation) ?? 0);
3995
+ if (t !== 0 && this._renderer.rotateBy(t) && this._state.set("transform.rotation", t), e.crop) {
3996
+ const s = e.crop.shape || "free", r = e.crop.aspectRatio || "free";
3997
+ e.crop.rect && (this._cropManager.applyFromPixelRect(e.crop.rect, s), this._state.set("crop.appliedRect", { ...e.crop.rect }), this._state.set("crop.appliedShape", s), this._state.set("crop.appliedAspect", r)), this._state.set("crop.shape", s), this._state.set("crop.aspect", r);
3998
+ }
3999
+ if (Array.isArray(e.filters)) {
4000
+ let s = null;
4001
+ for (const r of e.filters)
4002
+ if (r.enabled && (s === null && (s = r.id), this._state.toggleFilter(r.id, !0), this._filterManager.initializeValues(r.id), r.values))
4003
+ for (const [o, n] of Object.entries(r.values))
4004
+ this._state.setFilterValue(r.id, o, n);
4005
+ if (s !== null) {
4006
+ this._state.set("selectedFilter", s);
4007
+ const r = this._filterManager.getFilterDef(s);
4008
+ if (typeof (r == null ? void 0 : r.category) == "string") {
4009
+ const o = Ye[r.category] ?? r.category;
4010
+ this._state.set("selectedCategory", o);
4011
+ }
4012
+ }
4013
+ this._filterManager.applyFilters();
4014
+ }
4015
+ }
4016
+ /**
4017
+ * Open the file picker dialog
4018
+ */
4019
+ openFilePicker() {
4020
+ var e;
4021
+ (e = this._fileInput) == null || e.click();
4022
+ }
4023
+ /**
4024
+ * Export the current image.
4025
+ *
4026
+ * @param {string} format - 'png' or 'jpeg'
4027
+ * @param {number} quality - Quality for jpeg (0-1)
4028
+ * @param {Object} [options]
4029
+ * @param {number} [options.maxEdge] - Cap the longest edge to this pixel count
4030
+ * @param {number} [options.maxPixels] - Cap total pixel count (width*height)
4031
+ * @returns {string|null} Data URL
4032
+ */
4033
+ exportImage(e = "png", t = 0.92, i = {}) {
4034
+ return !this._state.get("hasImage") || !this._applyPendingCropBeforeExport("VanillaImageEditor.exportImage") ? null : this._renderer.exportImage(
4035
+ e,
4036
+ t,
4037
+ i.maxEdge ?? 0,
4038
+ i.dontUpscale !== !1,
4039
+ i.maxPixels ?? 0
4040
+ );
4041
+ }
4042
+ /**
4043
+ * Export the current image as a Blob after finalizing pending crop edits.
4044
+ *
4045
+ * @param {string} format - 'png' or 'jpeg'
4046
+ * @param {number} quality - Quality for jpeg (0-1)
4047
+ * @param {Object} [options]
4048
+ * @returns {Promise<{blob: Blob, width: number, height: number}|null>}
4049
+ */
4050
+ async exportBlob(e = "png", t = 0.92, i = {}) {
4051
+ return !this._state.get("hasImage") || !this._applyPendingCropBeforeExport("VanillaImageEditor.exportBlob") ? null : this._renderer.exportBlob(e, t, i);
4052
+ }
4053
+ _applyPendingCropBeforeExport(e) {
4054
+ return !(this._state.get("crop.rect") && !this._cropManager.apply());
4055
+ }
4056
+ /**
4057
+ * Save the image (triggers download and emits 'save' event)
4058
+ */
4059
+ async save() {
4060
+ if (!this._state.get("hasImage")) return;
4061
+ this._state.set("isSaving", !0);
4062
+ let e = null;
4063
+ try {
4064
+ const t = await this.exportBlob("png", 0.92);
4065
+ if (!t) throw new Error("Failed to export image");
4066
+ const i = document.createElement("a");
4067
+ e = URL.createObjectURL(t.blob), i.href = e, i.download = `edited-image-${Date.now()}.png`, document.body.appendChild(i), i.click(), document.body.removeChild(i), this.emit("save", {
4068
+ blob: t.blob,
4069
+ mimeType: t.blob.type || "image/png",
4070
+ dimensions: { width: t.width, height: t.height },
4071
+ state: this.getSerializableState()
4072
+ });
4073
+ } catch (t) {
4074
+ this.emit("error", { error: t });
4075
+ } finally {
4076
+ e && URL.revokeObjectURL(e), this._state.set("isSaving", !1);
4077
+ }
4078
+ }
4079
+ /**
4080
+ * Get a canonical, serializable snapshot of the current editor state.
4081
+ * Safe for persistence — returns a deep copy with no mutable references.
4082
+ * @returns {Object} ImageEditorSessionState
4083
+ */
4084
+ getSerializableState() {
4085
+ const e = this._state.get("activeFilters"), t = [];
4086
+ if (e)
4087
+ for (const o of e) {
4088
+ const n = this._state.getFilterValues(o);
4089
+ t.push({
4090
+ id: o,
4091
+ enabled: !0,
4092
+ values: { ...n }
4093
+ });
4094
+ }
4095
+ const i = this._state.get("crop.appliedRect"), s = this._state.get("crop.rect"), r = i || s;
4096
+ return {
4097
+ version: 1,
4098
+ crop: {
4099
+ rect: r ? { ...r } : null,
4100
+ aspectRatio: this._state.get("crop.appliedAspect") || this._state.get("crop.aspect") || "free",
4101
+ shape: this._state.get("crop.appliedShape") || this._state.get("crop.shape") || "free"
4102
+ },
4103
+ transform: {
4104
+ rotation: Z(this._state.get("transform.rotation") ?? 0)
4105
+ },
4106
+ filters: t
4107
+ };
4108
+ }
4109
+ /**
4110
+ * Close the editor (emits 'cancel' event)
4111
+ */
4112
+ close() {
4113
+ this.emit("cancel");
4114
+ }
4115
+ /**
4116
+ * Set zoom level
4117
+ * @param {number} zoom
4118
+ */
4119
+ setZoom(e) {
4120
+ this._renderer.setZoom(e);
4121
+ }
4122
+ /**
4123
+ * Fit image to screen
4124
+ */
4125
+ fitToScreen() {
4126
+ this._renderer.fitToScreen();
4127
+ }
4128
+ /**
4129
+ * Reset all filters
4130
+ */
4131
+ resetAll() {
4132
+ this._filterManager.resetAll();
4133
+ }
4134
+ rotateBy(e) {
4135
+ const t = this._state.get("transform.rotation") ?? 0;
4136
+ return this.setRotationAngle(Number(t) + Number(e));
4137
+ }
4138
+ setRotationAngle(e) {
4139
+ if (!this._state.get("hasImage")) return !1;
4140
+ const t = Z(this._state.get("transform.rotation") ?? 0), i = Z(e), s = ut(t, i), r = this._state.get("mode") === "crop";
4141
+ if (Math.abs(s) > 1e-3) {
4142
+ if (r && this._cropManager.disable(), !this._renderer.rotateBy(s))
4143
+ return r && this._cropManager.enable(), !1;
4144
+ this._filterManager.applyFilters(), this._state.set("crop.rect", null), this._state.set("crop.dirty", !1), r && this._cropManager.enable();
4145
+ }
4146
+ return this._state.set("transform.rotation", i), this.emit("transformChanged", { rotation: i }), !0;
4147
+ }
4148
+ resetRotation() {
4149
+ return this.setRotationAngle(0);
4150
+ }
4151
+ /**
4152
+ * Toggle theme
4153
+ */
4154
+ toggleTheme() {
4155
+ const e = this._state.get("isDarkMode");
4156
+ this._state.set("isDarkMode", !e);
4157
+ }
4158
+ /**
4159
+ * Set theme
4160
+ * @param {'light'|'dark'|'auto'} theme
4161
+ */
4162
+ setTheme(e) {
4163
+ this._state.set("theme", e), this._state.detectTheme();
4164
+ }
4165
+ /**
4166
+ * Set editor mode
4167
+ * @param {'filters'|'crop'} mode
4168
+ */
4169
+ setMode(e) {
4170
+ e === "crop" ? this._cropManager.enable() : this._cropManager.disable(), this._state.set("mode", e);
4171
+ }
4172
+ /**
4173
+ * Get current state (for debugging)
4174
+ * @returns {Object}
4175
+ */
4176
+ getState() {
4177
+ return this._state.getAll();
4178
+ }
4179
+ // ==================== Background Removal ====================
4180
+ /**
4181
+ * Remove the background from the current image
4182
+ * @param {Object} options
4183
+ * @param {string} [options.tier='balanced'] - Quality tier: 'fast', 'balanced', 'best'
4184
+ * @param {string} [options.model] - Explicit model name (overrides tier)
4185
+ * @param {boolean} [options.alpha_matting=false] - Enable alpha matting for edge refinement
4186
+ * @returns {Promise<{model: string, processMs: string}>}
4187
+ */
4188
+ async removeBackground(e = {}) {
4189
+ var t, i;
4190
+ if (!this._removeBgManager)
4191
+ throw new Error("Background removal is not enabled");
4192
+ if (!this._state.get("hasImage"))
4193
+ throw new Error("No image loaded");
4194
+ if (!this.canRemoveBackground())
4195
+ throw new Error("Background removal is not available for the current editor state");
4196
+ this._state.set("isProcessing", !0), this._showLoadingOverlay("Removing background...");
4197
+ try {
4198
+ const s = this.exportImage("png");
4199
+ if (!s)
4200
+ throw new Error("Failed to export image for background removal");
4201
+ const r = await this._removeBgManager.preparePreviewRequest(s, e), o = this._buildPreviewBackgroundRemovalContext(r), n = await this._removeBgManager.removeBackground(r.blob, r);
4202
+ return await this._isCurrentPreviewBackgroundRemovalContext(o, n) ? (await this.loadImage(n.dataUrl), this.emit("background-removed", {
4203
+ model: n.model,
4204
+ processMs: n.processMs
4205
+ }), {
4206
+ model: n.model,
4207
+ processMs: n.processMs
4208
+ }) : (this.emit("background-removal-stale", {
4209
+ operationId: o.operationId,
4210
+ sourceHash: o.sourceHash
4211
+ }), { stale: !0 });
4212
+ } catch (s) {
4213
+ throw this.emit("error", { error: s, context: "background-removal" }), s;
4214
+ } finally {
4215
+ (i = (t = this._state) == null ? void 0 : t.set) == null || i.call(t, "isProcessing", !1), this._hideLoadingOverlay();
4216
+ }
4217
+ }
4218
+ /**
4219
+ * Check if background removal is available
4220
+ * @returns {Promise<boolean>}
4221
+ */
4222
+ async isBackgroundRemovalAvailable() {
4223
+ return this._removeBgManager ? this._refreshBackgroundRemovalAvailability() : !1;
4224
+ }
4225
+ canRemoveBackground() {
4226
+ return !!this._removeBgManager && this._state.get("hasImage") === !0 && this._backgroundRemovalAvailable === !0;
4227
+ }
4228
+ async _refreshBackgroundRemovalAvailability() {
4229
+ if (!this._removeBgManager)
4230
+ return this._backgroundRemovalAvailable = !1, this._state.set("backgroundRemovalAvailable", !1), !1;
4231
+ const e = await this._removeBgManager.isAvailable();
4232
+ return this._destroyed ? !1 : (this._backgroundRemovalAvailable = e, this._state.set("backgroundRemovalAvailable", e), e);
4233
+ }
4234
+ _buildPreviewBackgroundRemovalContext(e) {
4235
+ return {
4236
+ loadVersion: this._loadVersion,
4237
+ imageUrl: this._state.get("imageUrl") ?? null,
4238
+ operationId: e.operationId,
4239
+ sessionKey: e.sessionKey,
4240
+ sourceHash: e.sourceHash,
4241
+ targetRef: e.targetRef ?? null
4242
+ };
4243
+ }
4244
+ async _isCurrentPreviewBackgroundRemovalContext(e, t = null) {
4245
+ if (this._destroyed || this._loadVersion !== e.loadVersion || this._state.get("hasImage") !== !0 || (this._state.get("imageUrl") ?? null) !== e.imageUrl || t && (t.operationId && t.operationId !== e.operationId || t.sessionKey && t.sessionKey !== e.sessionKey || t.sourceHash && t.sourceHash !== e.sourceHash || t.targetRef && e.targetRef && t.targetRef !== e.targetRef))
4246
+ return !1;
4247
+ const i = this.exportImage("png");
4248
+ return i ? await this._removeBgManager.computeImageDataHash(i) === e.sourceHash : !1;
4249
+ }
4250
+ // ==================== Loading Overlay ====================
4251
+ /**
4252
+ * Show the loading overlay
4253
+ * @param {string} message - Message to display
4254
+ */
4255
+ _showLoadingOverlay(e) {
4256
+ var i;
4257
+ if (!this._loadingOverlay)
4258
+ this._loadingOverlay = d("div", { className: "editor-loading-overlay" }, [
4259
+ d("div", { className: "editor-loading-spinner" }),
4260
+ d("div", { className: "editor-loading-text" }, e)
4261
+ ]);
4262
+ else {
4263
+ const s = this._loadingOverlay.querySelector(".editor-loading-text");
4264
+ s && (s.textContent = e);
4265
+ }
4266
+ const t = (i = this._canvasContainer) == null ? void 0 : i.parentElement;
4267
+ t && !this._loadingOverlay.parentElement && t.appendChild(this._loadingOverlay);
4268
+ }
4269
+ /**
4270
+ * Hide the loading overlay
4271
+ */
4272
+ _hideLoadingOverlay() {
4273
+ var e;
4274
+ (e = this._loadingOverlay) == null || e.remove();
4275
+ }
4276
+ /**
4277
+ * Destroy the editor and clean up
4278
+ */
4279
+ destroy() {
4280
+ var e, t, i, s, r, o, n, a, l, h, c, p;
4281
+ this._destroyed = !0, (t = (e = this._removeBgManager) == null ? void 0 : e.cancelActiveRequests) == null || t.call(e, "editor-destroy");
4282
+ for (const u of this._objectUrls)
4283
+ try {
4284
+ URL.revokeObjectURL(u);
4285
+ } catch {
4286
+ }
4287
+ this._objectUrls.clear(), this._activeObjectUrl = null, (i = this._resizeObserver) == null || i.disconnect(), this._renderer.destroy(), this._cropManager.disable(), this._handleWindowResize && window.removeEventListener("resize", this._handleWindowResize), (s = this._toolbar) == null || s.destroy(), (r = this._categoryCarousel) == null || r.destroy(), (o = this._filterCarousel) == null || o.destroy(), (n = this._filterAdjustments) == null || n.destroy(), (a = this._mobileFilterDrawer) == null || a.destroy(), (l = this._mobileActiveFilters) == null || l.destroy(), (h = this._cropControls) == null || h.destroy(), (c = this._activeFiltersPanel) == null || c.destroy(), (p = this._editorEl) == null || p.remove(), this._container = null, this._state = null, this.emit("destroyed"), this.removeAllListeners();
4288
+ }
4289
+ }
4290
+ export {
4291
+ Ye as A,
4292
+ Oe as B,
4293
+ ce as C,
4294
+ ue as E,
4295
+ Qe as F,
4296
+ Te as P,
4297
+ Xe as R,
4298
+ et as T,
4299
+ kt as V,
4300
+ me as a,
4301
+ j as b,
4302
+ he as c,
4303
+ Ce as d,
4304
+ d as e,
4305
+ ke as f,
4306
+ xe as g,
4307
+ we as h,
4308
+ ve as i,
4309
+ de as j,
4310
+ bt as k,
4311
+ yt as l,
4312
+ vt as m,
4313
+ wt as n,
4314
+ _e as o,
4315
+ xt as p,
4316
+ Ct as q,
4317
+ Ue as r,
4318
+ Le as s,
4319
+ He as t,
4320
+ O as u,
4321
+ gt as v,
4322
+ mt as w,
4323
+ dt as x,
4324
+ ht as y,
4325
+ _t as z
4326
+ };
4327
+ //# sourceMappingURL=editor-CiTXlIVO.js.map