@rogieking/figui3 8.9.31 → 8.9.32
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-lab/SKILL.md +5 -1
- package/.cursor/skills/fig-lab/reference.md +20 -0
- package/README.md +45 -7
- package/dist/fig-lab.css +1 -1
- package/dist/fig-lab.js +2 -2
- package/fig-lab.css +537 -78
- package/fig-lab.js +1149 -12
- package/package.json +1 -1
package/fig-lab.js
CHANGED
|
@@ -210,7 +210,7 @@ function figLabPropskitJsonValuesEqual(value, defaultValue) {
|
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
-
function figLabPerceivedColorTheme(context, color) {
|
|
213
|
+
function figLabPerceivedColorTheme(context, color, darkTextThreshold = 0.179) {
|
|
214
214
|
if (!context || !color) return null;
|
|
215
215
|
const probe = document.createElement("span");
|
|
216
216
|
probe.style.cssText =
|
|
@@ -218,8 +218,12 @@ function figLabPerceivedColorTheme(context, color) {
|
|
|
218
218
|
context.appendChild(probe);
|
|
219
219
|
const resolved = getComputedStyle(probe).color;
|
|
220
220
|
probe.remove();
|
|
221
|
-
|
|
222
|
-
const channels = resolved.
|
|
221
|
+
const rawChannels = resolved.match(/[\d.]+/g)?.slice(0, 3).map(Number);
|
|
222
|
+
const channels = resolved.startsWith("rgb")
|
|
223
|
+
? rawChannels
|
|
224
|
+
: resolved.startsWith("color(srgb") && rawChannels
|
|
225
|
+
? rawChannels.map((channel) => channel * 255)
|
|
226
|
+
: null;
|
|
223
227
|
if (!channels || channels.length < 3 || channels.some(Number.isNaN)) return null;
|
|
224
228
|
const [r, g, b] = channels.map((channel) => {
|
|
225
229
|
const value = channel / 255;
|
|
@@ -228,7 +232,7 @@ function figLabPerceivedColorTheme(context, color) {
|
|
|
228
232
|
: ((value + 0.055) / 1.055) ** 2.4;
|
|
229
233
|
});
|
|
230
234
|
const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
231
|
-
return luminance <=
|
|
235
|
+
return luminance <= darkTextThreshold ? "light" : "dark";
|
|
232
236
|
}
|
|
233
237
|
|
|
234
238
|
/* Unique IDs for lab components such as propskit-group. */
|
|
@@ -578,6 +582,7 @@ class PropskitSwitch extends HTMLElement {
|
|
|
578
582
|
"checked",
|
|
579
583
|
"value",
|
|
580
584
|
"default",
|
|
585
|
+
"variant",
|
|
581
586
|
]);
|
|
582
587
|
return this.getAttributeNames().filter(
|
|
583
588
|
(name) => !reserved.has(name) && !name.startsWith("data-"),
|
|
@@ -1011,6 +1016,7 @@ class PropskitColor extends HTMLElement {
|
|
|
1011
1016
|
"default",
|
|
1012
1017
|
"value",
|
|
1013
1018
|
"mode",
|
|
1019
|
+
"variant",
|
|
1014
1020
|
]);
|
|
1015
1021
|
return this.getAttributeNames().filter(
|
|
1016
1022
|
(name) => !reserved.has(name) && !name.startsWith("data-"),
|
|
@@ -1418,6 +1424,7 @@ class PropskitFill extends HTMLElement {
|
|
|
1418
1424
|
"text",
|
|
1419
1425
|
"default",
|
|
1420
1426
|
"value",
|
|
1427
|
+
"variant",
|
|
1421
1428
|
]);
|
|
1422
1429
|
return this.getAttributeNames().filter(
|
|
1423
1430
|
(name) => !reserved.has(name) && !name.startsWith("data-"),
|
|
@@ -1720,6 +1727,7 @@ class PropskitGradient extends HTMLElement {
|
|
|
1720
1727
|
"size",
|
|
1721
1728
|
"aria-label",
|
|
1722
1729
|
"default",
|
|
1730
|
+
"variant",
|
|
1723
1731
|
]);
|
|
1724
1732
|
return this.getAttributeNames().filter(
|
|
1725
1733
|
(name) => !reserved.has(name) && !name.startsWith("data-"),
|
|
@@ -2040,6 +2048,7 @@ class PropskitSelect extends HTMLElement {
|
|
|
2040
2048
|
"aria-label",
|
|
2041
2049
|
"full",
|
|
2042
2050
|
"default",
|
|
2051
|
+
"variant",
|
|
2043
2052
|
]);
|
|
2044
2053
|
// fig-dropdown consumes light-DOM <option>s, not an options attribute.
|
|
2045
2054
|
if (!this.#usesFigSelect) reserved.add("options");
|
|
@@ -2369,6 +2378,7 @@ class PropskitText extends HTMLElement {
|
|
|
2369
2378
|
"multiline",
|
|
2370
2379
|
"resizable",
|
|
2371
2380
|
"default",
|
|
2381
|
+
"variant",
|
|
2372
2382
|
]);
|
|
2373
2383
|
return this.getAttributeNames().filter(
|
|
2374
2384
|
(name) => !reserved.has(name) && !name.startsWith("data-"),
|
|
@@ -2596,6 +2606,7 @@ class PropskitNumber extends HTMLElement {
|
|
|
2596
2606
|
"style",
|
|
2597
2607
|
"id",
|
|
2598
2608
|
"default",
|
|
2609
|
+
"variant",
|
|
2599
2610
|
]);
|
|
2600
2611
|
return this.getAttributeNames().filter(
|
|
2601
2612
|
(name) => !reserved.has(name) && !name.startsWith("data-"),
|
|
@@ -2710,7 +2721,7 @@ figLabDefineElement("propskit-number", PropskitNumber);
|
|
|
2710
2721
|
* @attr {string} label - Field label. Empty values use "Position".
|
|
2711
2722
|
* @attr {string} units - Set to "percent" to show percentage units.
|
|
2712
2723
|
* @attr {boolean|string} disabled - Disables both number inputs.
|
|
2713
|
-
* @attr {string} size -
|
|
2724
|
+
* @attr {string} size - Defaults to the large row; set to "small" for compact.
|
|
2714
2725
|
* @fires input - Composed event with { x, y }.
|
|
2715
2726
|
* @fires change - Composed event with { x, y }.
|
|
2716
2727
|
*/
|
|
@@ -4229,7 +4240,7 @@ figLabDefineElement("propskit-point-point", PropskitPointPoint);
|
|
|
4229
4240
|
|
|
4230
4241
|
/* Collapsible property group — always collapsible (no collapsible attr). */
|
|
4231
4242
|
class PropskitGroup extends HTMLElement {
|
|
4232
|
-
static observedAttributes = ["name", "open", "show-reset", "disabled"];
|
|
4243
|
+
static observedAttributes = ["name", "open", "show-reset", "disabled", "size"];
|
|
4233
4244
|
|
|
4234
4245
|
static #CONTROL_SELECTOR = [
|
|
4235
4246
|
"propskit-color",
|
|
@@ -4245,6 +4256,7 @@ class PropskitGroup extends HTMLElement {
|
|
|
4245
4256
|
"propskit-slider",
|
|
4246
4257
|
"propskit-switch",
|
|
4247
4258
|
"propskit-text",
|
|
4259
|
+
"propskit-wheel",
|
|
4248
4260
|
"propskit-oscillator",
|
|
4249
4261
|
].join(",");
|
|
4250
4262
|
|
|
@@ -4258,6 +4270,7 @@ class PropskitGroup extends HTMLElement {
|
|
|
4258
4270
|
connectedCallback() {
|
|
4259
4271
|
this.#render();
|
|
4260
4272
|
this.#syncDisabled();
|
|
4273
|
+
this.#syncSize();
|
|
4261
4274
|
this.#bindDirtyListeners();
|
|
4262
4275
|
requestAnimationFrame(() => {
|
|
4263
4276
|
this.#syncDirtyState();
|
|
@@ -4291,6 +4304,10 @@ class PropskitGroup extends HTMLElement {
|
|
|
4291
4304
|
this.#syncDisabled();
|
|
4292
4305
|
return;
|
|
4293
4306
|
}
|
|
4307
|
+
if (name === "size") {
|
|
4308
|
+
this.#syncSize();
|
|
4309
|
+
return;
|
|
4310
|
+
}
|
|
4294
4311
|
this.#render();
|
|
4295
4312
|
}
|
|
4296
4313
|
|
|
@@ -4347,6 +4364,7 @@ class PropskitGroup extends HTMLElement {
|
|
|
4347
4364
|
if (!this.#childObserver) {
|
|
4348
4365
|
this.#childObserver = new MutationObserver(() => {
|
|
4349
4366
|
this.#syncDisabled();
|
|
4367
|
+
this.#syncSize();
|
|
4350
4368
|
this.#queueDirtySync();
|
|
4351
4369
|
});
|
|
4352
4370
|
}
|
|
@@ -4437,6 +4455,26 @@ class PropskitGroup extends HTMLElement {
|
|
|
4437
4455
|
?.toggleAttribute("disabled", disabled);
|
|
4438
4456
|
}
|
|
4439
4457
|
|
|
4458
|
+
#syncSize() {
|
|
4459
|
+
const small = this.getAttribute("size") === "small";
|
|
4460
|
+
for (const control of this.#controls()) {
|
|
4461
|
+
const generated = control.hasAttribute("data-propskit-group-size");
|
|
4462
|
+
if (small && !control.hasAttribute("size")) {
|
|
4463
|
+
control.setAttribute("data-propskit-group-size", "");
|
|
4464
|
+
control.setAttribute("size", "small");
|
|
4465
|
+
} else if (
|
|
4466
|
+
small &&
|
|
4467
|
+
generated &&
|
|
4468
|
+
control.getAttribute("size") !== "small"
|
|
4469
|
+
) {
|
|
4470
|
+
control.setAttribute("size", "small");
|
|
4471
|
+
} else if (!small && generated) {
|
|
4472
|
+
control.removeAttribute("size");
|
|
4473
|
+
control.removeAttribute("data-propskit-group-size");
|
|
4474
|
+
}
|
|
4475
|
+
}
|
|
4476
|
+
}
|
|
4477
|
+
|
|
4440
4478
|
#controlIsDirty(el) {
|
|
4441
4479
|
return el.isDefault === false;
|
|
4442
4480
|
}
|
|
@@ -4903,17 +4941,67 @@ class PropskitSlider extends HTMLElement {
|
|
|
4903
4941
|
|
|
4904
4942
|
#syncNumberTheme(sliderType) {
|
|
4905
4943
|
const numberInput = this.#slider?.querySelector("fig-input-number");
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4944
|
+
const usesColorSurface =
|
|
4945
|
+
sliderType === "hue" ||
|
|
4946
|
+
(sliderType === "opacity" && this.hasAttribute("color"));
|
|
4947
|
+
if (!usesColorSurface) {
|
|
4948
|
+
numberInput?.removeAttribute("theme");
|
|
4949
|
+
this.style.removeProperty("--propskit-slider-filled-text-color");
|
|
4909
4950
|
return;
|
|
4910
4951
|
}
|
|
4952
|
+
const min = Number(this.#slider?.min);
|
|
4953
|
+
const max = Number(this.#slider?.max);
|
|
4954
|
+
const value = Number(this.#slider?.value);
|
|
4955
|
+
const complete =
|
|
4956
|
+
Number.isFinite(min) &&
|
|
4957
|
+
Number.isFinite(max) &&
|
|
4958
|
+
max > min &&
|
|
4959
|
+
Number.isFinite(value)
|
|
4960
|
+
? Math.max(0, Math.min(1, (value - min) / (max - min)))
|
|
4961
|
+
: 0;
|
|
4962
|
+
const color = this.getAttribute("color");
|
|
4963
|
+
const effectiveColor =
|
|
4964
|
+
sliderType === "hue"
|
|
4965
|
+
? `hsl(${complete * 360}deg 100% 50%)`
|
|
4966
|
+
: `color-mix(in srgb, ${color} ${complete * 100}%, var(--figma-color-bg-secondary))`;
|
|
4967
|
+
const darkSurface =
|
|
4968
|
+
getComputedStyle(this).colorScheme.trim().toLowerCase() === "dark";
|
|
4911
4969
|
const theme = figLabPerceivedColorTheme(
|
|
4912
4970
|
this.#slider,
|
|
4913
|
-
|
|
4971
|
+
effectiveColor,
|
|
4972
|
+
sliderType === "hue" ? 0.35 : darkSurface ? 0.2 : 0.35,
|
|
4914
4973
|
);
|
|
4915
|
-
if (theme)
|
|
4916
|
-
|
|
4974
|
+
if (theme) {
|
|
4975
|
+
numberInput?.setAttribute("theme", theme);
|
|
4976
|
+
const useDefaultTextToken =
|
|
4977
|
+
(theme === "light" && darkSurface) ||
|
|
4978
|
+
(theme === "dark" && !darkSurface);
|
|
4979
|
+
this.style.setProperty(
|
|
4980
|
+
"--propskit-slider-filled-text-color",
|
|
4981
|
+
useDefaultTextToken
|
|
4982
|
+
? "var(--figma-color-text)"
|
|
4983
|
+
: "var(--figma-color-text-oninverse)",
|
|
4984
|
+
);
|
|
4985
|
+
} else {
|
|
4986
|
+
numberInput?.removeAttribute("theme");
|
|
4987
|
+
this.style.removeProperty("--propskit-slider-filled-text-color");
|
|
4988
|
+
}
|
|
4989
|
+
}
|
|
4990
|
+
|
|
4991
|
+
#syncProgressStyles() {
|
|
4992
|
+
if (!this.#slider) return;
|
|
4993
|
+
const min = Number(this.#slider.min);
|
|
4994
|
+
const max = Number(this.#slider.max);
|
|
4995
|
+
const value = Number(this.#slider.value);
|
|
4996
|
+
const complete =
|
|
4997
|
+
Number.isFinite(min) &&
|
|
4998
|
+
Number.isFinite(max) &&
|
|
4999
|
+
max > min &&
|
|
5000
|
+
Number.isFinite(value)
|
|
5001
|
+
? Math.max(0, Math.min(1, (value - min) / (max - min)))
|
|
5002
|
+
: 0;
|
|
5003
|
+
this.style.setProperty("--propskit-slider-complete", String(complete));
|
|
5004
|
+
this.#syncNumberTheme((this.getAttribute("type") || "range").toLowerCase());
|
|
4917
5005
|
}
|
|
4918
5006
|
|
|
4919
5007
|
#pushExternalValueToSlider() {
|
|
@@ -4924,6 +5012,7 @@ class PropskitSlider extends HTMLElement {
|
|
|
4924
5012
|
if (String(this.#slider.value) !== next) {
|
|
4925
5013
|
this.#slider.value = next;
|
|
4926
5014
|
}
|
|
5015
|
+
this.#syncProgressStyles();
|
|
4927
5016
|
}
|
|
4928
5017
|
|
|
4929
5018
|
#getForwardedSliderAttrNames() {
|
|
@@ -5409,6 +5498,7 @@ class PropskitSlider extends HTMLElement {
|
|
|
5409
5498
|
this.setAttribute("value", next);
|
|
5410
5499
|
}
|
|
5411
5500
|
}
|
|
5501
|
+
this.#syncProgressStyles();
|
|
5412
5502
|
this.dispatchEvent(
|
|
5413
5503
|
new CustomEvent(type, {
|
|
5414
5504
|
detail,
|
|
@@ -5455,6 +5545,1053 @@ class PropskitSlider extends HTMLElement {
|
|
|
5455
5545
|
}
|
|
5456
5546
|
figLabDefineElement("propskit-slider", PropskitSlider);
|
|
5457
5547
|
|
|
5548
|
+
/**
|
|
5549
|
+
* Full-surface numeric wheel: SVG ticks + fig-input-number.
|
|
5550
|
+
*
|
|
5551
|
+
* @attr {string} label - Field label. Omitted defaults to "Value"; authored values (including blank) are used as-is.
|
|
5552
|
+
* @attr {string} units - Optional unit forwarded to fig-input-number. Time aliases are normalized.
|
|
5553
|
+
* @attr {number} value - Numeric value. Unbounded unless min/max are set.
|
|
5554
|
+
* @attr {number} min - Inclusive lower bound. Omitted = unbounded below.
|
|
5555
|
+
* @attr {number} max - Inclusive upper bound. Omitted = unbounded above.
|
|
5556
|
+
* @attr {string} default - Reset target.
|
|
5557
|
+
* @attr {number} step - Scrub/keyboard/wheel increment. Defaults: s=0.1, ms=100, otherwise 1.
|
|
5558
|
+
* @attr {number} precision - Display decimals forwarded to fig-input-number. Defaults: s=2, otherwise 0.
|
|
5559
|
+
* @attr {boolean|string} elastic - Enables resisted handle movement while scrubbing. Defaults to true.
|
|
5560
|
+
* @attr {string} size - Defaults to the large row; set to "small" for compact.
|
|
5561
|
+
* @attr {boolean|string} disabled - Disables wheel and number.
|
|
5562
|
+
* @attr {string} variant - Set to "minimal" for compact chrome.
|
|
5563
|
+
* @fires input - Composed event while dragging or typing.
|
|
5564
|
+
* @fires change - Composed event on commit.
|
|
5565
|
+
*/
|
|
5566
|
+
class PropskitWheel extends HTMLElement {
|
|
5567
|
+
static #TICK_COUNT = 33;
|
|
5568
|
+
static #HALF_FOV_DEG = 60;
|
|
5569
|
+
static #PERSPECTIVE_K = 0.55;
|
|
5570
|
+
static #DRAGGING_BODY_CLASS = "fig-propskit-wheel-dragging";
|
|
5571
|
+
static #RESERVED_ATTRS = new Set([
|
|
5572
|
+
"label",
|
|
5573
|
+
"size",
|
|
5574
|
+
"disabled",
|
|
5575
|
+
"variant",
|
|
5576
|
+
"elastic",
|
|
5577
|
+
"default",
|
|
5578
|
+
"class",
|
|
5579
|
+
"style",
|
|
5580
|
+
"id",
|
|
5581
|
+
"oninput",
|
|
5582
|
+
"onchange",
|
|
5583
|
+
]);
|
|
5584
|
+
|
|
5585
|
+
#surface = null;
|
|
5586
|
+
#wheel = null;
|
|
5587
|
+
#svg = null;
|
|
5588
|
+
#tickPath = null;
|
|
5589
|
+
#tickMetrics = null;
|
|
5590
|
+
#tickMetricProbe = null;
|
|
5591
|
+
#wheelWidth = 0;
|
|
5592
|
+
#wheelHeight = 0;
|
|
5593
|
+
#layoutFrame = 0;
|
|
5594
|
+
#keyboardAnimationFrame = 0;
|
|
5595
|
+
#keyboardSettleTimer = 0;
|
|
5596
|
+
#label = null;
|
|
5597
|
+
#input = null;
|
|
5598
|
+
#hasCustomLabel = false;
|
|
5599
|
+
#observer = null;
|
|
5600
|
+
#resizeObserver = null;
|
|
5601
|
+
#managedInputAttrs = new Set();
|
|
5602
|
+
#initialValue = null;
|
|
5603
|
+
#isDragging = false;
|
|
5604
|
+
#dragPointerId = null;
|
|
5605
|
+
#dragStartX = 0;
|
|
5606
|
+
#dragStartValue = 0;
|
|
5607
|
+
#visualValue = null;
|
|
5608
|
+
#handleDragMaxPx = 0;
|
|
5609
|
+
#elasticMaxPx = 0;
|
|
5610
|
+
#elasticRangeRect = null;
|
|
5611
|
+
#elasticHostWidth = 0;
|
|
5612
|
+
#numberPointerId = null;
|
|
5613
|
+
#numberPointerStartX = 0;
|
|
5614
|
+
#numberPointerStartY = 0;
|
|
5615
|
+
#numberPointerStartValue = 0;
|
|
5616
|
+
#isNumberScrubbing = false;
|
|
5617
|
+
#suppressNumberClick = false;
|
|
5618
|
+
#numberClickResetTimer = 0;
|
|
5619
|
+
#boundHandleInput = null;
|
|
5620
|
+
#boundHandleChange = null;
|
|
5621
|
+
#boundPointerDown = this.#handlePointerDown.bind(this);
|
|
5622
|
+
#boundPointerMove = this.#handlePointerMove.bind(this);
|
|
5623
|
+
#boundPointerUp = this.#handlePointerUp.bind(this);
|
|
5624
|
+
#boundNumberPointerDown = this.#handleNumberPointerDown.bind(this);
|
|
5625
|
+
#boundNumberPointerMove = this.#handleNumberPointerMove.bind(this);
|
|
5626
|
+
#boundNumberPointerEnd = this.#handleNumberPointerEnd.bind(this);
|
|
5627
|
+
#boundWheel = this.#handleWheel.bind(this);
|
|
5628
|
+
#boundKeyDown = this.#handleKeyDown.bind(this);
|
|
5629
|
+
#boundClick = this.#handleClick.bind(this);
|
|
5630
|
+
|
|
5631
|
+
static get observedAttributes() {
|
|
5632
|
+
return ["label", "units", "value", "disabled", "step", "precision", "min", "max"];
|
|
5633
|
+
}
|
|
5634
|
+
|
|
5635
|
+
connectedCallback() {
|
|
5636
|
+
if (!this.#surface) this.#initialize();
|
|
5637
|
+
this.#syncLabel();
|
|
5638
|
+
this.#syncDisabled();
|
|
5639
|
+
this.#syncInputAttributes();
|
|
5640
|
+
this.#syncValueFromHost();
|
|
5641
|
+
this.#bindEvents();
|
|
5642
|
+
figLabConnectPropskitResetMenu(this);
|
|
5643
|
+
this.#observer?.disconnect();
|
|
5644
|
+
this.#observer = new MutationObserver((mutations) => {
|
|
5645
|
+
let syncInput = false;
|
|
5646
|
+
for (const mutation of mutations) {
|
|
5647
|
+
if (mutation.type !== "attributes") continue;
|
|
5648
|
+
const name = mutation.attributeName;
|
|
5649
|
+
if (
|
|
5650
|
+
!PropskitWheel.#RESERVED_ATTRS.has(name) &&
|
|
5651
|
+
!PropskitWheel.observedAttributes.includes(name) &&
|
|
5652
|
+
!name?.startsWith("data-")
|
|
5653
|
+
) {
|
|
5654
|
+
syncInput = true;
|
|
5655
|
+
}
|
|
5656
|
+
}
|
|
5657
|
+
if (syncInput) this.#syncInputAttributes();
|
|
5658
|
+
});
|
|
5659
|
+
this.#observer.observe(this, { attributes: true });
|
|
5660
|
+
this.#resizeObserver?.disconnect();
|
|
5661
|
+
this.#resizeObserver = new ResizeObserver((entries) => {
|
|
5662
|
+
const rect = entries.at(-1)?.contentRect;
|
|
5663
|
+
if (rect) this.#setWheelSize(rect.width, rect.height);
|
|
5664
|
+
});
|
|
5665
|
+
this.#resizeObserver.observe(this.#surface);
|
|
5666
|
+
this.#queueWheelLayout();
|
|
5667
|
+
}
|
|
5668
|
+
|
|
5669
|
+
disconnectedCallback() {
|
|
5670
|
+
this.#observer?.disconnect();
|
|
5671
|
+
this.#resizeObserver?.disconnect();
|
|
5672
|
+
if (this.#layoutFrame) cancelAnimationFrame(this.#layoutFrame);
|
|
5673
|
+
this.#layoutFrame = 0;
|
|
5674
|
+
this.#stopKeyboardAnimation();
|
|
5675
|
+
this.#unbindEvents();
|
|
5676
|
+
this.#stopDrag();
|
|
5677
|
+
figLabDisconnectPropskitResetMenu(this);
|
|
5678
|
+
}
|
|
5679
|
+
|
|
5680
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
5681
|
+
if (oldValue === newValue || !this.#surface) return;
|
|
5682
|
+
if (name === "label") this.#syncLabel();
|
|
5683
|
+
if (name === "disabled") this.#syncDisabled();
|
|
5684
|
+
if (name === "value" && !this.#isDragging) {
|
|
5685
|
+
this.#syncValueFromHost();
|
|
5686
|
+
}
|
|
5687
|
+
if (name === "units" || name === "step" || name === "precision") {
|
|
5688
|
+
this.#syncInputAttributes();
|
|
5689
|
+
this.#queueWheelLayout();
|
|
5690
|
+
}
|
|
5691
|
+
if (name === "min" || name === "max") {
|
|
5692
|
+
this.#syncInputAttributes();
|
|
5693
|
+
this.#commitValue(this.#numericValue());
|
|
5694
|
+
}
|
|
5695
|
+
}
|
|
5696
|
+
|
|
5697
|
+
#initialize() {
|
|
5698
|
+
this.#initialValue = this.getAttribute("value") ?? "0";
|
|
5699
|
+
const initialChildren = Array.from(this.childNodes).filter(
|
|
5700
|
+
(node) =>
|
|
5701
|
+
node.nodeType !== Node.TEXT_NODE || Boolean(node.textContent?.trim()),
|
|
5702
|
+
);
|
|
5703
|
+
const customLabel = initialChildren.find(
|
|
5704
|
+
(node) => node.nodeType === Node.ELEMENT_NODE && node.matches("label"),
|
|
5705
|
+
);
|
|
5706
|
+
|
|
5707
|
+
const surface = figLabCreateElement("div", {
|
|
5708
|
+
className: "propskit-wheel-surface",
|
|
5709
|
+
});
|
|
5710
|
+
const wheel = figLabCreateElement("div", {
|
|
5711
|
+
className: "propskit-wheel-wheel",
|
|
5712
|
+
tabindex: "0",
|
|
5713
|
+
role: "spinbutton",
|
|
5714
|
+
});
|
|
5715
|
+
const svg = figLabCreateSvgElement("svg", {
|
|
5716
|
+
className: "propskit-wheel-wheel-svg",
|
|
5717
|
+
"aria-hidden": "true",
|
|
5718
|
+
});
|
|
5719
|
+
const label = customLabel || document.createElement("label");
|
|
5720
|
+
const input = document.createElement("fig-input-number");
|
|
5721
|
+
const labelId = figLabUniqueId("propskit-wheel-label");
|
|
5722
|
+
label.id = labelId;
|
|
5723
|
+
wheel.setAttribute("aria-labelledby", labelId);
|
|
5724
|
+
|
|
5725
|
+
const handle = figLabCreateElement("div", {
|
|
5726
|
+
className: "propskit-wheel-handle",
|
|
5727
|
+
});
|
|
5728
|
+
wheel.append(svg);
|
|
5729
|
+
surface.append(wheel, handle, label, input);
|
|
5730
|
+
this.#surface = surface;
|
|
5731
|
+
this.#wheel = wheel;
|
|
5732
|
+
this.#svg = svg;
|
|
5733
|
+
this.#label = label;
|
|
5734
|
+
this.#input = input;
|
|
5735
|
+
this.#hasCustomLabel = Boolean(customLabel);
|
|
5736
|
+
this.replaceChildren(surface);
|
|
5737
|
+
|
|
5738
|
+
const metricProbe = document.createElement("div");
|
|
5739
|
+
metricProbe.setAttribute("aria-hidden", "true");
|
|
5740
|
+
metricProbe.style.cssText =
|
|
5741
|
+
"position:absolute;visibility:hidden;pointer-events:none;left:0;top:0";
|
|
5742
|
+
const maxEl = document.createElement("div");
|
|
5743
|
+
maxEl.style.height = "var(--propskit-wheel-tick-height)";
|
|
5744
|
+
const minEl = document.createElement("div");
|
|
5745
|
+
minEl.style.height = "var(--propskit-wheel-tick-height-min)";
|
|
5746
|
+
const maxWidthEl = document.createElement("div");
|
|
5747
|
+
maxWidthEl.style.width = "var(--propskit-wheel-tick-width)";
|
|
5748
|
+
const minWidthEl = document.createElement("div");
|
|
5749
|
+
minWidthEl.style.width = "var(--propskit-wheel-tick-width-min)";
|
|
5750
|
+
const insetEl = document.createElement("div");
|
|
5751
|
+
insetEl.style.width = "var(--propskit-wheel-tick-inset)";
|
|
5752
|
+
metricProbe.append(maxEl, minEl, maxWidthEl, minWidthEl, insetEl);
|
|
5753
|
+
surface.append(metricProbe);
|
|
5754
|
+
this.#tickMetricProbe = {
|
|
5755
|
+
maxEl,
|
|
5756
|
+
minEl,
|
|
5757
|
+
maxWidthEl,
|
|
5758
|
+
minWidthEl,
|
|
5759
|
+
insetEl,
|
|
5760
|
+
};
|
|
5761
|
+
|
|
5762
|
+
for (const node of initialChildren) {
|
|
5763
|
+
if (node === customLabel) continue;
|
|
5764
|
+
input.appendChild(node);
|
|
5765
|
+
}
|
|
5766
|
+
|
|
5767
|
+
this.#ensureTickPath();
|
|
5768
|
+
}
|
|
5769
|
+
|
|
5770
|
+
#ensureTickPath() {
|
|
5771
|
+
if (!this.#svg || this.#tickPath) return;
|
|
5772
|
+
this.#tickPath = figLabCreateSvgElement("path", {
|
|
5773
|
+
className: "propskit-wheel-tick",
|
|
5774
|
+
});
|
|
5775
|
+
this.#svg.append(this.#tickPath);
|
|
5776
|
+
}
|
|
5777
|
+
|
|
5778
|
+
#readTickMetrics() {
|
|
5779
|
+
if (this.#tickMetrics) return this.#tickMetrics;
|
|
5780
|
+
this.#tickMetrics = {
|
|
5781
|
+
maxH: this.#tickMetricProbe?.maxEl.getBoundingClientRect().height || 8,
|
|
5782
|
+
minH: this.#tickMetricProbe?.minEl.getBoundingClientRect().height || 4,
|
|
5783
|
+
maxW:
|
|
5784
|
+
this.#tickMetricProbe?.maxWidthEl.getBoundingClientRect().width || 2,
|
|
5785
|
+
minW:
|
|
5786
|
+
this.#tickMetricProbe?.minWidthEl.getBoundingClientRect().width || 1,
|
|
5787
|
+
inset: this.#tickMetricProbe?.insetEl.getBoundingClientRect().width || 0,
|
|
5788
|
+
};
|
|
5789
|
+
return this.#tickMetrics;
|
|
5790
|
+
}
|
|
5791
|
+
|
|
5792
|
+
#units() {
|
|
5793
|
+
const raw = (this.getAttribute("units") || "").trim();
|
|
5794
|
+
const normalized = raw.toLowerCase();
|
|
5795
|
+
if (
|
|
5796
|
+
normalized === "ms" ||
|
|
5797
|
+
normalized === "millisecond" ||
|
|
5798
|
+
normalized === "milliseconds"
|
|
5799
|
+
) {
|
|
5800
|
+
return "ms";
|
|
5801
|
+
}
|
|
5802
|
+
if (
|
|
5803
|
+
normalized === "s" ||
|
|
5804
|
+
normalized === "second" ||
|
|
5805
|
+
normalized === "seconds"
|
|
5806
|
+
) {
|
|
5807
|
+
return "s";
|
|
5808
|
+
}
|
|
5809
|
+
return raw;
|
|
5810
|
+
}
|
|
5811
|
+
|
|
5812
|
+
#defaultStep() {
|
|
5813
|
+
const units = this.#units();
|
|
5814
|
+
if (units === "s") return 0.1;
|
|
5815
|
+
if (units === "ms") return 100;
|
|
5816
|
+
return 1;
|
|
5817
|
+
}
|
|
5818
|
+
|
|
5819
|
+
#defaultPrecision() {
|
|
5820
|
+
return this.#units() === "s" ? 2 : 0;
|
|
5821
|
+
}
|
|
5822
|
+
|
|
5823
|
+
#step() {
|
|
5824
|
+
if (this.hasAttribute("step")) {
|
|
5825
|
+
const parsed = Number(this.getAttribute("step"));
|
|
5826
|
+
if (parsed > 0) return parsed;
|
|
5827
|
+
}
|
|
5828
|
+
return this.#defaultStep();
|
|
5829
|
+
}
|
|
5830
|
+
|
|
5831
|
+
#precision() {
|
|
5832
|
+
if (this.hasAttribute("precision")) {
|
|
5833
|
+
const parsed = Number(this.getAttribute("precision"));
|
|
5834
|
+
if (Number.isInteger(parsed) && parsed >= 0) return parsed;
|
|
5835
|
+
}
|
|
5836
|
+
return this.#defaultPrecision();
|
|
5837
|
+
}
|
|
5838
|
+
|
|
5839
|
+
#numericValue() {
|
|
5840
|
+
const parsed = Number(this.getAttribute("value") ?? this.#input?.value ?? 0);
|
|
5841
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
5842
|
+
}
|
|
5843
|
+
|
|
5844
|
+
#parseBound(name) {
|
|
5845
|
+
if (!this.hasAttribute(name)) return null;
|
|
5846
|
+
const raw = this.getAttribute(name);
|
|
5847
|
+
if (raw === null || raw.trim() === "") return null;
|
|
5848
|
+
const parsed = Number(raw);
|
|
5849
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
5850
|
+
}
|
|
5851
|
+
|
|
5852
|
+
#boundMin() {
|
|
5853
|
+
return this.#parseBound("min");
|
|
5854
|
+
}
|
|
5855
|
+
|
|
5856
|
+
#boundMax() {
|
|
5857
|
+
return this.#parseBound("max");
|
|
5858
|
+
}
|
|
5859
|
+
|
|
5860
|
+
#writeBound(name, nextValue) {
|
|
5861
|
+
if (nextValue === null || nextValue === undefined || nextValue === "") {
|
|
5862
|
+
this.removeAttribute(name);
|
|
5863
|
+
return;
|
|
5864
|
+
}
|
|
5865
|
+
const parsed = Number(nextValue);
|
|
5866
|
+
if (!Number.isFinite(parsed)) {
|
|
5867
|
+
this.removeAttribute(name);
|
|
5868
|
+
return;
|
|
5869
|
+
}
|
|
5870
|
+
this.setAttribute(name, String(parsed));
|
|
5871
|
+
}
|
|
5872
|
+
|
|
5873
|
+
#clamp(value) {
|
|
5874
|
+
let next = value;
|
|
5875
|
+
const min = this.#boundMin();
|
|
5876
|
+
const max = this.#boundMax();
|
|
5877
|
+
if (min !== null) next = Math.max(min, next);
|
|
5878
|
+
if (max !== null) next = Math.min(max, next);
|
|
5879
|
+
return next;
|
|
5880
|
+
}
|
|
5881
|
+
|
|
5882
|
+
#snap(value) {
|
|
5883
|
+
const step = this.#step();
|
|
5884
|
+
if (step <= 0) return value;
|
|
5885
|
+
const base = this.#boundMin() ?? 0;
|
|
5886
|
+
const snapped = Math.round((value - base) / step) * step + base;
|
|
5887
|
+
return Number(snapped.toPrecision(15));
|
|
5888
|
+
}
|
|
5889
|
+
|
|
5890
|
+
#commitValue(value, { snap = false, emit = null } = {}) {
|
|
5891
|
+
let next = snap ? this.#snap(value) : value;
|
|
5892
|
+
next = this.#clamp(next);
|
|
5893
|
+
const asString = String(next);
|
|
5894
|
+
if (this.getAttribute("value") !== asString) {
|
|
5895
|
+
this.setAttribute("value", asString);
|
|
5896
|
+
}
|
|
5897
|
+
if (this.#input?.getAttribute("value") !== asString) {
|
|
5898
|
+
this.#input?.setAttribute("value", asString);
|
|
5899
|
+
}
|
|
5900
|
+
this.#syncWheelAria(next);
|
|
5901
|
+
this.#queueWheelLayout();
|
|
5902
|
+
if (emit) {
|
|
5903
|
+
this.dispatchEvent(
|
|
5904
|
+
new CustomEvent(emit, {
|
|
5905
|
+
detail: next,
|
|
5906
|
+
bubbles: true,
|
|
5907
|
+
cancelable: true,
|
|
5908
|
+
composed: true,
|
|
5909
|
+
}),
|
|
5910
|
+
);
|
|
5911
|
+
}
|
|
5912
|
+
}
|
|
5913
|
+
|
|
5914
|
+
#syncValueFromHost() {
|
|
5915
|
+
const value = this.#numericValue();
|
|
5916
|
+
const clamped = this.#clamp(value);
|
|
5917
|
+
if (clamped !== value) {
|
|
5918
|
+
this.#commitValue(clamped);
|
|
5919
|
+
return;
|
|
5920
|
+
}
|
|
5921
|
+
const asString = String(value);
|
|
5922
|
+
if (this.#input?.getAttribute("value") !== asString) {
|
|
5923
|
+
this.#input?.setAttribute("value", asString);
|
|
5924
|
+
}
|
|
5925
|
+
this.#syncWheelAria(value);
|
|
5926
|
+
this.#queueWheelLayout();
|
|
5927
|
+
}
|
|
5928
|
+
|
|
5929
|
+
#syncWheelAria(value = this.#numericValue()) {
|
|
5930
|
+
if (!this.#wheel) return;
|
|
5931
|
+
const units = this.#units();
|
|
5932
|
+
const min = this.#boundMin();
|
|
5933
|
+
const max = this.#boundMax();
|
|
5934
|
+
this.#wheel.setAttribute("aria-valuenow", String(value));
|
|
5935
|
+
this.#wheel.setAttribute(
|
|
5936
|
+
"aria-valuetext",
|
|
5937
|
+
units
|
|
5938
|
+
? `${value} ${
|
|
5939
|
+
units === "s" ? "seconds" : units === "ms" ? "milliseconds" : units
|
|
5940
|
+
}`
|
|
5941
|
+
: String(value),
|
|
5942
|
+
);
|
|
5943
|
+
if (min === null) this.#wheel.removeAttribute("aria-valuemin");
|
|
5944
|
+
else this.#wheel.setAttribute("aria-valuemin", String(min));
|
|
5945
|
+
if (max === null) this.#wheel.removeAttribute("aria-valuemax");
|
|
5946
|
+
else this.#wheel.setAttribute("aria-valuemax", String(max));
|
|
5947
|
+
}
|
|
5948
|
+
|
|
5949
|
+
#syncLabel() {
|
|
5950
|
+
if (!this.#label) return;
|
|
5951
|
+
if (this.#hasCustomLabel) return;
|
|
5952
|
+
if (!this.hasAttribute("label")) {
|
|
5953
|
+
this.#label.textContent = "Value";
|
|
5954
|
+
return;
|
|
5955
|
+
}
|
|
5956
|
+
this.#label.textContent = this.getAttribute("label") ?? "";
|
|
5957
|
+
}
|
|
5958
|
+
|
|
5959
|
+
#syncDisabled() {
|
|
5960
|
+
const disabled = figLabBooleanAttribute(this, "disabled");
|
|
5961
|
+
this.#input?.toggleAttribute("disabled", disabled);
|
|
5962
|
+
if (!this.#wheel) return;
|
|
5963
|
+
if (disabled) {
|
|
5964
|
+
this.setAttribute("aria-disabled", "true");
|
|
5965
|
+
this.#wheel.setAttribute("tabindex", "-1");
|
|
5966
|
+
this.#wheel.setAttribute("aria-disabled", "true");
|
|
5967
|
+
} else {
|
|
5968
|
+
this.removeAttribute("aria-disabled");
|
|
5969
|
+
this.#wheel.setAttribute("tabindex", "0");
|
|
5970
|
+
this.#wheel.removeAttribute("aria-disabled");
|
|
5971
|
+
}
|
|
5972
|
+
}
|
|
5973
|
+
|
|
5974
|
+
#getForwardedInputAttrNames() {
|
|
5975
|
+
return this.getAttributeNames().filter(
|
|
5976
|
+
(name) =>
|
|
5977
|
+
!PropskitWheel.#RESERVED_ATTRS.has(name) && !name.startsWith("data-"),
|
|
5978
|
+
);
|
|
5979
|
+
}
|
|
5980
|
+
|
|
5981
|
+
#syncInputAttributes() {
|
|
5982
|
+
if (!this.#input) return;
|
|
5983
|
+
const inputAttrs = this.#getForwardedInputAttrNames();
|
|
5984
|
+
const nextManaged = new Set(inputAttrs);
|
|
5985
|
+
for (const attrName of this.#managedInputAttrs) {
|
|
5986
|
+
if (!nextManaged.has(attrName)) this.#input.removeAttribute(attrName);
|
|
5987
|
+
}
|
|
5988
|
+
for (const attrName of inputAttrs) {
|
|
5989
|
+
this.#input.setAttribute(attrName, this.getAttribute(attrName) ?? "");
|
|
5990
|
+
}
|
|
5991
|
+
const units = this.#units();
|
|
5992
|
+
if (units) this.#input.setAttribute("units", units);
|
|
5993
|
+
else this.#input.removeAttribute("units");
|
|
5994
|
+
nextManaged.add("units");
|
|
5995
|
+
if (!this.hasAttribute("step")) {
|
|
5996
|
+
this.#input.setAttribute("step", String(this.#defaultStep()));
|
|
5997
|
+
nextManaged.add("step");
|
|
5998
|
+
}
|
|
5999
|
+
if (!this.hasAttribute("precision")) {
|
|
6000
|
+
this.#input.setAttribute("precision", String(this.#defaultPrecision()));
|
|
6001
|
+
nextManaged.add("precision");
|
|
6002
|
+
}
|
|
6003
|
+
this.#managedInputAttrs = nextManaged;
|
|
6004
|
+
this.#syncWheelAria();
|
|
6005
|
+
}
|
|
6006
|
+
|
|
6007
|
+
#setWheelSize(width, height) {
|
|
6008
|
+
if (!this.#svg || width < 1 || height < 1) return;
|
|
6009
|
+
if (width === this.#wheelWidth && height === this.#wheelHeight) return;
|
|
6010
|
+
this.#wheelWidth = width;
|
|
6011
|
+
this.#wheelHeight = height;
|
|
6012
|
+
this.#tickMetrics = null;
|
|
6013
|
+
this.#svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
|
|
6014
|
+
this.#svg.setAttribute("width", String(width));
|
|
6015
|
+
this.#svg.setAttribute("height", String(height));
|
|
6016
|
+
this.#queueWheelLayout();
|
|
6017
|
+
}
|
|
6018
|
+
|
|
6019
|
+
#queueWheelLayout() {
|
|
6020
|
+
if (this.#layoutFrame) return;
|
|
6021
|
+
this.#layoutFrame = requestAnimationFrame(() => {
|
|
6022
|
+
if (!this.isConnected) {
|
|
6023
|
+
this.#layoutFrame = 0;
|
|
6024
|
+
return;
|
|
6025
|
+
}
|
|
6026
|
+
if (this.#wheelWidth < 1 || this.#wheelHeight < 1) {
|
|
6027
|
+
const rect = this.#wheel?.getBoundingClientRect();
|
|
6028
|
+
if (rect) this.#setWheelSize(rect.width, rect.height);
|
|
6029
|
+
}
|
|
6030
|
+
this.#layoutWheel();
|
|
6031
|
+
this.#layoutFrame = 0;
|
|
6032
|
+
});
|
|
6033
|
+
}
|
|
6034
|
+
|
|
6035
|
+
#layoutWheel() {
|
|
6036
|
+
if (!this.#tickPath) return;
|
|
6037
|
+
const width = this.#wheelWidth;
|
|
6038
|
+
const height = this.#wheelHeight;
|
|
6039
|
+
if (width < 1 || height < 1) return;
|
|
6040
|
+
|
|
6041
|
+
const value = this.#visualValue ?? this.#numericValue();
|
|
6042
|
+
const tickStep = 360 / PropskitWheel.#TICK_COUNT;
|
|
6043
|
+
const step = this.#step();
|
|
6044
|
+
const base = this.#boundMin() ?? 0;
|
|
6045
|
+
const offsetDeg = ((value - base) / step) * tickStep;
|
|
6046
|
+
const k = PropskitWheel.#PERSPECTIVE_K;
|
|
6047
|
+
const cx = width / 2;
|
|
6048
|
+
const cy = height / 2;
|
|
6049
|
+
|
|
6050
|
+
const { maxH, minH, maxW, minW, inset } = this.#readTickMetrics();
|
|
6051
|
+
const drawableWidth = Math.max(0, width - inset * 2);
|
|
6052
|
+
const radius = drawableWidth * 0.42;
|
|
6053
|
+
const fov = PropskitWheel.#HALF_FOV_DEG;
|
|
6054
|
+
const visibleHalf = Math.max(drawableWidth / 2, 1);
|
|
6055
|
+
const commands = [];
|
|
6056
|
+
|
|
6057
|
+
for (let index = 0; index < PropskitWheel.#TICK_COUNT; index += 1) {
|
|
6058
|
+
let theta = offsetDeg + index * tickStep;
|
|
6059
|
+
theta = ((((theta + 180) % 360) + 360) % 360) - 180;
|
|
6060
|
+
if (theta < -fov || theta >= fov) continue;
|
|
6061
|
+
const rad = (theta * Math.PI) / 180;
|
|
6062
|
+
const x = cx + (radius * Math.sin(rad)) / (1 - k * Math.cos(rad));
|
|
6063
|
+
const t = Math.min(1, Math.abs(x - cx) / Math.max(visibleHalf, 1));
|
|
6064
|
+
const taper = Math.cos(t * Math.PI * 0.5);
|
|
6065
|
+
const visualH = minH + (maxH - minH) * taper;
|
|
6066
|
+
const visualW = minW + (maxW - minW) * taper;
|
|
6067
|
+
const halfH = visualH / 2;
|
|
6068
|
+
const halfW = visualW / 2;
|
|
6069
|
+
const top = cy - halfH;
|
|
6070
|
+
const bottom = cy + halfH;
|
|
6071
|
+
const left = x - halfW;
|
|
6072
|
+
const right = x + halfW;
|
|
6073
|
+
commands.push(
|
|
6074
|
+
`M ${x} ${top}` +
|
|
6075
|
+
` Q ${right} ${top} ${right} ${top + halfW}` +
|
|
6076
|
+
` V ${bottom - halfW}` +
|
|
6077
|
+
` Q ${right} ${bottom} ${x} ${bottom}` +
|
|
6078
|
+
` Q ${left} ${bottom} ${left} ${bottom - halfW}` +
|
|
6079
|
+
` V ${top + halfW}` +
|
|
6080
|
+
` Q ${left} ${top} ${x} ${top} Z`,
|
|
6081
|
+
);
|
|
6082
|
+
}
|
|
6083
|
+
this.#tickPath.setAttribute("d", commands.join(" "));
|
|
6084
|
+
}
|
|
6085
|
+
|
|
6086
|
+
#bindEvents() {
|
|
6087
|
+
this.#boundHandleInput ??= this.#forwardInputEvent.bind(this, "input");
|
|
6088
|
+
this.#boundHandleChange ??= this.#forwardInputEvent.bind(this, "change");
|
|
6089
|
+
this.#input?.addEventListener("input", this.#boundHandleInput);
|
|
6090
|
+
this.#input?.addEventListener("change", this.#boundHandleChange);
|
|
6091
|
+
this.#surface?.addEventListener("pointerdown", this.#boundPointerDown);
|
|
6092
|
+
this.addEventListener("pointerdown", this.#boundNumberPointerDown, {
|
|
6093
|
+
capture: true,
|
|
6094
|
+
});
|
|
6095
|
+
this.#wheel?.addEventListener("wheel", this.#boundWheel, { passive: false });
|
|
6096
|
+
this.#wheel?.addEventListener("keydown", this.#boundKeyDown);
|
|
6097
|
+
this.addEventListener("click", this.#boundClick, true);
|
|
6098
|
+
}
|
|
6099
|
+
|
|
6100
|
+
#unbindEvents() {
|
|
6101
|
+
this.#input?.removeEventListener("input", this.#boundHandleInput);
|
|
6102
|
+
this.#input?.removeEventListener("change", this.#boundHandleChange);
|
|
6103
|
+
this.#surface?.removeEventListener("pointerdown", this.#boundPointerDown);
|
|
6104
|
+
this.removeEventListener("pointerdown", this.#boundNumberPointerDown, {
|
|
6105
|
+
capture: true,
|
|
6106
|
+
});
|
|
6107
|
+
this.#wheel?.removeEventListener("wheel", this.#boundWheel);
|
|
6108
|
+
this.#wheel?.removeEventListener("keydown", this.#boundKeyDown);
|
|
6109
|
+
this.removeEventListener("click", this.#boundClick, true);
|
|
6110
|
+
this.#stopNumberTracking();
|
|
6111
|
+
clearTimeout(this.#numberClickResetTimer);
|
|
6112
|
+
this.#numberClickResetTimer = 0;
|
|
6113
|
+
window.removeEventListener("pointermove", this.#boundPointerMove);
|
|
6114
|
+
window.removeEventListener("pointerup", this.#boundPointerUp);
|
|
6115
|
+
window.removeEventListener("pointercancel", this.#boundPointerUp);
|
|
6116
|
+
}
|
|
6117
|
+
|
|
6118
|
+
#forwardInputEvent(type, event) {
|
|
6119
|
+
event.stopImmediatePropagation();
|
|
6120
|
+
if (figLabBooleanAttribute(this, "disabled")) return;
|
|
6121
|
+
const raw =
|
|
6122
|
+
event instanceof CustomEvent && event.detail !== undefined
|
|
6123
|
+
? event.detail
|
|
6124
|
+
: this.#input?.value;
|
|
6125
|
+
const parsed = Number(raw);
|
|
6126
|
+
const next = Number.isFinite(parsed) ? parsed : this.#numericValue();
|
|
6127
|
+
this.#commitValue(next, { emit: type });
|
|
6128
|
+
}
|
|
6129
|
+
|
|
6130
|
+
#handleClick(event) {
|
|
6131
|
+
if (figLabBooleanAttribute(this, "disabled")) return;
|
|
6132
|
+
if (
|
|
6133
|
+
event.target instanceof Element &&
|
|
6134
|
+
event.target.closest("fig-input-number, fig-menu")
|
|
6135
|
+
) {
|
|
6136
|
+
if (
|
|
6137
|
+
this.#suppressNumberClick &&
|
|
6138
|
+
event.target.closest("fig-input-number")
|
|
6139
|
+
) {
|
|
6140
|
+
event.preventDefault();
|
|
6141
|
+
event.stopImmediatePropagation();
|
|
6142
|
+
this.#suppressNumberClick = false;
|
|
6143
|
+
}
|
|
6144
|
+
return;
|
|
6145
|
+
}
|
|
6146
|
+
this.#wheel?.focus();
|
|
6147
|
+
}
|
|
6148
|
+
|
|
6149
|
+
#handlePointerDown(event) {
|
|
6150
|
+
if (figLabBooleanAttribute(this, "disabled")) return;
|
|
6151
|
+
if (event.button !== 0) return;
|
|
6152
|
+
if (
|
|
6153
|
+
event.target instanceof Element &&
|
|
6154
|
+
event.target.closest("fig-input-number, fig-menu")
|
|
6155
|
+
) {
|
|
6156
|
+
return;
|
|
6157
|
+
}
|
|
6158
|
+
event.preventDefault();
|
|
6159
|
+
this.#stopKeyboardAnimation();
|
|
6160
|
+
this.#isDragging = true;
|
|
6161
|
+
this.#dragPointerId = event.pointerId;
|
|
6162
|
+
this.#dragStartX = event.clientX;
|
|
6163
|
+
this.#dragStartValue = this.#numericValue();
|
|
6164
|
+
this.#startElasticPull();
|
|
6165
|
+
this.setAttribute("data-active", "");
|
|
6166
|
+
document.body.classList.add(PropskitWheel.#DRAGGING_BODY_CLASS);
|
|
6167
|
+
this.#wheel?.focus();
|
|
6168
|
+
this.#surface?.setPointerCapture?.(event.pointerId);
|
|
6169
|
+
window.addEventListener("pointermove", this.#boundPointerMove);
|
|
6170
|
+
window.addEventListener("pointerup", this.#boundPointerUp);
|
|
6171
|
+
window.addEventListener("pointercancel", this.#boundPointerUp);
|
|
6172
|
+
}
|
|
6173
|
+
|
|
6174
|
+
#handlePointerMove(event) {
|
|
6175
|
+
if (!this.#isDragging) return;
|
|
6176
|
+
if (this.#dragPointerId !== null && event.pointerId !== this.#dragPointerId) {
|
|
6177
|
+
return;
|
|
6178
|
+
}
|
|
6179
|
+
this.#updateDrag(event.clientX, event.shiftKey ? 10 : 1);
|
|
6180
|
+
}
|
|
6181
|
+
|
|
6182
|
+
#updateDrag(clientX, speed = 1) {
|
|
6183
|
+
const width = this.#wheel?.clientWidth || 1;
|
|
6184
|
+
const visibleSteps =
|
|
6185
|
+
PropskitWheel.#TICK_COUNT *
|
|
6186
|
+
((PropskitWheel.#HALF_FOV_DEG * 2) / 360);
|
|
6187
|
+
const visibleUnits = this.#step() * visibleSteps;
|
|
6188
|
+
const delta =
|
|
6189
|
+
(clientX - this.#dragStartX) * (visibleUnits / width) * speed;
|
|
6190
|
+
const rawValue = this.#clamp(this.#dragStartValue + delta);
|
|
6191
|
+
this.#visualValue = rawValue;
|
|
6192
|
+
this.#commitValue(rawValue, {
|
|
6193
|
+
snap: true,
|
|
6194
|
+
emit: "input",
|
|
6195
|
+
});
|
|
6196
|
+
this.#updateElasticPull(clientX);
|
|
6197
|
+
}
|
|
6198
|
+
|
|
6199
|
+
#handlePointerUp(event) {
|
|
6200
|
+
if (!this.#isDragging) return;
|
|
6201
|
+
if (this.#dragPointerId !== null && event.pointerId !== this.#dragPointerId) {
|
|
6202
|
+
return;
|
|
6203
|
+
}
|
|
6204
|
+
if (this.#numericValue() !== this.#dragStartValue) {
|
|
6205
|
+
this.#commitValue(this.#numericValue(), { snap: true, emit: "change" });
|
|
6206
|
+
}
|
|
6207
|
+
this.#stopDrag();
|
|
6208
|
+
}
|
|
6209
|
+
|
|
6210
|
+
#stopDrag() {
|
|
6211
|
+
this.#isDragging = false;
|
|
6212
|
+
this.#dragPointerId = null;
|
|
6213
|
+
this.#visualValue = null;
|
|
6214
|
+
this.removeAttribute("data-active");
|
|
6215
|
+
document.body.classList.remove(PropskitWheel.#DRAGGING_BODY_CLASS);
|
|
6216
|
+
this.#resetElasticPull();
|
|
6217
|
+
window.removeEventListener("pointermove", this.#boundPointerMove);
|
|
6218
|
+
window.removeEventListener("pointerup", this.#boundPointerUp);
|
|
6219
|
+
window.removeEventListener("pointercancel", this.#boundPointerUp);
|
|
6220
|
+
this.#queueWheelLayout();
|
|
6221
|
+
}
|
|
6222
|
+
|
|
6223
|
+
#handleNumberPointerDown(event) {
|
|
6224
|
+
if (
|
|
6225
|
+
event.button !== 0 ||
|
|
6226
|
+
event.altKey ||
|
|
6227
|
+
figLabBooleanAttribute(this, "disabled") ||
|
|
6228
|
+
!(event.target instanceof Element)
|
|
6229
|
+
) {
|
|
6230
|
+
return;
|
|
6231
|
+
}
|
|
6232
|
+
const input = event.target.closest("fig-input-number input");
|
|
6233
|
+
if (!input || input === document.activeElement) return;
|
|
6234
|
+
|
|
6235
|
+
event.preventDefault();
|
|
6236
|
+
event.stopImmediatePropagation();
|
|
6237
|
+
this.#stopNumberTracking();
|
|
6238
|
+
this.#numberPointerId = event.pointerId;
|
|
6239
|
+
this.#numberPointerStartX = event.clientX;
|
|
6240
|
+
this.#numberPointerStartY = event.clientY;
|
|
6241
|
+
this.#numberPointerStartValue = this.#numericValue();
|
|
6242
|
+
window.addEventListener("pointermove", this.#boundNumberPointerMove);
|
|
6243
|
+
window.addEventListener("pointerup", this.#boundNumberPointerEnd, {
|
|
6244
|
+
once: true,
|
|
6245
|
+
});
|
|
6246
|
+
window.addEventListener("pointercancel", this.#boundNumberPointerEnd, {
|
|
6247
|
+
once: true,
|
|
6248
|
+
});
|
|
6249
|
+
window.addEventListener("blur", this.#boundNumberPointerEnd, {
|
|
6250
|
+
once: true,
|
|
6251
|
+
});
|
|
6252
|
+
}
|
|
6253
|
+
|
|
6254
|
+
#handleNumberPointerMove(event) {
|
|
6255
|
+
if (event.pointerId !== this.#numberPointerId) return;
|
|
6256
|
+
if (event.buttons === 0) {
|
|
6257
|
+
this.#handleNumberPointerEnd(event);
|
|
6258
|
+
return;
|
|
6259
|
+
}
|
|
6260
|
+
if (!this.#isNumberScrubbing) {
|
|
6261
|
+
const distance = Math.hypot(
|
|
6262
|
+
event.clientX - this.#numberPointerStartX,
|
|
6263
|
+
event.clientY - this.#numberPointerStartY,
|
|
6264
|
+
);
|
|
6265
|
+
if (distance < 4) return;
|
|
6266
|
+
this.#stopKeyboardAnimation();
|
|
6267
|
+
this.#isNumberScrubbing = true;
|
|
6268
|
+
this.setAttribute("data-number-scrubbing", "");
|
|
6269
|
+
this.#isDragging = true;
|
|
6270
|
+
this.#dragPointerId = event.pointerId;
|
|
6271
|
+
this.#dragStartX = this.#numberPointerStartX;
|
|
6272
|
+
this.#dragStartValue = this.#numberPointerStartValue;
|
|
6273
|
+
this.#startElasticPull();
|
|
6274
|
+
this.setAttribute("data-active", "");
|
|
6275
|
+
document.body.classList.add(PropskitWheel.#DRAGGING_BODY_CLASS);
|
|
6276
|
+
this.#wheel?.focus();
|
|
6277
|
+
}
|
|
6278
|
+
this.#updateDrag(event.clientX, event.shiftKey ? 10 : 1);
|
|
6279
|
+
}
|
|
6280
|
+
|
|
6281
|
+
#handleNumberPointerEnd(event) {
|
|
6282
|
+
if (
|
|
6283
|
+
event?.pointerId !== undefined &&
|
|
6284
|
+
this.#numberPointerId !== null &&
|
|
6285
|
+
event.pointerId !== this.#numberPointerId
|
|
6286
|
+
) {
|
|
6287
|
+
return;
|
|
6288
|
+
}
|
|
6289
|
+
const wasScrubbing = this.#isNumberScrubbing;
|
|
6290
|
+
this.#stopNumberTracking();
|
|
6291
|
+
if (wasScrubbing) {
|
|
6292
|
+
this.#commitValue(this.#numericValue(), { snap: true, emit: "change" });
|
|
6293
|
+
this.#stopDrag();
|
|
6294
|
+
this.#suppressNumberClick = true;
|
|
6295
|
+
clearTimeout(this.#numberClickResetTimer);
|
|
6296
|
+
this.#numberClickResetTimer = window.setTimeout(() => {
|
|
6297
|
+
this.#suppressNumberClick = false;
|
|
6298
|
+
this.#numberClickResetTimer = 0;
|
|
6299
|
+
}, 0);
|
|
6300
|
+
requestAnimationFrame(() => this.#wheel?.focus());
|
|
6301
|
+
return;
|
|
6302
|
+
}
|
|
6303
|
+
this.#input?.querySelector("input")?.focus();
|
|
6304
|
+
}
|
|
6305
|
+
|
|
6306
|
+
#stopNumberTracking() {
|
|
6307
|
+
window.removeEventListener("pointermove", this.#boundNumberPointerMove);
|
|
6308
|
+
window.removeEventListener("pointerup", this.#boundNumberPointerEnd);
|
|
6309
|
+
window.removeEventListener("pointercancel", this.#boundNumberPointerEnd);
|
|
6310
|
+
window.removeEventListener("blur", this.#boundNumberPointerEnd);
|
|
6311
|
+
this.#numberPointerId = null;
|
|
6312
|
+
this.#isNumberScrubbing = false;
|
|
6313
|
+
this.removeAttribute("data-number-scrubbing");
|
|
6314
|
+
}
|
|
6315
|
+
|
|
6316
|
+
#startElasticPull() {
|
|
6317
|
+
this.#resetElasticPull();
|
|
6318
|
+
if (this.getAttribute("elastic") === "false") return;
|
|
6319
|
+
this.#handleDragMaxPx = this.#readCssLength(
|
|
6320
|
+
"--propskit-wheel-handle-drag-max",
|
|
6321
|
+
);
|
|
6322
|
+
this.#elasticMaxPx = this.#readCssLength(
|
|
6323
|
+
"--propskit-wheel-elastic-distance",
|
|
6324
|
+
);
|
|
6325
|
+
const rect = this.#wheel?.getBoundingClientRect();
|
|
6326
|
+
if (rect) {
|
|
6327
|
+
this.#elasticRangeRect = {
|
|
6328
|
+
left: rect.left,
|
|
6329
|
+
right: rect.right,
|
|
6330
|
+
};
|
|
6331
|
+
}
|
|
6332
|
+
this.#elasticHostWidth = this.getBoundingClientRect().width;
|
|
6333
|
+
}
|
|
6334
|
+
|
|
6335
|
+
#readCssLength(propertyName) {
|
|
6336
|
+
let raw = getComputedStyle(this)
|
|
6337
|
+
.getPropertyValue(propertyName)
|
|
6338
|
+
.trim();
|
|
6339
|
+
if (raw.includes("var(") || !raw.endsWith("px")) {
|
|
6340
|
+
const probe = document.createElement("div");
|
|
6341
|
+
Object.assign(probe.style, {
|
|
6342
|
+
position: "absolute",
|
|
6343
|
+
visibility: "hidden",
|
|
6344
|
+
pointerEvents: "none",
|
|
6345
|
+
width: `var(${propertyName})`,
|
|
6346
|
+
});
|
|
6347
|
+
this.appendChild(probe);
|
|
6348
|
+
raw = getComputedStyle(probe).width;
|
|
6349
|
+
probe.remove();
|
|
6350
|
+
}
|
|
6351
|
+
const value = Number.parseFloat(raw);
|
|
6352
|
+
return Number.isFinite(value) ? Math.max(0, value) : 0;
|
|
6353
|
+
}
|
|
6354
|
+
|
|
6355
|
+
#updateElasticPull(pointerX) {
|
|
6356
|
+
if (!this.#handleDragMaxPx && !this.#elasticMaxPx) {
|
|
6357
|
+
this.#clearElasticPull();
|
|
6358
|
+
return;
|
|
6359
|
+
}
|
|
6360
|
+
const dragDelta = pointerX - this.#dragStartX;
|
|
6361
|
+
if (!dragDelta) {
|
|
6362
|
+
this.#clearElasticPull();
|
|
6363
|
+
return;
|
|
6364
|
+
}
|
|
6365
|
+
const offset =
|
|
6366
|
+
this.#handleDragMaxPx *
|
|
6367
|
+
Math.tanh(
|
|
6368
|
+
dragDelta / Math.max(1, this.#handleDragMaxPx * 3),
|
|
6369
|
+
);
|
|
6370
|
+
this.dataset.elasticDragging = "true";
|
|
6371
|
+
this.style.setProperty(
|
|
6372
|
+
"--propskit-wheel-handle-drag-offset",
|
|
6373
|
+
`${offset}px`,
|
|
6374
|
+
);
|
|
6375
|
+
|
|
6376
|
+
const rect = this.#elasticRangeRect;
|
|
6377
|
+
const overshoot = rect
|
|
6378
|
+
? pointerX < rect.left
|
|
6379
|
+
? pointerX - rect.left
|
|
6380
|
+
: pointerX > rect.right
|
|
6381
|
+
? pointerX - rect.right
|
|
6382
|
+
: 0
|
|
6383
|
+
: 0;
|
|
6384
|
+
if (!overshoot || !this.#elasticMaxPx) {
|
|
6385
|
+
this.style.removeProperty("--propskit-wheel-elastic-scale");
|
|
6386
|
+
this.style.removeProperty("--propskit-wheel-elastic-origin");
|
|
6387
|
+
return;
|
|
6388
|
+
}
|
|
6389
|
+
const stretch = Math.min(this.#elasticMaxPx, Math.abs(overshoot) * 0.5);
|
|
6390
|
+
const scale = this.#elasticHostWidth
|
|
6391
|
+
? (this.#elasticHostWidth + stretch) / this.#elasticHostWidth
|
|
6392
|
+
: 1;
|
|
6393
|
+
this.style.setProperty("--propskit-wheel-elastic-scale", `${scale}`);
|
|
6394
|
+
this.style.setProperty(
|
|
6395
|
+
"--propskit-wheel-elastic-origin",
|
|
6396
|
+
overshoot < 0 ? "right center" : "left center",
|
|
6397
|
+
);
|
|
6398
|
+
}
|
|
6399
|
+
|
|
6400
|
+
#resetElasticPull() {
|
|
6401
|
+
this.#clearElasticPull();
|
|
6402
|
+
this.#handleDragMaxPx = 0;
|
|
6403
|
+
this.#elasticMaxPx = 0;
|
|
6404
|
+
this.#elasticRangeRect = null;
|
|
6405
|
+
this.#elasticHostWidth = 0;
|
|
6406
|
+
}
|
|
6407
|
+
|
|
6408
|
+
#clearElasticPull() {
|
|
6409
|
+
this.removeAttribute("data-elastic-dragging");
|
|
6410
|
+
this.style.removeProperty("--propskit-wheel-handle-drag-offset");
|
|
6411
|
+
this.style.removeProperty("--propskit-wheel-elastic-scale");
|
|
6412
|
+
this.style.removeProperty("--propskit-wheel-elastic-origin");
|
|
6413
|
+
}
|
|
6414
|
+
|
|
6415
|
+
#handleWheel(event) {
|
|
6416
|
+
if (figLabBooleanAttribute(this, "disabled")) return;
|
|
6417
|
+
event.preventDefault();
|
|
6418
|
+
const direction = event.deltaY === 0 ? Math.sign(event.deltaX) : Math.sign(event.deltaY);
|
|
6419
|
+
if (!direction) return;
|
|
6420
|
+
const multiplier = event.shiftKey ? 10 : 1;
|
|
6421
|
+
this.#commitValue(this.#numericValue() + this.#step() * multiplier * direction, {
|
|
6422
|
+
snap: true,
|
|
6423
|
+
emit: "input",
|
|
6424
|
+
});
|
|
6425
|
+
this.#commitValue(this.#numericValue(), { emit: "change" });
|
|
6426
|
+
}
|
|
6427
|
+
|
|
6428
|
+
#handleKeyDown(event) {
|
|
6429
|
+
if (figLabBooleanAttribute(this, "disabled")) return;
|
|
6430
|
+
if (
|
|
6431
|
+
event.target instanceof Element &&
|
|
6432
|
+
event.target.closest("fig-input-number")
|
|
6433
|
+
) {
|
|
6434
|
+
return;
|
|
6435
|
+
}
|
|
6436
|
+
if (event.key === "Home") {
|
|
6437
|
+
const min = this.#boundMin();
|
|
6438
|
+
if (min === null) return;
|
|
6439
|
+
event.preventDefault();
|
|
6440
|
+
this.#commitValue(min, { snap: true, emit: "input" });
|
|
6441
|
+
this.#commitValue(this.#numericValue(), { emit: "change" });
|
|
6442
|
+
return;
|
|
6443
|
+
}
|
|
6444
|
+
if (event.key === "End") {
|
|
6445
|
+
const max = this.#boundMax();
|
|
6446
|
+
if (max === null) return;
|
|
6447
|
+
event.preventDefault();
|
|
6448
|
+
this.#commitValue(max, { snap: true, emit: "input" });
|
|
6449
|
+
this.#commitValue(this.#numericValue(), { emit: "change" });
|
|
6450
|
+
return;
|
|
6451
|
+
}
|
|
6452
|
+
const keys = ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"];
|
|
6453
|
+
if (!keys.includes(event.key)) return;
|
|
6454
|
+
event.preventDefault();
|
|
6455
|
+
const direction =
|
|
6456
|
+
event.key === "ArrowRight" || event.key === "ArrowUp" ? 1 : -1;
|
|
6457
|
+
const multiplier = event.shiftKey ? 10 : 1;
|
|
6458
|
+
const previousValue = this.#numericValue();
|
|
6459
|
+
this.#commitValue(previousValue + this.#step() * multiplier * direction, {
|
|
6460
|
+
snap: true,
|
|
6461
|
+
emit: "input",
|
|
6462
|
+
});
|
|
6463
|
+
this.#animateKeyboardMovement(
|
|
6464
|
+
previousValue,
|
|
6465
|
+
this.#numericValue(),
|
|
6466
|
+
direction,
|
|
6467
|
+
multiplier,
|
|
6468
|
+
);
|
|
6469
|
+
this.#commitValue(this.#numericValue(), { emit: "change" });
|
|
6470
|
+
}
|
|
6471
|
+
|
|
6472
|
+
#animateKeyboardMovement(from, to, direction, multiplier) {
|
|
6473
|
+
if (
|
|
6474
|
+
from === to ||
|
|
6475
|
+
globalThis.matchMedia?.("(prefers-reduced-motion: reduce)").matches
|
|
6476
|
+
) {
|
|
6477
|
+
this.#stopKeyboardAnimation();
|
|
6478
|
+
return;
|
|
6479
|
+
}
|
|
6480
|
+
|
|
6481
|
+
if (this.#keyboardAnimationFrame) {
|
|
6482
|
+
cancelAnimationFrame(this.#keyboardAnimationFrame);
|
|
6483
|
+
this.#keyboardAnimationFrame = 0;
|
|
6484
|
+
}
|
|
6485
|
+
clearTimeout(this.#keyboardSettleTimer);
|
|
6486
|
+
this.#keyboardSettleTimer = 0;
|
|
6487
|
+
this.removeAttribute("data-keyboard-settling");
|
|
6488
|
+
const visualFrom = this.#visualValue ?? from;
|
|
6489
|
+
const handleFrom =
|
|
6490
|
+
Number.parseFloat(
|
|
6491
|
+
this.style.getPropertyValue("--propskit-wheel-handle-drag-offset"),
|
|
6492
|
+
) || 0;
|
|
6493
|
+
const duration = multiplier > 1 ? 120 : 90;
|
|
6494
|
+
const startedAt = performance.now();
|
|
6495
|
+
const animateHandle = this.getAttribute("elastic") !== "false";
|
|
6496
|
+
this.setAttribute("data-keyboard-moving", "");
|
|
6497
|
+
|
|
6498
|
+
const frame = (now) => {
|
|
6499
|
+
const progress = Math.min(1, (now - startedAt) / duration);
|
|
6500
|
+
this.#visualValue = visualFrom + (to - visualFrom) * progress;
|
|
6501
|
+
if (animateHandle) {
|
|
6502
|
+
const eased = Math.sin((progress * Math.PI) / 2);
|
|
6503
|
+
const offset = handleFrom + (direction * 4 - handleFrom) * eased;
|
|
6504
|
+
this.style.setProperty(
|
|
6505
|
+
"--propskit-wheel-handle-drag-offset",
|
|
6506
|
+
`${offset}px`,
|
|
6507
|
+
);
|
|
6508
|
+
}
|
|
6509
|
+
this.#layoutWheel();
|
|
6510
|
+
|
|
6511
|
+
if (progress < 1) {
|
|
6512
|
+
this.#keyboardAnimationFrame = requestAnimationFrame(frame);
|
|
6513
|
+
return;
|
|
6514
|
+
}
|
|
6515
|
+
|
|
6516
|
+
this.#keyboardAnimationFrame = 0;
|
|
6517
|
+
this.#visualValue = null;
|
|
6518
|
+
this.removeAttribute("data-keyboard-moving");
|
|
6519
|
+
this.setAttribute("data-keyboard-settling", "");
|
|
6520
|
+
this.style.removeProperty("--propskit-wheel-handle-drag-offset");
|
|
6521
|
+
this.#keyboardSettleTimer = window.setTimeout(() => {
|
|
6522
|
+
this.removeAttribute("data-keyboard-settling");
|
|
6523
|
+
this.#keyboardSettleTimer = 0;
|
|
6524
|
+
}, 140);
|
|
6525
|
+
this.#queueWheelLayout();
|
|
6526
|
+
};
|
|
6527
|
+
|
|
6528
|
+
this.#keyboardAnimationFrame = requestAnimationFrame(frame);
|
|
6529
|
+
}
|
|
6530
|
+
|
|
6531
|
+
#stopKeyboardAnimation() {
|
|
6532
|
+
if (this.#keyboardAnimationFrame) {
|
|
6533
|
+
cancelAnimationFrame(this.#keyboardAnimationFrame);
|
|
6534
|
+
this.#keyboardAnimationFrame = 0;
|
|
6535
|
+
}
|
|
6536
|
+
clearTimeout(this.#keyboardSettleTimer);
|
|
6537
|
+
this.#keyboardSettleTimer = 0;
|
|
6538
|
+
this.#visualValue = null;
|
|
6539
|
+
this.removeAttribute("data-keyboard-moving");
|
|
6540
|
+
this.removeAttribute("data-keyboard-settling");
|
|
6541
|
+
this.style.removeProperty("--propskit-wheel-handle-drag-offset");
|
|
6542
|
+
if (this.isConnected) this.#queueWheelLayout();
|
|
6543
|
+
}
|
|
6544
|
+
|
|
6545
|
+
get value() {
|
|
6546
|
+
return this.getAttribute("value") ?? "0";
|
|
6547
|
+
}
|
|
6548
|
+
|
|
6549
|
+
set value(nextValue) {
|
|
6550
|
+
if (nextValue === null || nextValue === undefined || nextValue === "") {
|
|
6551
|
+
this.#commitValue(0);
|
|
6552
|
+
return;
|
|
6553
|
+
}
|
|
6554
|
+
const parsed = Number(nextValue);
|
|
6555
|
+
this.#commitValue(Number.isFinite(parsed) ? parsed : 0);
|
|
6556
|
+
}
|
|
6557
|
+
|
|
6558
|
+
get min() {
|
|
6559
|
+
return this.#boundMin();
|
|
6560
|
+
}
|
|
6561
|
+
|
|
6562
|
+
set min(nextValue) {
|
|
6563
|
+
this.#writeBound("min", nextValue);
|
|
6564
|
+
}
|
|
6565
|
+
|
|
6566
|
+
get max() {
|
|
6567
|
+
return this.#boundMax();
|
|
6568
|
+
}
|
|
6569
|
+
|
|
6570
|
+
set max(nextValue) {
|
|
6571
|
+
this.#writeBound("max", nextValue);
|
|
6572
|
+
}
|
|
6573
|
+
|
|
6574
|
+
get defaultValue() {
|
|
6575
|
+
return this.getAttribute("default") ?? this.#initialValue ?? "0";
|
|
6576
|
+
}
|
|
6577
|
+
|
|
6578
|
+
get isDefault() {
|
|
6579
|
+
return figLabPropskitValuesEqual(this.value, this.defaultValue);
|
|
6580
|
+
}
|
|
6581
|
+
|
|
6582
|
+
resetToDefault() {
|
|
6583
|
+
const parsed = Number(this.defaultValue);
|
|
6584
|
+
const value = Number.isFinite(parsed) ? parsed : 0;
|
|
6585
|
+
this.#commitValue(value);
|
|
6586
|
+
figLabEmitPropskitReset(this, value);
|
|
6587
|
+
}
|
|
6588
|
+
|
|
6589
|
+
focus(options) {
|
|
6590
|
+
this.#wheel?.focus(options);
|
|
6591
|
+
}
|
|
6592
|
+
}
|
|
6593
|
+
figLabDefineElement("propskit-wheel", PropskitWheel);
|
|
6594
|
+
|
|
5458
6595
|
/* Canvas Control */
|
|
5459
6596
|
class FigCanvasControl extends HTMLElement {
|
|
5460
6597
|
static observedAttributes = [
|