@node-projects/web-component-designer 0.2.17 → 0.2.18

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 (36) hide show
  1. package/dist/elements/helper/getBoxQuads.js +91 -6
  2. package/dist/elements/helper/getBoxQuads.js.map +1 -1
  3. package/dist/elements/services/DefaultServiceBootstrap.js +3 -0
  4. package/dist/elements/services/DefaultServiceBootstrap.js.map +1 -1
  5. package/dist/elements/widgets/designerView/extensions/contextMenu/ToolWindowsContextMenu.d.ts +8 -0
  6. package/dist/elements/widgets/designerView/extensions/contextMenu/ToolWindowsContextMenu.js +54 -0
  7. package/dist/elements/widgets/designerView/extensions/contextMenu/ToolWindowsContextMenu.js.map +1 -0
  8. package/dist/elements/widgets/designerView/extensions/svg/UnifiedGeometryExtension.d.ts +1 -0
  9. package/dist/elements/widgets/designerView/extensions/svg/UnifiedGeometryExtension.js +19 -1
  10. package/dist/elements/widgets/designerView/extensions/svg/UnifiedGeometryExtension.js.map +1 -1
  11. package/dist/elements/widgets/designerView/tools/toolBar/DesignerToolbar.js +7 -1
  12. package/dist/elements/widgets/designerView/tools/toolBar/DesignerToolbar.js.map +1 -1
  13. package/dist/elements/widgets/designerView/tools/toolBar/popups/BorderRadiusEditorWindow.d.ts +82 -0
  14. package/dist/elements/widgets/designerView/tools/toolBar/popups/BorderRadiusEditorWindow.js +1260 -0
  15. package/dist/elements/widgets/designerView/tools/toolBar/popups/BorderRadiusEditorWindow.js.map +1 -0
  16. package/dist/elements/widgets/designerView/tools/toolBar/popups/BoxShadowEditorWindow.d.ts +38 -0
  17. package/dist/elements/widgets/designerView/tools/toolBar/popups/BoxShadowEditorWindow.js +518 -0
  18. package/dist/elements/widgets/designerView/tools/toolBar/popups/BoxShadowEditorWindow.js.map +1 -0
  19. package/dist/elements/widgets/designerView/tools/toolBar/popups/DraggableToolWindow.d.ts +39 -0
  20. package/dist/elements/widgets/designerView/tools/toolBar/popups/DraggableToolWindow.js +181 -0
  21. package/dist/elements/widgets/designerView/tools/toolBar/popups/DraggableToolWindow.js.map +1 -0
  22. package/dist/elements/widgets/designerView/tools/toolBar/popups/GradientEditorWindow.d.ts +53 -0
  23. package/dist/elements/widgets/designerView/tools/toolBar/popups/GradientEditorWindow.js +753 -0
  24. package/dist/elements/widgets/designerView/tools/toolBar/popups/GradientEditorWindow.js.map +1 -0
  25. package/dist/elements/widgets/designerView/tools/toolBar/popups/TextShadowEditorWindow.d.ts +36 -0
  26. package/dist/elements/widgets/designerView/tools/toolBar/popups/TextShadowEditorWindow.js +449 -0
  27. package/dist/elements/widgets/designerView/tools/toolBar/popups/TextShadowEditorWindow.js.map +1 -0
  28. package/dist/elements/widgets/designerView/tools/toolBar/popups/TransformToolPopup.d.ts +8 -6
  29. package/dist/elements/widgets/designerView/tools/toolBar/popups/TransformToolPopup.js +56 -72
  30. package/dist/elements/widgets/designerView/tools/toolBar/popups/TransformToolPopup.js.map +1 -1
  31. package/dist/index-min.js +544 -7
  32. package/dist/index-min.js.map +4 -4
  33. package/dist/index.d.ts +6 -0
  34. package/dist/index.js +6 -0
  35. package/dist/index.js.map +1 -1
  36. package/package.json +1 -1
