@rogieking/figui3 6.9.9 → 6.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components.css +34 -4
- package/dist/components.css +1 -1
- package/dist/fig-editor.css +1 -1
- package/dist/fig-editor.js +22 -18
- package/dist/fig-lab.css +1 -1
- package/dist/fig-lab.js +30 -32
- package/dist/fig.css +1 -1
- package/dist/fig.js +17 -17
- package/fig-editor.css +0 -12
- package/fig-editor.js +26 -22
- package/fig-lab.css +120 -46
- package/fig-lab.js +576 -55
- package/fig.js +20 -3
- package/package.json +1 -1
package/fig-lab.js
CHANGED
|
@@ -40,6 +40,12 @@ class FigFieldSlider extends HTMLElement {
|
|
|
40
40
|
"color",
|
|
41
41
|
"text",
|
|
42
42
|
"full",
|
|
43
|
+
"elastic",
|
|
44
|
+
"size",
|
|
45
|
+
"name",
|
|
46
|
+
"class",
|
|
47
|
+
"data-wave-index",
|
|
48
|
+
"data-active",
|
|
43
49
|
"data-elastic-dragging",
|
|
44
50
|
"style",
|
|
45
51
|
]);
|
|
@@ -326,6 +332,7 @@ class FigFieldSlider extends HTMLElement {
|
|
|
326
332
|
|
|
327
333
|
#handleElasticPointerDown(event) {
|
|
328
334
|
if (event.button !== 0 || this.hasAttribute("disabled")) return;
|
|
335
|
+
if (this.getAttribute("elastic") === "false") return;
|
|
329
336
|
if (event.target?.closest?.("fig-input-number")) return;
|
|
330
337
|
const rangeInput =
|
|
331
338
|
this.#slider?.querySelector('input[type="range"]') ?? this.#rangeInput;
|
|
@@ -498,7 +505,6 @@ class FigFieldSlider extends HTMLElement {
|
|
|
498
505
|
this.removeAttribute("data-elastic-dragging");
|
|
499
506
|
this.style.removeProperty("--fig-field-slider-elastic-size");
|
|
500
507
|
this.style.removeProperty("--fig-field-slider-elastic-scale");
|
|
501
|
-
this.style.removeProperty("--fig-field-slider-elastic-origin");
|
|
502
508
|
}
|
|
503
509
|
|
|
504
510
|
#valueFromPointer(event) {
|
|
@@ -594,7 +600,7 @@ class FigFieldSlider extends HTMLElement {
|
|
|
594
600
|
}
|
|
595
601
|
|
|
596
602
|
#forwardSliderEvent(type, event) {
|
|
597
|
-
event.
|
|
603
|
+
event.stopImmediatePropagation();
|
|
598
604
|
if (type === "change") {
|
|
599
605
|
this.#resetElasticPull();
|
|
600
606
|
}
|
|
@@ -1833,11 +1839,13 @@ class FigInputOscillator extends HTMLElement {
|
|
|
1833
1839
|
#bounds = null;
|
|
1834
1840
|
#handleAmplitude = null;
|
|
1835
1841
|
#handleFrequency = null;
|
|
1836
|
-
#addTypeControl = null;
|
|
1837
1842
|
#fields = [];
|
|
1838
1843
|
#typeControls = [];
|
|
1839
1844
|
#waveRows = [];
|
|
1845
|
+
#waveGroups = [];
|
|
1846
|
+
#expandedWaveIndices = new Set();
|
|
1840
1847
|
#resizeObserver = null;
|
|
1848
|
+
#activeFieldInput = null;
|
|
1841
1849
|
|
|
1842
1850
|
static TYPES = [
|
|
1843
1851
|
{ name: "Wave", value: "sine" },
|
|
@@ -2090,8 +2098,8 @@ class FigInputOscillator extends HTMLElement {
|
|
|
2090
2098
|
<line class="fig-input-oscillator-baseline"></line>
|
|
2091
2099
|
<path class="fig-input-oscillator-path"></path>
|
|
2092
2100
|
<circle class="fig-input-oscillator-playhead"></circle>
|
|
2093
|
-
<foreignObject class="fig-input-oscillator-handle fig-input-oscillator-amplitude-handle" data-handle="amplitude" width="20" height="20"><div class="fig-input-oscillator-handle-inner"><fig-tooltip text="Amplitude"><fig-handle
|
|
2094
|
-
<foreignObject class="fig-input-oscillator-handle fig-input-oscillator-frequency-handle" data-handle="frequency" width="20" height="20"><div class="fig-input-oscillator-handle-inner"><fig-tooltip text="Frequency"><fig-handle
|
|
2101
|
+
<foreignObject class="fig-input-oscillator-handle fig-input-oscillator-amplitude-handle" data-handle="amplitude" width="20" height="20"><div class="fig-input-oscillator-handle-inner"><fig-tooltip text="Amplitude"><fig-handle size="small" aria-label="Oscillator amplitude handle"${disabled}></fig-handle></fig-tooltip></div></foreignObject>
|
|
2102
|
+
<foreignObject class="fig-input-oscillator-handle fig-input-oscillator-frequency-handle" data-handle="frequency" width="20" height="20"><div class="fig-input-oscillator-handle-inner"><fig-tooltip text="Frequency"><fig-handle size="small" aria-label="Oscillator frequency handle"${disabled}></fig-handle></fig-tooltip></div></foreignObject>
|
|
2095
2103
|
</svg>
|
|
2096
2104
|
</div>
|
|
2097
2105
|
${this.#isEditEnabled() ? this.#getWaveControlsHTML(disabled) : ""}`;
|
|
@@ -2100,12 +2108,6 @@ class FigInputOscillator extends HTMLElement {
|
|
|
2100
2108
|
#getWaveControlsHTML(disabled) {
|
|
2101
2109
|
return `<div class="fig-input-oscillator-waves">
|
|
2102
2110
|
${this.#waves.map((wave, index) => this.#getWaveRowHTML(wave, index, disabled)).join("")}
|
|
2103
|
-
<hstack class="fig-input-oscillator-add-wave">
|
|
2104
|
-
<fig-button class="fig-input-oscillator-add-type-button" type="select" variant="ghost" icon aria-label="Choose waveform type"${disabled}>
|
|
2105
|
-
<fig-icon name="settings"></fig-icon>
|
|
2106
|
-
${this.#getWaveTypeDropdownHTML("fig-input-oscillator-add-type", "sine", disabled)}
|
|
2107
|
-
</fig-button>
|
|
2108
|
-
</hstack>
|
|
2109
2111
|
</div>`;
|
|
2110
2112
|
}
|
|
2111
2113
|
|
|
@@ -2113,18 +2115,27 @@ class FigInputOscillator extends HTMLElement {
|
|
|
2113
2115
|
const removeDisabled = disabled || this.#waves.length <= 1 ? " disabled" : "";
|
|
2114
2116
|
const active = index === this.#activeWaveIndex ? " data-active" : "";
|
|
2115
2117
|
const label = FigInputOscillator.#labelForType(wave.type);
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
<
|
|
2120
|
-
|
|
2118
|
+
const open = this.#expandedWaveIndices.has(index) ? ' open="true"' : ' open="false"';
|
|
2119
|
+
return `<fig-group class="fig-input-oscillator-wave" collapsible borderless compact="true"${open} data-wave-index="${index}">
|
|
2120
|
+
<fig-header borderless>
|
|
2121
|
+
<h3>${label}</h3>
|
|
2122
|
+
<fig-tooltip text="Remove form">
|
|
2123
|
+
<fig-button class="fig-input-oscillator-remove-button" variant="ghost" icon data-wave-index="${index}" aria-label="Remove form"${removeDisabled}><fig-icon name="minus"></fig-icon></fig-button>
|
|
2124
|
+
</fig-tooltip>
|
|
2125
|
+
<fig-tooltip text="Add form">
|
|
2126
|
+
<fig-button class="fig-input-oscillator-add-type-button" type="select" variant="ghost" icon data-wave-index="${index}" aria-label="Add form"${disabled}>
|
|
2127
|
+
<fig-icon name="add"></fig-icon>
|
|
2128
|
+
${this.#getWaveTypeDropdownHTML("fig-input-oscillator-add-type", "sine", disabled, index)}
|
|
2129
|
+
</fig-button>
|
|
2130
|
+
</fig-tooltip>
|
|
2131
|
+
</fig-header>
|
|
2121
2132
|
<div class="fig-input-oscillator-fields" data-wave-index="${index}"${active}>
|
|
2122
2133
|
${this.#getNumberFieldHTML(index, "frequency", "Frequency", 0.1, 16, 0.1, "")}
|
|
2123
2134
|
${this.#getNumberFieldHTML(index, "amplitude", "Amplitude", -4, 4, 0.1, "")}
|
|
2124
2135
|
${this.#getNumberFieldHTML(index, "phase", "Phase", -360, 360, 1, "°")}
|
|
2125
2136
|
${this.#getNumberFieldHTML(index, "offset", "Offset", -4, 4, 0.1, "")}
|
|
2126
2137
|
</div>
|
|
2127
|
-
</
|
|
2138
|
+
</fig-group>`;
|
|
2128
2139
|
}
|
|
2129
2140
|
|
|
2130
2141
|
#getWaveTypeDropdownHTML(className, value, disabled, index = null) {
|
|
@@ -2135,9 +2146,9 @@ class FigInputOscillator extends HTMLElement {
|
|
|
2135
2146
|
<label>${type.name}</label>
|
|
2136
2147
|
</option>`;
|
|
2137
2148
|
}).join("");
|
|
2138
|
-
const indexAttr =
|
|
2139
|
-
|
|
2140
|
-
return `<fig-dropdown class="${className}" value="${value}" experimental="modern"${
|
|
2149
|
+
const indexAttr =
|
|
2150
|
+
index === null ? "" : ` data-wave-index="${FigInputOscillator.#escapeAttribute(String(index))}"`;
|
|
2151
|
+
return `<fig-dropdown class="${className}" value="${value}" experimental="modern" type="dropdown" label="Add form"${indexAttr}${disabled}>${options}</fig-dropdown>`;
|
|
2141
2152
|
}
|
|
2142
2153
|
|
|
2143
2154
|
#getNumberFieldHTML(index, name, label, min, max, step, units) {
|
|
@@ -2146,11 +2157,7 @@ class FigInputOscillator extends HTMLElement {
|
|
|
2146
2157
|
? ` units="${FigInputOscillator.#escapeAttribute(units)}"`
|
|
2147
2158
|
: "";
|
|
2148
2159
|
const wave = this.#waves[index] || FigInputOscillator.#defaultWave();
|
|
2149
|
-
return `<fig-field class="fig-input-oscillator-field" direction="horizontal" data-wave-index="${index}"
|
|
2150
|
-
<label>${label}</label>
|
|
2151
|
-
<fig-slider name="${name}" data-wave-index="${index}" value="${this.#round(wave[name])}" min="${min}" max="${max}" step="${step}" precision="${this.#precision}" text="true"${unitsAttr}${disabled}></fig-slider>
|
|
2152
|
-
</fig-field>
|
|
2153
|
-
`;
|
|
2160
|
+
return `<fig-field-slider class="fig-input-oscillator-field" label="${label}" direction="horizontal" name="${name}" data-wave-index="${index}" value="${this.#round(wave[name])}" min="${min}" max="${max}" step="${step}" precision="${this.#precision}" elastic="false"${unitsAttr}${disabled}></fig-field-slider>`;
|
|
2154
2161
|
}
|
|
2155
2162
|
|
|
2156
2163
|
#cacheRefs() {
|
|
@@ -2161,16 +2168,36 @@ class FigInputOscillator extends HTMLElement {
|
|
|
2161
2168
|
this.#bounds = this.querySelector(".fig-input-oscillator-bounds");
|
|
2162
2169
|
this.#handleAmplitude = this.querySelector('[data-handle="amplitude"]');
|
|
2163
2170
|
this.#handleFrequency = this.querySelector('[data-handle="frequency"]');
|
|
2164
|
-
this.#addTypeControl = this.querySelector(".fig-input-oscillator-add-type");
|
|
2165
2171
|
this.#typeControls = Array.from(
|
|
2166
2172
|
this.querySelectorAll(".fig-input-oscillator-wave-type"),
|
|
2167
2173
|
);
|
|
2168
|
-
this.#fields = Array.from(this.querySelectorAll("fig-slider[name]"));
|
|
2169
|
-
this.#
|
|
2170
|
-
|
|
2174
|
+
this.#fields = Array.from(this.querySelectorAll("fig-field-slider[name]"));
|
|
2175
|
+
this.#waveGroups = Array.from(
|
|
2176
|
+
this.querySelectorAll("fig-group.fig-input-oscillator-wave"),
|
|
2177
|
+
);
|
|
2178
|
+
this.#waveRows = Array.from(
|
|
2179
|
+
this.querySelectorAll(".fig-input-oscillator-fields"),
|
|
2171
2180
|
);
|
|
2172
2181
|
}
|
|
2173
2182
|
|
|
2183
|
+
#reindexExpandedWaves(removedIndex) {
|
|
2184
|
+
const nextExpanded = new Set();
|
|
2185
|
+
for (const index of this.#expandedWaveIndices) {
|
|
2186
|
+
if (index < removedIndex) nextExpanded.add(index);
|
|
2187
|
+
else if (index > removedIndex) nextExpanded.add(index - 1);
|
|
2188
|
+
}
|
|
2189
|
+
this.#expandedWaveIndices = nextExpanded;
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
#reindexExpandedWavesAfterInsert(insertIndex) {
|
|
2193
|
+
const nextExpanded = new Set();
|
|
2194
|
+
for (const index of this.#expandedWaveIndices) {
|
|
2195
|
+
nextExpanded.add(index >= insertIndex ? index + 1 : index);
|
|
2196
|
+
}
|
|
2197
|
+
nextExpanded.add(insertIndex);
|
|
2198
|
+
this.#expandedWaveIndices = nextExpanded;
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2174
2201
|
#setupResizeObserver() {
|
|
2175
2202
|
if (this.#resizeObserver || !window.ResizeObserver) return;
|
|
2176
2203
|
this.#resizeObserver = new ResizeObserver(() => {
|
|
@@ -2344,12 +2371,17 @@ class FigInputOscillator extends HTMLElement {
|
|
|
2344
2371
|
for (const field of this.#fields) {
|
|
2345
2372
|
field.addEventListener("input", (event) => {
|
|
2346
2373
|
event.stopPropagation();
|
|
2347
|
-
this.#
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2374
|
+
this.#activeFieldInput = field;
|
|
2375
|
+
try {
|
|
2376
|
+
this.#applyFieldValue(
|
|
2377
|
+
this.#indexFromElement(field),
|
|
2378
|
+
field.getAttribute("name"),
|
|
2379
|
+
event.detail ?? event.currentTarget?.value ?? event.target?.value,
|
|
2380
|
+
"input",
|
|
2381
|
+
);
|
|
2382
|
+
} finally {
|
|
2383
|
+
this.#activeFieldInput = null;
|
|
2384
|
+
}
|
|
2353
2385
|
});
|
|
2354
2386
|
field.addEventListener("change", (event) => {
|
|
2355
2387
|
event.stopPropagation();
|
|
@@ -2362,31 +2394,64 @@ class FigInputOscillator extends HTMLElement {
|
|
|
2362
2394
|
});
|
|
2363
2395
|
}
|
|
2364
2396
|
|
|
2365
|
-
for (const
|
|
2366
|
-
|
|
2367
|
-
this.#setActiveWave(this.#indexFromElement(
|
|
2397
|
+
for (const group of this.#waveGroups) {
|
|
2398
|
+
group.addEventListener("pointerdown", () => {
|
|
2399
|
+
this.#setActiveWave(this.#indexFromElement(group));
|
|
2368
2400
|
});
|
|
2369
|
-
|
|
2370
|
-
this.#setActiveWave(this.#indexFromElement(
|
|
2401
|
+
group.addEventListener("focusin", () => {
|
|
2402
|
+
this.#setActiveWave(this.#indexFromElement(group));
|
|
2403
|
+
});
|
|
2404
|
+
group.addEventListener("openchange", (event) => {
|
|
2405
|
+
const index = this.#indexFromElement(group);
|
|
2406
|
+
if (event.detail?.open) {
|
|
2407
|
+
this.#expandedWaveIndices.add(index);
|
|
2408
|
+
} else {
|
|
2409
|
+
this.#expandedWaveIndices.delete(index);
|
|
2410
|
+
}
|
|
2371
2411
|
});
|
|
2372
2412
|
}
|
|
2373
2413
|
|
|
2374
|
-
this
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2414
|
+
for (const control of this.querySelectorAll(".fig-input-oscillator-add-type")) {
|
|
2415
|
+
const stopHeaderToggle = (event) => {
|
|
2416
|
+
event.stopPropagation();
|
|
2417
|
+
};
|
|
2418
|
+
control.addEventListener("pointerdown", stopHeaderToggle);
|
|
2419
|
+
control.addEventListener("click", stopHeaderToggle);
|
|
2420
|
+
control.addEventListener("change", (event) => {
|
|
2421
|
+
if (event.target !== control) return;
|
|
2422
|
+
if (this.#isDisabled()) return;
|
|
2423
|
+
const insertAfter = this.#indexFromElement(control);
|
|
2424
|
+
const insertIndex = insertAfter + 1;
|
|
2425
|
+
const type = this.#normalizeType(event.detail ?? control.value);
|
|
2426
|
+
this.#waves.splice(insertIndex, 0, FigInputOscillator.#defaultWave(type));
|
|
2427
|
+
this.#reindexExpandedWavesAfterInsert(insertIndex);
|
|
2428
|
+
this.#activeWaveIndex = insertIndex;
|
|
2429
|
+
this.#render();
|
|
2430
|
+
this.#emit("input");
|
|
2431
|
+
this.#emit("change");
|
|
2432
|
+
});
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
for (const button of this.querySelectorAll(".fig-input-oscillator-add-type-button")) {
|
|
2436
|
+
const stopHeaderToggle = (event) => {
|
|
2437
|
+
event.stopPropagation();
|
|
2438
|
+
};
|
|
2439
|
+
button.addEventListener("pointerdown", stopHeaderToggle);
|
|
2440
|
+
button.addEventListener("click", stopHeaderToggle);
|
|
2441
|
+
button.closest("fig-tooltip")?.addEventListener("pointerdown", stopHeaderToggle);
|
|
2442
|
+
button.closest("fig-tooltip")?.addEventListener("click", stopHeaderToggle);
|
|
2443
|
+
}
|
|
2384
2444
|
|
|
2385
2445
|
for (const button of this.querySelectorAll(".fig-input-oscillator-remove-button")) {
|
|
2386
|
-
button.addEventListener("
|
|
2446
|
+
button.addEventListener("pointerdown", (event) => {
|
|
2447
|
+
event.stopPropagation();
|
|
2448
|
+
});
|
|
2449
|
+
button.addEventListener("click", (event) => {
|
|
2450
|
+
event.stopPropagation();
|
|
2387
2451
|
if (this.#isDisabled() || this.#waves.length <= 1) return;
|
|
2388
2452
|
const index = this.#indexFromElement(button);
|
|
2389
2453
|
this.#waves.splice(index, 1);
|
|
2454
|
+
this.#reindexExpandedWaves(index);
|
|
2390
2455
|
this.#activeWaveIndex = Math.min(this.#activeWaveIndex, this.#waves.length - 1);
|
|
2391
2456
|
this.#render();
|
|
2392
2457
|
this.#emit("input");
|
|
@@ -2472,7 +2537,7 @@ class FigInputOscillator extends HTMLElement {
|
|
|
2472
2537
|
...this.#waves[index],
|
|
2473
2538
|
[name]: next,
|
|
2474
2539
|
});
|
|
2475
|
-
this.#syncUI();
|
|
2540
|
+
this.#syncUI({ skipFieldSync: eventType == "input" });
|
|
2476
2541
|
this.#emit(eventType);
|
|
2477
2542
|
}
|
|
2478
2543
|
|
|
@@ -2524,6 +2589,13 @@ class FigInputOscillator extends HTMLElement {
|
|
|
2524
2589
|
|
|
2525
2590
|
#startDrag(event, type) {
|
|
2526
2591
|
this.#isDragging = type;
|
|
2592
|
+
this.#svg?.classList.add("dragging");
|
|
2593
|
+
const dragCursor =
|
|
2594
|
+
type === "frequency" ? "ew-resize" : type === "amplitude" ? "ns-resize" : "";
|
|
2595
|
+
const prevBodyCursor = document.body.style.cursor;
|
|
2596
|
+
if (dragCursor) {
|
|
2597
|
+
document.body.style.cursor = dragCursor;
|
|
2598
|
+
}
|
|
2527
2599
|
|
|
2528
2600
|
const onMove = (moveEvent) => {
|
|
2529
2601
|
if (!this.#isDragging) return;
|
|
@@ -2558,6 +2630,10 @@ class FigInputOscillator extends HTMLElement {
|
|
|
2558
2630
|
|
|
2559
2631
|
const onUp = () => {
|
|
2560
2632
|
this.#isDragging = null;
|
|
2633
|
+
this.#svg?.classList.remove("dragging");
|
|
2634
|
+
if (dragCursor) {
|
|
2635
|
+
document.body.style.cursor = prevBodyCursor;
|
|
2636
|
+
}
|
|
2561
2637
|
document.removeEventListener("pointermove", onMove);
|
|
2562
2638
|
document.removeEventListener("pointerup", onUp);
|
|
2563
2639
|
this.#emit("change");
|
|
@@ -2584,9 +2660,9 @@ class FigInputOscillator extends HTMLElement {
|
|
|
2584
2660
|
);
|
|
2585
2661
|
}
|
|
2586
2662
|
|
|
2587
|
-
#syncUI() {
|
|
2663
|
+
#syncUI({ skipFieldSync = false } = {}) {
|
|
2588
2664
|
this.#syncTypeControls();
|
|
2589
|
-
this.#syncFields();
|
|
2665
|
+
if (!skipFieldSync) this.#syncFields();
|
|
2590
2666
|
this.#syncActiveWave();
|
|
2591
2667
|
this.#updateWaveform();
|
|
2592
2668
|
}
|
|
@@ -2600,9 +2676,16 @@ class FigInputOscillator extends HTMLElement {
|
|
|
2600
2676
|
|
|
2601
2677
|
#syncFields() {
|
|
2602
2678
|
for (const field of this.#fields) {
|
|
2679
|
+
if (field === this.#activeFieldInput) continue;
|
|
2603
2680
|
const index = this.#indexFromElement(field);
|
|
2604
2681
|
const name = field.getAttribute("name");
|
|
2605
|
-
|
|
2682
|
+
const next = this.#round(this.#waves[index]?.[name] ?? 0);
|
|
2683
|
+
const slider = field.querySelector("fig-slider");
|
|
2684
|
+
if (slider) {
|
|
2685
|
+
slider.value = next;
|
|
2686
|
+
} else {
|
|
2687
|
+
field.setAttribute("value", String(next));
|
|
2688
|
+
}
|
|
2606
2689
|
}
|
|
2607
2690
|
}
|
|
2608
2691
|
|
|
@@ -3149,3 +3232,441 @@ class FigInputAngle extends HTMLElement {
|
|
|
3149
3232
|
}
|
|
3150
3233
|
customElements.define("fig-input-angle", FigInputAngle);
|
|
3151
3234
|
|
|
3235
|
+
/* Reorder wrapper */
|
|
3236
|
+
class FigReorder extends HTMLElement {
|
|
3237
|
+
static observedAttributes = ["axis", "handle", "disabled"];
|
|
3238
|
+
|
|
3239
|
+
static #DRAG_THRESHOLD = 6;
|
|
3240
|
+
|
|
3241
|
+
static #INTERACTIVE_SELECTORS = [
|
|
3242
|
+
"input",
|
|
3243
|
+
"button",
|
|
3244
|
+
"select",
|
|
3245
|
+
"textarea",
|
|
3246
|
+
"a[href]",
|
|
3247
|
+
"label",
|
|
3248
|
+
"summary",
|
|
3249
|
+
"fig-button",
|
|
3250
|
+
"fig-dropdown",
|
|
3251
|
+
"fig-slider",
|
|
3252
|
+
"fig-input-number",
|
|
3253
|
+
"fig-input-text",
|
|
3254
|
+
"fig-field-slider",
|
|
3255
|
+
"fig-checkbox",
|
|
3256
|
+
"fig-switch",
|
|
3257
|
+
"fig-combo-input",
|
|
3258
|
+
"fig-segmented-control",
|
|
3259
|
+
"fig-input-color",
|
|
3260
|
+
"fig-input-fill",
|
|
3261
|
+
'[contenteditable="true"]',
|
|
3262
|
+
];
|
|
3263
|
+
|
|
3264
|
+
#childObserver = null;
|
|
3265
|
+
#bindings = new Map();
|
|
3266
|
+
#drag = null;
|
|
3267
|
+
#indicator = null;
|
|
3268
|
+
|
|
3269
|
+
connectedCallback() {
|
|
3270
|
+
this.style.display = "contents";
|
|
3271
|
+
this.#syncChildren();
|
|
3272
|
+
this.#childObserver = new MutationObserver(() => this.#syncChildren());
|
|
3273
|
+
this.#childObserver.observe(this, { childList: true });
|
|
3274
|
+
}
|
|
3275
|
+
|
|
3276
|
+
disconnectedCallback() {
|
|
3277
|
+
this.#childObserver?.disconnect();
|
|
3278
|
+
this.#childObserver = null;
|
|
3279
|
+
this.#unbindAll();
|
|
3280
|
+
this.#cancelDrag();
|
|
3281
|
+
this.#removeIndicator();
|
|
3282
|
+
}
|
|
3283
|
+
|
|
3284
|
+
attributeChangedCallback() {
|
|
3285
|
+
if (this.isConnected) this.#syncChildren();
|
|
3286
|
+
}
|
|
3287
|
+
|
|
3288
|
+
get #disabled() {
|
|
3289
|
+
return (
|
|
3290
|
+
this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false"
|
|
3291
|
+
);
|
|
3292
|
+
}
|
|
3293
|
+
|
|
3294
|
+
get #axis() {
|
|
3295
|
+
const value = (this.getAttribute("axis") || "vertical").trim().toLowerCase();
|
|
3296
|
+
return value === "horizontal" ? "horizontal" : "vertical";
|
|
3297
|
+
}
|
|
3298
|
+
|
|
3299
|
+
get #handleSelector() {
|
|
3300
|
+
return (this.getAttribute("handle") || "").trim();
|
|
3301
|
+
}
|
|
3302
|
+
|
|
3303
|
+
#getElementChildren() {
|
|
3304
|
+
return [...this.children].filter((node) => node.nodeType === Node.ELEMENT_NODE);
|
|
3305
|
+
}
|
|
3306
|
+
|
|
3307
|
+
#syncChildren() {
|
|
3308
|
+
const children = this.#getElementChildren();
|
|
3309
|
+
const childSet = new Set(children);
|
|
3310
|
+
|
|
3311
|
+
for (const [child, binding] of this.#bindings) {
|
|
3312
|
+
if (!childSet.has(child)) {
|
|
3313
|
+
binding.target.removeEventListener(
|
|
3314
|
+
"pointerdown",
|
|
3315
|
+
binding.onPointerDown,
|
|
3316
|
+
true,
|
|
3317
|
+
);
|
|
3318
|
+
this.#bindings.delete(child);
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
|
|
3322
|
+
this.#clearHandleMarks(children);
|
|
3323
|
+
this.#clearReorderItemMarks(children);
|
|
3324
|
+
|
|
3325
|
+
if (this.#disabled || children.length < 2) {
|
|
3326
|
+
this.#unbindAll();
|
|
3327
|
+
return;
|
|
3328
|
+
}
|
|
3329
|
+
|
|
3330
|
+
for (const child of children) {
|
|
3331
|
+
const target = this.#getDragTarget(child);
|
|
3332
|
+
const existing = this.#bindings.get(child);
|
|
3333
|
+
if (existing && existing.target === target) continue;
|
|
3334
|
+
if (existing) {
|
|
3335
|
+
existing.target.removeEventListener(
|
|
3336
|
+
"pointerdown",
|
|
3337
|
+
existing.onPointerDown,
|
|
3338
|
+
true,
|
|
3339
|
+
);
|
|
3340
|
+
this.#bindings.delete(child);
|
|
3341
|
+
}
|
|
3342
|
+
if (!target) continue;
|
|
3343
|
+
this.#bindChild(child, target);
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3346
|
+
this.#markHandles(children);
|
|
3347
|
+
this.#markReorderItems(children);
|
|
3348
|
+
}
|
|
3349
|
+
|
|
3350
|
+
#clearReorderItemMarks(children) {
|
|
3351
|
+
for (const child of children) {
|
|
3352
|
+
child.removeAttribute("data-reorder-item");
|
|
3353
|
+
}
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3356
|
+
#markReorderItems(children) {
|
|
3357
|
+
for (const child of children) {
|
|
3358
|
+
child.setAttribute("data-reorder-item", "");
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
|
|
3362
|
+
#clearHandleMarks(children) {
|
|
3363
|
+
for (const child of children) {
|
|
3364
|
+
child
|
|
3365
|
+
.querySelectorAll("[data-reorder-handle]")
|
|
3366
|
+
.forEach((node) => node.removeAttribute("data-reorder-handle"));
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
|
|
3370
|
+
#markHandles(children) {
|
|
3371
|
+
if (!this.#handleSelector) return;
|
|
3372
|
+
for (const child of children) {
|
|
3373
|
+
const handle = child.querySelector(this.#handleSelector);
|
|
3374
|
+
if (handle) handle.setAttribute("data-reorder-handle", "");
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3377
|
+
|
|
3378
|
+
#getDragTarget(child) {
|
|
3379
|
+
if (this.#handleSelector) {
|
|
3380
|
+
return child.querySelector(this.#handleSelector);
|
|
3381
|
+
}
|
|
3382
|
+
return child;
|
|
3383
|
+
}
|
|
3384
|
+
|
|
3385
|
+
#bindChild(child, target) {
|
|
3386
|
+
const onPointerDown = (event) => {
|
|
3387
|
+
if (this.#disabled || this.#getElementChildren().length < 2) return;
|
|
3388
|
+
if (event.button !== 0) return;
|
|
3389
|
+
if (
|
|
3390
|
+
!this.#handleSelector &&
|
|
3391
|
+
this.#isInteractiveTarget(event.target, child)
|
|
3392
|
+
) {
|
|
3393
|
+
return;
|
|
3394
|
+
}
|
|
3395
|
+
this.#startPendingDrag(event, child, target);
|
|
3396
|
+
};
|
|
3397
|
+
|
|
3398
|
+
target.addEventListener("pointerdown", onPointerDown, true);
|
|
3399
|
+
this.#bindings.set(child, { target, onPointerDown });
|
|
3400
|
+
}
|
|
3401
|
+
|
|
3402
|
+
#isInteractiveTarget(target, child) {
|
|
3403
|
+
let node = target;
|
|
3404
|
+
while (node && node !== child) {
|
|
3405
|
+
if (node instanceof Element) {
|
|
3406
|
+
for (const selector of FigReorder.#INTERACTIVE_SELECTORS) {
|
|
3407
|
+
if (node.matches(selector)) return true;
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
node = node.parentElement;
|
|
3411
|
+
}
|
|
3412
|
+
return false;
|
|
3413
|
+
}
|
|
3414
|
+
|
|
3415
|
+
static #DRAGGING_BODY_CLASS = "fig-reorder-dragging";
|
|
3416
|
+
|
|
3417
|
+
static #setDocumentDragging(active) {
|
|
3418
|
+
document.body.classList.toggle(FigReorder.#DRAGGING_BODY_CLASS, active);
|
|
3419
|
+
}
|
|
3420
|
+
|
|
3421
|
+
#startPendingDrag(event, item, target) {
|
|
3422
|
+
this.#cancelDrag();
|
|
3423
|
+
|
|
3424
|
+
const state = {
|
|
3425
|
+
item,
|
|
3426
|
+
target,
|
|
3427
|
+
pointerId: event.pointerId,
|
|
3428
|
+
oldIndex: this.#getElementChildren().indexOf(item),
|
|
3429
|
+
startX: event.clientX,
|
|
3430
|
+
startY: event.clientY,
|
|
3431
|
+
active: false,
|
|
3432
|
+
onMove: null,
|
|
3433
|
+
onUp: null,
|
|
3434
|
+
onKeyDown: null,
|
|
3435
|
+
};
|
|
3436
|
+
|
|
3437
|
+
state.onMove = (moveEvent) => {
|
|
3438
|
+
if (moveEvent.pointerId !== state.pointerId) return;
|
|
3439
|
+
|
|
3440
|
+
const dx = moveEvent.clientX - state.startX;
|
|
3441
|
+
const dy = moveEvent.clientY - state.startY;
|
|
3442
|
+
|
|
3443
|
+
if (!state.active) {
|
|
3444
|
+
if (dx * dx + dy * dy < FigReorder.#DRAG_THRESHOLD * FigReorder.#DRAG_THRESHOLD) {
|
|
3445
|
+
return;
|
|
3446
|
+
}
|
|
3447
|
+
state.active = true;
|
|
3448
|
+
event.preventDefault();
|
|
3449
|
+
event.stopPropagation();
|
|
3450
|
+
item.classList.add("dragging");
|
|
3451
|
+
FigReorder.#setDocumentDragging(true);
|
|
3452
|
+
try {
|
|
3453
|
+
target.setPointerCapture(state.pointerId);
|
|
3454
|
+
} catch {}
|
|
3455
|
+
}
|
|
3456
|
+
|
|
3457
|
+
moveEvent.preventDefault();
|
|
3458
|
+
const pointer =
|
|
3459
|
+
this.#axis === "horizontal" ? moveEvent.clientX : moveEvent.clientY;
|
|
3460
|
+
const index = this.#getInsertIndex(pointer);
|
|
3461
|
+
this.#moveItemToIndex(item, index);
|
|
3462
|
+
this.#updateIndicator(index, item);
|
|
3463
|
+
};
|
|
3464
|
+
|
|
3465
|
+
state.onKeyDown = (keyEvent) => {
|
|
3466
|
+
if (keyEvent.key !== "Escape" || !state.active) return;
|
|
3467
|
+
keyEvent.preventDefault();
|
|
3468
|
+
this.#finishDrag(state, true);
|
|
3469
|
+
};
|
|
3470
|
+
|
|
3471
|
+
state.onUp = (upEvent) => {
|
|
3472
|
+
if (upEvent.pointerId !== state.pointerId) return;
|
|
3473
|
+
this.#finishDrag(state, false);
|
|
3474
|
+
};
|
|
3475
|
+
|
|
3476
|
+
this.#drag = state;
|
|
3477
|
+
window.addEventListener("pointermove", state.onMove);
|
|
3478
|
+
window.addEventListener("pointerup", state.onUp);
|
|
3479
|
+
window.addEventListener("pointercancel", state.onUp);
|
|
3480
|
+
window.addEventListener("keydown", state.onKeyDown);
|
|
3481
|
+
}
|
|
3482
|
+
|
|
3483
|
+
#getInsertIndex(pointer) {
|
|
3484
|
+
const items = this.#getElementChildren();
|
|
3485
|
+
const horizontal = this.#axis === "horizontal";
|
|
3486
|
+
|
|
3487
|
+
for (let i = 0; i < items.length; i++) {
|
|
3488
|
+
const rect = items[i].getBoundingClientRect();
|
|
3489
|
+
const midpoint = horizontal
|
|
3490
|
+
? rect.left + rect.width / 2
|
|
3491
|
+
: rect.top + rect.height / 2;
|
|
3492
|
+
if (pointer < midpoint) return i;
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3495
|
+
return items.length;
|
|
3496
|
+
}
|
|
3497
|
+
|
|
3498
|
+
#shouldShowIndicator(item, index) {
|
|
3499
|
+
const items = this.#getElementChildren();
|
|
3500
|
+
const currentIndex = items.indexOf(item);
|
|
3501
|
+
if (currentIndex === -1) return false;
|
|
3502
|
+
|
|
3503
|
+
const clamped = Math.max(0, Math.min(index, items.length));
|
|
3504
|
+
|
|
3505
|
+
// Hide only the redundant bottom line when the dragged item is already last.
|
|
3506
|
+
if (clamped >= items.length && currentIndex === items.length - 1) {
|
|
3507
|
+
return false;
|
|
3508
|
+
}
|
|
3509
|
+
|
|
3510
|
+
return true;
|
|
3511
|
+
}
|
|
3512
|
+
|
|
3513
|
+
#getReorderBounds(items) {
|
|
3514
|
+
if (!items.length) return null;
|
|
3515
|
+
|
|
3516
|
+
let left = Infinity;
|
|
3517
|
+
let right = -Infinity;
|
|
3518
|
+
let top = Infinity;
|
|
3519
|
+
let bottom = -Infinity;
|
|
3520
|
+
|
|
3521
|
+
for (const item of items) {
|
|
3522
|
+
const rect = item.getBoundingClientRect();
|
|
3523
|
+
left = Math.min(left, rect.left);
|
|
3524
|
+
right = Math.max(right, rect.right);
|
|
3525
|
+
top = Math.min(top, rect.top);
|
|
3526
|
+
bottom = Math.max(bottom, rect.bottom);
|
|
3527
|
+
}
|
|
3528
|
+
|
|
3529
|
+
return {
|
|
3530
|
+
left,
|
|
3531
|
+
top,
|
|
3532
|
+
width: right - left,
|
|
3533
|
+
height: bottom - top,
|
|
3534
|
+
};
|
|
3535
|
+
}
|
|
3536
|
+
|
|
3537
|
+
#ensureIndicator() {
|
|
3538
|
+
if (this.#indicator) return this.#indicator;
|
|
3539
|
+
|
|
3540
|
+
const indicator = document.createElement("div");
|
|
3541
|
+
indicator.className = "fig-reorder-indicator";
|
|
3542
|
+
indicator.setAttribute("data-axis", this.#axis);
|
|
3543
|
+
document.body.appendChild(indicator);
|
|
3544
|
+
this.#indicator = indicator;
|
|
3545
|
+
return indicator;
|
|
3546
|
+
}
|
|
3547
|
+
|
|
3548
|
+
#updateIndicator(index, item) {
|
|
3549
|
+
const items = this.#getElementChildren();
|
|
3550
|
+
if (!this.#shouldShowIndicator(item, index)) {
|
|
3551
|
+
this.#removeIndicator();
|
|
3552
|
+
return;
|
|
3553
|
+
}
|
|
3554
|
+
|
|
3555
|
+
const bounds = this.#getReorderBounds(items);
|
|
3556
|
+
if (!bounds) {
|
|
3557
|
+
this.#removeIndicator();
|
|
3558
|
+
return;
|
|
3559
|
+
}
|
|
3560
|
+
|
|
3561
|
+
const indicator = this.#ensureIndicator();
|
|
3562
|
+
indicator.setAttribute("data-axis", this.#axis);
|
|
3563
|
+
|
|
3564
|
+
if (this.#axis === "horizontal") {
|
|
3565
|
+
let x;
|
|
3566
|
+
if (index <= 0) {
|
|
3567
|
+
x = items[0].getBoundingClientRect().left;
|
|
3568
|
+
} else if (index >= items.length) {
|
|
3569
|
+
x = items[items.length - 1].getBoundingClientRect().right;
|
|
3570
|
+
} else {
|
|
3571
|
+
x = items[index].getBoundingClientRect().left;
|
|
3572
|
+
}
|
|
3573
|
+
|
|
3574
|
+
indicator.style.left = `${x - 1}px`;
|
|
3575
|
+
indicator.style.top = `${bounds.top}px`;
|
|
3576
|
+
indicator.style.width = "2px";
|
|
3577
|
+
indicator.style.height = `${bounds.height}px`;
|
|
3578
|
+
return;
|
|
3579
|
+
}
|
|
3580
|
+
|
|
3581
|
+
let y;
|
|
3582
|
+
if (index <= 0) {
|
|
3583
|
+
y = items[0].getBoundingClientRect().top;
|
|
3584
|
+
} else if (index >= items.length) {
|
|
3585
|
+
y = items[items.length - 1].getBoundingClientRect().bottom;
|
|
3586
|
+
} else {
|
|
3587
|
+
y = items[index].getBoundingClientRect().top;
|
|
3588
|
+
}
|
|
3589
|
+
|
|
3590
|
+
indicator.style.left = `${bounds.left}px`;
|
|
3591
|
+
indicator.style.top = `${y - 1}px`;
|
|
3592
|
+
indicator.style.width = `${bounds.width}px`;
|
|
3593
|
+
indicator.style.height = "2px";
|
|
3594
|
+
}
|
|
3595
|
+
|
|
3596
|
+
#removeIndicator() {
|
|
3597
|
+
this.#indicator?.remove();
|
|
3598
|
+
this.#indicator = null;
|
|
3599
|
+
}
|
|
3600
|
+
|
|
3601
|
+
#moveItemToIndex(item, index) {
|
|
3602
|
+
const items = this.#getElementChildren();
|
|
3603
|
+
const clamped = Math.max(0, Math.min(index, items.length));
|
|
3604
|
+
|
|
3605
|
+
if (clamped >= items.length) {
|
|
3606
|
+
if (items[items.length - 1] !== item) this.appendChild(item);
|
|
3607
|
+
return;
|
|
3608
|
+
}
|
|
3609
|
+
|
|
3610
|
+
const ref = items[clamped];
|
|
3611
|
+
if (ref !== item) this.insertBefore(item, ref);
|
|
3612
|
+
}
|
|
3613
|
+
|
|
3614
|
+
#finishDrag(state, revert) {
|
|
3615
|
+
const { item, oldIndex, active, onMove, onUp, onKeyDown } = state;
|
|
3616
|
+
|
|
3617
|
+
window.removeEventListener("pointermove", onMove);
|
|
3618
|
+
window.removeEventListener("pointerup", onUp);
|
|
3619
|
+
window.removeEventListener("pointercancel", onUp);
|
|
3620
|
+
window.removeEventListener("keydown", onKeyDown);
|
|
3621
|
+
|
|
3622
|
+
if (active) {
|
|
3623
|
+
if (revert) {
|
|
3624
|
+
this.#restoreItemIndex(item, oldIndex);
|
|
3625
|
+
} else {
|
|
3626
|
+
const newIndex = this.#getElementChildren().indexOf(item);
|
|
3627
|
+
if (newIndex !== -1 && newIndex !== oldIndex) {
|
|
3628
|
+
this.dispatchEvent(
|
|
3629
|
+
new CustomEvent("reorder", {
|
|
3630
|
+
bubbles: true,
|
|
3631
|
+
detail: { oldIndex, newIndex, item },
|
|
3632
|
+
}),
|
|
3633
|
+
);
|
|
3634
|
+
}
|
|
3635
|
+
}
|
|
3636
|
+
}
|
|
3637
|
+
|
|
3638
|
+
item.classList.remove("dragging");
|
|
3639
|
+
FigReorder.#setDocumentDragging(false);
|
|
3640
|
+
this.#removeIndicator();
|
|
3641
|
+
if (this.#drag === state) this.#drag = null;
|
|
3642
|
+
}
|
|
3643
|
+
|
|
3644
|
+
#restoreItemIndex(item, index) {
|
|
3645
|
+
const items = this.#getElementChildren().filter((node) => node !== item);
|
|
3646
|
+
const ref = items[index] ?? null;
|
|
3647
|
+
if (ref) {
|
|
3648
|
+
this.insertBefore(item, ref);
|
|
3649
|
+
return;
|
|
3650
|
+
}
|
|
3651
|
+
this.appendChild(item);
|
|
3652
|
+
}
|
|
3653
|
+
|
|
3654
|
+
#unbindAll() {
|
|
3655
|
+
for (const [, binding] of this.#bindings) {
|
|
3656
|
+
binding.target.removeEventListener(
|
|
3657
|
+
"pointerdown",
|
|
3658
|
+
binding.onPointerDown,
|
|
3659
|
+
true,
|
|
3660
|
+
);
|
|
3661
|
+
}
|
|
3662
|
+
this.#bindings.clear();
|
|
3663
|
+
}
|
|
3664
|
+
|
|
3665
|
+
#cancelDrag() {
|
|
3666
|
+
if (!this.#drag) return;
|
|
3667
|
+
this.#finishDrag(this.#drag, true);
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
|
|
3671
|
+
customElements.define("fig-reorder", FigReorder);
|
|
3672
|
+
|