@rogieking/figui3 11.1.6 → 11.2.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.
- package/.cursor/skills/fig-editor/SKILL.md +7 -3
- package/.cursor/skills/fig-editor/components.md +52 -1
- package/.cursor/skills/fig-editor/reference.md +18 -0
- package/.cursor/skills/fig-lab/SKILL.md +2 -2
- package/.cursor/skills/fig-lab/components.md +0 -32
- package/.cursor/skills/fig-lab/reference.md +0 -14
- package/.cursor/skills/figui3/SKILL.md +2 -2
- package/.cursor/skills/figui3/components.md +17 -1
- package/.cursor/skills/figui3/react.md +1 -0
- package/CHANGELOG.md +29 -0
- package/README.md +31 -2
- package/components.css +18 -16
- package/dist/components.css +1 -1
- package/dist/fig-editor.css +1 -1
- package/dist/fig-editor.js +3 -3
- package/dist/fig-lab.css +1 -1
- package/dist/fig-lab.js +1 -1
- package/dist/fig.css +1 -1
- package/dist/fig.js +5 -5
- package/fig-editor.css +104 -20
- package/fig-editor.js +1016 -19
- package/fig-lab.css +0 -75
- package/fig-lab.js +0 -672
- package/fig.js +242 -26
- package/package.json +1 -1
package/fig-editor.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/* Copyright (c) 2026 Rogie King. PolyForm Shield 1.0.0. See LICENSE. */
|
|
2
2
|
import "./fig.js";
|
|
3
|
-
import "./fig-lab.js";
|
|
4
3
|
|
|
5
4
|
function figEditorDefineElement(name, constructor) {
|
|
6
5
|
if (!customElements.get(name)) {
|
|
@@ -83,6 +82,73 @@ function figEditorCreateElement(tagName, attributes, children) {
|
|
|
83
82
|
return figEditorAppendChildren(element, children);
|
|
84
83
|
}
|
|
85
84
|
|
|
85
|
+
function figEditorNormalizePaletteValue(value) {
|
|
86
|
+
let entries = value;
|
|
87
|
+
if (typeof entries === "string") {
|
|
88
|
+
const raw = entries.trim();
|
|
89
|
+
if (!raw) return [];
|
|
90
|
+
try {
|
|
91
|
+
entries = JSON.parse(raw);
|
|
92
|
+
} catch {
|
|
93
|
+
entries = raw.includes(",") ? raw.split(",") : [raw];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (!Array.isArray(entries)) return [];
|
|
97
|
+
return entries.map((entry) => {
|
|
98
|
+
if (typeof entry === "string") {
|
|
99
|
+
const color = entry.trim();
|
|
100
|
+
return {
|
|
101
|
+
color: color.slice(0, 7) || "#D9D9D9",
|
|
102
|
+
alpha:
|
|
103
|
+
color.length > 7
|
|
104
|
+
? Math.max(0, Math.min(1, parseInt(color.slice(7, 9), 16) / 255))
|
|
105
|
+
: 1,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
if (entry && typeof entry === "object") {
|
|
109
|
+
const alpha =
|
|
110
|
+
entry.alpha !== undefined
|
|
111
|
+
? Number(entry.alpha)
|
|
112
|
+
: entry.opacity !== undefined
|
|
113
|
+
? Number(entry.opacity) / 100
|
|
114
|
+
: 1;
|
|
115
|
+
return {
|
|
116
|
+
color: String(entry.color || "#D9D9D9").slice(0, 7),
|
|
117
|
+
alpha: Number.isFinite(alpha) ? Math.max(0, Math.min(1, alpha)) : 1,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return { color: "#D9D9D9", alpha: 1 };
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function figEditorPaletteEntryValue(entry) {
|
|
125
|
+
const color = String(entry?.color || "#D9D9D9").slice(0, 7);
|
|
126
|
+
const alpha = Math.max(0, Math.min(1, Number(entry?.alpha ?? 1)));
|
|
127
|
+
if (alpha >= 1) return color;
|
|
128
|
+
return `${color}${Math.round(alpha * 255)
|
|
129
|
+
.toString(16)
|
|
130
|
+
.padStart(2, "0")}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function figEditorAveragePaletteEntries(first, second) {
|
|
134
|
+
if (!first && !second) return { color: "#D9D9D9", alpha: 1 };
|
|
135
|
+
if (!first || !second) return { ...(first || second) };
|
|
136
|
+
const firstRgb = figEditorHexToRgb(first.color);
|
|
137
|
+
const secondRgb = figEditorHexToRgb(second.color);
|
|
138
|
+
const color = `#${["r", "g", "b"]
|
|
139
|
+
.map((channel) =>
|
|
140
|
+
Math.round((firstRgb[channel] + secondRgb[channel]) / 2)
|
|
141
|
+
.toString(16)
|
|
142
|
+
.padStart(2, "0"),
|
|
143
|
+
)
|
|
144
|
+
.join("")
|
|
145
|
+
.toUpperCase()}`;
|
|
146
|
+
return {
|
|
147
|
+
color,
|
|
148
|
+
alpha: (Number(first.alpha ?? 1) + Number(second.alpha ?? 1)) / 2,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
86
152
|
const FIG_EDITOR_SVG_NAMESPACE = "http://www.w3.org/2000/svg";
|
|
87
153
|
|
|
88
154
|
function figEditorCreateSvgElement(tagName, attributes, children) {
|
|
@@ -282,6 +348,244 @@ function figEditorScrollElementToCenter(
|
|
|
282
348
|
}
|
|
283
349
|
|
|
284
350
|
|
|
351
|
+
/**
|
|
352
|
+
* A reusable editor for an ordered palette of solid colors.
|
|
353
|
+
* @attr {string} value - JSON array of hex strings or {color,alpha} objects, or comma-separated hex
|
|
354
|
+
* @attr {number} min - Minimum number of colors (default: 2)
|
|
355
|
+
* @attr {number} max - Maximum number of colors (unbounded when omitted)
|
|
356
|
+
* @attr {boolean} fixed - Locks palette length while preserving color editing and reordering
|
|
357
|
+
* @attr {boolean} disabled - Disables editing and reordering
|
|
358
|
+
* @fires input - During color editing or reordering (detail: normalized color array)
|
|
359
|
+
* @fires change - On committed color edits, add, remove, or reorder
|
|
360
|
+
*/
|
|
361
|
+
class FigPaletteEditor extends HTMLElement {
|
|
362
|
+
#colors = [];
|
|
363
|
+
#syncingValueAttribute = false;
|
|
364
|
+
|
|
365
|
+
static get observedAttributes() {
|
|
366
|
+
return ["value", "min", "max", "fixed", "disabled"];
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
get #min() {
|
|
370
|
+
const value = Number.parseInt(this.getAttribute("min"), 10);
|
|
371
|
+
return Number.isFinite(value) ? Math.max(0, value) : 2;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
get #max() {
|
|
375
|
+
const value = Number.parseInt(this.getAttribute("max"), 10);
|
|
376
|
+
return Number.isFinite(value) ? Math.max(this.#min, value) : Infinity;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
get #fixed() {
|
|
380
|
+
return figEditorBooleanAttribute(this, "fixed");
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
get #disabled() {
|
|
384
|
+
return figEditorBooleanAttribute(this, "disabled");
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
get value() {
|
|
388
|
+
return this.#colors.map((entry) => ({ ...entry }));
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
set value(value) {
|
|
392
|
+
this.setAttribute(
|
|
393
|
+
"value",
|
|
394
|
+
typeof value === "string" ? value : JSON.stringify(value ?? []),
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
connectedCallback() {
|
|
399
|
+
this.#colors = figEditorNormalizePaletteValue(this.getAttribute("value"));
|
|
400
|
+
this.#render();
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
404
|
+
if (oldValue === newValue || this.#syncingValueAttribute) return;
|
|
405
|
+
if (name === "value") {
|
|
406
|
+
this.#colors = figEditorNormalizePaletteValue(newValue);
|
|
407
|
+
}
|
|
408
|
+
if (this.isConnected) this.#render();
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
#reflectValue() {
|
|
412
|
+
this.#syncingValueAttribute = true;
|
|
413
|
+
this.setAttribute("value", JSON.stringify(this.#colors));
|
|
414
|
+
this.#syncingValueAttribute = false;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
#emit(type) {
|
|
418
|
+
this.dispatchEvent(
|
|
419
|
+
new CustomEvent(type, {
|
|
420
|
+
bubbles: true,
|
|
421
|
+
detail: this.value,
|
|
422
|
+
}),
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
#commit(type, { rerender = false } = {}) {
|
|
427
|
+
this.#reflectValue();
|
|
428
|
+
if (rerender) this.#render();
|
|
429
|
+
this.#emit(type);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
#createRemoveButton(index) {
|
|
433
|
+
const button = figEditorCreateElement(
|
|
434
|
+
"fig-button",
|
|
435
|
+
{
|
|
436
|
+
className: "fig-palette-editor-remove",
|
|
437
|
+
variant: "ghost",
|
|
438
|
+
icon: true,
|
|
439
|
+
"aria-label": `Remove color ${index + 1}`,
|
|
440
|
+
disabled:
|
|
441
|
+
this.#disabled || this.#colors.length <= this.#min || null,
|
|
442
|
+
},
|
|
443
|
+
figEditorCreateIcon("minus"),
|
|
444
|
+
);
|
|
445
|
+
button.addEventListener("click", () => {
|
|
446
|
+
const row = button.closest(".fig-palette-editor-color-row");
|
|
447
|
+
const currentIndex = Number.parseInt(row?.dataset.index ?? "", 10);
|
|
448
|
+
if (
|
|
449
|
+
this.#disabled ||
|
|
450
|
+
this.#fixed ||
|
|
451
|
+
this.#colors.length <= this.#min ||
|
|
452
|
+
!Number.isInteger(currentIndex)
|
|
453
|
+
) {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
this.#colors.splice(currentIndex, 1);
|
|
457
|
+
this.#reflectValue();
|
|
458
|
+
this.#render();
|
|
459
|
+
this.#emit("input");
|
|
460
|
+
this.#emit("change");
|
|
461
|
+
});
|
|
462
|
+
return button;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
#createRow(entry, index) {
|
|
466
|
+
const colorInput = figEditorCreateElement("fig-input-color", {
|
|
467
|
+
className: "fig-palette-editor-color",
|
|
468
|
+
value: figEditorPaletteEntryValue(entry),
|
|
469
|
+
text: true,
|
|
470
|
+
alpha: true,
|
|
471
|
+
full: true,
|
|
472
|
+
disabled: this.#disabled || null,
|
|
473
|
+
"aria-label": `Palette color ${index + 1}`,
|
|
474
|
+
});
|
|
475
|
+
const syncColor = (event, type) => {
|
|
476
|
+
event.stopPropagation();
|
|
477
|
+
const row = colorInput.closest(".fig-palette-editor-color-row");
|
|
478
|
+
const currentIndex = Number.parseInt(row?.dataset.index ?? "", 10);
|
|
479
|
+
if (!Number.isInteger(currentIndex) || !this.#colors[currentIndex]) return;
|
|
480
|
+
this.#colors[currentIndex] = {
|
|
481
|
+
color:
|
|
482
|
+
colorInput.hexOpaque ||
|
|
483
|
+
this.#colors[currentIndex]?.color ||
|
|
484
|
+
"#D9D9D9",
|
|
485
|
+
alpha:
|
|
486
|
+
colorInput.rgba?.a ?? this.#colors[currentIndex]?.alpha ?? 1,
|
|
487
|
+
};
|
|
488
|
+
this.#commit(type);
|
|
489
|
+
};
|
|
490
|
+
colorInput.addEventListener("input", (event) =>
|
|
491
|
+
syncColor(event, "input"),
|
|
492
|
+
);
|
|
493
|
+
colorInput.addEventListener("change", (event) =>
|
|
494
|
+
syncColor(event, "change"),
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
return figEditorCreateElement(
|
|
498
|
+
"fig-field",
|
|
499
|
+
{
|
|
500
|
+
className: "fig-palette-editor-color-row",
|
|
501
|
+
"data-index": index,
|
|
502
|
+
},
|
|
503
|
+
[
|
|
504
|
+
colorInput,
|
|
505
|
+
...(this.#fixed ? [] : [this.#createRemoveButton(index)]),
|
|
506
|
+
],
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
#handleReorder = (event) => {
|
|
511
|
+
const oldIndex = Number(event.detail?.oldIndex);
|
|
512
|
+
const newIndex = Number(event.detail?.newIndex);
|
|
513
|
+
if (
|
|
514
|
+
!Number.isInteger(oldIndex) ||
|
|
515
|
+
!Number.isInteger(newIndex) ||
|
|
516
|
+
oldIndex === newIndex ||
|
|
517
|
+
oldIndex < 0 ||
|
|
518
|
+
newIndex < 0 ||
|
|
519
|
+
oldIndex >= this.#colors.length ||
|
|
520
|
+
newIndex >= this.#colors.length
|
|
521
|
+
) {
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
const [entry] = this.#colors.splice(oldIndex, 1);
|
|
525
|
+
this.#colors.splice(newIndex, 0, entry);
|
|
526
|
+
this.querySelectorAll(".fig-palette-editor-color-row").forEach(
|
|
527
|
+
(row, index) => {
|
|
528
|
+
row.dataset.index = String(index);
|
|
529
|
+
row
|
|
530
|
+
.querySelector(".fig-palette-editor-color")
|
|
531
|
+
?.setAttribute("aria-label", `Palette color ${index + 1}`);
|
|
532
|
+
row
|
|
533
|
+
.querySelector(".fig-palette-editor-remove")
|
|
534
|
+
?.setAttribute("aria-label", `Remove color ${index + 1}`);
|
|
535
|
+
},
|
|
536
|
+
);
|
|
537
|
+
this.#reflectValue();
|
|
538
|
+
this.#emit("input");
|
|
539
|
+
this.#emit("change");
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
#render() {
|
|
543
|
+
const reorder = figEditorCreateElement(
|
|
544
|
+
"fig-reorder",
|
|
545
|
+
{
|
|
546
|
+
axis: "vertical",
|
|
547
|
+
items: ".fig-palette-editor-color-row",
|
|
548
|
+
"aria-label": "Palette colors",
|
|
549
|
+
disabled: this.#disabled || null,
|
|
550
|
+
},
|
|
551
|
+
this.#colors.map((entry, index) => this.#createRow(entry, index)),
|
|
552
|
+
);
|
|
553
|
+
reorder.addEventListener("reorder", this.#handleReorder);
|
|
554
|
+
|
|
555
|
+
const children = [reorder];
|
|
556
|
+
if (!this.#fixed) {
|
|
557
|
+
const addButton = figEditorCreateElement(
|
|
558
|
+
"fig-button",
|
|
559
|
+
{
|
|
560
|
+
className: "fig-palette-editor-add",
|
|
561
|
+
variant: "secondary",
|
|
562
|
+
full: true,
|
|
563
|
+
disabled:
|
|
564
|
+
this.#disabled || this.#colors.length >= this.#max || null,
|
|
565
|
+
},
|
|
566
|
+
"Add color",
|
|
567
|
+
);
|
|
568
|
+
addButton.addEventListener("click", () => {
|
|
569
|
+
if (this.#disabled || this.#colors.length >= this.#max) return;
|
|
570
|
+
this.#colors.push(
|
|
571
|
+
figEditorAveragePaletteEntries(
|
|
572
|
+
this.#colors.at(-2),
|
|
573
|
+
this.#colors.at(-1),
|
|
574
|
+
),
|
|
575
|
+
);
|
|
576
|
+
this.#reflectValue();
|
|
577
|
+
this.#render();
|
|
578
|
+
this.#emit("input");
|
|
579
|
+
this.#emit("change");
|
|
580
|
+
});
|
|
581
|
+
children.push(figEditorCreateElement("fig-field", {}, addButton));
|
|
582
|
+
}
|
|
583
|
+
this.replaceChildren(...children);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
figEditorDefineElement("fig-palette-editor", FigPaletteEditor);
|
|
587
|
+
|
|
588
|
+
|
|
285
589
|
/* Select — dropdown-styled trigger + fig-popup listbox */
|
|
286
590
|
/** Parse options attr — same formats as fig-options. */
|
|
287
591
|
function figSelectParseOptionsAttribute(raw) {
|
|
@@ -3842,8 +4146,11 @@ class FigFillPicker extends HTMLElement {
|
|
|
3842
4146
|
),
|
|
3843
4147
|
);
|
|
3844
4148
|
const actions = figEditorCreateElement(
|
|
3845
|
-
"
|
|
3846
|
-
{
|
|
4149
|
+
"fig-stack",
|
|
4150
|
+
{
|
|
4151
|
+
className: "fig-fill-picker-gradient-actions",
|
|
4152
|
+
gap: "1",
|
|
4153
|
+
},
|
|
3847
4154
|
[
|
|
3848
4155
|
createAction(
|
|
3849
4156
|
"Flip gradient",
|
|
@@ -3858,8 +4165,12 @@ class FigFillPicker extends HTMLElement {
|
|
|
3858
4165
|
],
|
|
3859
4166
|
);
|
|
3860
4167
|
const header = figEditorCreateElement(
|
|
3861
|
-
"fig-
|
|
3862
|
-
{
|
|
4168
|
+
"fig-header",
|
|
4169
|
+
{
|
|
4170
|
+
className: "fig-fill-picker-gradient-header",
|
|
4171
|
+
borderless: true,
|
|
4172
|
+
compact: true,
|
|
4173
|
+
},
|
|
3863
4174
|
[gradientType, actions],
|
|
3864
4175
|
);
|
|
3865
4176
|
const gradientBar = figEditorCreateElement("fig-input-gradient", {
|
|
@@ -3972,13 +4283,10 @@ class FigFillPicker extends HTMLElement {
|
|
|
3972
4283
|
if (!gradientBarInput) return;
|
|
3973
4284
|
this.#syncingGradientBar = true;
|
|
3974
4285
|
try {
|
|
3975
|
-
gradientBarInput.
|
|
3976
|
-
"
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
gradient: gradientToValueShape(gradient),
|
|
3980
|
-
}),
|
|
3981
|
-
);
|
|
4286
|
+
gradientBarInput.value = {
|
|
4287
|
+
type: "gradient",
|
|
4288
|
+
gradient: gradientToValueShape(gradient),
|
|
4289
|
+
};
|
|
3982
4290
|
} finally {
|
|
3983
4291
|
this.#syncingGradientBar = false;
|
|
3984
4292
|
}
|
|
@@ -4461,13 +4769,10 @@ class FigFillPicker extends HTMLElement {
|
|
|
4461
4769
|
);
|
|
4462
4770
|
if (barInput) {
|
|
4463
4771
|
this.#syncingGradientBar = true;
|
|
4464
|
-
barInput.
|
|
4465
|
-
"
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
gradient: gradientToValueShape(this.#gradient),
|
|
4469
|
-
}),
|
|
4470
|
-
);
|
|
4772
|
+
barInput.value = {
|
|
4773
|
+
type: "gradient",
|
|
4774
|
+
gradient: gradientToValueShape(this.#gradient),
|
|
4775
|
+
};
|
|
4471
4776
|
this.#syncingGradientBar = false;
|
|
4472
4777
|
}
|
|
4473
4778
|
|
|
@@ -6107,3 +6412,695 @@ class FigInterpolationSwatch extends HTMLElement {
|
|
|
6107
6412
|
}
|
|
6108
6413
|
}
|
|
6109
6414
|
figEditorDefineElement("fig-interpolation-swatch", FigInterpolationSwatch);
|
|
6415
|
+
|
|
6416
|
+
/* Reorder wrapper */
|
|
6417
|
+
/**
|
|
6418
|
+
* @attr {string} axis - Reorder direction: "vertical" (default) or "horizontal"
|
|
6419
|
+
* @attr {string} handle - Selector for the drag handle within each item
|
|
6420
|
+
* @attr {string} items - Selector limiting which direct children are reorderable
|
|
6421
|
+
* @attr {string} indicator - Drop indicator style: "default" or "ring"
|
|
6422
|
+
* @attr {boolean} disabled - Disable reordering
|
|
6423
|
+
*/
|
|
6424
|
+
class FigReorder extends HTMLElement {
|
|
6425
|
+
static observedAttributes = [
|
|
6426
|
+
"axis",
|
|
6427
|
+
"handle",
|
|
6428
|
+
"items",
|
|
6429
|
+
"indicator",
|
|
6430
|
+
"disabled",
|
|
6431
|
+
];
|
|
6432
|
+
|
|
6433
|
+
static #DRAG_THRESHOLD = 6;
|
|
6434
|
+
|
|
6435
|
+
static #NESTED_DRAG_SELECTORS = [
|
|
6436
|
+
'[draggable]:not([draggable="false"])',
|
|
6437
|
+
'img:not([draggable="false"])',
|
|
6438
|
+
'a[href]:not([draggable="false"])',
|
|
6439
|
+
'input[type="range"]',
|
|
6440
|
+
"fig-handle",
|
|
6441
|
+
"fig-slider",
|
|
6442
|
+
"fig-input-number",
|
|
6443
|
+
"fig-input-gradient",
|
|
6444
|
+
"fig-easing-curve",
|
|
6445
|
+
"fig-angle",
|
|
6446
|
+
"fig-input-audio",
|
|
6447
|
+
"fig-input-wheel",
|
|
6448
|
+
"fig-joystick",
|
|
6449
|
+
"fig-origin-grid",
|
|
6450
|
+
"fig-chooser",
|
|
6451
|
+
"fig-canvas-control",
|
|
6452
|
+
];
|
|
6453
|
+
|
|
6454
|
+
static #OWNED_ATTRIBUTES = new Set([
|
|
6455
|
+
"data-reorder-item",
|
|
6456
|
+
"data-reorder-generated-role",
|
|
6457
|
+
"data-reorder-handle",
|
|
6458
|
+
"role",
|
|
6459
|
+
"tabindex",
|
|
6460
|
+
"aria-label",
|
|
6461
|
+
"aria-roledescription",
|
|
6462
|
+
]);
|
|
6463
|
+
|
|
6464
|
+
#childObserver = null;
|
|
6465
|
+
#attributeObserver = null;
|
|
6466
|
+
#attributeSyncQueued = false;
|
|
6467
|
+
#bindings = new Map();
|
|
6468
|
+
#drag = null;
|
|
6469
|
+
#indicator = null;
|
|
6470
|
+
#liveRegion = null;
|
|
6471
|
+
|
|
6472
|
+
connectedCallback() {
|
|
6473
|
+
this.style.display = "contents";
|
|
6474
|
+
if (!this.hasAttribute("role")) this.setAttribute("role", "list");
|
|
6475
|
+
if (!this.hasAttribute("aria-label")) {
|
|
6476
|
+
this.setAttribute("aria-label", "Reorderable list");
|
|
6477
|
+
}
|
|
6478
|
+
this.#ensureLiveRegion();
|
|
6479
|
+
this.#syncChildren();
|
|
6480
|
+
this.#childObserver = new MutationObserver(() => this.#syncChildren());
|
|
6481
|
+
this.#childObserver.observe(this, { childList: true });
|
|
6482
|
+
this.#syncAttributeObserver();
|
|
6483
|
+
}
|
|
6484
|
+
|
|
6485
|
+
disconnectedCallback() {
|
|
6486
|
+
this.#childObserver?.disconnect();
|
|
6487
|
+
this.#childObserver = null;
|
|
6488
|
+
this.#attributeObserver?.disconnect();
|
|
6489
|
+
this.#attributeObserver = null;
|
|
6490
|
+
this.#unbindAll();
|
|
6491
|
+
this.#cancelDrag();
|
|
6492
|
+
this.#removeIndicator();
|
|
6493
|
+
this.#liveRegion?.remove();
|
|
6494
|
+
this.#liveRegion = null;
|
|
6495
|
+
}
|
|
6496
|
+
|
|
6497
|
+
attributeChangedCallback(name) {
|
|
6498
|
+
if (this.isConnected) {
|
|
6499
|
+
if (this.#disabled) this.#cancelDrag();
|
|
6500
|
+
if (name === "indicator") this.#syncIndicatorStyle();
|
|
6501
|
+
this.#syncAttributeObserver();
|
|
6502
|
+
this.#syncChildren();
|
|
6503
|
+
}
|
|
6504
|
+
}
|
|
6505
|
+
|
|
6506
|
+
refresh() {
|
|
6507
|
+
if (this.isConnected) this.#syncChildren();
|
|
6508
|
+
}
|
|
6509
|
+
|
|
6510
|
+
#syncAttributeObserver() {
|
|
6511
|
+
if (!this.#itemsSelector) {
|
|
6512
|
+
this.#attributeObserver?.disconnect();
|
|
6513
|
+
this.#attributeObserver = null;
|
|
6514
|
+
return;
|
|
6515
|
+
}
|
|
6516
|
+
if (this.#attributeObserver) return;
|
|
6517
|
+
this.#attributeObserver = new MutationObserver((records) => {
|
|
6518
|
+
const relevant = records.some(
|
|
6519
|
+
(record) =>
|
|
6520
|
+
record.target.parentElement === this &&
|
|
6521
|
+
!FigReorder.#OWNED_ATTRIBUTES.has(record.attributeName) &&
|
|
6522
|
+
!(record.attributeName === "class" && record.target === this.#drag?.item),
|
|
6523
|
+
);
|
|
6524
|
+
if (!relevant || this.#attributeSyncQueued) return;
|
|
6525
|
+
this.#attributeSyncQueued = true;
|
|
6526
|
+
queueMicrotask(() => {
|
|
6527
|
+
this.#attributeSyncQueued = false;
|
|
6528
|
+
if (this.isConnected) this.#syncChildren();
|
|
6529
|
+
});
|
|
6530
|
+
});
|
|
6531
|
+
this.#attributeObserver.observe(this, { subtree: true, attributes: true });
|
|
6532
|
+
}
|
|
6533
|
+
|
|
6534
|
+
get #disabled() {
|
|
6535
|
+
return (
|
|
6536
|
+
this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false"
|
|
6537
|
+
);
|
|
6538
|
+
}
|
|
6539
|
+
|
|
6540
|
+
get #axis() {
|
|
6541
|
+
const value = (this.getAttribute("axis") || "vertical").trim().toLowerCase();
|
|
6542
|
+
return value === "horizontal" ? "horizontal" : "vertical";
|
|
6543
|
+
}
|
|
6544
|
+
|
|
6545
|
+
get #handleSelector() {
|
|
6546
|
+
return (this.getAttribute("handle") || "").trim();
|
|
6547
|
+
}
|
|
6548
|
+
|
|
6549
|
+
get #itemsSelector() {
|
|
6550
|
+
return (this.getAttribute("items") || "").trim();
|
|
6551
|
+
}
|
|
6552
|
+
|
|
6553
|
+
get #indicatorStyle() {
|
|
6554
|
+
return (this.getAttribute("indicator") || "default").trim().toLowerCase();
|
|
6555
|
+
}
|
|
6556
|
+
|
|
6557
|
+
#getAllElementChildren() {
|
|
6558
|
+
return [...this.children].filter(
|
|
6559
|
+
(node) =>
|
|
6560
|
+
node.nodeType === Node.ELEMENT_NODE &&
|
|
6561
|
+
!node.hasAttribute("data-reorder-live"),
|
|
6562
|
+
);
|
|
6563
|
+
}
|
|
6564
|
+
|
|
6565
|
+
#getElementChildren() {
|
|
6566
|
+
const children = this.#getAllElementChildren();
|
|
6567
|
+
const selector = this.#itemsSelector;
|
|
6568
|
+
if (!selector) return children;
|
|
6569
|
+
try {
|
|
6570
|
+
return children.filter((child) => child.matches(selector));
|
|
6571
|
+
} catch {
|
|
6572
|
+
return [];
|
|
6573
|
+
}
|
|
6574
|
+
}
|
|
6575
|
+
|
|
6576
|
+
#syncChildren() {
|
|
6577
|
+
const allChildren = this.#getAllElementChildren();
|
|
6578
|
+
const children = this.#getElementChildren();
|
|
6579
|
+
const childSet = new Set(children);
|
|
6580
|
+
|
|
6581
|
+
for (const [child, binding] of this.#bindings) {
|
|
6582
|
+
if (!childSet.has(child)) {
|
|
6583
|
+
binding.target.removeEventListener(
|
|
6584
|
+
"pointerdown",
|
|
6585
|
+
binding.onPointerDown,
|
|
6586
|
+
true,
|
|
6587
|
+
);
|
|
6588
|
+
binding.target.removeEventListener("keydown", binding.onKeyDown);
|
|
6589
|
+
this.#restoreKeyboardAttrs(binding);
|
|
6590
|
+
this.#bindings.delete(child);
|
|
6591
|
+
}
|
|
6592
|
+
}
|
|
6593
|
+
|
|
6594
|
+
this.#clearHandleMarks(allChildren);
|
|
6595
|
+
this.#clearReorderItemMarks(allChildren);
|
|
6596
|
+
this.#markNonItems(allChildren.filter((child) => !childSet.has(child)));
|
|
6597
|
+
const reorderable = !this.#disabled && children.length >= 2;
|
|
6598
|
+
this.#markReorderItems(children, reorderable);
|
|
6599
|
+
|
|
6600
|
+
if (!reorderable) {
|
|
6601
|
+
this.#unbindAll();
|
|
6602
|
+
return;
|
|
6603
|
+
}
|
|
6604
|
+
|
|
6605
|
+
for (const child of children) {
|
|
6606
|
+
const target = this.#getDragTarget(child);
|
|
6607
|
+
const existing = this.#bindings.get(child);
|
|
6608
|
+
if (existing && existing.target === target) continue;
|
|
6609
|
+
if (existing) {
|
|
6610
|
+
existing.target.removeEventListener(
|
|
6611
|
+
"pointerdown",
|
|
6612
|
+
existing.onPointerDown,
|
|
6613
|
+
true,
|
|
6614
|
+
);
|
|
6615
|
+
this.#bindings.delete(child);
|
|
6616
|
+
}
|
|
6617
|
+
if (!target) continue;
|
|
6618
|
+
this.#bindChild(child, target);
|
|
6619
|
+
}
|
|
6620
|
+
|
|
6621
|
+
this.#markHandles(children);
|
|
6622
|
+
}
|
|
6623
|
+
|
|
6624
|
+
#clearReorderItemMarks(children) {
|
|
6625
|
+
for (const child of children) {
|
|
6626
|
+
child.removeAttribute("data-reorder-item");
|
|
6627
|
+
if (child.hasAttribute("data-reorder-generated-role")) {
|
|
6628
|
+
child.removeAttribute("role");
|
|
6629
|
+
child.removeAttribute("data-reorder-generated-role");
|
|
6630
|
+
}
|
|
6631
|
+
}
|
|
6632
|
+
}
|
|
6633
|
+
|
|
6634
|
+
#markReorderItems(children, reorderable = true) {
|
|
6635
|
+
for (const child of children) {
|
|
6636
|
+
if (reorderable) child.setAttribute("data-reorder-item", "");
|
|
6637
|
+
if (!child.hasAttribute("role")) {
|
|
6638
|
+
child.setAttribute("role", "listitem");
|
|
6639
|
+
child.setAttribute("data-reorder-generated-role", "");
|
|
6640
|
+
}
|
|
6641
|
+
}
|
|
6642
|
+
}
|
|
6643
|
+
|
|
6644
|
+
#markNonItems(children) {
|
|
6645
|
+
for (const child of children) {
|
|
6646
|
+
if (!child.hasAttribute("role")) {
|
|
6647
|
+
child.setAttribute("role", "none");
|
|
6648
|
+
child.setAttribute("data-reorder-generated-role", "");
|
|
6649
|
+
}
|
|
6650
|
+
}
|
|
6651
|
+
}
|
|
6652
|
+
|
|
6653
|
+
#clearHandleMarks(children) {
|
|
6654
|
+
for (const child of children) {
|
|
6655
|
+
child
|
|
6656
|
+
.querySelectorAll("[data-reorder-handle]")
|
|
6657
|
+
.forEach((node) => {
|
|
6658
|
+
node.removeAttribute("data-reorder-handle");
|
|
6659
|
+
node.removeAttribute("aria-roledescription");
|
|
6660
|
+
});
|
|
6661
|
+
}
|
|
6662
|
+
}
|
|
6663
|
+
|
|
6664
|
+
#markHandles(children) {
|
|
6665
|
+
if (!this.#handleSelector) return;
|
|
6666
|
+
for (const child of children) {
|
|
6667
|
+
const handle = child.querySelector(this.#handleSelector);
|
|
6668
|
+
if (handle) {
|
|
6669
|
+
handle.setAttribute("data-reorder-handle", "");
|
|
6670
|
+
handle.setAttribute("aria-roledescription", "reorder handle");
|
|
6671
|
+
}
|
|
6672
|
+
}
|
|
6673
|
+
}
|
|
6674
|
+
|
|
6675
|
+
#getDragTarget(child) {
|
|
6676
|
+
if (this.#handleSelector) {
|
|
6677
|
+
return child.querySelector(this.#handleSelector);
|
|
6678
|
+
}
|
|
6679
|
+
return child;
|
|
6680
|
+
}
|
|
6681
|
+
|
|
6682
|
+
#bindChild(child, target) {
|
|
6683
|
+
const onPointerDown = (event) => {
|
|
6684
|
+
if (this.#disabled || this.#getElementChildren().length < 2) return;
|
|
6685
|
+
if (event.button !== 0) return;
|
|
6686
|
+
if (
|
|
6687
|
+
!this.#handleSelector &&
|
|
6688
|
+
this.#isNestedDragTarget(event.target, child)
|
|
6689
|
+
) {
|
|
6690
|
+
return;
|
|
6691
|
+
}
|
|
6692
|
+
this.#startPendingDrag(event, child, target);
|
|
6693
|
+
};
|
|
6694
|
+
const originalTabIndex = target.getAttribute("tabindex");
|
|
6695
|
+
const originalAriaLabel = target.getAttribute("aria-label");
|
|
6696
|
+
const originalRole = target.getAttribute("role");
|
|
6697
|
+
const itemName =
|
|
6698
|
+
child.getAttribute("aria-label") ||
|
|
6699
|
+
child.textContent?.trim().replace(/\s+/g, " ").slice(0, 80) ||
|
|
6700
|
+
"item";
|
|
6701
|
+
target.setAttribute("tabindex", "0");
|
|
6702
|
+
target.setAttribute("aria-label", `Move ${itemName}`);
|
|
6703
|
+
if (target !== child && !target.hasAttribute("role")) {
|
|
6704
|
+
target.setAttribute("role", "button");
|
|
6705
|
+
}
|
|
6706
|
+
const onKeyDown = (event) => {
|
|
6707
|
+
if (this.#disabled) return;
|
|
6708
|
+
const horizontal = this.#axis === "horizontal";
|
|
6709
|
+
const previousKey = horizontal ? "ArrowLeft" : "ArrowUp";
|
|
6710
|
+
const nextKey = horizontal ? "ArrowRight" : "ArrowDown";
|
|
6711
|
+
if (
|
|
6712
|
+
event.key !== previousKey &&
|
|
6713
|
+
event.key !== nextKey &&
|
|
6714
|
+
event.key !== "Home" &&
|
|
6715
|
+
event.key !== "End"
|
|
6716
|
+
) {
|
|
6717
|
+
return;
|
|
6718
|
+
}
|
|
6719
|
+
event.preventDefault();
|
|
6720
|
+
event.stopPropagation();
|
|
6721
|
+
const items = this.#getElementChildren();
|
|
6722
|
+
const oldIndex = items.indexOf(child);
|
|
6723
|
+
let newIndex = oldIndex;
|
|
6724
|
+
if (event.key === previousKey) newIndex = Math.max(0, oldIndex - 1);
|
|
6725
|
+
else if (event.key === nextKey) {
|
|
6726
|
+
newIndex = Math.min(items.length - 1, oldIndex + 1);
|
|
6727
|
+
} else if (event.key === "Home") newIndex = 0;
|
|
6728
|
+
else if (event.key === "End") newIndex = items.length - 1;
|
|
6729
|
+
if (newIndex === oldIndex) {
|
|
6730
|
+
this.#announce(`${itemName}, position ${oldIndex + 1} of ${items.length}`);
|
|
6731
|
+
return;
|
|
6732
|
+
}
|
|
6733
|
+
this.#moveItemToFinalIndex(child, newIndex);
|
|
6734
|
+
this.#syncChildren();
|
|
6735
|
+
this.#getDragTarget(child)?.focus();
|
|
6736
|
+
this.dispatchEvent(
|
|
6737
|
+
new CustomEvent("reorder", {
|
|
6738
|
+
bubbles: true,
|
|
6739
|
+
detail: { oldIndex, newIndex, item: child },
|
|
6740
|
+
}),
|
|
6741
|
+
);
|
|
6742
|
+
this.#announce(`${itemName}, position ${newIndex + 1} of ${items.length}`);
|
|
6743
|
+
};
|
|
6744
|
+
|
|
6745
|
+
target.addEventListener("pointerdown", onPointerDown, true);
|
|
6746
|
+
target.addEventListener("keydown", onKeyDown);
|
|
6747
|
+
this.#bindings.set(child, {
|
|
6748
|
+
target,
|
|
6749
|
+
onPointerDown,
|
|
6750
|
+
onKeyDown,
|
|
6751
|
+
originalTabIndex,
|
|
6752
|
+
originalAriaLabel,
|
|
6753
|
+
originalRole,
|
|
6754
|
+
});
|
|
6755
|
+
}
|
|
6756
|
+
|
|
6757
|
+
#restoreKeyboardAttrs(binding) {
|
|
6758
|
+
const { target, originalTabIndex, originalAriaLabel, originalRole } = binding;
|
|
6759
|
+
if (originalTabIndex === null) target.removeAttribute("tabindex");
|
|
6760
|
+
else target.setAttribute("tabindex", originalTabIndex);
|
|
6761
|
+
if (originalAriaLabel === null) target.removeAttribute("aria-label");
|
|
6762
|
+
else target.setAttribute("aria-label", originalAriaLabel);
|
|
6763
|
+
if (originalRole === null) target.removeAttribute("role");
|
|
6764
|
+
else target.setAttribute("role", originalRole);
|
|
6765
|
+
}
|
|
6766
|
+
|
|
6767
|
+
#ensureLiveRegion() {
|
|
6768
|
+
if (this.#liveRegion?.isConnected) return;
|
|
6769
|
+
const region = document.createElement("span");
|
|
6770
|
+
region.setAttribute("data-reorder-live", "");
|
|
6771
|
+
region.setAttribute("role", "status");
|
|
6772
|
+
region.setAttribute("aria-live", "polite");
|
|
6773
|
+
region.setAttribute("aria-atomic", "true");
|
|
6774
|
+
Object.assign(region.style, {
|
|
6775
|
+
position: "absolute",
|
|
6776
|
+
width: "1px",
|
|
6777
|
+
height: "1px",
|
|
6778
|
+
padding: "0",
|
|
6779
|
+
margin: "-1px",
|
|
6780
|
+
overflow: "hidden",
|
|
6781
|
+
clip: "rect(0, 0, 0, 0)",
|
|
6782
|
+
whiteSpace: "nowrap",
|
|
6783
|
+
border: "0",
|
|
6784
|
+
});
|
|
6785
|
+
this.#liveRegion = region;
|
|
6786
|
+
document.body.appendChild(region);
|
|
6787
|
+
}
|
|
6788
|
+
|
|
6789
|
+
#announce(message) {
|
|
6790
|
+
if (!this.#liveRegion) return;
|
|
6791
|
+
this.#liveRegion.textContent = "";
|
|
6792
|
+
requestAnimationFrame(() => {
|
|
6793
|
+
if (this.#liveRegion) this.#liveRegion.textContent = message;
|
|
6794
|
+
});
|
|
6795
|
+
}
|
|
6796
|
+
|
|
6797
|
+
#isNestedDragTarget(target, child) {
|
|
6798
|
+
let node = target;
|
|
6799
|
+
while (node && node !== child) {
|
|
6800
|
+
if (node instanceof Element) {
|
|
6801
|
+
for (const selector of FigReorder.#NESTED_DRAG_SELECTORS) {
|
|
6802
|
+
if (node.matches(selector)) return true;
|
|
6803
|
+
}
|
|
6804
|
+
}
|
|
6805
|
+
node = node.parentElement;
|
|
6806
|
+
}
|
|
6807
|
+
return false;
|
|
6808
|
+
}
|
|
6809
|
+
|
|
6810
|
+
static #DRAGGING_BODY_CLASS = "fig-reorder-dragging";
|
|
6811
|
+
|
|
6812
|
+
static #setDocumentDragging(active) {
|
|
6813
|
+
document.body.classList.toggle(FigReorder.#DRAGGING_BODY_CLASS, active);
|
|
6814
|
+
}
|
|
6815
|
+
|
|
6816
|
+
#startPendingDrag(event, item, target) {
|
|
6817
|
+
this.#cancelDrag();
|
|
6818
|
+
|
|
6819
|
+
const state = {
|
|
6820
|
+
item,
|
|
6821
|
+
target,
|
|
6822
|
+
pointerId: event.pointerId,
|
|
6823
|
+
oldIndex: this.#getElementChildren().indexOf(item),
|
|
6824
|
+
targetIndex: this.#getElementChildren().indexOf(item),
|
|
6825
|
+
startX: event.clientX,
|
|
6826
|
+
startY: event.clientY,
|
|
6827
|
+
active: false,
|
|
6828
|
+
onMove: null,
|
|
6829
|
+
onUp: null,
|
|
6830
|
+
onKeyDown: null,
|
|
6831
|
+
};
|
|
6832
|
+
|
|
6833
|
+
state.onMove = (moveEvent) => {
|
|
6834
|
+
if (moveEvent.pointerId !== state.pointerId) return;
|
|
6835
|
+
|
|
6836
|
+
const dx = moveEvent.clientX - state.startX;
|
|
6837
|
+
const dy = moveEvent.clientY - state.startY;
|
|
6838
|
+
|
|
6839
|
+
if (!state.active) {
|
|
6840
|
+
if (dx * dx + dy * dy < FigReorder.#DRAG_THRESHOLD * FigReorder.#DRAG_THRESHOLD) {
|
|
6841
|
+
return;
|
|
6842
|
+
}
|
|
6843
|
+
state.active = true;
|
|
6844
|
+
event.preventDefault();
|
|
6845
|
+
event.stopPropagation();
|
|
6846
|
+
item.classList.add("dragging");
|
|
6847
|
+
FigReorder.#setDocumentDragging(true);
|
|
6848
|
+
try {
|
|
6849
|
+
target.setPointerCapture(state.pointerId);
|
|
6850
|
+
} catch {}
|
|
6851
|
+
}
|
|
6852
|
+
|
|
6853
|
+
moveEvent.preventDefault();
|
|
6854
|
+
const pointer =
|
|
6855
|
+
this.#axis === "horizontal" ? moveEvent.clientX : moveEvent.clientY;
|
|
6856
|
+
const index = this.#getInsertIndex(pointer);
|
|
6857
|
+
state.targetIndex = index;
|
|
6858
|
+
this.#updateIndicator(index, item);
|
|
6859
|
+
};
|
|
6860
|
+
|
|
6861
|
+
state.onKeyDown = (keyEvent) => {
|
|
6862
|
+
if (keyEvent.key !== "Escape" || !state.active) return;
|
|
6863
|
+
keyEvent.preventDefault();
|
|
6864
|
+
this.#finishDrag(state, true);
|
|
6865
|
+
};
|
|
6866
|
+
|
|
6867
|
+
state.onUp = (upEvent) => {
|
|
6868
|
+
if (upEvent.pointerId !== state.pointerId) return;
|
|
6869
|
+
this.#finishDrag(state, upEvent.type === "pointercancel");
|
|
6870
|
+
};
|
|
6871
|
+
|
|
6872
|
+
this.#drag = state;
|
|
6873
|
+
window.addEventListener("pointermove", state.onMove);
|
|
6874
|
+
window.addEventListener("pointerup", state.onUp);
|
|
6875
|
+
window.addEventListener("pointercancel", state.onUp);
|
|
6876
|
+
window.addEventListener("keydown", state.onKeyDown);
|
|
6877
|
+
}
|
|
6878
|
+
|
|
6879
|
+
#getInsertIndex(pointer) {
|
|
6880
|
+
const items = this.#getElementChildren();
|
|
6881
|
+
const horizontal = this.#axis === "horizontal";
|
|
6882
|
+
|
|
6883
|
+
for (let i = 0; i < items.length; i++) {
|
|
6884
|
+
const rect = items[i].getBoundingClientRect();
|
|
6885
|
+
const midpoint = horizontal
|
|
6886
|
+
? rect.left + rect.width / 2
|
|
6887
|
+
: rect.top + rect.height / 2;
|
|
6888
|
+
if (pointer < midpoint) return i;
|
|
6889
|
+
}
|
|
6890
|
+
|
|
6891
|
+
return items.length;
|
|
6892
|
+
}
|
|
6893
|
+
|
|
6894
|
+
#shouldShowIndicator(item, index) {
|
|
6895
|
+
const items = this.#getElementChildren();
|
|
6896
|
+
const currentIndex = items.indexOf(item);
|
|
6897
|
+
if (currentIndex === -1) return false;
|
|
6898
|
+
|
|
6899
|
+
const clamped = Math.max(0, Math.min(index, items.length));
|
|
6900
|
+
|
|
6901
|
+
if (index === currentIndex) return false;
|
|
6902
|
+
|
|
6903
|
+
// Hide only the redundant bottom line when the dragged item is already last.
|
|
6904
|
+
if (clamped >= items.length && currentIndex === items.length - 1) {
|
|
6905
|
+
return false;
|
|
6906
|
+
}
|
|
6907
|
+
|
|
6908
|
+
return true;
|
|
6909
|
+
}
|
|
6910
|
+
|
|
6911
|
+
#getReorderBounds(items) {
|
|
6912
|
+
if (!items.length) return null;
|
|
6913
|
+
|
|
6914
|
+
let left = Infinity;
|
|
6915
|
+
let right = -Infinity;
|
|
6916
|
+
let top = Infinity;
|
|
6917
|
+
let bottom = -Infinity;
|
|
6918
|
+
|
|
6919
|
+
for (const item of items) {
|
|
6920
|
+
const rect = item.getBoundingClientRect();
|
|
6921
|
+
left = Math.min(left, rect.left);
|
|
6922
|
+
right = Math.max(right, rect.right);
|
|
6923
|
+
top = Math.min(top, rect.top);
|
|
6924
|
+
bottom = Math.max(bottom, rect.bottom);
|
|
6925
|
+
}
|
|
6926
|
+
|
|
6927
|
+
return {
|
|
6928
|
+
left,
|
|
6929
|
+
top,
|
|
6930
|
+
width: right - left,
|
|
6931
|
+
height: bottom - top,
|
|
6932
|
+
};
|
|
6933
|
+
}
|
|
6934
|
+
|
|
6935
|
+
#getIndicatorRoot() {
|
|
6936
|
+
const topLayerSelectors = [":popover-open", ":modal", ":fullscreen"];
|
|
6937
|
+
let ancestor = this.parentElement;
|
|
6938
|
+
|
|
6939
|
+
while (ancestor) {
|
|
6940
|
+
const isTopLayer = topLayerSelectors.some((selector) => {
|
|
6941
|
+
try {
|
|
6942
|
+
return ancestor.matches(selector);
|
|
6943
|
+
} catch {
|
|
6944
|
+
return false;
|
|
6945
|
+
}
|
|
6946
|
+
});
|
|
6947
|
+
if (isTopLayer) return ancestor;
|
|
6948
|
+
ancestor = ancestor.parentElement;
|
|
6949
|
+
}
|
|
6950
|
+
|
|
6951
|
+
return document.body;
|
|
6952
|
+
}
|
|
6953
|
+
|
|
6954
|
+
#ensureIndicator() {
|
|
6955
|
+
if (this.#indicator) return this.#indicator;
|
|
6956
|
+
|
|
6957
|
+
const indicator = document.createElement("div");
|
|
6958
|
+
indicator.className = "fig-reorder-indicator";
|
|
6959
|
+
indicator.setAttribute("data-axis", this.#axis);
|
|
6960
|
+
indicator.classList.toggle(
|
|
6961
|
+
"fig-reorder-indicator-ring",
|
|
6962
|
+
this.#indicatorStyle === "ring",
|
|
6963
|
+
);
|
|
6964
|
+
this.#getIndicatorRoot().appendChild(indicator);
|
|
6965
|
+
this.#indicator = indicator;
|
|
6966
|
+
return indicator;
|
|
6967
|
+
}
|
|
6968
|
+
|
|
6969
|
+
#syncIndicatorStyle() {
|
|
6970
|
+
this.#indicator?.classList.toggle(
|
|
6971
|
+
"fig-reorder-indicator-ring",
|
|
6972
|
+
this.#indicatorStyle === "ring",
|
|
6973
|
+
);
|
|
6974
|
+
}
|
|
6975
|
+
|
|
6976
|
+
#updateIndicator(index, item) {
|
|
6977
|
+
const items = this.#getElementChildren();
|
|
6978
|
+
if (!this.#shouldShowIndicator(item, index)) {
|
|
6979
|
+
this.#removeIndicator();
|
|
6980
|
+
return;
|
|
6981
|
+
}
|
|
6982
|
+
|
|
6983
|
+
const bounds = this.#getReorderBounds(items);
|
|
6984
|
+
if (!bounds) {
|
|
6985
|
+
this.#removeIndicator();
|
|
6986
|
+
return;
|
|
6987
|
+
}
|
|
6988
|
+
|
|
6989
|
+
const indicator = this.#ensureIndicator();
|
|
6990
|
+
indicator.setAttribute("data-axis", this.#axis);
|
|
6991
|
+
|
|
6992
|
+
if (this.#axis === "horizontal") {
|
|
6993
|
+
let x;
|
|
6994
|
+
if (index <= 0) {
|
|
6995
|
+
x = items[0].getBoundingClientRect().left;
|
|
6996
|
+
} else if (index >= items.length) {
|
|
6997
|
+
x = items[items.length - 1].getBoundingClientRect().right;
|
|
6998
|
+
} else {
|
|
6999
|
+
x = items[index].getBoundingClientRect().left;
|
|
7000
|
+
}
|
|
7001
|
+
|
|
7002
|
+
indicator.style.left = `${x - 1}px`;
|
|
7003
|
+
indicator.style.top = `${bounds.top}px`;
|
|
7004
|
+
indicator.style.width = "2px";
|
|
7005
|
+
indicator.style.height = `${bounds.height}px`;
|
|
7006
|
+
return;
|
|
7007
|
+
}
|
|
7008
|
+
|
|
7009
|
+
let y;
|
|
7010
|
+
if (index <= 0) {
|
|
7011
|
+
y = items[0].getBoundingClientRect().top;
|
|
7012
|
+
} else if (index >= items.length) {
|
|
7013
|
+
y = items[items.length - 1].getBoundingClientRect().bottom;
|
|
7014
|
+
} else {
|
|
7015
|
+
y = items[index].getBoundingClientRect().top;
|
|
7016
|
+
}
|
|
7017
|
+
|
|
7018
|
+
indicator.style.left = `${bounds.left}px`;
|
|
7019
|
+
indicator.style.top = `${y - 1}px`;
|
|
7020
|
+
indicator.style.width = `${bounds.width}px`;
|
|
7021
|
+
indicator.style.height = "2px";
|
|
7022
|
+
}
|
|
7023
|
+
|
|
7024
|
+
#removeIndicator() {
|
|
7025
|
+
this.#indicator?.remove();
|
|
7026
|
+
this.#indicator = null;
|
|
7027
|
+
}
|
|
7028
|
+
|
|
7029
|
+
#moveItemToIndex(item, index) {
|
|
7030
|
+
const items = this.#getElementChildren();
|
|
7031
|
+
const clamped = Math.max(0, Math.min(index, items.length));
|
|
7032
|
+
|
|
7033
|
+
if (clamped >= items.length) {
|
|
7034
|
+
const last = items[items.length - 1];
|
|
7035
|
+
if (last && last !== item) last.after(item);
|
|
7036
|
+
return;
|
|
7037
|
+
}
|
|
7038
|
+
|
|
7039
|
+
const ref = items[clamped];
|
|
7040
|
+
if (ref !== item) this.insertBefore(item, ref);
|
|
7041
|
+
}
|
|
7042
|
+
|
|
7043
|
+
#moveItemToFinalIndex(item, newIndex) {
|
|
7044
|
+
const items = this.#getElementChildren().filter((candidate) => candidate !== item);
|
|
7045
|
+
const ref = items[newIndex] ?? null;
|
|
7046
|
+
if (ref) this.insertBefore(item, ref);
|
|
7047
|
+
else if (items.length) items[items.length - 1].after(item);
|
|
7048
|
+
else this.appendChild(item);
|
|
7049
|
+
}
|
|
7050
|
+
|
|
7051
|
+
#finishDrag(state, revert) {
|
|
7052
|
+
const { item, oldIndex, active, onMove, onUp, onKeyDown } = state;
|
|
7053
|
+
|
|
7054
|
+
window.removeEventListener("pointermove", onMove);
|
|
7055
|
+
window.removeEventListener("pointerup", onUp);
|
|
7056
|
+
window.removeEventListener("pointercancel", onUp);
|
|
7057
|
+
window.removeEventListener("keydown", onKeyDown);
|
|
7058
|
+
|
|
7059
|
+
if (active) {
|
|
7060
|
+
if (!revert) {
|
|
7061
|
+
this.#moveItemToIndex(item, state.targetIndex);
|
|
7062
|
+
const newIndex = this.#getElementChildren().indexOf(item);
|
|
7063
|
+
if (newIndex !== -1 && newIndex !== oldIndex) {
|
|
7064
|
+
this.dispatchEvent(
|
|
7065
|
+
new CustomEvent("reorder", {
|
|
7066
|
+
bubbles: true,
|
|
7067
|
+
detail: { oldIndex, newIndex, item },
|
|
7068
|
+
}),
|
|
7069
|
+
);
|
|
7070
|
+
const name =
|
|
7071
|
+
item.getAttribute("aria-label") ||
|
|
7072
|
+
item.textContent?.trim().replace(/\s+/g, " ").slice(0, 80) ||
|
|
7073
|
+
"item";
|
|
7074
|
+
this.#announce(
|
|
7075
|
+
`${name}, position ${newIndex + 1} of ${this.#getElementChildren().length}`,
|
|
7076
|
+
);
|
|
7077
|
+
}
|
|
7078
|
+
}
|
|
7079
|
+
}
|
|
7080
|
+
|
|
7081
|
+
item.classList.remove("dragging");
|
|
7082
|
+
FigReorder.#setDocumentDragging(false);
|
|
7083
|
+
this.#removeIndicator();
|
|
7084
|
+
if (this.#drag === state) this.#drag = null;
|
|
7085
|
+
}
|
|
7086
|
+
|
|
7087
|
+
#unbindAll() {
|
|
7088
|
+
for (const [, binding] of this.#bindings) {
|
|
7089
|
+
binding.target.removeEventListener(
|
|
7090
|
+
"pointerdown",
|
|
7091
|
+
binding.onPointerDown,
|
|
7092
|
+
true,
|
|
7093
|
+
);
|
|
7094
|
+
binding.target.removeEventListener("keydown", binding.onKeyDown);
|
|
7095
|
+
this.#restoreKeyboardAttrs(binding);
|
|
7096
|
+
}
|
|
7097
|
+
this.#bindings.clear();
|
|
7098
|
+
}
|
|
7099
|
+
|
|
7100
|
+
#cancelDrag() {
|
|
7101
|
+
if (!this.#drag) return;
|
|
7102
|
+
this.#finishDrag(this.#drag, true);
|
|
7103
|
+
}
|
|
7104
|
+
}
|
|
7105
|
+
|
|
7106
|
+
figEditorDefineElement("fig-reorder", FigReorder);
|