@@ -0,0 +1,1260 @@
1
+ import { css } from '@node-projects/base-custom-webcomponent';
2
+ import { DraggableToolWindow } from './DraggableToolWindow.js';
3
+ function defaultCorner() {
4
+ return { x: 0, y: 0, shape: 'round' };
5
+ }
6
+ function defaultSide() {
7
+ return { width: 1, style: 'solid', color: '#000000', opacity: 100 };
8
+ }
9
+ function defaultConfig() {
10
+ return {
11
+ topLeft: { ...defaultCorner() },
12
+ topRight: { ...defaultCorner() },
13
+ bottomRight: { ...defaultCorner() },
14
+ bottomLeft: { ...defaultCorner() },
15
+ uniform: true,
16
+ width: 1,
17
+ borderUniform: true,
18
+ top: { ...defaultSide() },
19
+ right: { ...defaultSide() },
20
+ bottom: { ...defaultSide() },
21
+ left: { ...defaultSide() },
22
+ };
23
+ }
24
+ function hexToRgb(hex) {
25
+ const r = parseInt(hex.slice(1, 3), 16);
26
+ const g = parseInt(hex.slice(3, 5), 16);
27
+ const b = parseInt(hex.slice(5, 7), 16);
28
+ return `${r},${g},${b}`;
29
+ }
30
+ function radiusToCss(c) {
31
+ return c.x === c.y ? `${c.x}%` : `${c.x}% ${c.y}%`;
32
+ }
33
+ function configToRadiusCss(cfg) {
34
+ const corners = [cfg.topLeft, cfg.topRight, cfg.bottomRight, cfg.bottomLeft];
35
+ const xs = corners.map(c => `${c.x}%`);
36
+ const ys = corners.map(c => `${c.y}%`);
37
+ const allCornersSame = corners.every(c => c.x === corners[0].x && c.y === corners[0].y);
38
+ if (allCornersSame)
39
+ return radiusToCss(corners[0]);
40
+ const allCircular = corners.every(c => c.x === c.y);
41
+ if (allCircular)
42
+ return xs.join(' ');
43
+ return `${xs.join(' ')} / ${ys.join(' ')}`;
44
+ }
45
+ function configToShapeCss(cfg) {
46
+ const shapes = [cfg.topLeft.shape, cfg.topRight.shape, cfg.bottomRight.shape, cfg.bottomLeft.shape];
47
+ const allRound = shapes.every(s => s === 'round');
48
+ if (allRound)
49
+ return null; // Don't output corner-shape if all are round (the default)
50
+ const allSame = shapes[0] === shapes[1] && shapes[1] === shapes[2] && shapes[2] === shapes[3];
51
+ if (allSame)
52
+ return shapes[0];
53
+ return shapes.join(' ');
54
+ }
55
+ function configToBorderCss(cfg) {
56
+ const width = cfg.width > 0 ? `${cfg.width}px` : '0px';
57
+ if (cfg.borderUniform) {
58
+ // Uniform border style and color
59
+ const { style, color, opacity } = cfg.top;
60
+ const rgba = `${hexToRgb(color)},${(opacity / 100).toFixed(2)}`;
61
+ return {
62
+ width,
63
+ style,
64
+ color: `rgba(${rgba})`,
65
+ };
66
+ }
67
+ else {
68
+ // Per-side border style and color
69
+ const sides = [cfg.top, cfg.right, cfg.bottom, cfg.left];
70
+ const styles = sides.map(s => s.style);
71
+ const colors = sides.map(s => {
72
+ const rgba = `${hexToRgb(s.color)},${(s.opacity / 100).toFixed(2)}`;
73
+ return `rgba(${rgba})`;
74
+ });
75
+ const styleCss = styles[0] === styles[1] && styles[1] === styles[2] && styles[2] === styles[3]
76
+ ? styles[0]
77
+ : styles.join(' ');
78
+ const colorCss = colors[0] === colors[1] && colors[1] === colors[2] && colors[2] === colors[3]
79
+ ? colors[0]
80
+ : colors.join(' ');
81
+ return {
82
+ width,
83
+ style: styleCss,
84
+ color: colorCss,
85
+ };
86
+ }
87
+ }
88
+ // Parse "10px 20px 30px 40px / 10px 20px 30px 40px" format
89
+ // or just "10% 20% 30% 40%" format
90
+ function parseCornerRadius(value) {
91
+ const cfg = defaultConfig();
92
+ if (!value)
93
+ return cfg;
94
+ value = value.trim();
95
+ if (!value)
96
+ return cfg;
97
+ // Parse radius: "X1% X2% X3% X4% / Y1% Y2% Y3% Y4%" or simpler formats
98
+ const parts = value.split('/').map(p => p.trim());
99
+ const xStr = parts[0] ?? '';
100
+ const yStr = parts[1] ?? xStr; // if no Y values, use X values
101
+ const parseTokens = (str) => {
102
+ return str.split(/\s+/).map(t => {
103
+ const n = parseFloat(t);
104
+ return isNaN(n) ? 0 : n;
105
+ });
106
+ };
107
+ const xTokens = parseTokens(xStr);
108
+ const yTokens = parseTokens(yStr);
109
+ // Fill in corners: 1→all, 2→[TL/BR, TR/BL], 3→[TL, TR/BL, BR], 4→[TL, TR, BR, BL]
110
+ const getVal = (tokens, idx) => {
111
+ if (tokens.length === 1)
112
+ return tokens[0];
113
+ if (tokens.length === 2)
114
+ return tokens[idx === 1 || idx === 2 ? 1 : 0];
115
+ if (tokens.length === 3)
116
+ return tokens[idx === 3 ? 1 : (idx === 2 ? 2 : 0)];
117
+ return tokens[idx] ?? 0;
118
+ };
119
+ cfg.topLeft.x = getVal(xTokens, 0);
120
+ cfg.topRight.x = getVal(xTokens, 1);
121
+ cfg.bottomRight.x = getVal(xTokens, 2);
122
+ cfg.bottomLeft.x = getVal(xTokens, 3);
123
+ cfg.topLeft.y = getVal(yTokens, 0);
124
+ cfg.topRight.y = getVal(yTokens, 1);
125
+ cfg.bottomRight.y = getVal(yTokens, 2);
126
+ cfg.bottomLeft.y = getVal(yTokens, 3);
127
+ return cfg;
128
+ }
129
+ function splitTopLevelWhitespace(value) {
130
+ const tokens = [];
131
+ let depth = 0;
132
+ let current = '';
133
+ for (const ch of value.trim()) {
134
+ if (ch === '(')
135
+ depth++;
136
+ else if (ch === ')')
137
+ depth--;
138
+ if (/\s/.test(ch) && depth === 0) {
139
+ const trimmed = current.trim();
140
+ if (trimmed)
141
+ tokens.push(trimmed);
142
+ current = '';
143
+ continue;
144
+ }
145
+ current += ch;
146
+ }
147
+ const trimmed = current.trim();
148
+ if (trimmed)
149
+ tokens.push(trimmed);
150
+ return tokens;
151
+ }
152
+ function expandQuadTokens(tokens) {
153
+ if (tokens.length === 1)
154
+ return [tokens[0], tokens[0], tokens[0], tokens[0]];
155
+ if (tokens.length === 2)
156
+ return [tokens[0], tokens[1], tokens[0], tokens[1]];
157
+ if (tokens.length === 3)
158
+ return [tokens[0], tokens[1], tokens[2], tokens[1]];
159
+ return [tokens[0], tokens[1], tokens[2], tokens[3]];
160
+ }
161
+ export class BorderRadiusEditorWindow extends DraggableToolWindow {
162
+ _designerCanvas;
163
+ _config = defaultConfig();
164
+ _previewBox;
165
+ _uniformCheck;
166
+ // Unified corner controls
167
+ _uniformXInput;
168
+ _uniformYInput;
169
+ _uniformShapeSelect;
170
+ _uniformXVal;
171
+ _uniformYVal;
172
+ // Top-left
173
+ _tlXInput;
174
+ _tlYInput;
175
+ _tlShapeSelect;
176
+ _tlXVal;
177
+ _tlYVal;
178
+ // Top-right
179
+ _trXInput;
180
+ _trYInput;
181
+ _trShapeSelect;
182
+ _trXVal;
183
+ _trYVal;
184
+ // Bottom-right
185
+ _brXInput;
186
+ _brYInput;
187
+ _brShapeSelect;
188
+ _brXVal;
189
+ _brYVal;
190
+ // Bottom-left
191
+ _blXInput;
192
+ _blYInput;
193
+ _blShapeSelect;
194
+ _blXVal;
195
+ _blYVal;
196
+ // Section divs
197
+ _cornersUniformDiv;
198
+ _cornersPersideDiv;
199
+ // Border controls
200
+ _widthInput;
201
+ _borderUniformCheck;
202
+ _widthVal;
203
+ // Uniform border controls
204
+ _styleSelect;
205
+ _colorInput;
206
+ _opacityInput;
207
+ _opacityVal;
208
+ // Per-side border controls
209
+ _topStyleSelect;
210
+ _topWidthInput;
211
+ _topWidthVal;
212
+ _topColorInput;
213
+ _topOpacityInput;
214
+ _topOpacityVal;
215
+ _rightStyleSelect;
216
+ _rightWidthInput;
217
+ _rightWidthVal;
218
+ _rightColorInput;
219
+ _rightOpacityInput;
220
+ _rightOpacityVal;
221
+ _bottomStyleSelect;
222
+ _bottomWidthInput;
223
+ _bottomWidthVal;
224
+ _bottomColorInput;
225
+ _bottomOpacityInput;
226
+ _bottomOpacityVal;
227
+ _leftStyleSelect;
228
+ _leftWidthInput;
229
+ _leftWidthVal;
230
+ _leftColorInput;
231
+ _leftOpacityInput;
232
+ _leftOpacityVal;
233
+ _borderUniformDiv;
234
+ _borderPersideDiv;
235
+ _cssOutput;
236
+ _loadBtn;
237
+ _copyBtn;
238
+ _applyBtn;
239
+ get windowTitle() { return 'Border Radius Editor'; }
240
+ get windowContentStyle() {
241
+ return css `
242
+ * { box-sizing: border-box; }
243
+ .bre-root {
244
+ display: flex;
245
+ flex-direction: column;
246
+ width: 860px;
247
+ max-width: calc(100vw - 48px);
248
+ color: #ddd;
249
+ font-family: sans-serif;
250
+ font-size: 12px;
251
+ gap: 4px;
252
+ padding: 6px;
253
+ background: #2a2a2a;
254
+ }
255
+ .editors-row {
256
+ display: grid;
257
+ grid-template-columns: 1fr 1fr;
258
+ gap: 8px;
259
+ align-items: start;
260
+ }
261
+ .section-column {
262
+ display: flex;
263
+ flex-direction: column;
264
+ gap: 4px;
265
+ }
266
+ .preview-area {
267
+ background: #1a1a1a;
268
+ border: 1px solid #444;
269
+ border-radius: 3px;
270
+ height: 70px;
271
+ display: flex;
272
+ align-items: center;
273
+ justify-content: center;
274
+ margin-bottom: 2px;
275
+ }
276
+ .preview-box {
277
+ width: 60px;
278
+ height: 60px;
279
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
280
+ border-radius: 0;
281
+ }
282
+ .uniform-row {
283
+ display: flex;
284
+ align-items: center;
285
+ gap: 6px;
286
+ font-size: 11px;
287
+ }
288
+ .uniform-row label {
289
+ display: flex;
290
+ align-items: center;
291
+ gap: 3px;
292
+ cursor: pointer;
293
+ user-select: none;
294
+ color: #aaa;
295
+ }
296
+ .uniform-row input[type=checkbox] {
297
+ cursor: pointer;
298
+ width: 14px;
299
+ height: 14px;
300
+ }
301
+ .section-header {
302
+ color: #999;
303
+ font-size: 10px;
304
+ font-weight: bold;
305
+ margin-top: 3px;
306
+ margin-bottom: 2px;
307
+ text-transform: uppercase;
308
+ }
309
+ .corner-grid {
310
+ display: grid;
311
+ grid-template-columns: 1fr 1fr;
312
+ gap: 4px;
313
+ }
314
+ .separate-grid {
315
+ display: grid;
316
+ grid-template-columns: repeat(2, minmax(0, 1fr));
317
+ gap: 4px;
318
+ }
319
+ .corner-section {
320
+ background: #1e1e1e;
321
+ border: 1px solid #444;
322
+ border-radius: 2px;
323
+ padding: 4px;
324
+ }
325
+ .corner-header {
326
+ color: #888;
327
+ font-size: 10px;
328
+ margin-bottom: 3px;
329
+ font-weight: bold;
330
+ }
331
+ .corner-row {
332
+ display: grid;
333
+ grid-template-columns: 35px 1fr 24px;
334
+ gap: 2px 3px;
335
+ align-items: center;
336
+ margin-bottom: 2px;
337
+ }
338
+ .corner-row:last-child { margin-bottom: 0; }
339
+ .corner-row label { color: #888; font-size: 10px; }
340
+ .corner-row input[type=range] { width: 100%; height: 16px; }
341
+ .corner-row input[type=color] { width: 100%; height: 18px; cursor: pointer; }
342
+ .corner-row .val-label { text-align: right; color: #777; font-size: 9px; }
343
+ .bre-root select {
344
+ background: #2e2e2e;
345
+ color: #ddd;
346
+ border: 1px solid #555;
347
+ border-radius: 2px;
348
+ padding: 2px 3px;
349
+ font-size: 10px;
350
+ height: 18px;
351
+ }
352
+ .corner-row select {
353
+ grid-column: 1 / -1;
354
+ }
355
+ .side-pair {
356
+ display: grid;
357
+ grid-template-columns: 1fr 1fr;
358
+ gap: 4px;
359
+ }
360
+ @media (max-width: 900px) {
361
+ .bre-root {
362
+ width: 420px;
363
+ }
364
+ .editors-row {
365
+ grid-template-columns: 1fr;
366
+ }
367
+ }
368
+ .separator {
369
+ height: 1px;
370
+ background: #444;
371
+ margin: 2px 0;
372
+ }
373
+ .css-out {
374
+ background: #1e1e1e;
375
+ border: 1px solid #333;
376
+ border-radius: 3px;
377
+ color: #7ec8e3;
378
+ font-family: monospace;
379
+ font-size: 10px;
380
+ padding: 4px;
381
+ resize: none;
382
+ width: 100%;
383
+ height: 50px;
384
+ }
385
+ .actions {
386
+ display: flex;
387
+ gap: 4px;
388
+ justify-content: flex-end;
389
+ }
390
+ .actions .load-btn { margin-right: auto; }
391
+ .actions button, .apply-btn {
392
+ padding: 3px 10px;
393
+ background: #3a3a3a;
394
+ color: #ddd;
395
+ border: 1px solid #555;
396
+ border-radius: 2px;
397
+ cursor: pointer;
398
+ font-size: 11px;
399
+ }
400
+ .actions button:hover, .apply-btn:hover { background: #555; }
401
+ .apply-btn {
402
+ background: #3a6a9a;
403
+ border-color: #2a5a8a;
404
+ }
405
+ .apply-btn:hover { background: #4a7aaa; }
406
+ `;
407
+ }
408
+ get windowTemplate() {
409
+ return `
410
+ <div class="bre-root">
411
+ <div class="preview-area">
412
+ <div class="preview-box" id="bre-preview"></div>
413
+ </div>
414
+
415
+ <div class="editors-row">
416
+ <div class="section-column">
417
+ <div class="section-header">Corners</div>
418
+ <div class="uniform-row">
419
+ <label><input type="checkbox" id="bre-uniform"> Separate corners</label>
420
+ </div>
421
+
422
+ <div id="bre-corners-uniform" class="corner-section">
423
+ <div class="corner-row">
424
+ <label>X</label>
425
+ <input type="range" id="bre-uniform-x" min="0" max="100" step="1">
426
+ <span class="val-label" id="bre-uniform-x-val">0%</span>
427
+ </div>
428
+ <div class="corner-row">
429
+ <label>Y</label>
430
+ <input type="range" id="bre-uniform-y" min="0" max="100" step="1">
431
+ <span class="val-label" id="bre-uniform-y-val">0%</span>
432
+ </div>
433
+ <select id="bre-uniform-shape">
434
+ <option value="round">Round</option>
435
+ <option value="bevel">Bevel</option>
436
+ <option value="scoop">Scoop</option>
437
+ <option value="inset">Inset</option>
438
+ <option value="notch">Notch</option>
439
+ </select>
440
+ </div>
441
+
442
+ <div id="bre-corners-perside" class="corner-grid separate-grid" style="display: none;">
443
+ <div class="corner-section">
444
+ <div class="corner-header">Top-left</div>
445
+ <div class="corner-row">
446
+ <label>X</label>
447
+ <input type="range" id="bre-tl-x" min="0" max="100" step="1">
448
+ <span class="val-label" id="bre-tl-x-val">0%</span>
449
+ </div>
450
+ <div class="corner-row">
451
+ <label>Y</label>
452
+ <input type="range" id="bre-tl-y" min="0" max="100" step="1">
453
+ <span class="val-label" id="bre-tl-y-val">0%</span>
454
+ </div>
455
+ <select id="bre-tl-shape">
456
+ <option value="round">Round</option>
457
+ <option value="bevel">Bevel</option>
458
+ <option value="scoop">Scoop</option>
459
+ <option value="inset">Inset</option>
460
+ <option value="notch">Notch</option>
461
+ </select>
462
+ </div>
463
+
464
+ <div class="corner-section">
465
+ <div class="corner-header">Top-right</div>
466
+ <div class="corner-row">
467
+ <label>X</label>
468
+ <input type="range" id="bre-tr-x" min="0" max="100" step="1">
469
+ <span class="val-label" id="bre-tr-x-val">0%</span>
470
+ </div>
471
+ <div class="corner-row">
472
+ <label>Y</label>
473
+ <input type="range" id="bre-tr-y" min="0" max="100" step="1">
474
+ <span class="val-label" id="bre-tr-y-val">0%</span>
475
+ </div>
476
+ <select id="bre-tr-shape">
477
+ <option value="round">Round</option>
478
+ <option value="bevel">Bevel</option>
479
+ <option value="scoop">Scoop</option>
480
+ <option value="inset">Inset</option>
481
+ <option value="notch">Notch</option>
482
+ </select>
483
+ </div>
484
+
485
+ <div class="corner-section">
486
+ <div class="corner-header">Bottom-right</div>
487
+ <div class="corner-row">
488
+ <label>X</label>
489
+ <input type="range" id="bre-br-x" min="0" max="100" step="1">
490
+ <span class="val-label" id="bre-br-x-val">0%</span>
491
+ </div>
492
+ <div class="corner-row">
493
+ <label>Y</label>
494
+ <input type="range" id="bre-br-y" min="0" max="100" step="1">
495
+ <span class="val-label" id="bre-br-y-val">0%</span>
496
+ </div>
497
+ <select id="bre-br-shape">
498
+ <option value="round">Round</option>
499
+ <option value="bevel">Bevel</option>
500
+ <option value="scoop">Scoop</option>
501
+ <option value="inset">Inset</option>
502
+ <option value="notch">Notch</option>
503
+ </select>
504
+ </div>
505
+
506
+ <div class="corner-section">
507
+ <div class="corner-header">Bottom-left</div>
508
+ <div class="corner-row">
509
+ <label>X</label>
510
+ <input type="range" id="bre-bl-x" min="0" max="100" step="1">
511
+ <span class="val-label" id="bre-bl-x-val">0%</span>
512
+ </div>
513
+ <div class="corner-row">
514
+ <label>Y</label>
515
+ <input type="range" id="bre-bl-y" min="0" max="100" step="1">
516
+ <span class="val-label" id="bre-bl-y-val">0%</span>
517
+ </div>
518
+ <select id="bre-bl-shape">
519
+ <option value="round">Round</option>
520
+ <option value="bevel">Bevel</option>
521
+ <option value="scoop">Scoop</option>
522
+ <option value="inset">Inset</option>
523
+ <option value="notch">Notch</option>
524
+ </select>
525
+ </div>
526
+ </div>
527
+ </div>
528
+
529
+ <div class="section-column">
530
+ <div class="section-header">Border</div>
531
+ <div class="corner-row">
532
+ <label>Width</label>
533
+ <input type="range" id="bre-width" min="0" max="50" step="1">
534
+ <span class="val-label" id="bre-width-val">0px</span>
535
+ </div>
536
+ <div class="uniform-row">
537
+ <label><input type="checkbox" id="bre-border-separate"> Separate sides</label>
538
+ </div>
539
+
540
+ <div id="bre-border-uniform-editor" class="corner-section">
541
+ <div class="corner-row">
542
+ <label>Style</label>
543
+ <select id="bre-style">
544
+ <option value="solid">Solid</option>
545
+ <option value="dashed">Dashed</option>
546
+ <option value="dotted">Dotted</option>
547
+ <option value="double">Double</option>
548
+ <option value="groove">Groove</option>
549
+ <option value="ridge">Ridge</option>
550
+ <option value="inset">Inset</option>
551
+ <option value="outset">Outset</option>
552
+ <option value="none">None</option>
553
+ </select>
554
+ </div>
555
+ <div class="corner-row">
556
+ <label>Color</label>
557
+ <input type="color" id="bre-color" value="#000000">
558
+ </div>
559
+ <div class="corner-row">
560
+ <label>Opacity</label>
561
+ <input type="range" id="bre-opacity" min="0" max="100" step="1">
562
+ <span class="val-label" id="bre-opacity-val">100%</span>
563
+ </div>
564
+ </div>
565
+
566
+ <div id="bre-border-perside" class="corner-grid separate-grid" style="display: none;">
567
+ <div class="corner-section">
568
+ <div class="corner-header">Top</div>
569
+ <select id="bre-top-style">
570
+ <option value="solid">Solid</option>
571
+ <option value="dashed">Dashed</option>
572
+ <option value="dotted">Dotted</option>
573
+ <option value="double">Double</option>
574
+ <option value="groove">Groove</option>
575
+ <option value="ridge">Ridge</option>
576
+ <option value="inset">Inset</option>
577
+ <option value="outset">Outset</option>
578
+ <option value="none">None</option>
579
+ </select>
580
+ <div class="corner-row">
581
+ <label>Width</label>
582
+ <input type="range" id="bre-top-width" min="0" max="50" step="1">
583
+ <span class="val-label" id="bre-top-width-val">0px</span>
584
+ </div>
585
+ <input type="color" id="bre-top-color" value="#000000" style="width: 100%; height: 20px; margin: 2px 0;">
586
+ <div class="corner-row" style="grid-template-columns: 1fr;">
587
+ <label style="grid-column: 1;">Opacity</label>
588
+ <input type="range" id="bre-top-opacity" min="0" max="100" step="1">
589
+ <span class="val-label" id="bre-top-opacity-val">100%</span>
590
+ </div>
591
+ </div>
592
+
593
+ <div class="corner-section">
594
+ <div class="corner-header">Right</div>
595
+ <select id="bre-right-style">
596
+ <option value="solid">Solid</option>
597
+ <option value="dashed">Dashed</option>
598
+ <option value="dotted">Dotted</option>
599
+ <option value="double">Double</option>
600
+ <option value="groove">Groove</option>
601
+ <option value="ridge">Ridge</option>
602
+ <option value="inset">Inset</option>
603
+ <option value="outset">Outset</option>
604
+ <option value="none">None</option>
605
+ </select>
606
+ <div class="corner-row">
607
+ <label>Width</label>
608
+ <input type="range" id="bre-right-width" min="0" max="50" step="1">
609
+ <span class="val-label" id="bre-right-width-val">0px</span>
610
+ </div>
611
+ <input type="color" id="bre-right-color" value="#000000" style="width: 100%; height: 20px; margin: 2px 0;">
612
+ <div class="corner-row" style="grid-template-columns: 1fr;">
613
+ <label style="grid-column: 1;">Opacity</label>
614
+ <input type="range" id="bre-right-opacity" min="0" max="100" step="1">
615
+ <span class="val-label" id="bre-right-opacity-val">100%</span>
616
+ </div>
617
+ </div>
618
+
619
+ <div class="corner-section">
620
+ <div class="corner-header">Bottom</div>
621
+ <select id="bre-bottom-style">
622
+ <option value="solid">Solid</option>
623
+ <option value="dashed">Dashed</option>
624
+ <option value="dotted">Dotted</option>
625
+ <option value="double">Double</option>
626
+ <option value="groove">Groove</option>
627
+ <option value="ridge">Ridge</option>
628
+ <option value="inset">Inset</option>
629
+ <option value="outset">Outset</option>
630
+ <option value="none">None</option>
631
+ </select>
632
+ <div class="corner-row">
633
+ <label>Width</label>
634
+ <input type="range" id="bre-bottom-width" min="0" max="50" step="1">
635
+ <span class="val-label" id="bre-bottom-width-val">0px</span>
636
+ </div>
637
+ <input type="color" id="bre-bottom-color" value="#000000" style="width: 100%; height: 20px; margin: 2px 0;">
638
+ <div class="corner-row" style="grid-template-columns: 1fr;">
639
+ <label style="grid-column: 1;">Opacity</label>
640
+ <input type="range" id="bre-bottom-opacity" min="0" max="100" step="1">
641
+ <span class="val-label" id="bre-bottom-opacity-val">100%</span>
642
+ </div>
643
+ </div>
644
+
645
+ <div class="corner-section">
646
+ <div class="corner-header">Left</div>
647
+ <select id="bre-left-style">
648
+ <option value="solid">Solid</option>
649
+ <option value="dashed">Dashed</option>
650
+ <option value="dotted">Dotted</option>
651
+ <option value="double">Double</option>
652
+ <option value="groove">Groove</option>
653
+ <option value="ridge">Ridge</option>
654
+ <option value="inset">Inset</option>
655
+ <option value="outset">Outset</option>
656
+ <option value="none">None</option>
657
+ </select>
658
+ <div class="corner-row">
659
+ <label>Width</label>
660
+ <input type="range" id="bre-left-width" min="0" max="50" step="1">
661
+ <span class="val-label" id="bre-left-width-val">0px</span>
662
+ </div>
663
+ <input type="color" id="bre-left-color" value="#000000" style="width: 100%; height: 20px; margin: 2px 0;">
664
+ <div class="corner-row" style="grid-template-columns: 1fr;">
665
+ <label style="grid-column: 1;">Opacity</label>
666
+ <input type="range" id="bre-left-opacity" min="0" max="100" step="1">
667
+ <span class="val-label" id="bre-left-opacity-val">100%</span>
668
+ </div>
669
+ </div>
670
+ </div>
671
+ </div>
672
+ </div>
673
+
674
+ <div class="separator"></div>
675
+
676
+ <textarea class="css-out" id="bre-css-out"></textarea>
677
+
678
+ <div class="actions">
679
+ <button class="load-btn" id="bre-load-btn">Load</button>
680
+ <button id="bre-copy-btn">Copy CSS</button>
681
+ <button class="apply-btn" id="bre-apply-btn">Apply to selection</button>
682
+ </div>
683
+ </div>`;
684
+ }
685
+ constructor(designerCanvas) {
686
+ super();
687
+ this._designerCanvas = designerCanvas;
688
+ this._previewBox = this._getDomElement('bre-preview');
689
+ this._uniformCheck = this._getDomElement('bre-uniform');
690
+ // Unified corner controls
691
+ this._uniformXInput = this._getDomElement('bre-uniform-x');
692
+ this._uniformYInput = this._getDomElement('bre-uniform-y');
693
+ this._uniformShapeSelect = this._getDomElement('bre-uniform-shape');
694
+ this._uniformXVal = this._getDomElement('bre-uniform-x-val');
695
+ this._uniformYVal = this._getDomElement('bre-uniform-y-val');
696
+ // Top-left
697
+ this._tlXInput = this._getDomElement('bre-tl-x');
698
+ this._tlYInput = this._getDomElement('bre-tl-y');
699
+ this._tlShapeSelect = this._getDomElement('bre-tl-shape');
700
+ this._tlXVal = this._getDomElement('bre-tl-x-val');
701
+ this._tlYVal = this._getDomElement('bre-tl-y-val');
702
+ // Top-right
703
+ this._trXInput = this._getDomElement('bre-tr-x');
704
+ this._trYInput = this._getDomElement('bre-tr-y');
705
+ this._trShapeSelect = this._getDomElement('bre-tr-shape');
706
+ this._trXVal = this._getDomElement('bre-tr-x-val');
707
+ this._trYVal = this._getDomElement('bre-tr-y-val');
708
+ // Bottom-right
709
+ this._brXInput = this._getDomElement('bre-br-x');
710
+ this._brYInput = this._getDomElement('bre-br-y');
711
+ this._brShapeSelect = this._getDomElement('bre-br-shape');
712
+ this._brXVal = this._getDomElement('bre-br-x-val');
713
+ this._brYVal = this._getDomElement('bre-br-y-val');
714
+ // Bottom-left
715
+ this._blXInput = this._getDomElement('bre-bl-x');
716
+ this._blYInput = this._getDomElement('bre-bl-y');
717
+ this._blShapeSelect = this._getDomElement('bre-bl-shape');
718
+ this._blXVal = this._getDomElement('bre-bl-x-val');
719
+ this._blYVal = this._getDomElement('bre-bl-y-val');
720
+ // Section divs
721
+ this._cornersUniformDiv = this._getDomElement('bre-corners-uniform');
722
+ this._cornersPersideDiv = this._getDomElement('bre-corners-perside');
723
+ // Border controls
724
+ this._widthInput = this._getDomElement('bre-width');
725
+ this._borderUniformCheck = this._getDomElement('bre-border-separate');
726
+ this._widthVal = this._getDomElement('bre-width-val');
727
+ // Uniform border controls
728
+ this._styleSelect = this._getDomElement('bre-style');
729
+ this._colorInput = this._getDomElement('bre-color');
730
+ this._opacityInput = this._getDomElement('bre-opacity');
731
+ this._opacityVal = this._getDomElement('bre-opacity-val');
732
+ // Per-side border controls
733
+ this._topStyleSelect = this._getDomElement('bre-top-style');
734
+ this._topWidthInput = this._getDomElement('bre-top-width');
735
+ this._topWidthVal = this._getDomElement('bre-top-width-val');
736
+ this._topColorInput = this._getDomElement('bre-top-color');
737
+ this._topOpacityInput = this._getDomElement('bre-top-opacity');
738
+ this._topOpacityVal = this._getDomElement('bre-top-opacity-val');
739
+ this._rightStyleSelect = this._getDomElement('bre-right-style');
740
+ this._rightWidthInput = this._getDomElement('bre-right-width');
741
+ this._rightWidthVal = this._getDomElement('bre-right-width-val');
742
+ this._rightColorInput = this._getDomElement('bre-right-color');
743
+ this._rightOpacityInput = this._getDomElement('bre-right-opacity');
744
+ this._rightOpacityVal = this._getDomElement('bre-right-opacity-val');
745
+ this._bottomStyleSelect = this._getDomElement('bre-bottom-style');
746
+ this._bottomWidthInput = this._getDomElement('bre-bottom-width');
747
+ this._bottomWidthVal = this._getDomElement('bre-bottom-width-val');
748
+ this._bottomColorInput = this._getDomElement('bre-bottom-color');
749
+ this._bottomOpacityInput = this._getDomElement('bre-bottom-opacity');
750
+ this._bottomOpacityVal = this._getDomElement('bre-bottom-opacity-val');
751
+ this._leftStyleSelect = this._getDomElement('bre-left-style');
752
+ this._leftWidthInput = this._getDomElement('bre-left-width');
753
+ this._leftWidthVal = this._getDomElement('bre-left-width-val');
754
+ this._leftColorInput = this._getDomElement('bre-left-color');
755
+ this._leftOpacityInput = this._getDomElement('bre-left-opacity');
756
+ this._leftOpacityVal = this._getDomElement('bre-left-opacity-val');
757
+ this._borderUniformDiv = this._getDomElement('bre-border-uniform-editor');
758
+ this._borderPersideDiv = this._getDomElement('bre-border-perside');
759
+ this._cssOutput = this._getDomElement('bre-css-out');
760
+ this._loadBtn = this._getDomElement('bre-load-btn');
761
+ this._copyBtn = this._getDomElement('bre-copy-btn');
762
+ this._applyBtn = this._getDomElement('bre-apply-btn');
763
+ // Uniform mode for corners
764
+ this._uniformCheck.onchange = () => {
765
+ this._config.uniform = !this._uniformCheck.checked;
766
+ this._cornersUniformDiv.style.display = this._config.uniform ? 'block' : 'none';
767
+ this._cornersPersideDiv.style.display = this._config.uniform ? 'none' : 'grid';
768
+ if (this._config.uniform) {
769
+ const tl = this._config.topLeft;
770
+ this._config.topRight = { ...tl };
771
+ this._config.bottomRight = { ...tl };
772
+ this._config.bottomLeft = { ...tl };
773
+ }
774
+ this._syncControls();
775
+ this._refresh();
776
+ };
777
+ // Wire up unified corner controls
778
+ this._uniformXInput.oninput = () => {
779
+ this._config.topLeft.x = Number(this._uniformXInput.value);
780
+ this._config.topRight.x = Number(this._uniformXInput.value);
781
+ this._config.bottomRight.x = Number(this._uniformXInput.value);
782
+ this._config.bottomLeft.x = Number(this._uniformXInput.value);
783
+ this._uniformXVal.textContent = this._config.topLeft.x + '%';
784
+ this._refresh();
785
+ };
786
+ this._uniformYInput.oninput = () => {
787
+ this._config.topLeft.y = Number(this._uniformYInput.value);
788
+ this._config.topRight.y = Number(this._uniformYInput.value);
789
+ this._config.bottomRight.y = Number(this._uniformYInput.value);
790
+ this._config.bottomLeft.y = Number(this._uniformYInput.value);
791
+ this._uniformYVal.textContent = this._config.topLeft.y + '%';
792
+ this._refresh();
793
+ };
794
+ this._uniformShapeSelect.onchange = () => {
795
+ const shape = this._uniformShapeSelect.value;
796
+ this._config.topLeft.shape = shape;
797
+ this._config.topRight.shape = shape;
798
+ this._config.bottomRight.shape = shape;
799
+ this._config.bottomLeft.shape = shape;
800
+ this._refresh();
801
+ };
802
+ // Wire up per-corner controls: Top-left
803
+ this._wireCornerControls('tl', this._tlXInput, this._tlYInput, this._tlShapeSelect, this._tlXVal, this._tlYVal);
804
+ this._wireCornerControls('tr', this._trXInput, this._trYInput, this._trShapeSelect, this._trXVal, this._trYVal);
805
+ this._wireCornerControls('br', this._brXInput, this._brYInput, this._brShapeSelect, this._brXVal, this._brYVal);
806
+ this._wireCornerControls('bl', this._blXInput, this._blYInput, this._blShapeSelect, this._blXVal, this._blYVal);
807
+ // Wire up border controls
808
+ this._widthInput.oninput = () => {
809
+ this._config.width = Number(this._widthInput.value);
810
+ this._config.top.width = this._config.width;
811
+ this._config.right.width = this._config.width;
812
+ this._config.bottom.width = this._config.width;
813
+ this._config.left.width = this._config.width;
814
+ this._widthVal.textContent = this._config.width + 'px';
815
+ this._refresh();
816
+ };
817
+ this._borderUniformCheck.onchange = () => {
818
+ this._config.borderUniform = !this._borderUniformCheck.checked;
819
+ if (this._config.borderUniform) {
820
+ this._config.width = this._config.top.width;
821
+ this._config.right.width = this._config.top.width;
822
+ this._config.bottom.width = this._config.top.width;
823
+ this._config.left.width = this._config.top.width;
824
+ }
825
+ this._borderUniformDiv.style.display = this._config.borderUniform ? 'block' : 'none';
826
+ this._borderPersideDiv.style.display = this._config.borderUniform ? 'none' : 'grid';
827
+ this._syncControls();
828
+ this._refresh();
829
+ };
830
+ // Uniform mode handlers for border
831
+ this._styleSelect.onchange = () => {
832
+ this._config.top.style = this._styleSelect.value;
833
+ this._config.right.style = this._styleSelect.value;
834
+ this._config.bottom.style = this._styleSelect.value;
835
+ this._config.left.style = this._styleSelect.value;
836
+ this._refresh();
837
+ };
838
+ this._colorInput.oninput = () => {
839
+ this._config.top.color = this._colorInput.value;
840
+ this._config.right.color = this._colorInput.value;
841
+ this._config.bottom.color = this._colorInput.value;
842
+ this._config.left.color = this._colorInput.value;
843
+ this._refresh();
844
+ };
845
+ this._opacityInput.oninput = () => {
846
+ const opacity = Number(this._opacityInput.value);
847
+ this._config.top.opacity = opacity;
848
+ this._config.right.opacity = opacity;
849
+ this._config.bottom.opacity = opacity;
850
+ this._config.left.opacity = opacity;
851
+ this._opacityVal.textContent = opacity + '%';
852
+ this._refresh();
853
+ };
854
+ // Per-side handlers
855
+ const wirePerSideBorderControls = (side, styleSelect, widthInput, widthVal, colorInput, opacityInput, opacityVal) => {
856
+ const getSide = () => this._config[side];
857
+ styleSelect.onchange = () => {
858
+ const sideCfg = getSide();
859
+ sideCfg.style = styleSelect.value;
860
+ this._refresh();
861
+ };
862
+ widthInput.oninput = () => {
863
+ const sideCfg = getSide();
864
+ sideCfg.width = Number(widthInput.value);
865
+ widthVal.textContent = sideCfg.width + 'px';
866
+ this._refresh();
867
+ };
868
+ colorInput.oninput = () => {
869
+ const sideCfg = getSide();
870
+ sideCfg.color = colorInput.value;
871
+ this._refresh();
872
+ };
873
+ opacityInput.oninput = () => {
874
+ const sideCfg = getSide();
875
+ sideCfg.opacity = Number(opacityInput.value);
876
+ opacityVal.textContent = sideCfg.opacity + '%';
877
+ this._refresh();
878
+ };
879
+ };
880
+ wirePerSideBorderControls('top', this._topStyleSelect, this._topWidthInput, this._topWidthVal, this._topColorInput, this._topOpacityInput, this._topOpacityVal);
881
+ wirePerSideBorderControls('right', this._rightStyleSelect, this._rightWidthInput, this._rightWidthVal, this._rightColorInput, this._rightOpacityInput, this._rightOpacityVal);
882
+ wirePerSideBorderControls('bottom', this._bottomStyleSelect, this._bottomWidthInput, this._bottomWidthVal, this._bottomColorInput, this._bottomOpacityInput, this._bottomOpacityVal);
883
+ wirePerSideBorderControls('left', this._leftStyleSelect, this._leftWidthInput, this._leftWidthVal, this._leftColorInput, this._leftOpacityInput, this._leftOpacityVal);
884
+ // Copy / Apply
885
+ this._copyBtn.onclick = () => { navigator.clipboard?.writeText(this._cssOutput.value).catch(() => { }); };
886
+ this._applyBtn.onclick = () => this._applyToSelection();
887
+ this._loadBtn.onclick = () => {
888
+ this._loadFromPrimarySelection();
889
+ this._syncControls();
890
+ this._refresh();
891
+ };
892
+ this._loadFromPrimarySelection();
893
+ this._syncControls();
894
+ this._refresh();
895
+ }
896
+ _wireCornerControls(cornerKey, xInput, yInput, shapeSelect, xVal, yVal) {
897
+ const getCorner = () => {
898
+ switch (cornerKey) {
899
+ case 'tl': return this._config.topLeft;
900
+ case 'tr': return this._config.topRight;
901
+ case 'br': return this._config.bottomRight;
902
+ case 'bl': return this._config.bottomLeft;
903
+ }
904
+ };
905
+ xInput.oninput = () => {
906
+ const liveCorner = getCorner();
907
+ liveCorner.x = Number(xInput.value);
908
+ if (this._config.uniform) {
909
+ this._config.topLeft.x = this._config.topRight.x = this._config.bottomRight.x = this._config.bottomLeft.x = liveCorner.x;
910
+ }
911
+ xVal.textContent = liveCorner.x + '%';
912
+ this._refresh();
913
+ };
914
+ yInput.oninput = () => {
915
+ const liveCorner = getCorner();
916
+ liveCorner.y = Number(yInput.value);
917
+ if (this._config.uniform) {
918
+ this._config.topLeft.y = this._config.topRight.y = this._config.bottomRight.y = this._config.bottomLeft.y = liveCorner.y;
919
+ }
920
+ yVal.textContent = liveCorner.y + '%';
921
+ this._refresh();
922
+ };
923
+ shapeSelect.onchange = () => {
924
+ const liveCorner = getCorner();
925
+ liveCorner.shape = shapeSelect.value;
926
+ if (this._config.uniform) {
927
+ this._config.topLeft.shape = this._config.topRight.shape = this._config.bottomRight.shape = this._config.bottomLeft.shape = liveCorner.shape;
928
+ }
929
+ this._refresh();
930
+ };
931
+ }
932
+ _loadFromPrimarySelection() {
933
+ const primary = this._designerCanvas?.instanceServiceContainer?.selectionService?.primarySelection;
934
+ if (!primary)
935
+ return;
936
+ const br = primary.getStyle('border-radius');
937
+ if (br) {
938
+ this._config = parseCornerRadius(br);
939
+ }
940
+ // Also load corner-shape property if it exists
941
+ const cs = primary.getStyle('corner-shape');
942
+ if (cs) {
943
+ const shapes = cs.trim().split(/\s+/).filter(Boolean);
944
+ const getShape = (idx) => {
945
+ if (shapes.length === 1)
946
+ return shapes[0];
947
+ if (shapes.length === 2)
948
+ return shapes[idx === 1 || idx === 2 ? 1 : 0];
949
+ if (shapes.length === 3)
950
+ return shapes[idx === 3 ? 1 : (idx === 2 ? 2 : 0)];
951
+ return shapes[idx] ?? 'round';
952
+ };
953
+ this._config.topLeft.shape = getShape(0);
954
+ this._config.topRight.shape = getShape(1);
955
+ this._config.bottomRight.shape = getShape(2);
956
+ this._config.bottomLeft.shape = getShape(3);
957
+ }
958
+ const corners = [this._config.topLeft, this._config.topRight, this._config.bottomRight, this._config.bottomLeft];
959
+ this._config.uniform = corners.every(c => c.x === corners[0].x &&
960
+ c.y === corners[0].y &&
961
+ c.shape === corners[0].shape);
962
+ // Load border width
963
+ const bw = primary.getStyle('border-width');
964
+ if (bw) {
965
+ const widthTokens = splitTopLevelWhitespace(bw).map(t => parseFloat(t)).filter(v => !isNaN(v));
966
+ const [top, right, bottom, left] = expandQuadTokens(widthTokens);
967
+ if (top != null)
968
+ this._config.top.width = top;
969
+ if (right != null)
970
+ this._config.right.width = right;
971
+ if (bottom != null)
972
+ this._config.bottom.width = bottom;
973
+ if (left != null)
974
+ this._config.left.width = left;
975
+ if (top != null)
976
+ this._config.width = top;
977
+ }
978
+ const topWidth = primary.getStyle('border-top-width');
979
+ const rightWidth = primary.getStyle('border-right-width');
980
+ const bottomWidth = primary.getStyle('border-bottom-width');
981
+ const leftWidth = primary.getStyle('border-left-width');
982
+ const hasPerSideWidth = !!(topWidth || rightWidth || bottomWidth || leftWidth);
983
+ if (hasPerSideWidth) {
984
+ this._config.borderUniform = false;
985
+ if (topWidth) {
986
+ const widthNum = parseFloat(topWidth);
987
+ if (!isNaN(widthNum))
988
+ this._config.top.width = widthNum;
989
+ }
990
+ if (rightWidth) {
991
+ const widthNum = parseFloat(rightWidth);
992
+ if (!isNaN(widthNum))
993
+ this._config.right.width = widthNum;
994
+ }
995
+ if (bottomWidth) {
996
+ const widthNum = parseFloat(bottomWidth);
997
+ if (!isNaN(widthNum))
998
+ this._config.bottom.width = widthNum;
999
+ }
1000
+ if (leftWidth) {
1001
+ const widthNum = parseFloat(leftWidth);
1002
+ if (!isNaN(widthNum))
1003
+ this._config.left.width = widthNum;
1004
+ }
1005
+ this._config.width = this._config.top.width;
1006
+ }
1007
+ // Load border style and color (per-side or uniform)
1008
+ const parseColorAndOpacity = (colorStr) => {
1009
+ const rgbaMatch = colorStr.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([\d.]+))?\s*\)/);
1010
+ if (rgbaMatch) {
1011
+ const hex = '#' + [parseInt(rgbaMatch[1]), parseInt(rgbaMatch[2]), parseInt(rgbaMatch[3])].map(v => v.toString(16).padStart(2, '0')).join('');
1012
+ const opacity = rgbaMatch[4] ? Math.round(parseFloat(rgbaMatch[4]) * 100) : 100;
1013
+ return { hex, opacity };
1014
+ }
1015
+ else if (colorStr.startsWith('#')) {
1016
+ return { hex: colorStr.slice(0, 7), opacity: 100 };
1017
+ }
1018
+ return { hex: '#000000', opacity: 100 };
1019
+ };
1020
+ // Try to load per-side styles
1021
+ const topStyle = primary.getStyle('border-top-style');
1022
+ const rightStyle = primary.getStyle('border-right-style');
1023
+ const bottomStyle = primary.getStyle('border-bottom-style');
1024
+ const leftStyle = primary.getStyle('border-left-style');
1025
+ if (topStyle || rightStyle || bottomStyle || leftStyle) {
1026
+ // Per-side mode
1027
+ this._config.borderUniform = false;
1028
+ if (topStyle)
1029
+ this._config.top.style = topStyle;
1030
+ if (rightStyle)
1031
+ this._config.right.style = rightStyle;
1032
+ if (bottomStyle)
1033
+ this._config.bottom.style = bottomStyle;
1034
+ if (leftStyle)
1035
+ this._config.left.style = leftStyle;
1036
+ }
1037
+ else {
1038
+ // Try uniform shorthand
1039
+ const bs = primary.getStyle('border-style');
1040
+ if (bs) {
1041
+ if (!hasPerSideWidth)
1042
+ this._config.borderUniform = true;
1043
+ const [top, right, bottom, left] = expandQuadTokens(splitTopLevelWhitespace(bs));
1044
+ if (top)
1045
+ this._config.top.style = top;
1046
+ if (right)
1047
+ this._config.right.style = right;
1048
+ if (bottom)
1049
+ this._config.bottom.style = bottom;
1050
+ if (left)
1051
+ this._config.left.style = left;
1052
+ }
1053
+ }
1054
+ // Try to load per-side colors
1055
+ const topColor = primary.getStyle('border-top-color');
1056
+ const rightColor = primary.getStyle('border-right-color');
1057
+ const bottomColor = primary.getStyle('border-bottom-color');
1058
+ const leftColor = primary.getStyle('border-left-color');
1059
+ if (topColor || rightColor || bottomColor || leftColor) {
1060
+ // Per-side mode
1061
+ this._config.borderUniform = false;
1062
+ if (topColor) {
1063
+ const parsed = parseColorAndOpacity(topColor);
1064
+ this._config.top.color = parsed.hex;
1065
+ this._config.top.opacity = parsed.opacity;
1066
+ }
1067
+ if (rightColor) {
1068
+ const parsed = parseColorAndOpacity(rightColor);
1069
+ this._config.right.color = parsed.hex;
1070
+ this._config.right.opacity = parsed.opacity;
1071
+ }
1072
+ if (bottomColor) {
1073
+ const parsed = parseColorAndOpacity(bottomColor);
1074
+ this._config.bottom.color = parsed.hex;
1075
+ this._config.bottom.opacity = parsed.opacity;
1076
+ }
1077
+ if (leftColor) {
1078
+ const parsed = parseColorAndOpacity(leftColor);
1079
+ this._config.left.color = parsed.hex;
1080
+ this._config.left.opacity = parsed.opacity;
1081
+ }
1082
+ }
1083
+ else {
1084
+ // Try uniform shorthand
1085
+ const bc = primary.getStyle('border-color');
1086
+ if (bc) {
1087
+ if (!hasPerSideWidth)
1088
+ this._config.borderUniform = true;
1089
+ const [top, right, bottom, left] = expandQuadTokens(splitTopLevelWhitespace(bc));
1090
+ if (top) {
1091
+ const parsed = parseColorAndOpacity(top);
1092
+ this._config.top.color = parsed.hex;
1093
+ this._config.top.opacity = parsed.opacity;
1094
+ }
1095
+ if (right) {
1096
+ const parsed = parseColorAndOpacity(right);
1097
+ this._config.right.color = parsed.hex;
1098
+ this._config.right.opacity = parsed.opacity;
1099
+ }
1100
+ if (bottom) {
1101
+ const parsed = parseColorAndOpacity(bottom);
1102
+ this._config.bottom.color = parsed.hex;
1103
+ this._config.bottom.opacity = parsed.opacity;
1104
+ }
1105
+ if (left) {
1106
+ const parsed = parseColorAndOpacity(left);
1107
+ this._config.left.color = parsed.hex;
1108
+ this._config.left.opacity = parsed.opacity;
1109
+ }
1110
+ }
1111
+ }
1112
+ const sides = [this._config.top, this._config.right, this._config.bottom, this._config.left];
1113
+ this._config.borderUniform = sides.every(s => s.width === sides[0].width &&
1114
+ s.style === sides[0].style &&
1115
+ s.color === sides[0].color &&
1116
+ s.opacity === sides[0].opacity);
1117
+ this._config.width = this._config.top.width;
1118
+ }
1119
+ _syncControls() {
1120
+ // Sync corner controls
1121
+ this._uniformCheck.checked = !this._config.uniform;
1122
+ this._cornersUniformDiv.style.display = this._config.uniform ? 'block' : 'none';
1123
+ this._cornersPersideDiv.style.display = this._config.uniform ? 'none' : 'grid';
1124
+ // Sync unified corner controls
1125
+ if (this._config.uniform) {
1126
+ this._uniformXInput.value = String(this._config.topLeft.x);
1127
+ this._uniformYInput.value = String(this._config.topLeft.y);
1128
+ this._uniformShapeSelect.value = this._config.topLeft.shape;
1129
+ this._uniformXVal.textContent = this._config.topLeft.x + '%';
1130
+ this._uniformYVal.textContent = this._config.topLeft.y + '%';
1131
+ }
1132
+ const syncCorner = (x, y, shape, xVal, yVal, corner) => {
1133
+ x.value = String(corner.x);
1134
+ y.value = String(corner.y);
1135
+ shape.value = corner.shape;
1136
+ xVal.textContent = corner.x + '%';
1137
+ yVal.textContent = corner.y + '%';
1138
+ };
1139
+ syncCorner(this._tlXInput, this._tlYInput, this._tlShapeSelect, this._tlXVal, this._tlYVal, this._config.topLeft);
1140
+ syncCorner(this._trXInput, this._trYInput, this._trShapeSelect, this._trXVal, this._trYVal, this._config.topRight);
1141
+ syncCorner(this._brXInput, this._brYInput, this._brShapeSelect, this._brXVal, this._brYVal, this._config.bottomRight);
1142
+ syncCorner(this._blXInput, this._blYInput, this._blShapeSelect, this._blXVal, this._blYVal, this._config.bottomLeft);
1143
+ // Sync border controls
1144
+ this._widthInput.value = String(this._config.width);
1145
+ this._widthVal.textContent = this._config.width + 'px';
1146
+ this._borderUniformCheck.checked = !this._config.borderUniform;
1147
+ this._borderUniformDiv.style.display = this._config.borderUniform ? 'block' : 'none';
1148
+ this._borderPersideDiv.style.display = this._config.borderUniform ? 'none' : 'grid';
1149
+ if (this._config.borderUniform) {
1150
+ // Sync uniform controls
1151
+ this._styleSelect.value = this._config.top.style;
1152
+ this._colorInput.value = this._config.top.color;
1153
+ this._opacityInput.value = String(this._config.top.opacity);
1154
+ this._opacityVal.textContent = this._config.top.opacity + '%';
1155
+ }
1156
+ else {
1157
+ // Sync per-side controls
1158
+ this._topStyleSelect.value = this._config.top.style;
1159
+ this._topWidthInput.value = String(this._config.top.width);
1160
+ this._topWidthVal.textContent = this._config.top.width + 'px';
1161
+ this._topColorInput.value = this._config.top.color;
1162
+ this._topOpacityInput.value = String(this._config.top.opacity);
1163
+ this._topOpacityVal.textContent = this._config.top.opacity + '%';
1164
+ this._rightStyleSelect.value = this._config.right.style;
1165
+ this._rightWidthInput.value = String(this._config.right.width);
1166
+ this._rightWidthVal.textContent = this._config.right.width + 'px';
1167
+ this._rightColorInput.value = this._config.right.color;
1168
+ this._rightOpacityInput.value = String(this._config.right.opacity);
1169
+ this._rightOpacityVal.textContent = this._config.right.opacity + '%';
1170
+ this._bottomStyleSelect.value = this._config.bottom.style;
1171
+ this._bottomWidthInput.value = String(this._config.bottom.width);
1172
+ this._bottomWidthVal.textContent = this._config.bottom.width + 'px';
1173
+ this._bottomColorInput.value = this._config.bottom.color;
1174
+ this._bottomOpacityInput.value = String(this._config.bottom.opacity);
1175
+ this._bottomOpacityVal.textContent = this._config.bottom.opacity + '%';
1176
+ this._leftStyleSelect.value = this._config.left.style;
1177
+ this._leftWidthInput.value = String(this._config.left.width);
1178
+ this._leftWidthVal.textContent = this._config.left.width + 'px';
1179
+ this._leftColorInput.value = this._config.left.color;
1180
+ this._leftOpacityInput.value = String(this._config.left.opacity);
1181
+ this._leftOpacityVal.textContent = this._config.left.opacity + '%';
1182
+ }
1183
+ }
1184
+ _buildCss() {
1185
+ const radius = configToRadiusCss(this._config);
1186
+ const shape = configToShapeCss(this._config);
1187
+ const borderCss = {};
1188
+ const { width, style, color } = configToBorderCss(this._config);
1189
+ if (this._config.borderUniform) {
1190
+ // Uniform: use shorthand
1191
+ borderCss['border-width'] = width;
1192
+ borderCss['border-style'] = style;
1193
+ borderCss['border-color'] = color;
1194
+ }
1195
+ else {
1196
+ // Per-side
1197
+ const sides = ['top', 'right', 'bottom', 'left'];
1198
+ const sides_data = [this._config.top, this._config.right, this._config.bottom, this._config.left];
1199
+ for (let i = 0; i < sides.length; i++) {
1200
+ const side = sides[i];
1201
+ const cfg = sides_data[i];
1202
+ borderCss[`border-${side}-width`] = `${cfg.width}px`;
1203
+ borderCss[`border-${side}-style`] = cfg.style;
1204
+ const rgba = `${hexToRgb(cfg.color)},${(cfg.opacity / 100).toFixed(2)}`;
1205
+ borderCss[`border-${side}-color`] = `rgba(${rgba})`;
1206
+ }
1207
+ }
1208
+ return {
1209
+ radius,
1210
+ shape,
1211
+ borderCss,
1212
+ };
1213
+ }
1214
+ _refresh() {
1215
+ const { radius, shape, borderCss } = this._buildCss();
1216
+ this._previewBox.style.borderRadius = radius;
1217
+ // Set corner-shape in preview if not round
1218
+ if (shape) {
1219
+ this._previewBox.style.cornerShape = shape;
1220
+ }
1221
+ else {
1222
+ this._previewBox.style.cornerShape = '';
1223
+ }
1224
+ // Set border in preview
1225
+ Object.entries(borderCss).forEach(([prop, value]) => {
1226
+ this._previewBox.style[prop.replace(/-([a-z])/g, (_, l) => l.toUpperCase())] = value;
1227
+ });
1228
+ // Output all properties
1229
+ let output = `border-radius: ${radius};`;
1230
+ if (shape) {
1231
+ output += `\ncorner-shape: ${shape};`;
1232
+ }
1233
+ Object.entries(borderCss).forEach(([prop, value]) => {
1234
+ output += `\n${prop}: ${value};`;
1235
+ });
1236
+ this._cssOutput.value = output;
1237
+ }
1238
+ _applyToSelection() {
1239
+ const items = this._designerCanvas?.instanceServiceContainer?.selectionService?.selectedElements;
1240
+ if (!items?.length)
1241
+ return;
1242
+ const { radius, shape, borderCss } = this._buildCss();
1243
+ const group = items[0].openGroup('set border-radius and border properties');
1244
+ for (const item of items) {
1245
+ item.setStyle('border-radius', radius);
1246
+ if (shape) {
1247
+ item.setStyle('corner-shape', shape);
1248
+ }
1249
+ else {
1250
+ item.removeStyle('corner-shape');
1251
+ }
1252
+ Object.entries(borderCss).forEach(([prop, value]) => {
1253
+ item.setStyle(prop, value);
1254
+ });
1255
+ }
1256
+ group.commit();
1257
+ }
1258
+ }
1259
+ customElements.define('node-projects-border-radius-editor-window', BorderRadiusEditorWindow);
1260
+ //# sourceMappingURL=BorderRadiusEditorWindow.js.map