@scaleflex/crop 2.0.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 (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +452 -0
  3. package/dist/a11y/aria.d.ts +5 -0
  4. package/dist/a11y/keyboard.d.ts +13 -0
  5. package/dist/animation/lerp.d.ts +15 -0
  6. package/dist/animation/spring.d.ts +15 -0
  7. package/dist/canvas/bleed-layer.d.ts +6 -0
  8. package/dist/canvas/crop-frame.d.ts +32 -0
  9. package/dist/canvas/grid-layer.d.ts +7 -0
  10. package/dist/canvas/hit-test.d.ts +10 -0
  11. package/dist/canvas/image-layer.d.ts +28 -0
  12. package/dist/canvas/overlay-layer.d.ts +6 -0
  13. package/dist/canvas/renderer.d.ts +34 -0
  14. package/dist/chunks/sfx-crop-1LGASewd.cjs +353 -0
  15. package/dist/chunks/sfx-crop-CEe6OfTZ.js +2030 -0
  16. package/dist/core/config.d.ts +10 -0
  17. package/dist/core/crop-controller.d.ts +65 -0
  18. package/dist/core/types.d.ts +270 -0
  19. package/dist/define.cjs +1194 -0
  20. package/dist/define.d.ts +1 -0
  21. package/dist/define.js +1746 -0
  22. package/dist/elements/base.d.ts +17 -0
  23. package/dist/elements/icons.d.ts +21 -0
  24. package/dist/elements/parse-shapes.d.ts +13 -0
  25. package/dist/elements/popover-anchor.d.ts +20 -0
  26. package/dist/elements/sfx-crop-canvas.d.ts +24 -0
  27. package/dist/elements/sfx-crop-canvas.styles.d.ts +1 -0
  28. package/dist/elements/sfx-crop-rotate.d.ts +42 -0
  29. package/dist/elements/sfx-crop-rotate.styles.d.ts +5 -0
  30. package/dist/elements/sfx-crop-shapes.d.ts +67 -0
  31. package/dist/elements/sfx-crop-shapes.styles.d.ts +6 -0
  32. package/dist/elements/sfx-crop-toolbar.d.ts +64 -0
  33. package/dist/elements/sfx-crop-toolbar.styles.d.ts +7 -0
  34. package/dist/elements/sfx-crop-zoom.d.ts +66 -0
  35. package/dist/elements/sfx-crop-zoom.styles.d.ts +7 -0
  36. package/dist/elements/sfx-crop.d.ts +134 -0
  37. package/dist/elements/sfx-crop.styles.d.ts +9 -0
  38. package/dist/export/exporter.d.ts +19 -0
  39. package/dist/index.cjs +2 -0
  40. package/dist/index.d.ts +22 -0
  41. package/dist/index.js +65 -0
  42. package/dist/interactions/drag-crop.d.ts +10 -0
  43. package/dist/interactions/pinch-zoom.d.ts +14 -0
  44. package/dist/interactions/pointer-tracker.d.ts +29 -0
  45. package/dist/interactions/resize-handles.d.ts +13 -0
  46. package/dist/interactions/wheel-zoom.d.ts +12 -0
  47. package/dist/react/define-CVJd5aYk.cjs +1545 -0
  48. package/dist/react/define-t4Z6KaLY.js +2590 -0
  49. package/dist/react/index-B-csHwK2.cjs +2 -0
  50. package/dist/react/index-CktjrogS.js +1468 -0
  51. package/dist/react/index.cjs +2 -0
  52. package/dist/react/index.d.ts +21 -0
  53. package/dist/react/index.js +10 -0
  54. package/dist/react/sfx-crop.d.ts +86 -0
  55. package/dist/react/use-sfx-crop-controller.d.ts +74 -0
  56. package/dist/react/use-sfx-crop.d.ts +31 -0
  57. package/dist/styles/shared.css.d.ts +20 -0
  58. package/dist/transforms/constrain.d.ts +68 -0
  59. package/dist/transforms/matrix.d.ts +23 -0
  60. package/dist/transforms/transform-state.d.ts +12 -0
  61. package/dist/utils/events.d.ts +16 -0
  62. package/dist/utils/math.d.ts +12 -0
  63. package/package.json +108 -0
@@ -0,0 +1,1194 @@
1
+ "use strict";const p=require("./chunks/sfx-crop-1LGASewd.cjs"),c=require("lit"),a=require("lit/decorators.js"),m=require("lit/directives/unsafe-html.js"),U='<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 9V7a2 2 0 0 0-2-2h-6"/><path d="m15 8-3-3 3-3"/><path d="M4 14a2 2 0 0 0-2 2v3a2 2 0 0 0 2 2h13a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2Z"/></svg>',q='<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h3"/><path d="M16 3h3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-3"/><path d="M12 20v2"/><path d="M12 15v2"/><path d="M12 10v2"/><path d="M12 5v2"/></svg>',N='<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 2v14a2 2 0 0 0 2 2h14"/><path d="M18 22V8a2 2 0 0 0-2-2H2"/></svg>',K='<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="14" x="2" y="3" rx="2"/><line x1="8" x2="16" y1="21" y2="21"/><line x1="12" x2="12" y1="17" y2="21"/></svg>',G='<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="20" x="5" y="2" rx="2" ry="2"/><path d="M12 18h.01"/></svg>',F='<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/></svg>',H='<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="5"/></svg>',W='<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="12" height="20" x="6" y="2" rx="2"/><rect width="20" height="12" x="2" y="6" rx="2"/></svg>',Z='<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>',J='<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 22h20"/><rect x="3" y="6" width="18" height="14" rx="2" transform="rotate(-10 12 13)"/></svg>',Q='<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" x2="16.65" y1="21" y2="16.65"/><line x1="11" x2="11" y1="8" y2="14"/><line x1="8" x2="14" y1="11" y2="11"/></svg>',tt='<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" x2="16.65" y1="21" y2="16.65"/><line x1="8" x2="14" y1="11" y2="11"/></svg>',et='<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" x2="16.65" y1="21" y2="16.65"/><line x1="11" x2="11" y1="8" y2="14"/><line x1="8" x2="14" y1="11" y2="11"/></svg>',ot='<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 3-6.7L3 8"/><path d="M3 3v5h5"/></svg>',h={rotateLeft:"",flipHorizontal:"",tilt:"",loupe:"",zoomIn:"",zoomOut:"",cropAspect:"",cropCustom:"",cropCircle:"",cropRoundedRect:"",orientLandscape:"",orientPortrait:"",chevronDown:"",reset:""};h.rotateLeft=U;h.flipHorizontal=q;h.tilt=J;h.loupe=et;h.zoomIn=Q;h.zoomOut=tt;h.cropAspect=W;h.cropCustom=N;h.cropCircle=F;h.cropRoundedRect=H;h.orientLandscape=K;h.orientPortrait=G;h.chevronDown=Z;h.reset=ot;function u(n,t){const e=t==null?void 0:t[n];return typeof e=="string"&&e.length>0?e:h[n]}const rt=c.css`
2
+ :host {
3
+ /* Inline-flex so the host doesn't take toolbar space — the actual ruler
4
+ below uses position:fixed and anchors to the canvas bottom-center via
5
+ createPopoverAnchor, exactly where the legacy popover lived. */
6
+ position: relative;
7
+ display: inline-flex;
8
+ align-items: center;
9
+ }
10
+
11
+ .sfx-cr-rotate-root {
12
+ position: relative;
13
+ display: flex;
14
+ align-items: center;
15
+ justify-content: center;
16
+ }
17
+
18
+ .sfx-cr-rotate-popover {
19
+ position: fixed;
20
+ top: var(--sfx-cr-popover-top, 50%);
21
+ left: var(--sfx-cr-popover-left, 50%);
22
+ transform: translateX(-50%);
23
+ display: flex;
24
+ flex-direction: column;
25
+ align-items: center;
26
+ gap: 4px;
27
+ padding: 0;
28
+ background: transparent;
29
+ border: none;
30
+ box-shadow: none;
31
+ pointer-events: auto;
32
+ white-space: nowrap;
33
+ z-index: 10;
34
+ }
35
+
36
+ /* Ruler viewport — fixed width, overflow clipped. Ticks scroll inside. */
37
+ .sfx-cr-rotate-ruler {
38
+ position: relative;
39
+ width: var(--ruler-w, 260px);
40
+ height: 30px;
41
+ overflow: hidden;
42
+ cursor: grab;
43
+ touch-action: none;
44
+ user-select: none;
45
+ -webkit-user-select: none;
46
+ }
47
+ .sfx-cr-rotate-ruler.is-dragging { cursor: grabbing; }
48
+ .sfx-cr-rotate-ruler:focus-visible {
49
+ outline: 2px solid var(--sfx-cr-ring);
50
+ outline-offset: 2px;
51
+ border-radius: 4px;
52
+ }
53
+
54
+ /* Tick strip — absolutely positioned ticks, the whole strip is
55
+ translated along X in sync with the current value. */
56
+ .sfx-cr-rotate-ticks {
57
+ position: absolute;
58
+ top: 50%;
59
+ left: 0;
60
+ height: 100%;
61
+ will-change: transform;
62
+ /* Dark halo wrapping every tick so the white ink stays legible over a
63
+ bright photo. Applied to the strip so all ticks share one shadow pass. */
64
+ filter:
65
+ drop-shadow(0 0 1px var(--sfx-cr-ruler-halo, oklch(0 0 0 / 0.85)))
66
+ drop-shadow(0 0 2px var(--sfx-cr-ruler-halo, oklch(0 0 0 / 0.85)));
67
+ }
68
+
69
+ .sfx-cr-rotate-tick {
70
+ position: absolute;
71
+ top: 50%;
72
+ width: 1px;
73
+ height: 8px;
74
+ margin-left: -0.5px;
75
+ margin-top: -4px;
76
+ border-radius: 0.5px;
77
+ background: var(--sfx-cr-ruler-ink, var(--sfx-cr-text));
78
+ opacity: 0.9;
79
+ }
80
+ .sfx-cr-rotate-tick--major {
81
+ width: 1px;
82
+ height: 12px;
83
+ margin-left: -0.5px;
84
+ margin-top: -6px;
85
+ opacity: 1;
86
+ }
87
+
88
+ /* Fixed center indicator — single vertical line, slightly taller than ticks. */
89
+ .sfx-cr-rotate-indicator {
90
+ position: absolute;
91
+ top: calc(50% + 4px);
92
+ height: 16px;
93
+ left: 50%;
94
+ width: 4px;
95
+ margin-left: -2px;
96
+ background: var(--sfx-cr-ruler-ink, var(--sfx-cr-text));
97
+ border-radius: 2px;
98
+ pointer-events: none;
99
+ /* Same dark halo as the ticks so the centre marker stays visible. */
100
+ filter:
101
+ drop-shadow(0 0 1px var(--sfx-cr-ruler-halo, oklch(0 0 0 / 0.85)))
102
+ drop-shadow(0 0 2px var(--sfx-cr-ruler-halo, oklch(0 0 0 / 0.85)));
103
+ }
104
+
105
+ .sfx-cr-rotate-value {
106
+ font-size: 14px;
107
+ font-weight: 400;
108
+ color: var(--sfx-cr-ruler-ink, var(--sfx-cr-text));
109
+ text-align: center;
110
+ font-variant-numeric: tabular-nums;
111
+ letter-spacing: 0.2px;
112
+ /* Dark halo so the degree readout reads over both bright and dark photos. */
113
+ text-shadow:
114
+ 0 0 2px var(--sfx-cr-ruler-halo, oklch(0 0 0 / 0.85)),
115
+ 0 1px 2px var(--sfx-cr-ruler-halo, oklch(0 0 0 / 0.85));
116
+ }
117
+
118
+ @media (max-width: 768px) {
119
+ .sfx-cr-rotate-ruler { width: 220px; height: 22px; }
120
+ .sfx-cr-rotate-tick { height: 6px; margin-top: -3px; }
121
+ .sfx-cr-rotate-tick--major { height: 9px; margin-top: -4.5px; }
122
+ .sfx-cr-rotate-indicator {
123
+ top: calc(50% + 3px);
124
+ height: 12px;
125
+ width: 3px;
126
+ margin-left: -1.5px;
127
+ }
128
+ .sfx-cr-rotate-value { font-size: 12px; }
129
+ .sfx-cr-rotate-popover {
130
+ transform: translateX(-50%) translateY(24px);
131
+ }
132
+ }
133
+ @media (max-width: 480px) {
134
+ .sfx-cr-rotate-ruler { width: 180px; height: 20px; }
135
+ .sfx-cr-rotate-tick { height: 5px; margin-top: -2.5px; }
136
+ .sfx-cr-rotate-tick--major { height: 8px; margin-top: -4px; }
137
+ .sfx-cr-rotate-indicator {
138
+ top: calc(50% + 2px);
139
+ height: 10px;
140
+ }
141
+ .sfx-cr-rotate-value { font-size: 11px; }
142
+ .sfx-cr-rotate-popover {
143
+ transform: translateX(-50%) translateY(30px);
144
+ }
145
+ }
146
+
147
+ /* Narrow editor — make the ruler more compact when the toolbar
148
+ collapses into the vertical left rail. */
149
+ @container sfxcrop (max-width: 760px) {
150
+ .sfx-cr-rotate-ruler { width: 140px; height: 18px; }
151
+ .sfx-cr-rotate-tick { height: 5px; margin-top: -2.5px; }
152
+ .sfx-cr-rotate-tick--major { height: 8px; margin-top: -4px; }
153
+ .sfx-cr-rotate-indicator {
154
+ top: calc(50% + 2px);
155
+ height: 10px;
156
+ width: 3px;
157
+ margin-left: -1.5px;
158
+ }
159
+ .sfx-cr-rotate-value { font-size: 11px; }
160
+ }
161
+ `,it=72;function V(n,t){let e=null,r=!1;const o=()=>{var y;let x=n;for(let C=0;C<6;C++){const z=x.getRootNode();if(!(z instanceof ShadowRoot))break;if(x=z.host,((y=x.tagName)==null?void 0:y.toLowerCase())==="sfx-crop")return x}return null},i=()=>{var z;const x=(z=n.shadowRoot)==null?void 0:z.querySelector(t),y=o();if(!x||!y)return;const C=y.getBoundingClientRect();x.style.setProperty("--sfx-cr-popover-left",`${C.left+C.width/2}px`),x.style.setProperty("--sfx-cr-popover-top",`${C.bottom-it}px`)},s=()=>i();return{start:()=>{if(r)return;r=!0,i(),window.addEventListener("resize",s),window.addEventListener("scroll",s,!0);const x=o();x&&typeof ResizeObserver<"u"&&(e=new ResizeObserver(()=>i()),e.observe(x))},stop:()=>{r&&(r=!1,window.removeEventListener("resize",s),window.removeEventListener("scroll",s,!0),e==null||e.disconnect(),e=null)},update:i}}var st=Object.defineProperty,P=(n,t,e,r)=>{for(var o=void 0,i=n.length-1,s;i>=0;i--)(s=n[i])&&(o=s(t,e,o)||o);return o&&st(t,e,o),o};const S=6,T=260,R=1,nt=5,D=class D extends p.SfxCropBaseElement{constructor(){super(...arguments),this.value=0,this.min=-45,this.max=45,this.icons={},this.dragging=!1,this.activePointer=null,this.pointerStartX=0,this.pointerStartValue=0,this.popoverAnchor=V(this,".sfx-cr-rotate-popover"),this.onKeyDown=t=>{t.key==="ArrowLeft"?(t.preventDefault(),this.emit(p.clamp(this.value-(t.shiftKey?5:1),this.min,this.max))):t.key==="ArrowRight"&&(t.preventDefault(),this.emit(p.clamp(this.value+(t.shiftKey?5:1),this.min,this.max)))},this.onPointerDown=t=>{if(this.activePointer===null){this.activePointer=t.pointerId,this.pointerStartX=t.clientX,this.pointerStartValue=this.value,this.dragging=!0;try{t.currentTarget.setPointerCapture(t.pointerId)}catch{}}},this.onPointerMove=t=>{if(this.activePointer!==t.pointerId)return;const e=t.clientX-this.pointerStartX,r=this.pointerStartValue-e/S;this.emit(p.clamp(r,this.min,this.max))},this.onPointerUp=t=>{if(this.activePointer===t.pointerId){this.activePointer=null,this.dragging=!1;try{t.currentTarget.releasePointerCapture(t.pointerId)}catch{}Math.abs(this.value)<2&&this.emit(0)}},this.onReset=()=>{this.emit(0)}}connectedCallback(){super.connectedCallback(),this.popoverAnchor.start()}disconnectedCallback(){super.disconnectedCallback(),this.popoverAnchor.stop()}updated(t){(t.has("value")||t.has("dragging"))&&this.popoverAnchor.update()}render(){const t=`${this.value>0?"+":""}${this.value.toFixed(1)}°`,e=T/2-(this.value-this.min)*S,r=T/S,o=4,i=Math.max(this.min,Math.floor(this.value-r/2-o)),s=Math.min(this.max,Math.ceil(this.value+r/2+o)),g=[];for(let d=Math.ceil(i/R)*R;d<=s;d+=R)g.push({deg:d,major:d%nt===0});return c.html`
162
+ <div class="sfx-cr-rotate-root" @keydown=${this.onKeyDown}>
163
+ <div class="sfx-cr-rotate-popover" role="group" aria-label="Fine rotation">
164
+ <div
165
+ class=${`sfx-cr-rotate-ruler${this.dragging?" is-dragging":""}`}
166
+ role="slider"
167
+ aria-valuemin=${String(this.min)}
168
+ aria-valuemax=${String(this.max)}
169
+ aria-valuenow=${String(this.value)}
170
+ aria-valuetext=${t}
171
+ tabindex="0"
172
+ style=${`--ruler-w: ${T}px`}
173
+ @pointerdown=${this.onPointerDown}
174
+ @pointermove=${this.onPointerMove}
175
+ @pointerup=${this.onPointerUp}
176
+ @pointercancel=${this.onPointerUp}
177
+ @dblclick=${this.onReset}
178
+ >
179
+ <div class="sfx-cr-rotate-ticks" style=${`transform: translateX(${e.toFixed(1)}px)`}>
180
+ ${g.map(d=>c.html`
181
+ <span
182
+ class=${`sfx-cr-rotate-tick${d.major?" sfx-cr-rotate-tick--major":""}`}
183
+ style=${`left: ${((d.deg-this.min)*S).toFixed(1)}px`}
184
+ ></span>
185
+ `)}
186
+ </div>
187
+ <div class="sfx-cr-rotate-indicator" aria-hidden="true"></div>
188
+ </div>
189
+ <span class="sfx-cr-rotate-value">${t}</span>
190
+ </div>
191
+ </div>
192
+ `}setValue(t){this.value=p.clamp(t,this.min,this.max)}emit(t){this.value!==t&&(this.value=t,this.dispatchEvent(new CustomEvent("sfx-crop-rotate-change",{detail:{degrees:t},bubbles:!0,composed:!0})))}};D.styles=[p.baseStyles,rt];let w=D;P([a.property({type:Number})],w.prototype,"value");P([a.property({type:Number})],w.prototype,"min");P([a.property({type:Number})],w.prototype,"max");P([a.property({attribute:!1})],w.prototype,"icons");P([a.state()],w.prototype,"dragging");const at=c.css`
193
+ :host {
194
+ position: relative;
195
+ display: inline-block;
196
+ }
197
+
198
+ .sfx-cr-shape-trigger {
199
+ display: flex;
200
+ align-items: center;
201
+ gap: 6px;
202
+ padding: 8px 14px;
203
+ min-width: 84px;
204
+ height: 36px;
205
+ background: transparent;
206
+ color: var(--sfx-cr-text-secondary);
207
+ border: 1.5px solid var(--sfx-cr-border);
208
+ border-radius: 50px;
209
+ cursor: pointer;
210
+ font-family: var(--sfx-cr-font);
211
+ font-size: 14px;
212
+ font-weight: 500;
213
+ transition:
214
+ background var(--sfx-cr-transition),
215
+ border-color var(--sfx-cr-transition),
216
+ color var(--sfx-cr-transition),
217
+ transform var(--sfx-cr-transition),
218
+ box-shadow var(--sfx-cr-transition);
219
+ white-space: nowrap;
220
+ letter-spacing: 0.1px;
221
+ }
222
+
223
+ /* Fixed variant: translucent pill so the trigger reads over the photo
224
+ (no dimmed crop mask behind it). Plain background — no filter/transform. */
225
+ :host([variant="fixed"]) .sfx-cr-shape-trigger {
226
+ background: var(--sfx-cr-overlay-color);
227
+ }
228
+
229
+ .sfx-cr-shape-trigger:hover {
230
+ border-color: var(--sfx-cr-primary);
231
+ transform: translateY(-1px);
232
+ }
233
+
234
+ .sfx-cr-shape-trigger:focus-visible {
235
+ outline: 2px solid var(--sfx-cr-ring);
236
+ outline-offset: 2px;
237
+ }
238
+
239
+ .sfx-cr-shape-trigger-icon {
240
+ display: flex;
241
+ width: 20px;
242
+ height: 20px;
243
+ }
244
+ .sfx-cr-shape-trigger-icon svg { width: 100%; height: 100%; display: block; }
245
+
246
+ .sfx-cr-shape-trigger-label { line-height: 1; }
247
+
248
+ .sfx-cr-shape-chevron {
249
+ display: flex;
250
+ width: 14px;
251
+ height: 14px;
252
+ margin-left: auto;
253
+ transition: transform var(--sfx-cr-transition);
254
+ }
255
+ .sfx-cr-shape-chevron svg { width: 100%; height: 100%; display: block; }
256
+
257
+ :host([open]) .sfx-cr-shape-chevron {
258
+ transform: rotate(180deg);
259
+ }
260
+
261
+ .sfx-cr-shape-dropdown {
262
+ position: absolute;
263
+ top: calc(100% + 8px);
264
+ right: 0;
265
+ /* Size to the widest option + orientation toggle. Clamp to avoid
266
+ spilling off the viewport on very narrow screens. */
267
+ width: max-content;
268
+ min-width: 88px;
269
+ max-width: min(92vw, 200px);
270
+ max-height: min(55vh, 360px);
271
+ padding: 4px;
272
+ /* Barely-there grey tint with translucency so the panel picks up
273
+ whatever sits behind it (image, dark overlay, etc.) without reading
274
+ as a solid white box. backdrop-filter blur keeps text crisp on
275
+ busy backgrounds. */
276
+ background: var(--sfx-cr-dropdown-bg);
277
+ border: 1px solid var(--sfx-cr-border);
278
+ border-radius: var(--sfx-cr-radius-lg, 8px);
279
+ box-shadow: var(--sfx-cr-dropdown-shadow);
280
+ backdrop-filter: blur(14px) saturate(160%);
281
+ -webkit-backdrop-filter: blur(14px) saturate(160%);
282
+ display: flex;
283
+ flex-direction: column;
284
+ z-index: 100;
285
+ opacity: 0;
286
+ transform: translateY(6px) scale(0.96);
287
+ pointer-events: none;
288
+ transition: opacity 120ms ease-in, transform 120ms ease-in;
289
+ }
290
+
291
+ /* Orientation toggle — naked icon-only buttons centered at the top.
292
+ No border, no background: only the rectangle SVG is visible. Active
293
+ state is signalled by the icon's color (text) vs. inactive (muted). */
294
+ .sfx-cr-shape-orient {
295
+ display: flex;
296
+ justify-content: center;
297
+ gap: 8px;
298
+ margin-bottom: 4px;
299
+ }
300
+
301
+ .sfx-cr-shape-orient-btn {
302
+ display: flex;
303
+ align-items: center;
304
+ justify-content: center;
305
+ width: 36px;
306
+ height: 32px;
307
+ padding: 0;
308
+ background: transparent;
309
+ color: var(--sfx-cr-text-muted);
310
+ border: none;
311
+ border-radius: 4px;
312
+ cursor: pointer;
313
+ transition: color var(--sfx-cr-transition);
314
+ }
315
+ .sfx-cr-shape-orient-btn:hover {
316
+ color: var(--sfx-cr-text-secondary);
317
+ }
318
+ .sfx-cr-shape-orient-btn.is-active {
319
+ color: var(--sfx-cr-text-secondary);
320
+ }
321
+ .sfx-cr-shape-orient-btn svg {
322
+ width: 24px;
323
+ height: 24px;
324
+ /* display:block kills SVG's default baseline drop (inline elements sit
325
+ on the text baseline, leaving a few sub-pixels of descender space at
326
+ the bottom — enough to push a tightly-fitted icon visibly off-center).
327
+ The -1px translate is optical correction: both Lucide monitor (stand
328
+ under the screen) and smartphone (home-indicator dot near the bottom)
329
+ carry visual weight below their geometric centre. */
330
+ display: block;
331
+ transform: translateY(-1px);
332
+ }
333
+
334
+ .sfx-cr-shape-list {
335
+ flex: 1 1 auto;
336
+ display: flex;
337
+ flex-direction: column;
338
+ gap: 1px;
339
+ overflow-y: auto;
340
+ scrollbar-width: thin;
341
+ }
342
+ .sfx-cr-shape-list::-webkit-scrollbar {
343
+ width: 4px;
344
+ }
345
+ .sfx-cr-shape-list::-webkit-scrollbar-thumb {
346
+ background: var(--sfx-cr-border);
347
+ border-radius: 2px;
348
+ }
349
+
350
+ :host([open]) .sfx-cr-shape-dropdown {
351
+ opacity: 1;
352
+ transform: translateY(0) scale(1);
353
+ pointer-events: auto;
354
+ transition: opacity 220ms cubic-bezier(0.34, 1.2, 0.64, 1),
355
+ transform 220ms cubic-bezier(0.34, 1.2, 0.64, 1);
356
+ }
357
+
358
+ .sfx-cr-shape-option {
359
+ display: flex;
360
+ align-items: center;
361
+ gap: 8px;
362
+ /* Stretch to the dropdown's resolved (content-sized) width so all
363
+ rows share the same left edge + hover highlight. */
364
+ width: 100%;
365
+ padding: 5px 10px;
366
+ height: 30px;
367
+ background: transparent;
368
+ color: var(--sfx-cr-text-secondary);
369
+ border: none;
370
+ border-radius: var(--sfx-cr-radius-sm, 4px);
371
+ cursor: pointer;
372
+ font-family: var(--sfx-cr-font);
373
+ font-size: 14px;
374
+ font-weight: 500;
375
+ text-align: left;
376
+ transition: background var(--sfx-cr-transition), color var(--sfx-cr-transition);
377
+ }
378
+
379
+ .sfx-cr-shape-option:hover {
380
+ background: var(--sfx-cr-dropdown-hover);
381
+ color: var(--sfx-cr-primary);
382
+ }
383
+
384
+ .sfx-cr-shape-option:focus-visible {
385
+ outline: 2px solid var(--sfx-cr-ring);
386
+ outline-offset: -2px;
387
+ }
388
+
389
+ .sfx-cr-shape-option--active {
390
+ background: var(--sfx-cr-primary-bg);
391
+ color: var(--sfx-cr-primary);
392
+ }
393
+
394
+ .sfx-cr-shape-option-icon {
395
+ display: flex;
396
+ align-items: center;
397
+ justify-content: center;
398
+ width: 18px;
399
+ height: 18px;
400
+ color: var(--sfx-cr-text-secondary);
401
+ flex-shrink: 0;
402
+ }
403
+ .sfx-cr-shape-option-icon svg { width: 100%; height: 100%; display: block; }
404
+ .sfx-cr-shape-option--active .sfx-cr-shape-option-icon { color: var(--sfx-cr-primary); }
405
+
406
+ @media (max-width: 768px) {
407
+ /* Drop label + chevron and shrink to a square icon-only pill so
408
+ the shape icon sits dead-center. The base 84px min-width was
409
+ sized for the desktop "[icon] Aspect 16:9 ▾" layout and leaves
410
+ empty pill space on the right once those pieces are hidden. */
411
+ .sfx-cr-shape-trigger-label { display: none; }
412
+ .sfx-cr-shape-chevron { display: none; }
413
+ .sfx-cr-shape-trigger {
414
+ min-width: 0;
415
+ width: 36px;
416
+ padding: 0;
417
+ gap: 0;
418
+ justify-content: center;
419
+ }
420
+ }
421
+
422
+ /* Narrow editor: drop the textual label + chevron so the trigger
423
+ reduces to an icon-only 30×30 capsule that matches the rest of
424
+ the compact left-rail toolbar. Anchor the dropdown to the left
425
+ edge since the trigger now lives in the left column. Container
426
+ query keyed off the sfxcrop named container so a narrow editor on
427
+ a wide desktop also collapses. */
428
+ @container sfxcrop (max-width: 760px) {
429
+ /* Label/chevron also need hiding when only the editor (not viewport)
430
+ is narrow — the 768px @media above only triggers on small viewports. */
431
+ .sfx-cr-shape-trigger-label { display: none; }
432
+ .sfx-cr-shape-chevron { display: none; }
433
+ .sfx-cr-shape-trigger {
434
+ min-width: 0;
435
+ width: 30px;
436
+ height: 30px;
437
+ padding: 0;
438
+ gap: 0;
439
+ justify-content: center;
440
+ }
441
+ /* Shrink the icon SLOT (not just the SVG) and center its contents,
442
+ so the 16×16 SVG sits dead-center inside the 30×30 trigger. The
443
+ base .sfx-cr-shape-trigger-icon is 20×20 with display:flex but
444
+ no justify/align — leaving a 16×16 SVG inside top-left aligned. */
445
+ .sfx-cr-shape-trigger-icon {
446
+ width: 16px;
447
+ height: 16px;
448
+ align-items: center;
449
+ justify-content: center;
450
+ }
451
+ .sfx-cr-shape-trigger svg {
452
+ width: 16px;
453
+ height: 16px;
454
+ }
455
+ /* Fixed variant: match the toolbar's uniform 40×40 round icon buttons. */
456
+ :host([variant="fixed"]) .sfx-cr-shape-trigger {
457
+ width: 40px;
458
+ height: 40px;
459
+ }
460
+ /* Classic left-rail only: the trigger sits in the vertical rail, so the
461
+ dropdown opens to the SIDE (right of the trigger). Excluded from the
462
+ fixed variant — there the toolbar is a horizontal top bar, so the menu
463
+ must keep the default downward opening (below the trigger) to avoid
464
+ running off the screen edge. */
465
+ :host(:not([variant="fixed"])) .sfx-cr-shape-dropdown {
466
+ right: auto;
467
+ left: calc(100% + 6px);
468
+ top: auto;
469
+ bottom: -30px;
470
+ transform: translateY(6px) scale(0.96);
471
+ }
472
+ :host(:not([variant="fixed"])[open]) .sfx-cr-shape-dropdown {
473
+ transform: translateY(0) scale(1);
474
+ }
475
+ }
476
+ `;var pt=Object.defineProperty,k=(n,t,e,r)=>{for(var o=void 0,i=n.length-1,s;i>=0;i--)(s=n[i])&&(o=s(t,e,o)||o);return o&&pt(t,e,o),o};function X(n){let o,i;n>=1?(o=18,i=18/n):(i=18,o=18*n);const s=Math.max(0,o-1.5),g=Math.max(0,i-1.5),d=(22-s)/2,x=(22-g)/2,y=Math.min(3.5,Math.min(s,g)/4);return`<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22" fill="none" stroke="currentColor" stroke-width="${1.5}"><rect x="${d.toFixed(2)}" y="${x.toFixed(2)}" width="${s.toFixed(2)}" height="${g.toFixed(2)}" rx="${y.toFixed(2)}"/></svg>`}function ct(){return{free:{value:"free",label:"Custom",icon:N,show:"both"},circle:{value:"circle",label:"Circle",icon:F,show:"both"},"rounded-rect":{value:"rounded-rect",label:"Rounded",icon:H,show:"both"},square:{value:"square",label:"1:1",icon:X(1),show:"both"}}}const Y=ct();function O(n){const t=Y[n];if(t)return t;const e=p.parseRatio(n);return e===null?null:{value:n,label:n,icon:X(e),show:e>1?"landscape":e<1?"portrait":"both"}}const M=class M extends p.SfxCropBaseElement{constructor(){super(...arguments),this.value="free",this.shapes=["free","square","16:9","4:3","3:2","5:4","2:1","9:16","3:4","2:3","4:5","1:2"],this.open=!1,this.variant="classic",this.icons={},this.orientation="landscape",this.focusedIndex=-1,this.focusRafId=null,this.docClickHandler=()=>{this.open&&this.close()},this.onTriggerClick=t=>{if(t.stopPropagation(),this.open)this.close();else{const e=this.getVisibleOptions();this.focusedIndex=Math.max(0,e.findIndex(r=>r.value===this.value)),this.open=!0,this.dispatchEvent(new CustomEvent("sfx-crop-popover-open",{detail:{source:"shapes"},bubbles:!0,composed:!0}))}},this.onKeyDown=t=>{if(!this.open)return;const e=this.getVisibleOptions();switch(t.key){case"Escape":t.preventDefault(),t.stopPropagation(),this.close();break;case"ArrowDown":t.preventDefault(),t.stopPropagation(),this.focusOption(Math.min(this.focusedIndex+1,e.length-1));break;case"ArrowUp":t.preventDefault(),t.stopPropagation(),this.focusOption(Math.max(this.focusedIndex-1,0));break;case"ArrowLeft":t.preventDefault(),t.stopPropagation(),this.setOrientation("landscape");break;case"ArrowRight":t.preventDefault(),t.stopPropagation(),this.setOrientation("portrait");break;case"Enter":case" ":if(t.preventDefault(),t.stopPropagation(),this.focusedIndex>=0&&this.focusedIndex<e.length){const r=e[this.focusedIndex].value;this.value=r,this.close(),this.emit(r)}break}}}connectedCallback(){super.connectedCallback(),document.addEventListener("click",this.docClickHandler)}disconnectedCallback(){super.disconnectedCallback(),document.removeEventListener("click",this.docClickHandler),this.focusRafId!==null&&(cancelAnimationFrame(this.focusRafId),this.focusRafId=null)}updated(t){if(t.has("open")&&this.open){const e=O(this.value);e&&e.show!=="both"&&(this.orientation=e.show),this.focusRafId=requestAnimationFrame(()=>{this.focusRafId=null;const o=this.getVisibleOptions().findIndex(i=>i.value===this.value);this.focusOption(o>=0?o:0)})}}render(){const t=this.getVisibleOptions(),e=O(this.value)??Y.free;return c.html`
477
+ <div
478
+ @keydown=${this.onKeyDown}
479
+ @click=${r=>r.stopPropagation()}
480
+ >
481
+ <button
482
+ type="button"
483
+ class="sfx-cr-shape-trigger"
484
+ aria-label="Select crop shape"
485
+ aria-haspopup="listbox"
486
+ aria-expanded=${this.open?"true":"false"}
487
+ @click=${this.onTriggerClick}
488
+ >
489
+ <span class="sfx-cr-shape-trigger-icon">${m.unsafeHTML(u("cropAspect",this.icons))}</span>
490
+ <span class="sfx-cr-shape-trigger-label">${e.label}</span>
491
+ <span class="sfx-cr-shape-chevron">${m.unsafeHTML(u("chevronDown",this.icons))}</span>
492
+ </button>
493
+ <div class="sfx-cr-shape-dropdown" role="listbox">
494
+ <div class="sfx-cr-shape-orient" role="tablist" aria-label="Orientation">
495
+ <button
496
+ type="button"
497
+ class=${`sfx-cr-shape-orient-btn${this.orientation==="landscape"?" is-active":""}`}
498
+ role="tab"
499
+ aria-selected=${this.orientation==="landscape"?"true":"false"}
500
+ aria-label="Landscape orientations"
501
+ @click=${()=>this.setOrientation("landscape")}
502
+ >${m.unsafeHTML(u("orientLandscape",this.icons))}</button>
503
+ <button
504
+ type="button"
505
+ class=${`sfx-cr-shape-orient-btn${this.orientation==="portrait"?" is-active":""}`}
506
+ role="tab"
507
+ aria-selected=${this.orientation==="portrait"?"true":"false"}
508
+ aria-label="Portrait orientations"
509
+ @click=${()=>this.setOrientation("portrait")}
510
+ >${m.unsafeHTML(u("orientPortrait",this.icons))}</button>
511
+ </div>
512
+ <div class="sfx-cr-shape-list" role="presentation">
513
+ ${t.map((r,o)=>c.html`
514
+ <button
515
+ type="button"
516
+ class=${`sfx-cr-shape-option${r.value===this.value?" sfx-cr-shape-option--active":""}`}
517
+ role="option"
518
+ aria-selected=${r.value===this.value?"true":"false"}
519
+ @click=${i=>this.onOptionClick(i,r.value)}
520
+ data-index=${String(o)}
521
+ >
522
+ <span class="sfx-cr-shape-option-icon">${m.unsafeHTML(this.optionIcon(r))}</span>
523
+ <span class="sfx-cr-shape-option-label">${r.label}</span>
524
+ </button>
525
+ `)}
526
+ </div>
527
+ </div>
528
+ </div>
529
+ `}optionIcon(t){switch(t.value){case"free":return u("cropCustom",this.icons);case"circle":return u("cropCircle",this.icons);case"rounded-rect":return u("cropRoundedRect",this.icons);default:return t.icon}}setValue(t){this.value=t}setOrientation(t){this.orientation!==t&&(this.orientation=t,this.focusedIndex=-1)}getVisibleOptions(){const t=[];for(const e of this.shapes){const r=O(e);r&&(r.show!=="both"&&r.show!==this.orientation||t.push(r))}return t.sort((e,r)=>{const o={free:0,circle:1,"rounded-rect":2,square:3},i=o[e.value],s=o[r.value];if(i!==void 0&&s!==void 0)return i-s;if(i!==void 0)return-1;if(s!==void 0)return 1;const g=p.getAspectRatio(e.value)??1,d=p.getAspectRatio(r.value)??1;return this.orientation==="portrait"?d-g:g-d})}emit(t){this.dispatchEvent(new CustomEvent("sfx-crop-shape-change",{detail:{shape:t},bubbles:!0,composed:!0}))}close(){this.open=!1}onOptionClick(t,e){t.stopPropagation(),this.value=e,this.close(),this.emit(e)}focusOption(t){this.focusedIndex=t;const e=this.renderRoot.querySelector(`.sfx-cr-shape-option[data-index="${t}"]`);e==null||e.focus()}};M.styles=[p.baseStyles,at];let v=M;k([a.property({type:String})],v.prototype,"value");k([a.property({attribute:!1})],v.prototype,"shapes");k([a.property({type:Boolean,reflect:!0})],v.prototype,"open");k([a.property({type:String,reflect:!0})],v.prototype,"variant");k([a.property({attribute:!1})],v.prototype,"icons");k([a.state()],v.prototype,"orientation");k([a.state()],v.prototype,"focusedIndex");const lt=c.css`
530
+ :host {
531
+ position: absolute;
532
+ top: 16px;
533
+ left: 0;
534
+ right: 0;
535
+ display: flex;
536
+ justify-content: center;
537
+ z-index: 5;
538
+ animation: sfx-cr-toolbar-enter 300ms ease forwards;
539
+ pointer-events: none;
540
+ }
541
+
542
+ :host([toolbar-position="bottom"]) {
543
+ top: auto;
544
+ bottom: 16px;
545
+ }
546
+
547
+ /* Fixed variant: the editor box IS the crop frame, so the floating bar sits
548
+ directly over the photo. Classic gets its button contrast for free from
549
+ the dimmed crop mask; fixed has none, so give each control its own
550
+ translucent pill to stay legible over arbitrary image content.
551
+
552
+ IMPORTANT: backgrounds only — no backdrop-filter / transform / filter
553
+ here or on any ancestor. Those create a containing block for
554
+ position:fixed descendants, which would re-anchor the canvas-bottom ruler
555
+ + shape popover (both fixed-positioned) to the bar and knock the 45-deg
556
+ tilt ruler off-screen. A plain background is safe. */
557
+ :host([variant="fixed"]) .sfx-cr-toolbar,
558
+ :host([variant="fixed"]) .sfx-cr-toolbar-group {
559
+ /* Fixed variant runs a slightly tighter uniform gap than classic. */
560
+ gap: 12px;
561
+ }
562
+ :host([variant="fixed"]) .sfx-cr-reset-btn,
563
+ :host([variant="fixed"]) .sfx-cr-toolbar-btn {
564
+ background: var(--sfx-cr-overlay-color);
565
+ }
566
+
567
+ /* Primary "Done" action — pinned to the right edge of the toolbar strip,
568
+ away from the centered control cluster. The host spans the full block
569
+ width (left:0/right:0), so right:16px sits near the block's edge. */
570
+ .sfx-cr-done-btn {
571
+ position: absolute;
572
+ right: 16px;
573
+ top: 50%;
574
+ transform: translateY(-50%);
575
+ pointer-events: auto;
576
+ display: inline-flex;
577
+ align-items: center;
578
+ justify-content: center;
579
+ height: 36px;
580
+ padding: 0 18px;
581
+ background: var(--sfx-cr-primary);
582
+ color: #fff;
583
+ border: none;
584
+ border-radius: 50px;
585
+ cursor: pointer;
586
+ font-family: var(--sfx-cr-font);
587
+ font-size: 14px;
588
+ font-weight: 600;
589
+ letter-spacing: 0.1px;
590
+ white-space: nowrap;
591
+ box-shadow: var(--sfx-cr-toolbar-shadow);
592
+ transition:
593
+ background var(--sfx-cr-transition),
594
+ transform var(--sfx-cr-transition);
595
+ }
596
+ .sfx-cr-done-btn:hover {
597
+ background: var(--sfx-cr-primary-hover);
598
+ transform: translateY(-50%) scale(1.02);
599
+ }
600
+ .sfx-cr-done-btn:active {
601
+ transform: translateY(-50%) scale(0.97);
602
+ }
603
+ .sfx-cr-done-btn:focus-visible {
604
+ outline: 2px solid var(--sfx-cr-ring);
605
+ outline-offset: 2px;
606
+ }
607
+
608
+ /* Hole-punch pattern: the toolbar host is pointer-transparent and
609
+ individual interactive children below opt back in. Gaps between
610
+ buttons fall through to the canvas — important when a crop handle
611
+ ends up under the floating toolbar and the user wants to grab it.
612
+ The rotate / zoom / shapes sub-elements already follow the same
613
+ pattern in their own stylesheets. */
614
+ .sfx-cr-toolbar {
615
+ display: flex;
616
+ flex-direction: row;
617
+ align-items: center;
618
+ gap: 16px;
619
+ padding: 4px 8px;
620
+ background: transparent;
621
+ color: var(--sfx-cr-toolbar-color);
622
+ border: none;
623
+ border-radius: 0;
624
+ box-shadow: none;
625
+ pointer-events: none;
626
+ }
627
+
628
+ .sfx-cr-toolbar-group {
629
+ display: flex;
630
+ flex-direction: row;
631
+ align-items: center;
632
+ /* Match the toolbar's own gap so spacing is uniform across every button
633
+ (Reset, rotate, flip, shapes), not tighter inside the group. */
634
+ gap: 16px;
635
+ }
636
+
637
+ /* Sub-element hosts — same hole-punch opt-in as the plain buttons.
638
+ Their hosts are tight inline-flex wrappers around a single trigger,
639
+ so re-enabling them adds no extra dead zone. */
640
+ sfx-crop-rotate,
641
+ sfx-crop-shapes {
642
+ pointer-events: auto;
643
+ }
644
+
645
+ /* The fine-tilt ruler renders as a canvas-anchored fixed popover, but its
646
+ inline host still occupies a zero-width slot in the toolbar row — which
647
+ doubles the gap between the flip button and the shape selector. Pull the
648
+ host out of flow so every visible button stays evenly spaced (both
649
+ variants). The popover is position:fixed, so this doesn't move it. */
650
+ sfx-crop-rotate {
651
+ position: absolute;
652
+ }
653
+
654
+ .sfx-cr-toolbar-btn {
655
+ pointer-events: auto;
656
+ width: 52px;
657
+ height: 36px;
658
+ display: flex;
659
+ align-items: center;
660
+ justify-content: center;
661
+ background: transparent;
662
+ color: var(--sfx-cr-text-secondary);
663
+ /* Transparent default border → hover swaps in a colored ring without
664
+ the layout shifting from 0 → 1 px. */
665
+ border: 1px solid transparent;
666
+ border-radius: 999px;
667
+ cursor: pointer;
668
+ padding: 0;
669
+ transition:
670
+ border-color var(--sfx-cr-transition),
671
+ color var(--sfx-cr-transition),
672
+ transform var(--sfx-cr-transition);
673
+ }
674
+
675
+ .sfx-cr-toolbar-btn:hover {
676
+ border-color: var(--sfx-cr-primary);
677
+ transform: translateY(-1px);
678
+ }
679
+
680
+ .sfx-cr-toolbar-btn:active {
681
+ transform: translateY(0) scale(0.96);
682
+ }
683
+
684
+ .sfx-cr-toolbar-btn:disabled {
685
+ opacity: 0.45;
686
+ cursor: not-allowed;
687
+ }
688
+
689
+ .sfx-cr-toolbar-btn:disabled:hover {
690
+ border-color: transparent;
691
+ color: var(--sfx-cr-text-muted);
692
+ transform: none;
693
+ }
694
+
695
+ .sfx-cr-toolbar-btn:focus-visible {
696
+ outline: 2px solid var(--sfx-cr-ring);
697
+ outline-offset: 2px;
698
+ }
699
+
700
+ .sfx-cr-toolbar-btn svg {
701
+ width: 20px;
702
+ height: 20px;
703
+ display: block;
704
+ }
705
+
706
+ /* Reset pill — mirrors the shape-selector trigger visual language:
707
+ capsule border, transparent fill, primary-blue tint on hover. Lives
708
+ before every other control so the user can wipe back to the initial
709
+ state in one click. */
710
+ .sfx-cr-reset-btn {
711
+ pointer-events: auto;
712
+ display: flex;
713
+ align-items: center;
714
+ gap: 6px;
715
+ padding: 8px 14px;
716
+ min-width: 84px;
717
+ height: 36px;
718
+ background: transparent;
719
+ color: var(--sfx-cr-text-secondary);
720
+ border: 1.5px solid var(--sfx-cr-border);
721
+ border-radius: 50px;
722
+ cursor: pointer;
723
+ font-family: var(--sfx-cr-font);
724
+ font-size: 14px;
725
+ font-weight: 500;
726
+ letter-spacing: 0.1px;
727
+ white-space: nowrap;
728
+ transition:
729
+ background var(--sfx-cr-transition),
730
+ border-color var(--sfx-cr-transition),
731
+ color var(--sfx-cr-transition),
732
+ transform var(--sfx-cr-transition),
733
+ box-shadow var(--sfx-cr-transition);
734
+ }
735
+ .sfx-cr-reset-btn:hover {
736
+ border-color: var(--sfx-cr-primary);
737
+ transform: translateY(-1px);
738
+ }
739
+ .sfx-cr-reset-btn:focus-visible {
740
+ outline: 2px solid var(--sfx-cr-ring);
741
+ outline-offset: 2px;
742
+ }
743
+ .sfx-cr-reset-btn svg {
744
+ width: 18px;
745
+ height: 18px;
746
+ display: block;
747
+ }
748
+
749
+ @media (max-width: 768px) {
750
+ .sfx-cr-toolbar {
751
+ flex-wrap: wrap;
752
+ justify-content: center;
753
+ }
754
+ }
755
+
756
+ @media (max-width: 480px) {
757
+ .sfx-cr-toolbar {
758
+ padding: 6px 8px;
759
+ gap: 8px;
760
+ }
761
+ .sfx-cr-toolbar-group {
762
+ gap: 8px;
763
+ }
764
+ .sfx-cr-toolbar-btn {
765
+ width: 40px;
766
+ height: 32px;
767
+ }
768
+ .sfx-cr-toolbar-btn svg {
769
+ width: 18px;
770
+ height: 18px;
771
+ display: block;
772
+ }
773
+ }
774
+
775
+ /* Narrow editor (component itself is small, regardless of viewport):
776
+ stack the toolbar vertically along the LEFT edge, icons only.
777
+ Tucked tight against the edge with a compact 30×30 footprint so
778
+ the photo behind keeps maximum breathing room. Triggered by the
779
+ sfxcrop container set on the sfx-crop host — matches editor
780
+ width, not viewport width, so a narrow column on a wide desktop
781
+ gets the same compact layout.
782
+
783
+ EXCLUDED from the fixed variant via :not([variant="fixed"]): a fixed
784
+ frame is often portrait/phone-shaped (narrow but tall), where the left
785
+ rail looks wrong — fixed keeps the horizontal top bar at any width. */
786
+ @container sfxcrop (max-width: 760px) {
787
+ :host(:not([variant="fixed"])) {
788
+ top: 8px;
789
+ bottom: 8px;
790
+ /* Stay full-width (don't collapse to a left-hugging strip): the Done
791
+ button is absolutely positioned against this host, so the host must
792
+ keep spanning to the container's right edge for Done to stay in the
793
+ top-right corner. The button column is tucked left with padding
794
+ instead of by repositioning the whole host. */
795
+ left: 0;
796
+ right: 0;
797
+ padding-left: 4px;
798
+ justify-content: flex-start;
799
+ /* Cross-axis centers the stacked button column vertically inside
800
+ the full-height host strip — toolbar floats in the middle of
801
+ the photo regardless of its content height. */
802
+ align-items: center;
803
+ }
804
+ :host(:not([variant="fixed"])[toolbar-position="bottom"]) {
805
+ top: 8px;
806
+ bottom: 8px;
807
+ }
808
+ /* Keep Done pinned to the editor's top-right corner — exactly where the
809
+ desktop bar puts it — even though the host now spans the full photo
810
+ height (top:50% would otherwise drop it to the vertical middle). A bit
811
+ smaller and tucked tighter into the corner to free up the photo. */
812
+ :host(:not([variant="fixed"])) .sfx-cr-done-btn {
813
+ top: 6px;
814
+ right: 8px;
815
+ height: 28px;
816
+ padding: 0 12px;
817
+ font-size: 12px;
818
+ transform: none;
819
+ }
820
+ :host(:not([variant="fixed"])) .sfx-cr-done-btn:hover { transform: scale(1.02); }
821
+ :host(:not([variant="fixed"])) .sfx-cr-done-btn:active { transform: scale(0.97); }
822
+ :host(:not([variant="fixed"])) .sfx-cr-toolbar {
823
+ flex-direction: column;
824
+ flex-wrap: nowrap;
825
+ align-items: center;
826
+ justify-content: flex-start;
827
+ gap: 4px;
828
+ padding: 3px;
829
+ }
830
+ :host(:not([variant="fixed"])) .sfx-cr-toolbar-group {
831
+ flex-direction: column;
832
+ gap: 4px;
833
+ }
834
+ :host(:not([variant="fixed"])) .sfx-cr-toolbar-btn {
835
+ width: 30px;
836
+ height: 30px;
837
+ }
838
+ :host(:not([variant="fixed"])) .sfx-cr-toolbar-btn svg {
839
+ width: 16px;
840
+ height: 16px;
841
+ }
842
+ :host(:not([variant="fixed"])) .sfx-cr-reset-btn {
843
+ width: 30px;
844
+ height: 30px;
845
+ min-width: 0;
846
+ padding: 0;
847
+ gap: 0;
848
+ justify-content: center;
849
+ }
850
+ :host(:not([variant="fixed"])) .sfx-cr-reset-btn svg {
851
+ width: 16px;
852
+ height: 16px;
853
+ }
854
+ :host(:not([variant="fixed"])) .sfx-cr-reset-btn span {
855
+ display: none;
856
+ }
857
+ }
858
+
859
+ /* Fixed + narrow (portrait/phone frame): keep the horizontal top bar, but
860
+ render every control as a uniform 40×40 round icon button, centered in
861
+ the frame. (Reset / shapes labels are dropped — icon only.) */
862
+ @container sfxcrop (max-width: 760px) {
863
+ :host([variant="fixed"]) .sfx-cr-toolbar {
864
+ flex-wrap: wrap;
865
+ justify-content: center;
866
+ gap: 10px;
867
+ }
868
+ :host([variant="fixed"]) .sfx-cr-toolbar-group {
869
+ gap: 10px;
870
+ }
871
+ :host([variant="fixed"]) .sfx-cr-reset-btn,
872
+ :host([variant="fixed"]) .sfx-cr-toolbar-btn {
873
+ width: 40px;
874
+ height: 40px;
875
+ min-width: 0;
876
+ padding: 0;
877
+ gap: 0;
878
+ justify-content: center;
879
+ }
880
+ :host([variant="fixed"]) .sfx-cr-reset-btn span {
881
+ display: none;
882
+ }
883
+ :host([variant="fixed"]) .sfx-cr-done-btn {
884
+ /* Stay in the top-right corner when the bar wraps to multiple rows
885
+ (top:50% would otherwise centre Done over the taller wrapped bar),
886
+ a bit smaller and tucked tighter into the corner. */
887
+ top: 6px;
888
+ right: 8px;
889
+ height: 28px;
890
+ padding: 0 12px;
891
+ font-size: 12px;
892
+ transform: none;
893
+ }
894
+ :host([variant="fixed"]) .sfx-cr-done-btn:hover { transform: scale(1.02); }
895
+ :host([variant="fixed"]) .sfx-cr-done-btn:active { transform: scale(0.97); }
896
+ }
897
+ `;var ht=Object.defineProperty,f=(n,t,e,r)=>{for(var o=void 0,i=n.length-1,s;i>=0;i--)(s=n[i])&&(o=s(t,e,o)||o);return o&&ht(t,e,o),o};const _=class _ extends p.SfxCropBaseElement{constructor(){super(...arguments),this.shape="free",this.rotation=0,this.showRotateButton=!0,this.showFlipButton=!0,this.showRotateSlider=!0,this.showShapeSelector=!0,this.toolbarPosition="top",this.variant="classic",this.availableShapes=null,this.icons={},this.onPopoverOpen=t=>{var r;((r=t.detail)==null?void 0:r.source)!=="shapes"&&this.shapesEl&&(this.shapesEl.open=!1)}}render(){const t=this.showRotateButton||this.showFlipButton,e=p.parseAvailableShapes(this.availableShapes)??[...p.DEFAULT_SHAPES];return c.html`
898
+ <div class="sfx-cr-toolbar" @sfx-crop-popover-open=${this.onPopoverOpen}>
899
+ <button
900
+ type="button"
901
+ class="sfx-cr-reset-btn"
902
+ aria-label="Reset all changes"
903
+ @click=${()=>this.emit({type:"reset"})}
904
+ >
905
+ ${m.unsafeHTML(u("reset",this.icons))}
906
+ <span>Reset</span>
907
+ </button>
908
+
909
+ ${t?c.html`
910
+ <div class="sfx-cr-toolbar-group">
911
+ ${this.showRotateButton?c.html`
912
+ <button
913
+ type="button"
914
+ class="sfx-cr-toolbar-btn"
915
+ aria-label="Rotate left 90°"
916
+ @click=${()=>this.emit({type:"rotate-left"})}
917
+ >${m.unsafeHTML(u("rotateLeft",this.icons))}</button>
918
+ `:null}
919
+ ${this.showFlipButton?c.html`
920
+ <button
921
+ type="button"
922
+ class="sfx-cr-toolbar-btn"
923
+ aria-label="Flip horizontal"
924
+ @click=${()=>this.emit({type:"flip-h"})}
925
+ >${m.unsafeHTML(u("flipHorizontal",this.icons))}</button>
926
+ `:null}
927
+ </div>
928
+ `:null}
929
+
930
+ ${this.showRotateSlider?c.html`
931
+ <sfx-crop-rotate
932
+ .value=${this.rotation}
933
+ .icons=${this.icons}
934
+ @sfx-crop-rotate-change=${r=>this.emit({type:"rotation",value:r.detail.degrees})}
935
+ ></sfx-crop-rotate>
936
+ `:null}
937
+
938
+ ${this.showShapeSelector?c.html`
939
+ <sfx-crop-shapes
940
+ .value=${this.shape}
941
+ .shapes=${e}
942
+ variant=${this.variant}
943
+ .icons=${this.icons}
944
+ @sfx-crop-shape-change=${r=>this.emit({type:"shape",value:r.detail.shape})}
945
+ ></sfx-crop-shapes>
946
+ `:null}
947
+ </div>
948
+
949
+ <button
950
+ type="button"
951
+ class="sfx-cr-done-btn"
952
+ part="done"
953
+ aria-label="Done"
954
+ @click=${()=>this.emit({type:"save"})}
955
+ >Done</button>
956
+ `}setRotationValue(t){var e;this.rotation=t,(e=this.rotateEl)==null||e.setValue(t)}setShapeValue(t){var e;this.shape=t,(e=this.shapesEl)==null||e.setValue(t)}emit(t){this.dispatchEvent(new CustomEvent("sfx-crop-toolbar-command",{detail:t,bubbles:!0,composed:!0}))}};_.styles=[p.baseStyles,p.toolbarEnterKeyframes,lt];let l=_;f([a.property({type:String})],l.prototype,"shape");f([a.property({type:Number})],l.prototype,"rotation");f([a.property({type:Boolean,attribute:"show-rotate-button"})],l.prototype,"showRotateButton");f([a.property({type:Boolean,attribute:"show-flip-button"})],l.prototype,"showFlipButton");f([a.property({type:Boolean,attribute:"show-rotate-slider"})],l.prototype,"showRotateSlider");f([a.property({type:Boolean,attribute:"show-shape-selector"})],l.prototype,"showShapeSelector");f([a.property({type:String,attribute:"toolbar-position",reflect:!0})],l.prototype,"toolbarPosition");f([a.property({type:String,reflect:!0})],l.prototype,"variant");f([a.property({attribute:"available-shapes"})],l.prototype,"availableShapes");f([a.property({attribute:!1})],l.prototype,"icons");f([a.query("sfx-crop-rotate")],l.prototype,"rotateEl");f([a.query("sfx-crop-shapes")],l.prototype,"shapesEl");const dt=c.css`
957
+ :host {
958
+ position: relative;
959
+ display: inline-flex;
960
+ align-items: center;
961
+ }
962
+
963
+ .sfx-cr-zoom-root {
964
+ position: relative;
965
+ display: flex;
966
+ align-items: center;
967
+ justify-content: center;
968
+ }
969
+
970
+ /* Trigger — visually matches the other toolbar icon buttons. */
971
+ .sfx-cr-zoom-trigger {
972
+ width: 44px;
973
+ height: 36px;
974
+ display: flex;
975
+ align-items: center;
976
+ justify-content: center;
977
+ padding: 0;
978
+ background: transparent;
979
+ color: var(--sfx-cr-text-secondary);
980
+ border: 1px solid transparent;
981
+ border-radius: 999px;
982
+ cursor: pointer;
983
+ transition:
984
+ border-color var(--sfx-cr-transition),
985
+ color var(--sfx-cr-transition),
986
+ transform var(--sfx-cr-transition);
987
+ }
988
+ .sfx-cr-zoom-trigger:hover {
989
+ border-color: var(--sfx-cr-primary);
990
+ color: var(--sfx-cr-primary);
991
+ transform: translateY(-1px);
992
+ }
993
+ .sfx-cr-zoom-trigger:active {
994
+ transform: translateY(0) scale(0.96);
995
+ }
996
+ .sfx-cr-zoom-trigger:focus-visible {
997
+ outline: 2px solid var(--sfx-cr-ring);
998
+ outline-offset: 2px;
999
+ }
1000
+ .sfx-cr-zoom-trigger svg {
1001
+ width: 20px;
1002
+ height: 20px;
1003
+ display: block;
1004
+ }
1005
+
1006
+ :host([open]) .sfx-cr-zoom-trigger {
1007
+ color: var(--sfx-cr-primary);
1008
+ }
1009
+
1010
+ /* Popover — transparent, floats above the trigger. */
1011
+ .sfx-cr-zoom-popover {
1012
+ position: fixed;
1013
+ top: var(--sfx-cr-popover-top, 50%);
1014
+ left: var(--sfx-cr-popover-left, 50%);
1015
+ transform: translateX(-50%) translateY(6px) scale(0.98);
1016
+ display: flex;
1017
+ flex-direction: column;
1018
+ align-items: center;
1019
+ gap: 4px;
1020
+ padding: 0;
1021
+ background: transparent;
1022
+ border: none;
1023
+ box-shadow: none;
1024
+ opacity: 0;
1025
+ pointer-events: none;
1026
+ transition: opacity 120ms ease-in, transform 120ms ease-in;
1027
+ white-space: nowrap;
1028
+ z-index: 10;
1029
+ }
1030
+
1031
+ :host([open]) .sfx-cr-zoom-popover {
1032
+ opacity: 1;
1033
+ transform: translateX(-50%) translateY(0) scale(1);
1034
+ pointer-events: auto;
1035
+ transition: opacity 220ms cubic-bezier(0.34, 1.2, 0.64, 1),
1036
+ transform 220ms cubic-bezier(0.34, 1.2, 0.64, 1);
1037
+ }
1038
+
1039
+ .sfx-cr-zoom-ruler {
1040
+ position: relative;
1041
+ width: var(--ruler-w, 260px);
1042
+ height: 30px;
1043
+ overflow: hidden;
1044
+ cursor: grab;
1045
+ touch-action: none;
1046
+ user-select: none;
1047
+ -webkit-user-select: none;
1048
+ }
1049
+ .sfx-cr-zoom-ruler.is-dragging { cursor: grabbing; }
1050
+ .sfx-cr-zoom-ruler:focus-visible {
1051
+ outline: 2px solid var(--sfx-cr-ring);
1052
+ outline-offset: 2px;
1053
+ border-radius: 4px;
1054
+ }
1055
+
1056
+ .sfx-cr-zoom-ticks {
1057
+ position: absolute;
1058
+ top: 50%;
1059
+ left: 0;
1060
+ height: 100%;
1061
+ will-change: transform;
1062
+ }
1063
+
1064
+ .sfx-cr-zoom-tick {
1065
+ position: absolute;
1066
+ top: 50%;
1067
+ width: 1px;
1068
+ height: 8px;
1069
+ margin-left: -0.5px;
1070
+ margin-top: -4px;
1071
+ border-radius: 0.5px;
1072
+ background: var(--sfx-cr-text);
1073
+ opacity: 0.55;
1074
+ }
1075
+ .sfx-cr-zoom-tick--major {
1076
+ width: 1px;
1077
+ height: 12px;
1078
+ margin-left: -0.5px;
1079
+ margin-top: -6px;
1080
+ opacity: 0.9;
1081
+ }
1082
+
1083
+ .sfx-cr-zoom-indicator {
1084
+ position: absolute;
1085
+ top: calc(50% + 4px);
1086
+ height: 16px;
1087
+ left: 50%;
1088
+ width: 4px;
1089
+ margin-left: -2px;
1090
+ background: var(--sfx-cr-text);
1091
+ border-radius: 2px;
1092
+ pointer-events: none;
1093
+ }
1094
+
1095
+ .sfx-cr-zoom-value {
1096
+ font-size: 14px;
1097
+ font-weight: 400;
1098
+ color: var(--sfx-cr-text);
1099
+ text-align: center;
1100
+ font-variant-numeric: tabular-nums;
1101
+ letter-spacing: 0.2px;
1102
+ }
1103
+
1104
+ @media (max-width: 768px) {
1105
+ /* Slimmer ruler + smaller readout on phones, mirroring the rotate
1106
+ popover. Push the popover down toward the canvas bottom edge so
1107
+ it doesn't sit in the middle of the photo. */
1108
+ .sfx-cr-zoom-ruler { width: 220px; height: 22px; }
1109
+ .sfx-cr-zoom-tick { height: 6px; margin-top: -3px; }
1110
+ .sfx-cr-zoom-tick--major { height: 9px; margin-top: -4.5px; }
1111
+ .sfx-cr-zoom-indicator {
1112
+ top: calc(50% + 3px);
1113
+ height: 12px;
1114
+ width: 3px;
1115
+ margin-left: -1.5px;
1116
+ }
1117
+ .sfx-cr-zoom-value { font-size: 12px; }
1118
+ :host([open]) .sfx-cr-zoom-popover {
1119
+ transform: translateX(-50%) translateY(24px) scale(1);
1120
+ }
1121
+ }
1122
+ @media (max-width: 480px) {
1123
+ .sfx-cr-zoom-ruler { width: 180px; height: 20px; }
1124
+ .sfx-cr-zoom-tick { height: 5px; margin-top: -2.5px; }
1125
+ .sfx-cr-zoom-tick--major { height: 8px; margin-top: -4px; }
1126
+ .sfx-cr-zoom-indicator {
1127
+ top: calc(50% + 2px);
1128
+ height: 10px;
1129
+ }
1130
+ .sfx-cr-zoom-value { font-size: 11px; }
1131
+ :host([open]) .sfx-cr-zoom-popover {
1132
+ transform: translateX(-50%) translateY(30px) scale(1);
1133
+ }
1134
+ }
1135
+
1136
+ /* Narrow editor — match the compact 30×30 trigger sizing of the
1137
+ rest of the vertical left-rail toolbar. Container query so a
1138
+ narrow editor on a wide desktop also collapses. */
1139
+ @container sfxcrop (max-width: 600px) {
1140
+ .sfx-cr-zoom-trigger {
1141
+ width: 30px;
1142
+ height: 30px;
1143
+ }
1144
+ .sfx-cr-zoom-trigger svg {
1145
+ width: 16px;
1146
+ height: 16px;
1147
+ }
1148
+ }
1149
+ `;var xt=Object.defineProperty,$=(n,t,e,r)=>{for(var o=void 0,i=n.length-1,s;i>=0;i--)(s=n[i])&&(o=s(t,e,o)||o);return o&&xt(t,e,o),o};function I(n,t,e){return Math.log(n/t)/Math.log(e/t)}function A(n,t,e){return t*Math.pow(e/t,n)}const E=260,L=260,B=40,ft=5,j=class j extends p.SfxCropBaseElement{constructor(){super(...arguments),this.min=.5,this.max=5,this.value=1,this.open=!1,this.icons={},this.dragging=!1,this.activePointer=null,this.pointerStartX=0,this.pointerStartSlider=0,this.autoCloseTimer=null,this.docClickHandler=()=>{this.open&&(this.clearAutoClose(),this.open=!1)},this.popoverAnchor=V(this,".sfx-cr-zoom-popover"),this.onTriggerClick=t=>{t.stopPropagation(),this.clearAutoClose(),this.open=!this.open,this.open&&this.dispatchEvent(new CustomEvent("sfx-crop-popover-open",{detail:{source:"zoom"},bubbles:!0,composed:!0}))},this.onKeyDown=t=>{if(this.open){if(t.key==="Escape"){t.preventDefault(),t.stopPropagation(),this.open=!1;return}if(t.key==="ArrowLeft"||t.key==="ArrowRight"){t.preventDefault();const e=t.key==="ArrowRight"?1:-1,r=t.shiftKey?.05:.01,o=I(this.value,this.min,this.max),i=p.clamp(o+e*r,0,1);this.emit(A(i,this.min,this.max))}}},this.onPointerDown=t=>{if(this.activePointer===null){this.clearAutoClose(),this.activePointer=t.pointerId,this.pointerStartX=t.clientX,this.pointerStartSlider=I(this.value,this.min,this.max),this.dragging=!0;try{t.currentTarget.setPointerCapture(t.pointerId)}catch{}}},this.onPointerMove=t=>{if(this.activePointer!==t.pointerId)return;const e=t.clientX-this.pointerStartX,r=p.clamp(this.pointerStartSlider-e/E,0,1);this.emit(A(r,this.min,this.max))},this.onPointerUp=t=>{if(this.activePointer===t.pointerId){this.activePointer=null,this.dragging=!1;try{t.currentTarget.releasePointerCapture(t.pointerId)}catch{}}},this.onReset=()=>{this.emit(1)}}connectedCallback(){super.connectedCallback(),document.addEventListener("click",this.docClickHandler)}disconnectedCallback(){super.disconnectedCallback(),document.removeEventListener("click",this.docClickHandler),this.popoverAnchor.stop(),this.clearAutoClose()}updated(t){t.has("open")&&(this.open?this.popoverAnchor.start():this.popoverAnchor.stop()),this.open&&(t.has("value")||t.has("dragging"))&&this.popoverAnchor.update()}render(){const t=I(this.value,this.min,this.max),e=Math.round(this.value*100),r=L/2-t*E,o=[],i=1/B;for(let s=0;s<=B;s++)o.push({pos:s*i,major:s%ft===0});return c.html`
1150
+ <div
1151
+ class="sfx-cr-zoom-root"
1152
+ @click=${s=>s.stopPropagation()}
1153
+ @keydown=${this.onKeyDown}
1154
+ >
1155
+ <button
1156
+ type="button"
1157
+ class="sfx-cr-zoom-trigger"
1158
+ aria-label=${`Zoom — ${e}%`}
1159
+ aria-haspopup="dialog"
1160
+ aria-expanded=${this.open?"true":"false"}
1161
+ @click=${this.onTriggerClick}
1162
+ >${m.unsafeHTML(u("loupe",this.icons))}</button>
1163
+
1164
+ <div class="sfx-cr-zoom-popover" role="group" aria-label="Zoom controls">
1165
+ <div
1166
+ class=${`sfx-cr-zoom-ruler${this.dragging?" is-dragging":""}`}
1167
+ role="slider"
1168
+ aria-valuemin=${String(this.min)}
1169
+ aria-valuemax=${String(this.max)}
1170
+ aria-valuenow=${this.value.toFixed(2)}
1171
+ aria-valuetext=${`${e}%`}
1172
+ tabindex="0"
1173
+ style=${`--ruler-w: ${L}px`}
1174
+ @pointerdown=${this.onPointerDown}
1175
+ @pointermove=${this.onPointerMove}
1176
+ @pointerup=${this.onPointerUp}
1177
+ @pointercancel=${this.onPointerUp}
1178
+ @dblclick=${this.onReset}
1179
+ >
1180
+ <div class="sfx-cr-zoom-ticks" style=${`transform: translateX(${r.toFixed(1)}px)`}>
1181
+ ${o.map(s=>c.html`
1182
+ <span
1183
+ class=${`sfx-cr-zoom-tick${s.major?" sfx-cr-zoom-tick--major":""}`}
1184
+ style=${`left: ${(s.pos*E).toFixed(1)}px`}
1185
+ ></span>
1186
+ `)}
1187
+ </div>
1188
+ <div class="sfx-cr-zoom-indicator" aria-hidden="true"></div>
1189
+ </div>
1190
+ <span class="sfx-cr-zoom-value">${e}%</span>
1191
+ </div>
1192
+ </div>
1193
+ `}setValue(t){this.value=p.clamp(t,this.min,this.max)}showTemporarily(t=1500){if(this.open&&this.autoCloseTimer===null)return;const e=this.open;this.open=!0,e||this.dispatchEvent(new CustomEvent("sfx-crop-popover-open",{detail:{source:"zoom"},bubbles:!0,composed:!0})),this.clearAutoClose(),this.autoCloseTimer=setTimeout(()=>{this.autoCloseTimer=null,this.open=!1},t)}clearAutoClose(){this.autoCloseTimer!==null&&(clearTimeout(this.autoCloseTimer),this.autoCloseTimer=null)}emit(t){this.value!==t&&(this.value=t,this.dispatchEvent(new CustomEvent("sfx-crop-zoom-change",{detail:{scale:t},bubbles:!0,composed:!0})))}};j.styles=[p.baseStyles,dt];let b=j;$([a.property({type:Number})],b.prototype,"min");$([a.property({type:Number})],b.prototype,"max");$([a.property({type:Number})],b.prototype,"value");$([a.property({type:Boolean,reflect:!0})],b.prototype,"open");$([a.property({attribute:!1})],b.prototype,"icons");$([a.state()],b.prototype,"dragging");p.safeDefine("sfx-crop-zoom",b);p.safeDefine("sfx-crop-rotate",w);p.safeDefine("sfx-crop-shapes",v);p.safeDefine("sfx-crop-canvas",p.SfxCropCanvasElement);p.safeDefine("sfx-crop-toolbar",l);p.safeDefine("sfx-crop",p.SfxCropElement);
1194
+ //# sourceMappingURL=define.cjs.map