@rogieking/figui3 11.6.1 → 11.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/skills/fig-editor/components.md +10 -5
- package/.cursor/skills/fig-editor/reference.md +10 -4
- package/.cursor/skills/figui3/reference.md +8 -0
- package/CHANGELOG.md +32 -0
- package/README.md +28 -7
- package/dist/fig-color.js +1 -0
- package/dist/fig-editor.css +1 -1
- package/dist/fig-editor.js +3 -3
- package/dist/fig-property.css +1 -1
- package/dist/fig.js +5 -5
- package/fig-color.js +406 -0
- package/fig-editor.css +1 -16
- package/fig-editor.js +39 -594
- package/fig-gradient-editor.css +6 -1
- package/fig-gradient-editor.js +622 -237
- package/fig-palette-editor.css +20 -0
- package/fig-palette-editor.js +815 -0
- package/fig-popup-gradient.js +9 -1
- package/fig-popup-palette.js +13 -2
- package/fig-property.css +4 -0
- package/fig.js +16 -165
- package/package.json +7 -2
package/fig-gradient-editor.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* Copyright (c) 2026 Rogie King. PolyForm Shield 1.0.0. See LICENSE. */
|
|
2
2
|
import "./fig.js";
|
|
3
3
|
import "./fig-property.js";
|
|
4
|
+
import { figConvertRGB, figHexToRGB } from "./fig-color.js";
|
|
4
5
|
|
|
5
6
|
const DEFAULT_GRADIENT = {
|
|
6
7
|
type: "linear",
|
|
@@ -55,6 +56,68 @@ function normalizeHex(color) {
|
|
|
55
56
|
: "#888888";
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
function stopsSemanticallyEqual(a, b) {
|
|
60
|
+
return (
|
|
61
|
+
Number(a?.position) === Number(b?.position) &&
|
|
62
|
+
normalizeHex(a?.color) === normalizeHex(b?.color) &&
|
|
63
|
+
Number(a?.opacity ?? 100) === Number(b?.opacity ?? 100)
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function stableSemanticValue(value) {
|
|
68
|
+
if (Array.isArray(value)) return value.map(stableSemanticValue);
|
|
69
|
+
if (!value || typeof value !== "object") return value;
|
|
70
|
+
return Object.fromEntries(
|
|
71
|
+
Object.keys(value)
|
|
72
|
+
.filter((key) => value[key] !== undefined)
|
|
73
|
+
.sort()
|
|
74
|
+
.map((key) => [key, stableSemanticValue(value[key])]),
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function gradientsSemanticallyEqual(a, b) {
|
|
79
|
+
return (
|
|
80
|
+
!!a &&
|
|
81
|
+
!!b &&
|
|
82
|
+
JSON.stringify(stableSemanticValue(a)) ===
|
|
83
|
+
JSON.stringify(stableSemanticValue(b))
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function cloneStops(stops) {
|
|
88
|
+
return stops.map((stop) => ({ ...stop }));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function matchUnchangedStops(previous, next) {
|
|
92
|
+
const rows = previous.length;
|
|
93
|
+
const columns = next.length;
|
|
94
|
+
const lengths = Array.from({ length: rows + 1 }, () =>
|
|
95
|
+
Array(columns + 1).fill(0),
|
|
96
|
+
);
|
|
97
|
+
for (let i = rows - 1; i >= 0; i -= 1) {
|
|
98
|
+
for (let j = columns - 1; j >= 0; j -= 1) {
|
|
99
|
+
lengths[i][j] = stopsSemanticallyEqual(previous[i], next[j])
|
|
100
|
+
? lengths[i + 1][j + 1] + 1
|
|
101
|
+
: Math.max(lengths[i + 1][j], lengths[i][j + 1]);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const matches = [];
|
|
105
|
+
let i = 0;
|
|
106
|
+
let j = 0;
|
|
107
|
+
while (i < rows && j < columns) {
|
|
108
|
+
if (stopsSemanticallyEqual(previous[i], next[j])) {
|
|
109
|
+
matches.push([i, j]);
|
|
110
|
+
i += 1;
|
|
111
|
+
j += 1;
|
|
112
|
+
} else if (lengths[i + 1][j] >= lengths[i][j + 1]) {
|
|
113
|
+
i += 1;
|
|
114
|
+
} else {
|
|
115
|
+
j += 1;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return matches;
|
|
119
|
+
}
|
|
120
|
+
|
|
58
121
|
export function normalizeGradientEditorValue(value) {
|
|
59
122
|
let parsed = value;
|
|
60
123
|
if (typeof parsed === "string") {
|
|
@@ -95,11 +158,13 @@ export function normalizeGradientEditorValue(value) {
|
|
|
95
158
|
}
|
|
96
159
|
|
|
97
160
|
function colorValue(hex, alpha, colorSpace) {
|
|
98
|
-
const r =
|
|
99
|
-
const g = parseInt(hex.slice(3, 5), 16);
|
|
100
|
-
const b = parseInt(hex.slice(5, 7), 16);
|
|
161
|
+
const { r, g, b } = figHexToRGB(hex);
|
|
101
162
|
if (colorSpace === "display-p3") {
|
|
102
|
-
|
|
163
|
+
const p3 = figConvertRGB(r, g, b, {
|
|
164
|
+
from: "srgb",
|
|
165
|
+
to: "display-p3",
|
|
166
|
+
});
|
|
167
|
+
return `color(display-p3 ${+(p3.r / 255).toFixed(4)} ${+(p3.g / 255).toFixed(4)} ${+(p3.b / 255).toFixed(4)} / ${alpha})`;
|
|
103
168
|
}
|
|
104
169
|
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
105
170
|
}
|
|
@@ -133,6 +198,8 @@ export function figGradientCSS(value, { colorSpace = "srgb" } = {}) {
|
|
|
133
198
|
* @attr {string} value - JSON gradient config or full gradient fill value
|
|
134
199
|
* @attr {string} color-space - `srgb` or `display-p3`
|
|
135
200
|
* @attr {boolean} disabled - Disables all editing
|
|
201
|
+
* @attr {"fade"|"visible"} overflow - Stop-list overflow mode (default: fade)
|
|
202
|
+
* @attr {number} show-rows - Visible stop rows before overflow (default: 6.5)
|
|
136
203
|
* @fires input - During edits with `{ type, gradient, css }`
|
|
137
204
|
* @fires change - On committed edits with `{ type, gradient, css }`
|
|
138
205
|
*/
|
|
@@ -141,9 +208,21 @@ class FigGradientEditor extends HTMLElement {
|
|
|
141
208
|
#syncingValue = false;
|
|
142
209
|
#syncingBar = false;
|
|
143
210
|
#pickerAnchor = null;
|
|
211
|
+
#mounted = false;
|
|
212
|
+
#gradientType = null;
|
|
213
|
+
#gradientBar = null;
|
|
214
|
+
#reorder = null;
|
|
215
|
+
#flipButton = null;
|
|
216
|
+
#rotateButton = null;
|
|
217
|
+
#addButton = null;
|
|
218
|
+
#stopRows = [];
|
|
219
|
+
#renderedStops = [];
|
|
220
|
+
#selectedStopRow = null;
|
|
221
|
+
#pointerInteractionActive = false;
|
|
222
|
+
#deferredExternalGradient = null;
|
|
144
223
|
|
|
145
224
|
static get observedAttributes() {
|
|
146
|
-
return ["value", "color-space", "disabled"];
|
|
225
|
+
return ["value", "color-space", "disabled", "overflow", "show-rows"];
|
|
147
226
|
}
|
|
148
227
|
|
|
149
228
|
get value() {
|
|
@@ -173,20 +252,45 @@ class FigGradientEditor extends HTMLElement {
|
|
|
173
252
|
}
|
|
174
253
|
|
|
175
254
|
connectedCallback() {
|
|
176
|
-
|
|
177
|
-
this.#
|
|
255
|
+
const incoming = normalizeGradientEditorValue(this.getAttribute("value"));
|
|
256
|
+
if (!gradientsSemanticallyEqual(this.#gradient, incoming)) {
|
|
257
|
+
this.#gradient = incoming;
|
|
258
|
+
}
|
|
259
|
+
this.#mount();
|
|
260
|
+
this.#sync();
|
|
178
261
|
}
|
|
179
262
|
|
|
180
263
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
181
264
|
if (oldValue === newValue || this.#syncingValue) return;
|
|
182
265
|
if (name === "value") {
|
|
183
|
-
|
|
266
|
+
const incoming = normalizeGradientEditorValue(newValue);
|
|
267
|
+
if (gradientsSemanticallyEqual(this.#gradient, incoming)) {
|
|
268
|
+
if (this.#pointerInteractionActive) {
|
|
269
|
+
this.#deferredExternalGradient = null;
|
|
270
|
+
}
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (this.#pointerInteractionActive) {
|
|
274
|
+
this.#deferredExternalGradient = incoming;
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
this.#deferredExternalGradient = null;
|
|
278
|
+
this.#gradient = incoming;
|
|
279
|
+
}
|
|
280
|
+
if (this.isConnected && this.#mounted) this.#sync();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
disconnectedCallback() {
|
|
284
|
+
this.#endPointerInteraction({ reconcile: false });
|
|
285
|
+
if (this.#deferredExternalGradient) {
|
|
286
|
+
this.#gradient = this.#deferredExternalGradient;
|
|
287
|
+
this.#deferredExternalGradient = null;
|
|
288
|
+
this.#reflectValue();
|
|
184
289
|
}
|
|
185
|
-
if (this.isConnected) this.#render();
|
|
186
290
|
}
|
|
187
291
|
|
|
188
292
|
refreshLayout() {
|
|
189
|
-
this
|
|
293
|
+
this.#gradientBar?.refreshLayout?.();
|
|
190
294
|
}
|
|
191
295
|
|
|
192
296
|
#disabled() {
|
|
@@ -196,6 +300,15 @@ class FigGradientEditor extends HTMLElement {
|
|
|
196
300
|
);
|
|
197
301
|
}
|
|
198
302
|
|
|
303
|
+
#overflowMode() {
|
|
304
|
+
return this.getAttribute("overflow") === "visible" ? "visible" : "fade";
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
#showRows() {
|
|
308
|
+
const value = Number(this.getAttribute("show-rows"));
|
|
309
|
+
return Number.isFinite(value) && value > 0 ? value : null;
|
|
310
|
+
}
|
|
311
|
+
|
|
199
312
|
#reflectValue() {
|
|
200
313
|
this.#syncingValue = true;
|
|
201
314
|
this.setAttribute("value", JSON.stringify(this.#gradient));
|
|
@@ -221,16 +334,14 @@ class FigGradientEditor extends HTMLElement {
|
|
|
221
334
|
);
|
|
222
335
|
}
|
|
223
336
|
|
|
224
|
-
#
|
|
225
|
-
|
|
226
|
-
|
|
337
|
+
#mount() {
|
|
338
|
+
if (this.#mounted) return;
|
|
339
|
+
this.#gradientType = createElement(
|
|
227
340
|
"fig-select",
|
|
228
341
|
{
|
|
229
342
|
className: "fig-fill-picker-gradient-type",
|
|
230
343
|
label: "Gradient type",
|
|
231
|
-
value: this.#gradient.type,
|
|
232
344
|
variant: "ghost",
|
|
233
|
-
disabled,
|
|
234
345
|
},
|
|
235
346
|
createElement(
|
|
236
347
|
"fig-select-options",
|
|
@@ -244,22 +355,38 @@ class FigGradientEditor extends HTMLElement {
|
|
|
244
355
|
),
|
|
245
356
|
),
|
|
246
357
|
);
|
|
247
|
-
const action = (label, className, icon) =>
|
|
248
|
-
createElement(
|
|
249
|
-
"fig-
|
|
250
|
-
{
|
|
251
|
-
|
|
252
|
-
"
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
"aria-label": label,
|
|
258
|
-
disabled,
|
|
259
|
-
},
|
|
260
|
-
createIcon(icon),
|
|
261
|
-
),
|
|
358
|
+
const action = (label, className, icon) => {
|
|
359
|
+
const button = createElement(
|
|
360
|
+
"fig-button",
|
|
361
|
+
{
|
|
362
|
+
icon: true,
|
|
363
|
+
variant: "ghost",
|
|
364
|
+
className,
|
|
365
|
+
"aria-label": label,
|
|
366
|
+
},
|
|
367
|
+
createIcon(icon),
|
|
262
368
|
);
|
|
369
|
+
return {
|
|
370
|
+
button,
|
|
371
|
+
tooltip: createElement(
|
|
372
|
+
"fig-tooltip",
|
|
373
|
+
{ text: label },
|
|
374
|
+
button,
|
|
375
|
+
),
|
|
376
|
+
};
|
|
377
|
+
};
|
|
378
|
+
const flipAction = action(
|
|
379
|
+
"Flip gradient",
|
|
380
|
+
"fig-fill-picker-gradient-flip",
|
|
381
|
+
"swap",
|
|
382
|
+
);
|
|
383
|
+
const rotateAction = action(
|
|
384
|
+
"Rotate gradient",
|
|
385
|
+
"fig-fill-picker-gradient-rotate",
|
|
386
|
+
"rotate",
|
|
387
|
+
);
|
|
388
|
+
this.#flipButton = flipAction.button;
|
|
389
|
+
this.#rotateButton = rotateAction.button;
|
|
263
390
|
const header = createElement(
|
|
264
391
|
"fig-header",
|
|
265
392
|
{
|
|
@@ -268,42 +395,30 @@ class FigGradientEditor extends HTMLElement {
|
|
|
268
395
|
compact: true,
|
|
269
396
|
},
|
|
270
397
|
[
|
|
271
|
-
gradientType,
|
|
398
|
+
this.#gradientType,
|
|
272
399
|
createElement(
|
|
273
400
|
"fig-stack",
|
|
274
401
|
{
|
|
275
402
|
className: "fig-fill-picker-gradient-actions",
|
|
276
403
|
gap: "1",
|
|
277
404
|
},
|
|
278
|
-
[
|
|
279
|
-
action("Flip gradient", "fig-fill-picker-gradient-flip", "swap"),
|
|
280
|
-
action(
|
|
281
|
-
"Rotate gradient",
|
|
282
|
-
"fig-fill-picker-gradient-rotate",
|
|
283
|
-
"rotate",
|
|
284
|
-
),
|
|
285
|
-
],
|
|
405
|
+
[flipAction.tooltip, rotateAction.tooltip],
|
|
286
406
|
),
|
|
287
407
|
],
|
|
288
408
|
);
|
|
289
|
-
|
|
409
|
+
this.#gradientBar = createElement("fig-input-gradient", {
|
|
290
410
|
className: "fig-fill-picker-gradient-bar-input",
|
|
291
411
|
"aria-label": "Gradient stops",
|
|
292
412
|
edit: "true",
|
|
293
413
|
mode: "tip",
|
|
294
414
|
size: "large",
|
|
295
|
-
disabled,
|
|
296
|
-
value: JSON.stringify({
|
|
297
|
-
type: "gradient",
|
|
298
|
-
gradient: this.#gradient,
|
|
299
|
-
}),
|
|
300
415
|
});
|
|
301
416
|
const preview = createElement(
|
|
302
417
|
"fig-preview",
|
|
303
418
|
{ className: "fig-fill-picker-gradient-preview" },
|
|
304
|
-
gradientBar,
|
|
419
|
+
this.#gradientBar,
|
|
305
420
|
);
|
|
306
|
-
|
|
421
|
+
this.#addButton = createElement(
|
|
307
422
|
"fig-button",
|
|
308
423
|
{
|
|
309
424
|
icon: true,
|
|
@@ -311,12 +426,10 @@ class FigGradientEditor extends HTMLElement {
|
|
|
311
426
|
className: "fig-fill-picker-gradient-add",
|
|
312
427
|
"aria-label": "Add gradient stop",
|
|
313
428
|
title: "Add stop",
|
|
314
|
-
disabled,
|
|
315
429
|
},
|
|
316
430
|
createIcon("add"),
|
|
317
431
|
);
|
|
318
|
-
|
|
319
|
-
reorder.toggleAttribute("disabled", disabled);
|
|
432
|
+
this.#reorder = document.createElement("fig-reorder");
|
|
320
433
|
const stops = createElement(
|
|
321
434
|
"div",
|
|
322
435
|
{ className: "fig-fill-picker-gradient-stops" },
|
|
@@ -327,106 +440,48 @@ class FigGradientEditor extends HTMLElement {
|
|
|
327
440
|
className: "fig-fill-picker-gradient-stops-header",
|
|
328
441
|
borderless: true,
|
|
329
442
|
},
|
|
330
|
-
[createElement("h3", {}, "Stops"), addButton],
|
|
443
|
+
[createElement("h3", {}, "Stops"), this.#addButton],
|
|
331
444
|
),
|
|
332
445
|
createElement(
|
|
333
446
|
"div",
|
|
334
447
|
{ className: "fig-fill-picker-gradient-stops-list" },
|
|
335
|
-
reorder,
|
|
448
|
+
this.#reorder,
|
|
336
449
|
),
|
|
337
450
|
],
|
|
338
451
|
);
|
|
339
452
|
|
|
340
453
|
this.replaceChildren(header, preview, stops);
|
|
341
|
-
this.#
|
|
342
|
-
this.#
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
#setupEvents() {
|
|
346
|
-
const select = this.querySelector(".fig-fill-picker-gradient-type");
|
|
347
|
-
select?.addEventListener("change", (event) => {
|
|
348
|
-
event.stopPropagation();
|
|
349
|
-
const value =
|
|
350
|
-
typeof event.detail === "string" ? event.detail : event.target?.value;
|
|
351
|
-
if (!["linear", "radial", "angular"].includes(value)) return;
|
|
352
|
-
this.#gradient.type = value;
|
|
353
|
-
this.#syncPreview();
|
|
354
|
-
this.#emit("input");
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
this.querySelector(".fig-fill-picker-gradient-rotate")?.addEventListener(
|
|
358
|
-
"click",
|
|
359
|
-
() => {
|
|
360
|
-
this.#gradient.angle = (Number(this.#gradient.angle) + 90) % 360;
|
|
361
|
-
this.#syncPreview();
|
|
362
|
-
this.#emit("input");
|
|
363
|
-
},
|
|
454
|
+
this.#gradientType.addEventListener("change", this.#handleTypeChange);
|
|
455
|
+
this.#gradientType.addEventListener("optionhover", (event) =>
|
|
456
|
+
event.stopPropagation(),
|
|
364
457
|
);
|
|
365
|
-
this.
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
this.#emit("input");
|
|
374
|
-
},
|
|
458
|
+
this.#rotateButton.addEventListener("click", this.#handleRotate);
|
|
459
|
+
this.#flipButton.addEventListener("click", this.#handleFlip);
|
|
460
|
+
this.#addButton.addEventListener("click", this.#handleAdd);
|
|
461
|
+
this.#gradientBar.addEventListener("input", this.#handleBarEvent);
|
|
462
|
+
this.#gradientBar.addEventListener("change", this.#handleBarEvent);
|
|
463
|
+
this.#gradientBar.addEventListener(
|
|
464
|
+
"pointerdown",
|
|
465
|
+
this.#handleBarStopSelection,
|
|
375
466
|
);
|
|
376
|
-
this.
|
|
377
|
-
"
|
|
378
|
-
|
|
379
|
-
this.#gradient.stops.push({
|
|
380
|
-
position: 50,
|
|
381
|
-
color: "#888888",
|
|
382
|
-
opacity: 100,
|
|
383
|
-
});
|
|
384
|
-
this.#gradient.stops.sort((a, b) => a.position - b.position);
|
|
385
|
-
this.#render();
|
|
386
|
-
this.#emit("input");
|
|
387
|
-
},
|
|
467
|
+
this.#gradientBar.addEventListener(
|
|
468
|
+
"focusin",
|
|
469
|
+
this.#handleBarStopSelection,
|
|
388
470
|
);
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
bar?.addEventListener("input", (event) => {
|
|
404
|
-
if (syncBar(event)) this.#emit("input");
|
|
405
|
-
});
|
|
406
|
-
bar?.addEventListener("change", (event) => {
|
|
407
|
-
if (syncBar(event)) this.#emit("change");
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
this.querySelector("fig-reorder")?.addEventListener("reorder", (event) => {
|
|
411
|
-
const { oldIndex, newIndex } = event.detail ?? {};
|
|
412
|
-
if (
|
|
413
|
-
!Number.isInteger(oldIndex) ||
|
|
414
|
-
!Number.isInteger(newIndex) ||
|
|
415
|
-
oldIndex === newIndex
|
|
416
|
-
) {
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
419
|
-
this.#flushRows();
|
|
420
|
-
const [stop] = this.#gradient.stops.splice(oldIndex, 1);
|
|
421
|
-
this.#gradient.stops.splice(newIndex, 0, stop);
|
|
422
|
-
this.querySelectorAll(".fig-fill-picker-gradient-stop-row").forEach(
|
|
423
|
-
(row, index) => {
|
|
424
|
-
row.dataset.index = String(index);
|
|
425
|
-
},
|
|
426
|
-
);
|
|
427
|
-
this.#syncPreview();
|
|
428
|
-
this.#emit("input");
|
|
429
|
-
});
|
|
471
|
+
this.#gradientBar.addEventListener("click", this.#handleBarStopSelection);
|
|
472
|
+
this.#gradientBar.addEventListener(
|
|
473
|
+
"keydown",
|
|
474
|
+
this.#handleDeleteSelectedStop,
|
|
475
|
+
);
|
|
476
|
+
this.#reorder.addEventListener("reorder", this.#handleReorder);
|
|
477
|
+
this.#reorder.addEventListener("keydown", this.#handleDeleteSelectedStop);
|
|
478
|
+
this.addEventListener("pointerdown", this.#handlePointerDown, true);
|
|
479
|
+
this.addEventListener(
|
|
480
|
+
"lostpointercapture",
|
|
481
|
+
this.#handlePointerEnd,
|
|
482
|
+
true,
|
|
483
|
+
);
|
|
484
|
+
this.#mounted = true;
|
|
430
485
|
}
|
|
431
486
|
|
|
432
487
|
#formatStopColor(stop) {
|
|
@@ -459,127 +514,457 @@ class FigGradientEditor extends HTMLElement {
|
|
|
459
514
|
};
|
|
460
515
|
}
|
|
461
516
|
|
|
462
|
-
#
|
|
463
|
-
const index =
|
|
517
|
+
#readStopIntoGradient(row) {
|
|
518
|
+
const index = this.#stopRows.indexOf(row);
|
|
464
519
|
if (!Number.isInteger(index) || !this.#gradient.stops[index]) return;
|
|
465
520
|
this.#gradient.stops[index] = {
|
|
466
521
|
...this.#gradient.stops[index],
|
|
467
522
|
...this.#readRow(row),
|
|
468
523
|
};
|
|
469
|
-
const stop = this.#gradient.stops[index];
|
|
470
|
-
row
|
|
471
|
-
.querySelector(".fig-fill-picker-stop-position")
|
|
472
|
-
?.setAttribute("value", String(stop.position));
|
|
473
|
-
row
|
|
474
|
-
.querySelector(".fig-fill-picker-stop-color")
|
|
475
|
-
?.setAttribute("value", this.#formatStopColor(stop));
|
|
476
524
|
}
|
|
477
525
|
|
|
478
526
|
#flushRows() {
|
|
479
|
-
this.
|
|
480
|
-
this.#syncRow(row),
|
|
481
|
-
);
|
|
527
|
+
this.#stopRows.forEach((row) => this.#readStopIntoGradient(row));
|
|
482
528
|
}
|
|
483
529
|
|
|
484
|
-
#
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
530
|
+
#createStopRow() {
|
|
531
|
+
const position = createElement("fig-input-number", {
|
|
532
|
+
className: "fig-fill-picker-stop-position",
|
|
533
|
+
"aria-label": "Gradient stop position",
|
|
534
|
+
min: "0",
|
|
535
|
+
max: "100",
|
|
536
|
+
units: "%",
|
|
537
|
+
});
|
|
538
|
+
const color = createElement("fig-input-color", {
|
|
539
|
+
className: "fig-fill-picker-stop-color",
|
|
540
|
+
"aria-label": "Gradient stop color",
|
|
541
|
+
text: "true",
|
|
542
|
+
alpha: "true",
|
|
543
|
+
picker: "figma",
|
|
544
|
+
"picker-dialog-position": "right",
|
|
545
|
+
});
|
|
546
|
+
const row = createElement(
|
|
547
|
+
"fig-property",
|
|
548
|
+
{
|
|
549
|
+
className: "fig-fill-picker-gradient-stop-row",
|
|
550
|
+
leading: "drag",
|
|
551
|
+
trailing: "remove",
|
|
552
|
+
"show-leading": "hover",
|
|
553
|
+
"show-trailing": true,
|
|
554
|
+
},
|
|
555
|
+
createElement(
|
|
556
|
+
"fig-stack",
|
|
490
557
|
{
|
|
491
|
-
className: "fig-fill-picker-gradient-stop-
|
|
492
|
-
"
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
"show-trailing": true,
|
|
558
|
+
className: "fig-fill-picker-gradient-stop-inputs",
|
|
559
|
+
slot: "input",
|
|
560
|
+
direction: "horizontal",
|
|
561
|
+
gap: "2",
|
|
562
|
+
full: true,
|
|
497
563
|
},
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
value: stop.position,
|
|
514
|
-
units: "%",
|
|
515
|
-
disabled,
|
|
516
|
-
}),
|
|
517
|
-
createElement("fig-input-color", {
|
|
518
|
-
className: "fig-fill-picker-stop-color",
|
|
519
|
-
"aria-label": "Gradient stop color",
|
|
520
|
-
text: "true",
|
|
521
|
-
alpha: "true",
|
|
522
|
-
picker: "figma",
|
|
523
|
-
"picker-dialog-position": "right",
|
|
524
|
-
value: this.#formatStopColor(stop),
|
|
525
|
-
disabled,
|
|
526
|
-
}),
|
|
527
|
-
],
|
|
528
|
-
),
|
|
529
|
-
);
|
|
530
|
-
const position = row.querySelector(".fig-fill-picker-stop-position");
|
|
531
|
-
position.addEventListener("input", () => {
|
|
532
|
-
this.#syncRow(row);
|
|
533
|
-
this.#syncPreview();
|
|
534
|
-
this.#emit("input");
|
|
564
|
+
[position, color],
|
|
565
|
+
),
|
|
566
|
+
);
|
|
567
|
+
position.addEventListener("input", (event) => {
|
|
568
|
+
event.stopPropagation();
|
|
569
|
+
this.#handleRowControl(row, "input");
|
|
570
|
+
});
|
|
571
|
+
position.addEventListener("change", (event) => {
|
|
572
|
+
event.stopPropagation();
|
|
573
|
+
this.#handleRowControl(row, "change");
|
|
574
|
+
});
|
|
575
|
+
for (const type of ["input", "change"]) {
|
|
576
|
+
color.addEventListener(type, (event) => {
|
|
577
|
+
event.stopPropagation();
|
|
578
|
+
this.#handleRowControl(row, type);
|
|
535
579
|
});
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
580
|
+
}
|
|
581
|
+
row.addEventListener("remove", (event) => {
|
|
582
|
+
event.stopPropagation();
|
|
583
|
+
this.#removeStopRow(row);
|
|
584
|
+
});
|
|
585
|
+
row.addEventListener("pointerdown", (event) => {
|
|
586
|
+
if (event.button === 0) this.#selectStopRow(row);
|
|
587
|
+
});
|
|
588
|
+
row.addEventListener("click", (event) => {
|
|
589
|
+
this.#selectStopRow(row);
|
|
590
|
+
const fromEditableControl = event.composedPath().some(
|
|
591
|
+
(target) =>
|
|
592
|
+
target instanceof Element &&
|
|
593
|
+
target.matches(
|
|
594
|
+
'input, textarea, select, fig-input-number, fig-input-color, fig-input-text, [contenteditable]:not([contenteditable="false"])',
|
|
595
|
+
),
|
|
596
|
+
);
|
|
597
|
+
if (!fromEditableControl && row.isConnected) {
|
|
598
|
+
row.focus({ preventScroll: true });
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
row.addEventListener("focusin", () => this.#selectStopRow(row));
|
|
602
|
+
return row;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
#selectStopRow(row) {
|
|
606
|
+
if (!row || !this.#stopRows.includes(row)) return;
|
|
607
|
+
this.#selectedStopRow = row;
|
|
608
|
+
this.#stopRows.forEach((stopRow) => {
|
|
609
|
+
stopRow.toggleAttribute("selected", stopRow === row);
|
|
610
|
+
});
|
|
611
|
+
this.#syncSelectedHandle();
|
|
612
|
+
row.scrollIntoView({ block: "nearest" });
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
#syncSelectedHandle() {
|
|
616
|
+
const selectedIndex = this.#stopRows.indexOf(this.#selectedStopRow);
|
|
617
|
+
this.#gradientBar
|
|
618
|
+
?.querySelectorAll(
|
|
619
|
+
".fig-input-gradient-track fig-handle:not(.fig-input-gradient-ghost)",
|
|
620
|
+
)
|
|
621
|
+
.forEach((handle, index) => {
|
|
622
|
+
if (index === selectedIndex) handle.select?.();
|
|
623
|
+
else handle.deselect?.();
|
|
540
624
|
});
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
#reconcileStopRows() {
|
|
628
|
+
if (!this.#reorder) return;
|
|
629
|
+
const desiredStops = this.#gradient.stops;
|
|
630
|
+
const previousRows = this.#stopRows.filter((row) => row.isConnected);
|
|
631
|
+
let nextRows;
|
|
632
|
+
|
|
633
|
+
if (previousRows.length === desiredStops.length) {
|
|
634
|
+
nextRows = [...previousRows];
|
|
635
|
+
} else {
|
|
636
|
+
nextRows = Array(desiredStops.length).fill(null);
|
|
637
|
+
const matches = matchUnchangedStops(this.#renderedStops, desiredStops);
|
|
638
|
+
const usedPrevious = new Set();
|
|
639
|
+
for (const [previousIndex, nextIndex] of matches) {
|
|
640
|
+
const row = previousRows[previousIndex];
|
|
641
|
+
if (!row) continue;
|
|
642
|
+
nextRows[nextIndex] = row;
|
|
643
|
+
usedPrevious.add(previousIndex);
|
|
549
644
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
645
|
+
const reusable = previousRows.filter(
|
|
646
|
+
(_, index) => !usedPrevious.has(index),
|
|
647
|
+
);
|
|
648
|
+
nextRows = nextRows.map((row) => row ?? reusable.shift() ?? this.#createStopRow());
|
|
649
|
+
const retained = new Set(nextRows);
|
|
650
|
+
previousRows.forEach((row) => {
|
|
651
|
+
if (!retained.has(row)) row.remove();
|
|
556
652
|
});
|
|
557
|
-
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
let cursor = this.#reorder.firstElementChild;
|
|
656
|
+
for (const row of nextRows) {
|
|
657
|
+
if (row === cursor) {
|
|
658
|
+
cursor = cursor.nextElementSibling;
|
|
659
|
+
} else {
|
|
660
|
+
this.#reorder.insertBefore(row, cursor);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
this.#stopRows = nextRows;
|
|
664
|
+
this.#stopRows.forEach((row, index) => {
|
|
665
|
+
this.#syncStopRow(row, desiredStops[index], index);
|
|
558
666
|
});
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
667
|
+
if (!this.#stopRows.includes(this.#selectedStopRow)) {
|
|
668
|
+
this.#selectedStopRow = this.#stopRows[0] ?? null;
|
|
669
|
+
}
|
|
670
|
+
this.#stopRows.forEach((row) => {
|
|
671
|
+
row.toggleAttribute("selected", row === this.#selectedStopRow);
|
|
672
|
+
});
|
|
673
|
+
this.#syncSelectedHandle();
|
|
674
|
+
this.#renderedStops = cloneStops(desiredStops);
|
|
675
|
+
this.#syncNestedPickerAnchors();
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
#syncStopRow(row, stop, index) {
|
|
679
|
+
const disabled = this.#disabled();
|
|
680
|
+
row.dataset.index = String(index);
|
|
681
|
+
const position = row.querySelector(".fig-fill-picker-stop-position");
|
|
682
|
+
const color = row.querySelector(".fig-fill-picker-stop-color");
|
|
683
|
+
this.#setControlValue(position, stop.position);
|
|
684
|
+
this.#setControlValue(color, this.#formatStopColor(stop));
|
|
685
|
+
position?.toggleAttribute("disabled", disabled);
|
|
686
|
+
color?.toggleAttribute("disabled", disabled);
|
|
687
|
+
const remove = row.querySelector(
|
|
688
|
+
':scope > [data-fig-property-action="remove"]',
|
|
689
|
+
);
|
|
690
|
+
if (remove) {
|
|
691
|
+
remove.classList.add("fig-fill-picker-stop-remove");
|
|
692
|
+
remove.setAttribute("aria-label", "Remove gradient stop");
|
|
693
|
+
remove.toggleAttribute(
|
|
694
|
+
"disabled",
|
|
695
|
+
disabled || this.#gradient.stops.length <= 2,
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
#setControlValue(control, value) {
|
|
701
|
+
if (!control) return;
|
|
702
|
+
const nextValue = String(value);
|
|
703
|
+
if (control.getAttribute("value") === nextValue) return;
|
|
704
|
+
const active = control.contains(document.activeElement)
|
|
705
|
+
? document.activeElement
|
|
706
|
+
: null;
|
|
707
|
+
const selection =
|
|
708
|
+
active &&
|
|
709
|
+
"selectionStart" in active &&
|
|
710
|
+
typeof active.selectionStart === "number"
|
|
711
|
+
? {
|
|
712
|
+
start: active.selectionStart,
|
|
713
|
+
end: active.selectionEnd,
|
|
714
|
+
direction: active.selectionDirection,
|
|
715
|
+
}
|
|
716
|
+
: null;
|
|
717
|
+
control.setAttribute("value", nextValue);
|
|
718
|
+
if (selection && active === document.activeElement) {
|
|
719
|
+
try {
|
|
720
|
+
active.setSelectionRange(
|
|
721
|
+
selection.start,
|
|
722
|
+
selection.end,
|
|
723
|
+
selection.direction,
|
|
571
724
|
);
|
|
725
|
+
} catch {
|
|
726
|
+
// Selection APIs are not available for every input type.
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
#sync() {
|
|
732
|
+
if (!this.#mounted) return;
|
|
733
|
+
const disabled = this.#disabled();
|
|
734
|
+
const fade = this.#overflowMode() === "fade";
|
|
735
|
+
this.#reorder.classList.toggle("fig-overflow-fade", fade);
|
|
736
|
+
const showRows = this.#showRows();
|
|
737
|
+
if (showRows === null) {
|
|
738
|
+
this.#reorder.style.removeProperty(
|
|
739
|
+
"--fig-gradient-editor-overflow-show-rows",
|
|
740
|
+
);
|
|
741
|
+
} else {
|
|
742
|
+
this.#reorder.style.setProperty(
|
|
743
|
+
"--fig-gradient-editor-overflow-show-rows",
|
|
744
|
+
String(showRows),
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
this.#setControlValue(this.#gradientType, this.#gradient.type);
|
|
748
|
+
this.#gradientType.toggleAttribute("disabled", disabled);
|
|
749
|
+
this.#flipButton.toggleAttribute("disabled", disabled);
|
|
750
|
+
this.#rotateButton.toggleAttribute("disabled", disabled);
|
|
751
|
+
this.#addButton.toggleAttribute("disabled", disabled);
|
|
752
|
+
this.#reorder.toggleAttribute("disabled", disabled);
|
|
753
|
+
this.#gradientBar.toggleAttribute("disabled", disabled);
|
|
754
|
+
const barValue = normalizeGradientEditorValue(this.#gradientBar.value);
|
|
755
|
+
if (!gradientsSemanticallyEqual(barValue, this.#gradient)) {
|
|
756
|
+
this.#syncingBar = true;
|
|
757
|
+
this.#gradientBar.value = {
|
|
758
|
+
type: "gradient",
|
|
759
|
+
gradient: this.#gradient,
|
|
760
|
+
};
|
|
761
|
+
this.#syncingBar = false;
|
|
762
|
+
}
|
|
763
|
+
this.#reconcileStopRows();
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
#applyInternalGradient(value, eventType, { focusIndex = null } = {}) {
|
|
767
|
+
const next = normalizeGradientEditorValue(value);
|
|
768
|
+
const changed = !gradientsSemanticallyEqual(this.#gradient, next);
|
|
769
|
+
if (changed) {
|
|
770
|
+
this.#gradient = next;
|
|
771
|
+
this.#sync();
|
|
772
|
+
}
|
|
773
|
+
if (eventType === "change" || changed) this.#emit(eventType);
|
|
774
|
+
if (focusIndex !== null) this.#focusStopValue(focusIndex);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
#focusStopValue(index) {
|
|
778
|
+
requestAnimationFrame(() => {
|
|
779
|
+
if (!this.isConnected) return;
|
|
780
|
+
const row = this.querySelector(
|
|
781
|
+
`.fig-fill-picker-gradient-stop-row[data-index="${index}"]`,
|
|
782
|
+
);
|
|
783
|
+
const input = row?.querySelector(
|
|
784
|
+
".fig-fill-picker-stop-color fig-input-text input",
|
|
785
|
+
);
|
|
786
|
+
this.#selectStopRow(row);
|
|
787
|
+
input?.focus();
|
|
788
|
+
input?.select();
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
#handleTypeChange = (event) => {
|
|
793
|
+
event.stopPropagation();
|
|
794
|
+
const value =
|
|
795
|
+
typeof event.detail === "string" ? event.detail : event.target?.value;
|
|
796
|
+
if (!["linear", "radial", "angular"].includes(value)) return;
|
|
797
|
+
this.#applyInternalGradient({ ...this.#gradient, type: value }, "input");
|
|
798
|
+
};
|
|
799
|
+
|
|
800
|
+
#handleRotate = () => {
|
|
801
|
+
this.#applyInternalGradient(
|
|
802
|
+
{
|
|
803
|
+
...this.#gradient,
|
|
804
|
+
angle: (Number(this.#gradient.angle) + 90) % 360,
|
|
572
805
|
},
|
|
806
|
+
"input",
|
|
573
807
|
);
|
|
574
|
-
|
|
808
|
+
};
|
|
809
|
+
|
|
810
|
+
#handleFlip = () => {
|
|
811
|
+
const stops = this.#gradient.stops
|
|
812
|
+
.map((stop) => ({ ...stop, position: 100 - stop.position }))
|
|
813
|
+
.sort((a, b) => a.position - b.position);
|
|
814
|
+
this.#applyInternalGradient({ ...this.#gradient, stops }, "input");
|
|
815
|
+
};
|
|
816
|
+
|
|
817
|
+
#handleAdd = () => {
|
|
818
|
+
const stops = cloneStops(this.#gradient.stops);
|
|
819
|
+
const selectedIndex = Math.max(
|
|
820
|
+
0,
|
|
821
|
+
this.#stopRows.indexOf(this.#selectedStopRow),
|
|
822
|
+
);
|
|
823
|
+
const selectedPosition = Number(stops[selectedIndex]?.position) || 0;
|
|
824
|
+
const nextPosition = Number(
|
|
825
|
+
stops[selectedIndex + 1]?.position ?? selectedPosition,
|
|
826
|
+
);
|
|
827
|
+
const addedStop = {
|
|
828
|
+
position: (selectedPosition + nextPosition) / 2,
|
|
829
|
+
color: "#888888",
|
|
830
|
+
opacity: 100,
|
|
831
|
+
};
|
|
832
|
+
const addedIndex = selectedIndex + 1;
|
|
833
|
+
stops.splice(addedIndex, 0, addedStop);
|
|
834
|
+
this.#applyInternalGradient(
|
|
835
|
+
{ ...this.#gradient, stops },
|
|
836
|
+
"input",
|
|
837
|
+
{ focusIndex: addedIndex },
|
|
838
|
+
);
|
|
839
|
+
};
|
|
840
|
+
|
|
841
|
+
#handleBarEvent = (event) => {
|
|
842
|
+
event.stopPropagation();
|
|
843
|
+
if (this.#syncingBar || !event.detail?.gradient) return;
|
|
844
|
+
this.#applyInternalGradient(
|
|
845
|
+
{ ...this.#gradient, ...event.detail.gradient },
|
|
846
|
+
event.type,
|
|
847
|
+
);
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
#handleBarStopSelection = (event) => {
|
|
851
|
+
const handle = event.target?.closest?.(
|
|
852
|
+
"fig-handle:not(.fig-input-gradient-ghost)",
|
|
853
|
+
);
|
|
854
|
+
const index = Number(handle?.dataset.stopIndex);
|
|
855
|
+
if (!Number.isInteger(index)) return;
|
|
856
|
+
this.#selectStopRow(this.#stopRows[index]);
|
|
857
|
+
};
|
|
858
|
+
|
|
859
|
+
#handleDeleteSelectedStop = (event) => {
|
|
860
|
+
if (
|
|
861
|
+
event.defaultPrevented ||
|
|
862
|
+
!["Delete", "Backspace"].includes(event.key) ||
|
|
863
|
+
this.#disabled() ||
|
|
864
|
+
this.#gradient.stops.length <= 2
|
|
865
|
+
) {
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
const handle = event.target?.closest?.(
|
|
869
|
+
"fig-handle[selected]:not(.fig-input-gradient-ghost)",
|
|
870
|
+
);
|
|
871
|
+
const row = event.target?.closest?.(
|
|
872
|
+
".fig-fill-picker-gradient-stop-row[selected]",
|
|
873
|
+
);
|
|
874
|
+
const fromSelectedHandle =
|
|
875
|
+
handle && this.#gradientBar.contains(handle);
|
|
876
|
+
const fromEditableControl = event.composedPath().some(
|
|
877
|
+
(target) =>
|
|
878
|
+
target instanceof Element &&
|
|
879
|
+
target.matches(
|
|
880
|
+
'input, textarea, select, fig-input-number, fig-input-color, fig-input-text, [contenteditable]:not([contenteditable="false"])',
|
|
881
|
+
),
|
|
882
|
+
);
|
|
883
|
+
const fromSelectedRow =
|
|
884
|
+
row === this.#selectedStopRow && !fromEditableControl;
|
|
885
|
+
if (!fromSelectedHandle && !fromSelectedRow) return;
|
|
886
|
+
event.preventDefault();
|
|
887
|
+
this.#removeStopRow(this.#selectedStopRow);
|
|
888
|
+
requestAnimationFrame(() => {
|
|
889
|
+
const selectedIndex = this.#stopRows.indexOf(this.#selectedStopRow);
|
|
890
|
+
this.#gradientBar
|
|
891
|
+
?.querySelectorAll(
|
|
892
|
+
".fig-input-gradient-track fig-handle:not(.fig-input-gradient-ghost)",
|
|
893
|
+
)
|
|
894
|
+
[selectedIndex]?.focus();
|
|
895
|
+
});
|
|
896
|
+
};
|
|
897
|
+
|
|
898
|
+
#handleReorder = (event) => {
|
|
899
|
+
event.stopPropagation();
|
|
900
|
+
const { oldIndex, newIndex } = event.detail ?? {};
|
|
901
|
+
if (
|
|
902
|
+
!Number.isInteger(oldIndex) ||
|
|
903
|
+
!Number.isInteger(newIndex) ||
|
|
904
|
+
oldIndex === newIndex
|
|
905
|
+
) {
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
this.#flushRows();
|
|
909
|
+
const stops = cloneStops(this.#gradient.stops);
|
|
910
|
+
const [stop] = stops.splice(oldIndex, 1);
|
|
911
|
+
stops.splice(newIndex, 0, stop);
|
|
912
|
+
const [row] = this.#stopRows.splice(oldIndex, 1);
|
|
913
|
+
this.#stopRows.splice(newIndex, 0, row);
|
|
914
|
+
this.#applyInternalGradient({ ...this.#gradient, stops }, "input");
|
|
915
|
+
};
|
|
916
|
+
|
|
917
|
+
#handleRowControl(row, eventType) {
|
|
918
|
+
const index = this.#stopRows.indexOf(row);
|
|
919
|
+
if (index < 0) return;
|
|
920
|
+
const stops = cloneStops(this.#gradient.stops);
|
|
921
|
+
stops[index] = { ...stops[index], ...this.#readRow(row) };
|
|
922
|
+
this.#applyInternalGradient({ ...this.#gradient, stops }, eventType);
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
#removeStopRow(row) {
|
|
926
|
+
if (this.#gradient.stops.length <= 2) return;
|
|
927
|
+
const index = this.#stopRows.indexOf(row);
|
|
928
|
+
if (index < 0) return;
|
|
929
|
+
if (row.contains(document.activeElement)) this.#addButton.focus();
|
|
930
|
+
if (row === this.#selectedStopRow) {
|
|
931
|
+
this.#selectedStopRow =
|
|
932
|
+
this.#stopRows[index + 1] ?? this.#stopRows[index - 1] ?? null;
|
|
933
|
+
}
|
|
934
|
+
const stops = cloneStops(this.#gradient.stops);
|
|
935
|
+
stops.splice(index, 1);
|
|
936
|
+
this.#applyInternalGradient({ ...this.#gradient, stops }, "input");
|
|
575
937
|
}
|
|
576
938
|
|
|
577
|
-
#
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
this.#
|
|
581
|
-
|
|
582
|
-
this.#
|
|
939
|
+
#handlePointerDown = (event) => {
|
|
940
|
+
if (event.button !== 0 || this.#pointerInteractionActive) return;
|
|
941
|
+
this.#pointerInteractionActive = true;
|
|
942
|
+
window.addEventListener("pointerup", this.#handlePointerEnd, true);
|
|
943
|
+
window.addEventListener("pointercancel", this.#handlePointerEnd, true);
|
|
944
|
+
window.addEventListener("blur", this.#handlePointerEnd, true);
|
|
945
|
+
};
|
|
946
|
+
|
|
947
|
+
#handlePointerEnd = () => {
|
|
948
|
+
window.removeEventListener("pointerup", this.#handlePointerEnd, true);
|
|
949
|
+
window.removeEventListener("pointercancel", this.#handlePointerEnd, true);
|
|
950
|
+
window.removeEventListener("blur", this.#handlePointerEnd, true);
|
|
951
|
+
queueMicrotask(() =>
|
|
952
|
+
this.#endPointerInteraction({ reconcile: true }),
|
|
953
|
+
);
|
|
954
|
+
};
|
|
955
|
+
|
|
956
|
+
#endPointerInteraction({ reconcile }) {
|
|
957
|
+
window.removeEventListener("pointerup", this.#handlePointerEnd, true);
|
|
958
|
+
window.removeEventListener("pointercancel", this.#handlePointerEnd, true);
|
|
959
|
+
window.removeEventListener("blur", this.#handlePointerEnd, true);
|
|
960
|
+
const wasActive = this.#pointerInteractionActive;
|
|
961
|
+
this.#pointerInteractionActive = false;
|
|
962
|
+
if (!reconcile || !wasActive || !this.#deferredExternalGradient) return;
|
|
963
|
+
const deferred = this.#deferredExternalGradient;
|
|
964
|
+
this.#deferredExternalGradient = null;
|
|
965
|
+
this.#gradient = deferred;
|
|
966
|
+
this.#reflectValue();
|
|
967
|
+
this.#sync();
|
|
583
968
|
}
|
|
584
969
|
|
|
585
970
|
#syncNestedPickerAnchors() {
|