@bensitu/image-editor 1.5.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/README.md +370 -466
  2. package/dist/cjs/index.cjs +5422 -0
  3. package/dist/cjs/index.cjs.map +1 -0
  4. package/dist/esm/animation/animation-queue.js +67 -0
  5. package/dist/esm/animation/animation-queue.js.map +1 -0
  6. package/dist/esm/core/callback-reporter.js +23 -0
  7. package/dist/esm/core/callback-reporter.js.map +1 -0
  8. package/dist/esm/core/default-options.js +322 -0
  9. package/dist/esm/core/default-options.js.map +1 -0
  10. package/dist/esm/core/errors.js +156 -0
  11. package/dist/esm/core/errors.js.map +1 -0
  12. package/dist/esm/core/operation-guard.js +129 -0
  13. package/dist/esm/core/operation-guard.js.map +1 -0
  14. package/dist/esm/core/public-types.js +4 -0
  15. package/dist/esm/core/public-types.js.map +1 -0
  16. package/dist/esm/core/state-serializer.js +251 -0
  17. package/dist/esm/core/state-serializer.js.map +1 -0
  18. package/dist/esm/crop/crop-controller.js +403 -0
  19. package/dist/esm/crop/crop-controller.js.map +1 -0
  20. package/dist/esm/export/export-format.js +53 -0
  21. package/dist/esm/export/export-format.js.map +1 -0
  22. package/dist/esm/export/export-service.js +596 -0
  23. package/dist/esm/export/export-service.js.map +1 -0
  24. package/dist/esm/fabric/fabric-adapter.js +37 -0
  25. package/dist/esm/fabric/fabric-adapter.js.map +1 -0
  26. package/dist/esm/fabric/fabric-animation.js +37 -0
  27. package/dist/esm/fabric/fabric-animation.js.map +1 -0
  28. package/dist/esm/history/command.js +2 -0
  29. package/dist/esm/history/command.js.map +1 -0
  30. package/dist/esm/history/history-manager.js +103 -0
  31. package/dist/esm/history/history-manager.js.map +1 -0
  32. package/dist/esm/image/image-loader.js +245 -0
  33. package/dist/esm/image/image-loader.js.map +1 -0
  34. package/dist/esm/image/image-resampler.js +55 -0
  35. package/dist/esm/image/image-resampler.js.map +1 -0
  36. package/dist/esm/image/layout-manager.js +224 -0
  37. package/dist/esm/image/layout-manager.js.map +1 -0
  38. package/dist/esm/image/transform-controller.js +132 -0
  39. package/dist/esm/image/transform-controller.js.map +1 -0
  40. package/dist/esm/image-editor.js +1740 -0
  41. package/dist/esm/image-editor.js.map +1 -0
  42. package/dist/esm/index.js +5 -0
  43. package/dist/esm/index.js.map +1 -0
  44. package/dist/esm/mask/mask-factory.js +332 -0
  45. package/dist/esm/mask/mask-factory.js.map +1 -0
  46. package/dist/esm/mask/mask-label-manager.js +120 -0
  47. package/dist/esm/mask/mask-label-manager.js.map +1 -0
  48. package/dist/esm/mask/mask-list.js +47 -0
  49. package/dist/esm/mask/mask-list.js.map +1 -0
  50. package/dist/esm/mask/mask-style.js +182 -0
  51. package/dist/esm/mask/mask-style.js.map +1 -0
  52. package/dist/esm/ui/dom-bindings.js +60 -0
  53. package/dist/esm/ui/dom-bindings.js.map +1 -0
  54. package/dist/esm/ui/ui-state.js +25 -0
  55. package/dist/esm/ui/ui-state.js.map +1 -0
  56. package/dist/esm/ui/visibility-state.js +11 -0
  57. package/dist/esm/ui/visibility-state.js.map +1 -0
  58. package/dist/esm/utils/canvas-region.js +100 -0
  59. package/dist/esm/utils/canvas-region.js.map +1 -0
  60. package/dist/esm/utils/dom.js +6 -0
  61. package/dist/esm/utils/dom.js.map +1 -0
  62. package/dist/esm/utils/file.js +53 -0
  63. package/dist/esm/utils/file.js.map +1 -0
  64. package/dist/esm/utils/number.js +24 -0
  65. package/dist/esm/utils/number.js.map +1 -0
  66. package/dist/esm/utils/timeout.js +17 -0
  67. package/dist/esm/utils/timeout.js.map +1 -0
  68. package/dist/types/animation/animation-queue.d.ts +111 -0
  69. package/dist/types/animation/animation-queue.d.ts.map +1 -0
  70. package/dist/types/core/callback-reporter.d.ts +125 -0
  71. package/dist/types/core/callback-reporter.d.ts.map +1 -0
  72. package/dist/types/core/default-options.d.ts +56 -0
  73. package/dist/types/core/default-options.d.ts.map +1 -0
  74. package/dist/types/core/errors.d.ts +142 -0
  75. package/dist/types/core/errors.d.ts.map +1 -0
  76. package/dist/types/core/operation-guard.d.ts +192 -0
  77. package/dist/types/core/operation-guard.d.ts.map +1 -0
  78. package/dist/types/core/public-types.d.ts +678 -0
  79. package/dist/types/core/public-types.d.ts.map +1 -0
  80. package/dist/types/core/state-serializer.d.ts +301 -0
  81. package/dist/types/core/state-serializer.d.ts.map +1 -0
  82. package/dist/types/crop/crop-controller.d.ts +407 -0
  83. package/dist/types/crop/crop-controller.d.ts.map +1 -0
  84. package/dist/types/export/export-format.d.ts +136 -0
  85. package/dist/types/export/export-format.d.ts.map +1 -0
  86. package/dist/types/export/export-service.d.ts +333 -0
  87. package/dist/types/export/export-service.d.ts.map +1 -0
  88. package/dist/types/fabric/fabric-adapter.d.ts +74 -0
  89. package/dist/types/fabric/fabric-adapter.d.ts.map +1 -0
  90. package/dist/types/fabric/fabric-animation.d.ts +141 -0
  91. package/dist/types/fabric/fabric-animation.d.ts.map +1 -0
  92. package/dist/types/history/command.d.ts +16 -0
  93. package/dist/types/history/command.d.ts.map +1 -0
  94. package/dist/types/history/history-manager.d.ts +129 -0
  95. package/dist/types/history/history-manager.d.ts.map +1 -0
  96. package/dist/types/image/image-loader.d.ts +265 -0
  97. package/dist/types/image/image-loader.d.ts.map +1 -0
  98. package/dist/types/image/image-resampler.d.ts +139 -0
  99. package/dist/types/image/image-resampler.d.ts.map +1 -0
  100. package/dist/types/image/layout-manager.d.ts +255 -0
  101. package/dist/types/image/layout-manager.d.ts.map +1 -0
  102. package/dist/types/image/transform-controller.d.ts +287 -0
  103. package/dist/types/image/transform-controller.d.ts.map +1 -0
  104. package/dist/types/image-editor.d.ts +650 -0
  105. package/dist/types/image-editor.d.ts.map +1 -0
  106. package/dist/types/index.d.cts +31 -0
  107. package/dist/types/index.d.cts.map +1 -0
  108. package/dist/types/index.d.ts +31 -0
  109. package/dist/types/index.d.ts.map +1 -0
  110. package/dist/types/mask/mask-factory.d.ts +209 -0
  111. package/dist/types/mask/mask-factory.d.ts.map +1 -0
  112. package/dist/types/mask/mask-label-manager.d.ts +171 -0
  113. package/dist/types/mask/mask-label-manager.d.ts.map +1 -0
  114. package/dist/types/mask/mask-list.d.ts +144 -0
  115. package/dist/types/mask/mask-list.d.ts.map +1 -0
  116. package/dist/types/mask/mask-style.d.ts +338 -0
  117. package/dist/types/mask/mask-style.d.ts.map +1 -0
  118. package/dist/types/ui/dom-bindings.d.ts +103 -0
  119. package/dist/types/ui/dom-bindings.d.ts.map +1 -0
  120. package/dist/types/ui/ui-state.d.ts +112 -0
  121. package/dist/types/ui/ui-state.d.ts.map +1 -0
  122. package/dist/types/ui/visibility-state.d.ts +77 -0
  123. package/dist/types/ui/visibility-state.d.ts.map +1 -0
  124. package/dist/types/utils/canvas-region.d.ts +177 -0
  125. package/dist/types/utils/canvas-region.d.ts.map +1 -0
  126. package/dist/types/utils/dom.d.ts +26 -0
  127. package/dist/types/utils/dom.d.ts.map +1 -0
  128. package/dist/types/utils/file.d.ts +80 -0
  129. package/dist/types/utils/file.d.ts.map +1 -0
  130. package/dist/types/utils/number.d.ts +132 -0
  131. package/dist/types/utils/number.d.ts.map +1 -0
  132. package/dist/types/utils/timeout.d.ts +84 -0
  133. package/dist/types/utils/timeout.d.ts.map +1 -0
  134. package/dist/umd/image-editor.umd.js +2 -0
  135. package/dist/umd/image-editor.umd.js.map +1 -0
  136. package/package.json +72 -66
  137. package/dist/image-editor.cjs +0 -4185
  138. package/dist/image-editor.cjs.map +0 -7
  139. package/dist/image-editor.esm.js +0 -4154
  140. package/dist/image-editor.esm.js.map +0 -7
  141. package/dist/image-editor.esm.min.js +0 -9
  142. package/dist/image-editor.esm.min.js.map +0 -7
  143. package/dist/image-editor.esm.min.mjs +0 -9
  144. package/dist/image-editor.esm.min.mjs.map +0 -7
  145. package/dist/image-editor.esm.mjs +0 -4154
  146. package/dist/image-editor.esm.mjs.map +0 -7
  147. package/dist/image-editor.js +0 -4151
  148. package/dist/image-editor.js.map +0 -7
  149. package/dist/image-editor.min.js +0 -9
  150. package/dist/image-editor.min.js.map +0 -7
  151. package/image-editor.d.ts +0 -269
  152. package/src/browser.js +0 -11
  153. package/src/esm.js +0 -9
  154. package/src/image-editor.js +0 -4775
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAE,WAAW,EAAE,CAAC;AACvB,eAAe,WAAW,CAAC;AAE3B,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAItD,YAAY,EAER,kBAAkB,EAClB,eAAe,EACf,UAAU,EAEV,WAAW,EACX,UAAU,EACV,kBAAkB,EAElB,gBAAgB,EAChB,qBAAqB,EAErB,UAAU,EACV,UAAU,EACV,eAAe,EACf,kBAAkB,EAElB,aAAa,EACb,aAAa,EACb,qBAAqB,EAErB,UAAU,EACV,mBAAmB,EACnB,sBAAsB,EAEtB,SAAS,EACT,gBAAgB,EAChB,oBAAoB,EACpB,0BAA0B,EAC1B,oBAAoB,EAEpB,YAAY,EAEZ,YAAY,GACf,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Canonical public API barrel for the image-editor library.
3
+ *
4
+ * The package surface consists of:
5
+ * - `ImageEditor` (default and named export) — the only public class.
6
+ * - `isMaskObject` — runtime type guard for mask objects.
7
+ * - The documented public types listed below.
8
+ *
9
+ * Internal helpers (animation queue, command, history manager, controllers,
10
+ * services, managers, and utility modules) are intentionally not re-exported;
11
+ * they are implementation details and may change without notice.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * import * as fabric from 'fabric';
16
+ * import { ImageEditor } from '@bensitu/image-editor';
17
+ * import type {
18
+ * ImageEditorOptions,
19
+ * MaskConfig,
20
+ * MaskObject,
21
+ * } from '@bensitu/image-editor';
22
+ * ```
23
+ *
24
+ * @module
25
+ */
26
+ import { ImageEditor } from './image-editor.js';
27
+ export { ImageEditor };
28
+ export default ImageEditor;
29
+ export { isMaskObject } from './core/public-types.js';
30
+ export type { ImageEditorOptions, ResolvedOptions, LayoutMode, LabelConfig, CropConfig, CropExportFileType, LoadImageOptions, RemoveAllMasksOptions, MaskConfig, MaskObject, MaskNumericProp, ResolvedMaskConfig, ImageMimeType, ImageFileType, NormalizedImageFormat, ExportArea, Base64ExportOptions, ImageFileExportOptions, ImageInfo, ImageEditorState, ImageEditorSelection, ImageEditorCallbackContext, ImageEditorOperation, ElementIdMap, FabricModule, } from './core/public-types.js';
31
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAE,WAAW,EAAE,CAAC;AACvB,eAAe,WAAW,CAAC;AAE3B,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAItD,YAAY,EAER,kBAAkB,EAClB,eAAe,EACf,UAAU,EAEV,WAAW,EACX,UAAU,EACV,kBAAkB,EAElB,gBAAgB,EAChB,qBAAqB,EAErB,UAAU,EACV,UAAU,EACV,eAAe,EACf,kBAAkB,EAElB,aAAa,EACb,aAAa,EACb,qBAAqB,EAErB,UAAU,EACV,mBAAmB,EACnB,sBAAsB,EAEtB,SAAS,EACT,gBAAgB,EAChB,oBAAoB,EACpB,0BAA0B,EAC1B,oBAAoB,EAEpB,YAAY,EAEZ,YAAY,GACf,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Function-based mask creation entry point used by the
3
+ * `ImageEditor` orchestrator.
4
+ *
5
+ * ## Owned contracts
6
+ *
7
+ * - On a successful `createMask`, the editor SHALL
8
+ * increment `maskCounter` and assign the result to `mask.maskId`. Counter
9
+ * bookkeeping flows through `context.getMaskCounter` / `context.setMaskCounter` so
10
+ * the orchestrator retains ownership of the field across loadImage and
11
+ * loadFromState (which reset / restore the counter).
12
+ * - Together with `core/state-serializer.ts`, mask IDs
13
+ * stay unique across mixed `createMask` / `mergeMasks` / `undo` / `redo`
14
+ * sequences because the counter is monotonic.
15
+ * - `createMask` is the public mask-creation entry point.
16
+ * - For `'rect' | 'circle' | 'ellipse' | 'polygon'`
17
+ * the corresponding Fabric shape is built with explicit
18
+ * `originX: 'left'`, `originY: 'top'`, plus the resolved color, opacity,
19
+ * angle, and the user's `styles` block.
20
+ * - When `config.fabricGenerator` is supplied it is
21
+ * called with `(resolvedConfig, canvas, options)` and its return value is
22
+ * used verbatim as the mask object.
23
+ * - Post-create order is fixed: add to canvas →
24
+ * update list DOM → `setActiveObject` (when `selectable !== false`) →
25
+ * `saveState` → `config.onCreate(mask, canvas)`.
26
+ * - `config.onCreate` is invoked exactly once,
27
+ * strictly after `saveState` has run.
28
+ * - Falsy values supplied via `config.styles` (`0`,
29
+ * `false`, `null`, `''`, `NaN`) are applied verbatim. The factory does
30
+ * NOT use `??` to default stroke / strokeWidth / strokeDashArray when the
31
+ * key is explicitly present on `styles`.
32
+ * - `hasControls`, `selectable`, `transparentCorners`,
33
+ * `strokeUniform` use the `'foo' in config ? … : default` pattern so that
34
+ * an explicit `false` is preserved.
35
+ * - When a polygon mask is built, its visible
36
+ * bounding-box top-left SHALL equal the resolved `(left, top)`. Fabric
37
+ * v7's `Polygon` constructor positions the object so the polygon's
38
+ * `pathOffset` is centered on `(left, top)`, which means the bounding
39
+ * rect generally does NOT land at `(left, top)`. The factory therefore
40
+ * constructs the polygon without `left`/`top`, reads the resulting
41
+ * bounding rect, and shifts the object by the delta so the rendered
42
+ * bounding box top-left matches the requested coordinate.
43
+ * - Polygon points may be supplied as `{ x, y }`
44
+ * objects or `[x, y]` tuples; both forms are normalized via
45
+ * `coercePoint` from `utils/number.ts` before reaching Fabric.
46
+ *
47
+ * - `removeAllMasks(options?)` accepts a
48
+ * `RemoveAllMasksOptions` argument with `saveHistory` defaulting to
49
+ * `true`, pushing a single history entry for the batch.
50
+ * - `removeAllMasks({ saveHistory: false})` removes
51
+ * masks without pushing a history entry (used by merge/crop pipelines).
52
+ * - `removeAllMasks` is operation-guard-rejected
53
+ * while `isAnimating` is `true`. The guard lives on the orchestrator;
54
+ * this module is only invoked after the guard has cleared.
55
+ * - `removeAllMasks` clears `lastMask` to `null`,
56
+ * so subsequent `createMask` calls cannot auto-place relative to a
57
+ * removed reference and `maskId` uniqueness is preserved across mixed
58
+ * `createMask` / `removeAllMasks` / `undo` / `redo` sequences.
59
+ *
60
+ * ## Out of scope (handled by sibling tasks)
61
+ *
62
+ * - Mask label creation/synchronization — see `mask/mask-label-manager.ts`.
63
+ *
64
+ * ## Design notes
65
+ *
66
+ * - The orchestrator owns the editor-level state (`maskCounter`,
67
+ * `lastMask`, the canvas, `saveState`, `updateMaskList`). The factory
68
+ * reads/writes those slots through getter/setter callbacks supplied in
69
+ * {@link CreateMaskContext} so this module is independent of the
70
+ * `ImageEditor` class shape.
71
+ * - `expandCanvasIfNeeded` is optional. The orchestrator may supply it to
72
+ * route through `setCanvasSizePx` (which forces a synchronous reflow on
73
+ * the scroll container, see `image-editor.ts`). When absent, the factory
74
+ * falls back to the public Fabric API `canvas.setDimensions`.
75
+ *
76
+ * @module
77
+ */
78
+ import type * as FabricNS from 'fabric';
79
+ import type { FabricModule, MaskConfig, MaskObject, RemoveAllMasksOptions, ResolvedOptions } from '../core/public-types.js';
80
+ /**
81
+ * State and orchestration callbacks the mask factory needs from the
82
+ * `ImageEditor` orchestrator.
83
+ *
84
+ * The factory does NOT own any of these slots — it only reads and updates
85
+ * them through the supplied accessors so ownership of `maskCounter`,
86
+ * `lastMask`, history snapshots, and the mask list DOM stays on the editor.
87
+ */
88
+ export interface CreateMaskContext {
89
+ /** Injected Fabric.js v7 module used to construct the shape. */
90
+ fabric: FabricModule;
91
+ /** The live Fabric canvas the mask is added to. */
92
+ canvas: FabricNS.Canvas;
93
+ /** Fully resolved editor options (defaults already merged). */
94
+ options: ResolvedOptions;
95
+ /** Last mask reference, used for the auto-place-to-right behavior. */
96
+ getLastMask(): MaskObject | null;
97
+ setLastMask(mask: MaskObject | null): void;
98
+ /** Mask counter — owned by the orchestrator. */
99
+ getMaskCounter(): number;
100
+ setMaskCounter(n: number): void;
101
+ /** Re-render the mask list DOM (UI ownership lives in `mask/mask-list.ts`). */
102
+ updateMaskList(): void;
103
+ /** Save canvas state to history. */
104
+ saveCanvasState(): void;
105
+ /**
106
+ * Optional canvas resize hook used when `options.expandCanvasToImage` is
107
+ * `true` and the placed mask would extend past the current canvas size.
108
+ * If omitted, the factory calls `canvas.setDimensions` directly. The
109
+ * orchestrator typically passes `setCanvasSizePx` here so the scroll
110
+ * container reflows synchronously with the new canvas size.
111
+ */
112
+ expandCanvasIfNeeded?: (width: number, height: number) => void;
113
+ }
114
+ /**
115
+ * Create a mask via the resolved {@link MaskConfig} and add it to the
116
+ * canvas.
117
+ *
118
+ * Creation steps:
119
+ *
120
+ * 1. Resolve the config: apply defaults, then resolve placement
121
+ * (`left`/`top`) and dimensions (`width`/`height`/`rx`/`ry`/`radius`)
122
+ * via {@link resolveNumeric} so percentages and factory functions
123
+ * collapse to pixel numbers before Fabric shape construction.
124
+ * 2. Optionally expand the canvas if the placement would overflow.
125
+ * 3. Build the Fabric shape — switch on `config.shape`, or call
126
+ * `config.fabricGenerator` if provided.
127
+ * 4. Apply common mask properties. Falsy flags (`hasControls`,
128
+ * `selectable`, `transparentCorners`, `strokeUniform`) use the
129
+ * `'foo' in config ? … : default` pattern so an explicit `false` is
130
+ * preserved. Stroke / strokeWidth /
131
+ * strokeDashArray pulled out of `styles` use the same `in` check so
132
+ * `null` and `0` are preserved verbatim.
133
+ * 5. Increment `maskCounter` and assign `maskId`, `maskName`,
134
+ * `originalAlpha`.
135
+ * 6. Post-create order: add to canvas → `updateMaskList` →
136
+ * `setActiveObject` (when `selectable !== false`) → `saveCanvasState`
137
+ * → `config.onCreate(mask, canvas)`.
138
+ *
139
+ * @param context - Orchestration context — see {@link CreateMaskContext}.
140
+ * @param config - User-supplied mask configuration.
141
+ * @returns The created mask object, or `null` if the canvas is unset.
142
+ */
143
+ export declare function createMask(context: CreateMaskContext, config?: MaskConfig): MaskObject | null;
144
+ /**
145
+ * Orchestration callbacks needed by {@link removeSelectedMask} and
146
+ * {@link removeAllMasks}. The helpers do NOT own any of these slots — they
147
+ * read and update them through the supplied accessors so ownership of the
148
+ * canvas, mask label DOM, mask list DOM, history, and `lastMask` stays on
149
+ * the editor.
150
+ */
151
+ export interface RemoveMaskContext {
152
+ /** The live Fabric canvas the mask(s) are removed from. */
153
+ canvas: FabricNS.Canvas;
154
+ /**
155
+ * Remove the label overlay associated with `mask` (if any). The
156
+ * orchestrator typically delegates to `mask/mask-label-manager.ts`.
157
+ */
158
+ removeLabelForMask(mask: MaskObject): void;
159
+ /** Re-render the mask list DOM (UI ownership in `mask/mask-list.ts`). */
160
+ updateMaskList(): void;
161
+ /** Push a single history entry for the removal batch. */
162
+ saveCanvasState(): void;
163
+ /**
164
+ * Reset the orchestrator's `lastMask` reference. Called with `null`
165
+ * when every mask is removed so the next `createMask` does not
166
+ * auto-place relative to a removed mask.
167
+ */
168
+ setLastMask(mask: MaskObject | null): void;
169
+ }
170
+ /**
171
+ * Remove the currently selected mask (if it is a {@link MaskObject}).
172
+ *
173
+ * Steps:
174
+ *
175
+ * 1. Read the active object from the canvas. No-op if missing or not a mask.
176
+ * 2. Remove the mask's label overlay via {@link RemoveMaskContext.removeLabelForMask}.
177
+ * 3. Remove the mask object from the canvas and clear the active selection.
178
+ * 4. Re-render the mask list DOM and the canvas.
179
+ * 5. Push a single history entry via {@link RemoveMaskContext.saveCanvasState}.
180
+ *
181
+ * @param context - Orchestration context — see {@link RemoveMaskContext}.
182
+ */
183
+ export declare function removeSelectedMask(context: RemoveMaskContext): void;
184
+ /**
185
+ * Remove all masks (and their label overlays) from the canvas.
186
+ *
187
+ * When `options.saveHistory` is `false`, the helper does NOT push a history
188
+ * entry — used by the internal `mergeMasks` and `applyCrop` pipelines, which
189
+ * already record one enclosing history entry for the operation. The default
190
+ * (and the public-facing call from `ImageEditor.removeAllMasks`) is
191
+ * `saveHistory: true`, which pushes a single entry for the batch.
192
+ *
193
+ * Steps:
194
+ *
195
+ * 1. Collect every {@link MaskObject} on the canvas. No-op if none.
196
+ * 2. For each mask: remove its label overlay, then remove the mask object
197
+ * from the canvas.
198
+ * 3. Clear the active selection.
199
+ * 4. Reset `lastMask` to `null` so the next `createMask` does not
200
+ * auto-place relative to a removed reference.
201
+ * 5. Re-render the mask list DOM and the canvas.
202
+ * 6. Conditionally push a history entry depending on
203
+ * `options.saveHistory`.
204
+ *
205
+ * @param context - Orchestration context — see {@link RemoveMaskContext}.
206
+ * @param options - Bulk-removal options. Defaults to `{ saveHistory: true}`.
207
+ */
208
+ export declare function removeAllMasks(context: RemoveMaskContext, options?: RemoveAllMasksOptions): void;
209
+ //# sourceMappingURL=mask-factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mask-factory.d.ts","sourceRoot":"","sources":["../../../src/mask/mask-factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4EG;AAEH,OAAO,KAAK,KAAK,QAAQ,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EACR,YAAY,EACZ,UAAU,EACV,UAAU,EACV,qBAAqB,EAErB,eAAe,EAClB,MAAM,yBAAyB,CAAC;AAcjC;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAC9B,gEAAgE;IAChE,MAAM,EAAE,YAAY,CAAC;IACrB,mDAAmD;IACnD,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC;IACxB,+DAA+D;IAC/D,OAAO,EAAE,eAAe,CAAC;IACzB,sEAAsE;IACtE,WAAW,IAAI,UAAU,GAAG,IAAI,CAAC;IACjC,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,GAAG,IAAI,CAAC;IAC3C,gDAAgD;IAChD,cAAc,IAAI,MAAM,CAAC;IACzB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,+EAA+E;IAC/E,cAAc,IAAI,IAAI,CAAC;IACvB,oCAAoC;IACpC,eAAe,IAAI,IAAI,CAAC;IACxB;;;;;;OAMG;IACH,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CAClE;AA+GD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,EAAE,MAAM,GAAE,UAAe,GAAG,UAAU,GAAG,IAAI,CAsSjG;AA6BD;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAC9B,2DAA2D;IAC3D,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC;IACxB;;;OAGG;IACH,kBAAkB,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IAC3C,yEAAyE;IACzE,cAAc,IAAI,IAAI,CAAC;IACvB,yDAAyD;IACzD,eAAe,IAAI,IAAI,CAAC;IACxB;;;;OAIG;IACH,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,GAAG,IAAI,CAAC;CAC9C;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAYnE;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,cAAc,CAC1B,OAAO,EAAE,iBAAiB,EAC1B,OAAO,GAAE,qBAA0B,GACpC,IAAI,CAqBN"}
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Per-mask label overlay creation, positioning, show/hide, and
3
+ * removal. Owns the legacy `createLabelForMask`, `syncMaskLabel`,
4
+ * `showLabelForMask`, `hideAllMaskLabels`, and
5
+ * `removeLabelForMask` logic that was inlined on the editor
6
+ * in legacy and is now extracted into pure(ish) helpers that take a
7
+ * {@link MaskLabelManagerContext}.
8
+ *
9
+ * ## Owned contracts
10
+ *
11
+ * - Label text is computed via
12
+ * `options.label.getText(mask, mask.maskId - 1)`. The index argument is
13
+ * the stable creation index (`maskId - 1`), NOT the live canvas list
14
+ * position. legacy passed `this.maskCounter` here, which drifted whenever
15
+ * masks were added or removed; the current contract pins the index to the
16
+ * mask's own identity so labels stay consistent across
17
+ * `createMask` / `removeSelectedMask` / `removeAllMasks` / `undo`/`redo`.
18
+ *
19
+ * - Every label-overlay text object SHALL be
20
+ * constructed with `originX: 'left'`, `originY: 'top'`, since the label
21
+ * is positioned by its top-left corner. The defaults injected by this
22
+ * module set those origins explicitly, and {@link syncMaskLabel}
23
+ * re-asserts them on every sync so an externally-mutated label still
24
+ * honors the contract.
25
+ *
26
+ * - **Pretty_Printer filter** — Every label object is
27
+ * tagged with `maskLabel = true` so `core/state-serializer.ts` can
28
+ * exclude it from history snapshots. Labels are session-only and never
29
+ * persisted.
30
+ *
31
+ * ## Out of scope (handled by sibling modules)
32
+ *
33
+ * - Mask creation, falsy-style preservation, polygon placement — see
34
+ * `mask/mask-factory.ts`.
35
+ * - Mask list DOM rendering — see `mask/mask-list.ts`.
36
+ * - Hover/selection appearance — see `mask/mask-style.ts`.
37
+ *
38
+ * ## Implementation notes
39
+ *
40
+ * - The orchestrator (`src/image-editor.ts`) owns the canvas reference,
41
+ * resolved options, and Fabric module. The helpers in this module
42
+ * receive those slots through a {@link MaskLabelManagerContext} so the
43
+ * module is independent of the `ImageEditor` class shape and can be unit
44
+ * tested in isolation against a stub Fabric environment.
45
+ * - When `options.label.create` is supplied, the user owns the full label
46
+ * build (it may even return `null` to opt out for a particular mask).
47
+ * When the factory returns `null` or the option is absent, this module
48
+ * falls back to a default Fabric text constructed from the deep-merged
49
+ * `options.label.textOptions`. The default already carries
50
+ * `originX: 'left'`/`originY: 'top'` via `core/default-options.ts`, but
51
+ * we set them explicitly here as well so the contract holds even when a
52
+ * caller bypasses `resolveOptions` (e.g. in unit tests).
53
+ *
54
+ * @module
55
+ */
56
+ import type * as FabricNS from 'fabric';
57
+ import type { FabricModule, MaskObject, ResolvedOptions } from '../core/public-types.js';
58
+ /**
59
+ * State the label helpers read from the `ImageEditor` orchestrator.
60
+ *
61
+ * The module does NOT own any of these slots — it only reads them so
62
+ * ownership of the canvas, Fabric module, and resolved options stays on the
63
+ * orchestrator (where legacy left them).
64
+ */
65
+ export interface MaskLabelManagerContext {
66
+ /** Injected Fabric.js v7 module used to construct the label text. */
67
+ fabric: FabricModule;
68
+ /** The live Fabric canvas the label overlay is added to. */
69
+ canvas: FabricNS.Canvas;
70
+ /** Fully resolved editor options (defaults already merged). */
71
+ options: ResolvedOptions;
72
+ }
73
+ /**
74
+ * Remove the label overlay associated with `mask` (if any).
75
+ *
76
+ * No-op when the canvas is unset or the mask has no `labelObject`. Each
77
+ * removal step is wrapped in `try/catch` so a stale Fabric reference does
78
+ * not break callers that iterate every mask (e.g. {@link hideAllMaskLabels}
79
+ * or `removeAllMasks`).
80
+ *
81
+ * Steps:
82
+ *
83
+ * 1. If `mask.labelObject` is present and still on the canvas, remove it.
84
+ * 2. Delete `mask.labelObject` so subsequent {@link showLabelForMask} calls
85
+ * rebuild it instead of reusing a stale reference.
86
+ *
87
+ * @param context - Orchestration context — see {@link MaskLabelManagerContext}.
88
+ * @param mask - The mask whose label overlay should be removed.
89
+ */
90
+ export declare function removeLabelForMask(context: MaskLabelManagerContext, mask: MaskObject): void;
91
+ /**
92
+ * Create (or recreate) the label overlay for `mask`.
93
+ *
94
+ * No-op when the canvas is unset or `options.maskLabelOnSelect` is `false`.
95
+ *
96
+ * Steps:
97
+ *
98
+ * 1. Remove any existing label so a stale reference is never reused.
99
+ * 2. If `options.label.create` is a function, call it with
100
+ * `(mask, fabric)` and use its return value when truthy. The factory
101
+ * may return `null` to opt out, in which case the default builder runs.
102
+ * 3. Otherwise (or when the factory returned `null`), compute label text
103
+ * via `options.label.getText(mask, mask.maskId - 1)` so the index is
104
+ * the stable creation index rather than the live list position. Build
105
+ * a Fabric text using `options.label.textOptions` with `originX: 'left'`
106
+ * and `originY: 'top'` re-asserted explicitly.
107
+ * 4. Tag the resulting object with `maskLabel = true` so the state
108
+ * serializer filters it out of history snapshots.
109
+ * 5. Attach the label to the mask, add it to the canvas, bring it to the
110
+ * front, and run an initial {@link syncMaskLabel} to position it.
111
+ *
112
+ * @param context - Orchestration context — see {@link MaskLabelManagerContext}.
113
+ * @param mask - The mask the label overlay is being created for.
114
+ */
115
+ export declare function createLabelForMask(context: MaskLabelManagerContext, mask: MaskObject): void;
116
+ /**
117
+ * Re-position the label overlay to track its mask's current bounding box,
118
+ * angle, and visibility.
119
+ *
120
+ * No-op when the canvas is unset, `options.maskLabelOnSelect` is `false`,
121
+ * or the mask has no `labelObject`. Designed to be called from Fabric event
122
+ * handlers (`object:moving`, `object:scaling`, `object:rotating`,
123
+ * `object:modified`) without checking those guards at every call site.
124
+ *
125
+ * Geometry (matches legacy):
126
+ *
127
+ * - The label's top-left is placed `options.maskLabelOffset` pixels from
128
+ * the mask's top-left corner, along the vector from the top-left to the
129
+ * center of the rotated bounding box. This keeps the offset visually
130
+ * consistent regardless of mask rotation.
131
+ * - The label inherits the mask's `angle` so it rotates with the mask.
132
+ * - `originX: 'left'` and `originY: 'top'` are re-asserted on every sync
133
+ * to.
134
+ *
135
+ * @param context - Orchestration context — see {@link MaskLabelManagerContext}.
136
+ * @param mask - The mask whose label should be repositioned.
137
+ */
138
+ export declare function syncMaskLabel(context: MaskLabelManagerContext, mask: MaskObject): void;
139
+ /**
140
+ * Ensure the label for `mask` is present and visible.
141
+ *
142
+ * No-op when `options.maskLabelOnSelect` is `false`. Creates a fresh label
143
+ * via {@link createLabelForMask} when the mask has none, otherwise toggles
144
+ * `visible` to `true` and re-syncs the position. Used by the orchestrator's
145
+ * selection handler when a single mask becomes the active object.
146
+ *
147
+ * @param context - Orchestration context — see {@link MaskLabelManagerContext}.
148
+ * @param mask - The mask whose label should be shown.
149
+ */
150
+ export declare function showLabelForMask(context: MaskLabelManagerContext, mask: MaskObject): void;
151
+ /**
152
+ * Remove every label overlay currently on the canvas and detach the
153
+ * `labelObject` reference from every mask.
154
+ *
155
+ * Called by the orchestrator before serialization (`saveState`) and
156
+ * after `loadFromJSON`-driven restores so that label objects — which
157
+ * Fabric DOES serialize unless filtered — never leak into history.
158
+ *
159
+ * Steps:
160
+ *
161
+ * 1. Remove every object flagged with `maskLabel === true` from the
162
+ * canvas. Each removal is wrapped in `try/catch` so a stale Fabric
163
+ * reference does not break the loop.
164
+ * 2. Delete `labelObject` from every mask object so a subsequent
165
+ * {@link showLabelForMask} rebuilds the label rather than reusing a
166
+ * detached reference.
167
+ *
168
+ * @param context - Orchestration context — see {@link MaskLabelManagerContext}.
169
+ */
170
+ export declare function hideAllMaskLabels(context: MaskLabelManagerContext): void;
171
+ //# sourceMappingURL=mask-label-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mask-label-manager.d.ts","sourceRoot":"","sources":["../../../src/mask/mask-label-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH,OAAO,KAAK,KAAK,QAAQ,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAIzF;;;;;;GAMG;AACH,MAAM,WAAW,uBAAuB;IACpC,qEAAqE;IACrE,MAAM,EAAE,YAAY,CAAC;IACrB,4DAA4D;IAC5D,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC;IACxB,+DAA+D;IAC/D,OAAO,EAAE,eAAe,CAAC;CAC5B;AAUD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,uBAAuB,EAAE,IAAI,EAAE,UAAU,GAAG,IAAI,CAc3F;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,uBAAuB,EAAE,IAAI,EAAE,UAAU,GAAG,IAAI,CAgE3F;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,uBAAuB,EAAE,IAAI,EAAE,UAAU,GAAG,IAAI,CA2BtF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,EAAE,IAAI,EAAE,UAAU,GAAG,IAAI,CASzF;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAuBxE"}
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Mask list DOM rendering — owns the legacy `updateMaskList` and
3
+ * `_handleMaskListClick` logic that was inlined on the editor in
4
+ * legacy and is now extracted into pure(ish) helpers that take a
5
+ * {@link MaskListContext}.
6
+ *
7
+ * ## Owned contracts
8
+ *
9
+ * - When the mask list DOM container is bound,
10
+ * {@link renderMaskList} renders exactly one `<li>` per mask currently on
11
+ * the canvas, in canvas object order. The list is rebuilt from scratch on
12
+ * every call so it stays in sync with `canvas.getObjects` after creates,
13
+ * removals, undo/redo, and JSON restores.
14
+ *
15
+ * - Every `<li>` carries a `data-mask-id` attribute
16
+ * (via `dataset.maskId`) equal to the mask's `maskId`. Clients keying off
17
+ * the list (the click handler below, the test suite under
18
+ * `tests/properties/mask-list-dom.test.mjs`) MUST use this attribute and
19
+ * never the visible label text — the label may be rewritten by the
20
+ * integrator via `options.label.getText`, so it is NOT a stable key.
21
+ *
22
+ * - Clicking a list item selects the mask whose
23
+ * `maskId` equals the clicked item's `data-mask-id`, regardless of where
24
+ * the item currently sits in the list. The click handler does NOT use the
25
+ * item's array index, so reordering the canvas (e.g. via Fabric layering)
26
+ * does not break selection.
27
+ *
28
+ * ## Out of scope (handled by sibling modules)
29
+ *
30
+ * - Label text generation and `mask.maskId - 1` index — see
31
+ * `mask/mask-label-manager.ts`.
32
+ * - Mask creation, removal, and the post-create `updateMaskList` call — see
33
+ * `mask/mask-factory.ts`. The factory
34
+ * invokes the orchestrator's `updateMaskList` callback, which delegates
35
+ * here.
36
+ * - Hover/selection appearance on the canvas — see `mask/mask-style.ts`.
37
+ *
38
+ * ## Implementation notes
39
+ *
40
+ * - The orchestrator (`src/image-editor.ts`) owns the canvas reference, the
41
+ * resolved `elements` table, and the selection callback. The helpers in
42
+ * this module receive those slots through a {@link MaskListContext} so the
43
+ * module is independent of the `ImageEditor` class shape and can be unit
44
+ * tested in isolation against a stub Fabric environment plus a JSDOM
45
+ * container.
46
+ * - The DOM contract — `<li class="list-group-item mask-item">` with a
47
+ * `dataset.maskId` — matches legacy verbatim so existing CSS, theme overrides,
48
+ * and integrator selectors continue to work unchanged.
49
+ * - Each render replaces the container's `innerHTML`. That is intentional:
50
+ * it guarantees the DOM mirrors `canvas.getObjects` exactly, and the
51
+ * Fabric event handlers (`object:added`, `object:removed`, `selection:*`)
52
+ * already fire often enough that an incremental diff is not justified.
53
+ * `innerHTML = ''` also detaches every prior `onclick` handler we attached
54
+ * below, so there is no need to track listeners separately.
55
+ *
56
+ * @module
57
+ */
58
+ import type * as FabricNS from 'fabric';
59
+ import type { MaskObject } from '../core/public-types.js';
60
+ /**
61
+ * State the mask-list helpers read from the `ImageEditor` orchestrator.
62
+ *
63
+ * The module does NOT own any of these slots — it only reads them so
64
+ * ownership of the canvas, the resolved DOM element ID map, and the
65
+ * selection-changed pipeline stays on the orchestrator (where legacy left
66
+ * them).
67
+ */
68
+ export interface MaskListContext {
69
+ /**
70
+ * The live Fabric canvas the list mirrors. May be `null` after
71
+ * `dispose` or before `init` has run; the helpers no-op in that
72
+ * case.
73
+ */
74
+ canvas: FabricNS.Canvas | null;
75
+ /**
76
+ * Returns the resolved DOM element ID for the mask list container, or a
77
+ * falsy value when the integrator omitted `maskList` from the `idMap`.
78
+ * The orchestrator's `elements.maskList` slot fills this role.
79
+ */
80
+ getListElementId(): string | null | undefined;
81
+ /**
82
+ * Invoked by the click handler after `setActiveObject(mask)` has run,
83
+ * so the orchestrator can drive its selection-changed pipeline (label
84
+ * overlay, hover/selection styling, list highlight) the same way it
85
+ * does for canvas-originated selections. Mirrors legacy's
86
+ * `_handleSelectionChanged([mask])` call from `_handleMaskListClick`.
87
+ */
88
+ onMaskSelected(mask: MaskObject): void;
89
+ }
90
+ /**
91
+ * Re-render the mask list DOM from `canvas.getObjects`.
92
+ *
93
+ * No-op when the canvas is unset, the integrator did not supply a
94
+ * `maskList` element ID, or the configured element does not exist in the
95
+ * document. Tolerating partial DOM is intentional — the editor supports
96
+ * being driven from code without any sidebar UI (the editor's
97
+ * tolerated-bindings contract).
98
+ *
99
+ * Steps:
100
+ *
101
+ * 1. Resolve the list element via `context.getListElementId`. Bail out if
102
+ * missing.
103
+ * 2. Clear the container with `innerHTML = ''`. This also detaches every
104
+ * `onclick` handler attached on the previous render, so there is no
105
+ * listener bookkeeping to track separately.
106
+ * 3. For each {@link MaskObject} returned by `canvas.getObjects` (in
107
+ * canvas object order), build a fresh `<li>`:
108
+ * - class `list-group-item mask-item` (matches legacy verbatim so
109
+ * existing CSS keeps working);
110
+ * - `textContent` set to `mask.maskName` (the label-text contract
111
+ * owned by `mask/mask-label-manager.ts`; this list shows the same
112
+ * identifier);
113
+ * - `dataset.maskId` set to `String(mask.maskId)` so the click
114
+ * handler and tests can key off a stable identifier;
115
+ * - an `onclick` handler that looks up the mask by `maskId` —
116
+ * regardless of the item's current position in the list — calls
117
+ * `setActiveObject` on the canvas, and
118
+ * forwards to `context.onMaskSelected(mask)` so the orchestrator's
119
+ * selection-changed pipeline runs.
120
+ *
121
+ * @param context - Orchestration context — see {@link MaskListContext}.
122
+ */
123
+ export declare function renderMaskList(context: MaskListContext): void;
124
+ /**
125
+ * Toggle the `active` CSS class on every `<li class="mask-item">` so the
126
+ * one whose `data-mask-id` matches `selectedMask.maskId` is highlighted.
127
+ *
128
+ * Matches legacy's `updateMaskListSelection` behavior except that selection is
129
+ * keyed off `data-mask-id` instead of the list-item text content. legacy used
130
+ * the visible name (`textContent === selectedMask.maskName`), which broke
131
+ * when an integrator overrode `options.label.getText` to render anything
132
+ * other than `maskName`. Keying off `data-mask-id` keeps the highlight in
133
+ * lock-step with the stable `data-mask-id` identifier and tolerates any
134
+ * label-text customization.
135
+ *
136
+ * No-op when the integrator did not supply a `maskList` element ID or the
137
+ * configured element does not exist in the document.
138
+ *
139
+ * @param context - Orchestration context — see {@link MaskListContext}.
140
+ * @param selectedMask - The currently selected mask, or `null` to clear the
141
+ * highlight.
142
+ */
143
+ export declare function updateMaskListSelection(context: MaskListContext, selectedMask: MaskObject | null): void;
144
+ //# sourceMappingURL=mask-list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mask-list.d.ts","sourceRoot":"","sources":["../../../src/mask/mask-list.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AAEH,OAAO,KAAK,KAAK,QAAQ,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAG1D;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC5B;;;;OAIG;IACH,MAAM,EAAE,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC;IAC/B;;;;OAIG;IACH,gBAAgB,IAAI,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAC9C;;;;;;OAMG;IACH,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;CAC1C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CA0C7D;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,uBAAuB,CACnC,OAAO,EAAE,eAAe,EACxB,YAAY,EAAE,UAAU,GAAG,IAAI,GAChC,IAAI,CAWN"}