@rogieking/figui3 8.9.49 → 8.10.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 +3 -3
- package/.cursor/skills/fig-editor/components.md +2 -2
- package/.cursor/skills/fig-editor/reference.md +3 -0
- package/.cursor/skills/fig-lab/SKILL.md +14 -5
- package/.cursor/skills/fig-lab/components.md +90 -12
- package/.cursor/skills/fig-lab/reference.md +107 -11
- package/.cursor/skills/figui3/components.md +3 -0
- package/.cursor/skills/figui3/react.md +4 -0
- package/README.md +171 -56
- package/components.css +34 -12
- package/dist/components.css +1 -1
- package/dist/fig-editor.css +1 -1
- package/dist/fig-editor.js +18 -5
- package/dist/fig-lab.css +1 -1
- package/dist/fig-lab.js +1 -3
- package/dist/fig.css +1 -1
- package/dist/fig.js +7 -7
- package/fig-editor.css +10 -0
- package/fig-editor.js +100 -10
- package/fig-lab.css +1496 -452
- package/fig-lab.js +2171 -1146
- package/fig.js +73 -30
- package/package.json +1 -1
package/fig-lab.js
CHANGED
|
@@ -163,16 +163,38 @@ function figLabDisconnectPropskitResetMenu(host) {
|
|
|
163
163
|
state.menu.removeEventListener("change", state.handleChange);
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
function
|
|
166
|
+
function figLabPropskitEventDetail(host, value = host.value) {
|
|
167
|
+
const detail = {
|
|
168
|
+
control: host.localName,
|
|
169
|
+
value,
|
|
170
|
+
};
|
|
171
|
+
const name = host.getAttribute("name");
|
|
172
|
+
if (name !== null && name !== "") detail.name = name;
|
|
173
|
+
return detail;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function figLabDispatchPropskitEvent(host, type, value = host.value) {
|
|
177
|
+
host.dispatchEvent(
|
|
178
|
+
new CustomEvent(type, {
|
|
179
|
+
detail: figLabPropskitEventDetail(host, value),
|
|
180
|
+
bubbles: true,
|
|
181
|
+
cancelable: true,
|
|
182
|
+
composed: true,
|
|
183
|
+
}),
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function figLabFiniteNumberOrNull(value) {
|
|
188
|
+
if (value === null || value === undefined || String(value).trim() === "") {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
const number = Number(value);
|
|
192
|
+
return Number.isFinite(number) ? number : null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function figLabEmitPropskitReset(host) {
|
|
167
196
|
for (const type of ["input", "change"]) {
|
|
168
|
-
host
|
|
169
|
-
new CustomEvent(type, {
|
|
170
|
-
detail,
|
|
171
|
-
bubbles: true,
|
|
172
|
-
cancelable: true,
|
|
173
|
-
composed: true,
|
|
174
|
-
}),
|
|
175
|
-
);
|
|
197
|
+
figLabDispatchPropskitEvent(host, type);
|
|
176
198
|
}
|
|
177
199
|
}
|
|
178
200
|
|
|
@@ -242,6 +264,66 @@ function figLabUniqueId(prefix = "fig-lab") {
|
|
|
242
264
|
return `${prefix}-${figLabUniqueIdCounter}`;
|
|
243
265
|
}
|
|
244
266
|
|
|
267
|
+
class FigLabPropskitElement extends HTMLElement {
|
|
268
|
+
get name() {
|
|
269
|
+
return this.getAttribute("name") ?? "";
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
set name(value) {
|
|
273
|
+
if (value === null || value === undefined || value === "") {
|
|
274
|
+
this.removeAttribute("name");
|
|
275
|
+
} else {
|
|
276
|
+
this.setAttribute("name", String(value));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function figLabSyncPropskitLabel(
|
|
282
|
+
host,
|
|
283
|
+
surface,
|
|
284
|
+
label,
|
|
285
|
+
hasCustomLabel = false,
|
|
286
|
+
) {
|
|
287
|
+
if (!surface || !label) return null;
|
|
288
|
+
const hasLabelAttr = host.hasAttribute("label");
|
|
289
|
+
const rawLabel = host.getAttribute("label") ?? "";
|
|
290
|
+
const hidden = hasLabelAttr && rawLabel.trim() === "";
|
|
291
|
+
|
|
292
|
+
if (!hasCustomLabel) {
|
|
293
|
+
label.textContent = hasLabelAttr ? rawLabel : "Label";
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
host.toggleAttribute("data-label-empty", hidden);
|
|
297
|
+
if (hidden) {
|
|
298
|
+
label.remove();
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (!label.id) label.id = figLabUniqueId(`${host.localName}-label`);
|
|
303
|
+
if (label.parentElement !== surface) surface.prepend(label);
|
|
304
|
+
return label.id;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function figLabSyncPropskitControlLabel(
|
|
308
|
+
host,
|
|
309
|
+
control,
|
|
310
|
+
labelId,
|
|
311
|
+
fallback = "Label",
|
|
312
|
+
) {
|
|
313
|
+
if (!control) return;
|
|
314
|
+
const explicitLabel = host.getAttribute("aria-label")?.trim();
|
|
315
|
+
if (explicitLabel) {
|
|
316
|
+
control.setAttribute("aria-label", explicitLabel);
|
|
317
|
+
control.removeAttribute("aria-labelledby");
|
|
318
|
+
} else if (labelId) {
|
|
319
|
+
control.setAttribute("aria-labelledby", labelId);
|
|
320
|
+
control.removeAttribute("aria-label");
|
|
321
|
+
} else {
|
|
322
|
+
control.setAttribute("aria-label", fallback);
|
|
323
|
+
control.removeAttribute("aria-labelledby");
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
245
327
|
/* Presentation-only composition surface for AI prompt controls. */
|
|
246
328
|
class FigAiPrompt extends HTMLElement {}
|
|
247
329
|
figLabDefineElement("fig-ai-prompt", FigAiPrompt);
|
|
@@ -453,9 +535,9 @@ class FigAttachments extends HTMLElement {
|
|
|
453
535
|
}
|
|
454
536
|
figLabDefineElement("fig-attachments", FigAttachments);
|
|
455
537
|
|
|
456
|
-
/*
|
|
457
|
-
class PropskitSwitch extends
|
|
458
|
-
#
|
|
538
|
+
/* PropsKit switch surface */
|
|
539
|
+
class PropskitSwitch extends FigLabPropskitElement {
|
|
540
|
+
#surface = null;
|
|
459
541
|
#label = null;
|
|
460
542
|
#switch = null;
|
|
461
543
|
#hasCustomLabel = false;
|
|
@@ -467,12 +549,12 @@ class PropskitSwitch extends HTMLElement {
|
|
|
467
549
|
#initialChecked = false;
|
|
468
550
|
|
|
469
551
|
static get observedAttributes() {
|
|
470
|
-
return ["label", "
|
|
552
|
+
return ["label", "aria-label", "variant"];
|
|
471
553
|
}
|
|
472
554
|
|
|
473
555
|
connectedCallback() {
|
|
474
|
-
if (!this.#
|
|
475
|
-
this.#
|
|
556
|
+
if (!this.#surface) this.#initialize();
|
|
557
|
+
this.#syncSurface();
|
|
476
558
|
this.#syncSwitchAttributes();
|
|
477
559
|
this.#bindSwitchEvents();
|
|
478
560
|
this.removeEventListener("click", this.#boundHandleClick);
|
|
@@ -481,22 +563,22 @@ class PropskitSwitch extends HTMLElement {
|
|
|
481
563
|
|
|
482
564
|
if (!this.#observer) {
|
|
483
565
|
this.#observer = new MutationObserver((mutations) => {
|
|
484
|
-
let
|
|
566
|
+
let syncSurface = false;
|
|
485
567
|
let syncSwitch = false;
|
|
486
568
|
|
|
487
569
|
for (const mutation of mutations) {
|
|
488
570
|
if (mutation.type !== "attributes") continue;
|
|
489
571
|
if (
|
|
490
572
|
mutation.attributeName === "label" ||
|
|
491
|
-
mutation.attributeName === "
|
|
573
|
+
mutation.attributeName === "aria-label"
|
|
492
574
|
) {
|
|
493
|
-
|
|
575
|
+
syncSurface = true;
|
|
494
576
|
} else {
|
|
495
577
|
syncSwitch = true;
|
|
496
578
|
}
|
|
497
579
|
}
|
|
498
580
|
|
|
499
|
-
if (
|
|
581
|
+
if (syncSurface) this.#syncSurface();
|
|
500
582
|
if (syncSwitch) this.#syncSwitchAttributes();
|
|
501
583
|
});
|
|
502
584
|
}
|
|
@@ -512,8 +594,12 @@ class PropskitSwitch extends HTMLElement {
|
|
|
512
594
|
}
|
|
513
595
|
|
|
514
596
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
515
|
-
if (oldValue === newValue || !this.#
|
|
516
|
-
if (name === "
|
|
597
|
+
if (oldValue === newValue || !this.#surface) return;
|
|
598
|
+
if (name === "variant") {
|
|
599
|
+
this.#syncSwitchAttributes();
|
|
600
|
+
} else if (name === "label" || name === "aria-label") {
|
|
601
|
+
this.#syncSurface();
|
|
602
|
+
}
|
|
517
603
|
}
|
|
518
604
|
|
|
519
605
|
#initialize() {
|
|
@@ -525,48 +611,68 @@ class PropskitSwitch extends HTMLElement {
|
|
|
525
611
|
const customLabel = initialChildren.find(
|
|
526
612
|
(node) => node.nodeType === Node.ELEMENT_NODE && node.matches("label"),
|
|
527
613
|
);
|
|
528
|
-
const
|
|
614
|
+
const surface = figLabCreateElement("div", {
|
|
615
|
+
className: "propskit-switch-surface",
|
|
616
|
+
});
|
|
529
617
|
const label = customLabel || document.createElement("label");
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
const onSegment = document.createElement("fig-segment");
|
|
533
|
-
switchControl.setAttribute("sizing", "equal");
|
|
534
|
-
offSegment.setAttribute("value", "off");
|
|
535
|
-
offSegment.textContent = "Off";
|
|
536
|
-
onSegment.setAttribute("value", "on");
|
|
537
|
-
onSegment.textContent = "On";
|
|
538
|
-
switchControl.append(offSegment, onSegment);
|
|
539
|
-
|
|
540
|
-
field.append(label, switchControl);
|
|
541
|
-
this.#field = field;
|
|
618
|
+
surface.append(label);
|
|
619
|
+
this.#surface = surface;
|
|
542
620
|
this.#label = label;
|
|
543
|
-
this.#switch = switchControl;
|
|
544
621
|
this.#hasCustomLabel = Boolean(customLabel);
|
|
545
|
-
this.replaceChildren(
|
|
622
|
+
this.replaceChildren(surface);
|
|
623
|
+
this.#ensureSwitchControl();
|
|
624
|
+
}
|
|
546
625
|
|
|
626
|
+
#controlVariant() {
|
|
627
|
+
return this.getAttribute("variant") === "segmented-control"
|
|
628
|
+
? "segmented-control"
|
|
629
|
+
: "switch";
|
|
547
630
|
}
|
|
548
631
|
|
|
549
|
-
#
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
632
|
+
#ensureSwitchControl() {
|
|
633
|
+
const tagName =
|
|
634
|
+
this.#controlVariant() === "segmented-control"
|
|
635
|
+
? "fig-segmented-control"
|
|
636
|
+
: "fig-switch";
|
|
637
|
+
if (this.#switch?.localName === tagName) return;
|
|
554
638
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
639
|
+
this.#unbindSwitchEvents();
|
|
640
|
+
this.#switch?.remove();
|
|
641
|
+
|
|
642
|
+
const switchControl = document.createElement(tagName);
|
|
643
|
+
if (tagName === "fig-segmented-control") {
|
|
644
|
+
const offSegment = document.createElement("fig-segment");
|
|
645
|
+
const onSegment = document.createElement("fig-segment");
|
|
646
|
+
switchControl.setAttribute("sizing", "equal");
|
|
647
|
+
offSegment.setAttribute("value", "off");
|
|
648
|
+
offSegment.textContent = "Off";
|
|
649
|
+
onSegment.setAttribute("value", "on");
|
|
650
|
+
onSegment.textContent = "On";
|
|
651
|
+
switchControl.append(offSegment, onSegment);
|
|
564
652
|
}
|
|
565
653
|
|
|
566
|
-
this.#
|
|
567
|
-
|
|
568
|
-
|
|
654
|
+
this.#switch = switchControl;
|
|
655
|
+
this.#managedSwitchAttrs.clear();
|
|
656
|
+
this.#surface?.append(switchControl);
|
|
657
|
+
if (this.isConnected) this.#bindSwitchEvents();
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
#syncSurface() {
|
|
661
|
+
const labelId = figLabSyncPropskitLabel(
|
|
662
|
+
this,
|
|
663
|
+
this.#surface,
|
|
664
|
+
this.#label,
|
|
665
|
+
this.#hasCustomLabel,
|
|
569
666
|
);
|
|
667
|
+
figLabSyncPropskitControlLabel(this, this.#switch, labelId, "Switch");
|
|
668
|
+
if (this.#switch?.localName === "fig-switch" && this.#switch.input) {
|
|
669
|
+
figLabSyncPropskitControlLabel(
|
|
670
|
+
this,
|
|
671
|
+
this.#switch.input,
|
|
672
|
+
labelId,
|
|
673
|
+
"Switch",
|
|
674
|
+
);
|
|
675
|
+
}
|
|
570
676
|
}
|
|
571
677
|
|
|
572
678
|
#getForwardedSwitchAttrNames() {
|
|
@@ -574,6 +680,7 @@ class PropskitSwitch extends HTMLElement {
|
|
|
574
680
|
"label",
|
|
575
681
|
"direction",
|
|
576
682
|
"size",
|
|
683
|
+
"aria-label",
|
|
577
684
|
"oninput",
|
|
578
685
|
"onchange",
|
|
579
686
|
"class",
|
|
@@ -590,6 +697,7 @@ class PropskitSwitch extends HTMLElement {
|
|
|
590
697
|
}
|
|
591
698
|
|
|
592
699
|
#syncSwitchAttributes() {
|
|
700
|
+
this.#ensureSwitchControl();
|
|
593
701
|
if (!this.#switch) return;
|
|
594
702
|
const switchAttrs = this.#getForwardedSwitchAttrNames();
|
|
595
703
|
const nextManaged = new Set(switchAttrs);
|
|
@@ -601,11 +709,15 @@ class PropskitSwitch extends HTMLElement {
|
|
|
601
709
|
this.#switch.setAttribute(attrName, this.getAttribute(attrName) ?? "");
|
|
602
710
|
}
|
|
603
711
|
|
|
604
|
-
this
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
712
|
+
const checked = figLabBooleanAttribute(this, "checked");
|
|
713
|
+
if (this.#switch.localName === "fig-switch") {
|
|
714
|
+
this.#switch.checked = checked;
|
|
715
|
+
this.#switch.value = checked ? "on" : "off";
|
|
716
|
+
} else {
|
|
717
|
+
this.#switch.setAttribute("value", checked ? "on" : "off");
|
|
718
|
+
}
|
|
608
719
|
this.#managedSwitchAttrs = nextManaged;
|
|
720
|
+
this.#syncSurface();
|
|
609
721
|
}
|
|
610
722
|
|
|
611
723
|
#bindSwitchEvents() {
|
|
@@ -629,52 +741,59 @@ class PropskitSwitch extends HTMLElement {
|
|
|
629
741
|
#forwardSwitchEvent(type, event) {
|
|
630
742
|
event.stopImmediatePropagation();
|
|
631
743
|
if (figLabBooleanAttribute(this, "disabled")) return;
|
|
632
|
-
const checked =
|
|
744
|
+
const checked =
|
|
745
|
+
this.#switch?.localName === "fig-switch"
|
|
746
|
+
? Boolean(this.#switch.checked)
|
|
747
|
+
: this.#switch?.value === "on";
|
|
633
748
|
this.toggleAttribute("checked", checked);
|
|
634
|
-
|
|
635
|
-
checked,
|
|
636
|
-
value: this.getAttribute("value") ?? "",
|
|
637
|
-
};
|
|
638
|
-
this.dispatchEvent(
|
|
639
|
-
new CustomEvent(type, {
|
|
640
|
-
detail,
|
|
641
|
-
bubbles: true,
|
|
642
|
-
cancelable: true,
|
|
643
|
-
composed: true,
|
|
644
|
-
}),
|
|
645
|
-
);
|
|
749
|
+
figLabDispatchPropskitEvent(this, type);
|
|
646
750
|
}
|
|
647
751
|
|
|
648
752
|
#handleClick(event) {
|
|
649
753
|
if (figLabBooleanAttribute(this, "disabled")) return;
|
|
650
754
|
if (
|
|
651
755
|
event.target instanceof Element &&
|
|
652
|
-
event.target.closest("fig-segmented-control, fig-menu")
|
|
756
|
+
event.target.closest("fig-switch, fig-segmented-control, fig-menu")
|
|
653
757
|
) {
|
|
654
758
|
return;
|
|
655
759
|
}
|
|
656
|
-
|
|
657
|
-
|
|
760
|
+
if (this.#switch?.localName === "fig-switch") {
|
|
761
|
+
this.#switch.click();
|
|
762
|
+
} else {
|
|
763
|
+
const value = this.#switch?.value === "on" ? "off" : "on";
|
|
764
|
+
this.#switch?.querySelector(`fig-segment[value="${value}"]`)?.click();
|
|
765
|
+
}
|
|
658
766
|
}
|
|
659
767
|
|
|
660
768
|
get checked() {
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
769
|
+
if (!this.#switch) return figLabBooleanAttribute(this, "checked");
|
|
770
|
+
return this.#switch.localName === "fig-switch"
|
|
771
|
+
? Boolean(this.#switch.checked)
|
|
772
|
+
: this.#switch.value === "on";
|
|
664
773
|
}
|
|
665
774
|
|
|
666
775
|
set checked(nextChecked) {
|
|
667
776
|
const checked = Boolean(nextChecked);
|
|
668
777
|
this.toggleAttribute("checked", checked);
|
|
669
|
-
if (this.#switch)
|
|
778
|
+
if (!this.#switch) return;
|
|
779
|
+
if (this.#switch.localName === "fig-switch") {
|
|
780
|
+
this.#switch.checked = checked;
|
|
781
|
+
this.#switch.value = checked ? "on" : "off";
|
|
782
|
+
} else {
|
|
783
|
+
this.#switch.value = checked ? "on" : "off";
|
|
784
|
+
}
|
|
670
785
|
}
|
|
671
786
|
|
|
672
787
|
get value() {
|
|
673
|
-
return this
|
|
788
|
+
return this.checked;
|
|
674
789
|
}
|
|
675
790
|
|
|
676
791
|
set value(nextValue) {
|
|
677
|
-
|
|
792
|
+
const checked =
|
|
793
|
+
typeof nextValue === "string"
|
|
794
|
+
? !["", "false", "off", "0"].includes(nextValue.trim().toLowerCase())
|
|
795
|
+
: Boolean(nextValue);
|
|
796
|
+
this.checked = checked;
|
|
678
797
|
}
|
|
679
798
|
|
|
680
799
|
get defaultValue() {
|
|
@@ -690,17 +809,18 @@ class PropskitSwitch extends HTMLElement {
|
|
|
690
809
|
resetToDefault() {
|
|
691
810
|
const checked = this.defaultValue;
|
|
692
811
|
this.checked = checked;
|
|
693
|
-
figLabEmitPropskitReset(this
|
|
694
|
-
checked,
|
|
695
|
-
value: this.getAttribute("value") ?? "",
|
|
696
|
-
});
|
|
812
|
+
figLabEmitPropskitReset(this);
|
|
697
813
|
}
|
|
698
814
|
|
|
699
815
|
focus(options) {
|
|
700
|
-
|
|
701
|
-
this.#switch
|
|
702
|
-
|
|
703
|
-
|
|
816
|
+
if (this.#switch?.localName === "fig-switch") {
|
|
817
|
+
this.#switch.focus(options);
|
|
818
|
+
} else {
|
|
819
|
+
const selected =
|
|
820
|
+
this.#switch?.querySelector("fig-segment[selected]") ||
|
|
821
|
+
this.#switch?.querySelector("fig-segment");
|
|
822
|
+
selected?.focus(options);
|
|
823
|
+
}
|
|
704
824
|
}
|
|
705
825
|
}
|
|
706
826
|
figLabDefineElement("propskit-switch", PropskitSwitch);
|
|
@@ -865,9 +985,9 @@ function figLabFillSwatchAlpha(fill) {
|
|
|
865
985
|
}
|
|
866
986
|
}
|
|
867
987
|
|
|
868
|
-
/*
|
|
869
|
-
class PropskitColor extends
|
|
870
|
-
#
|
|
988
|
+
/* PropsKit color surface */
|
|
989
|
+
class PropskitColor extends FigLabPropskitElement {
|
|
990
|
+
#surface = null;
|
|
871
991
|
#label = null;
|
|
872
992
|
#input = null;
|
|
873
993
|
#swatch = null;
|
|
@@ -880,12 +1000,12 @@ class PropskitColor extends HTMLElement {
|
|
|
880
1000
|
#initialValue = null;
|
|
881
1001
|
|
|
882
1002
|
static get observedAttributes() {
|
|
883
|
-
return ["label", "
|
|
1003
|
+
return ["label", "aria-label"];
|
|
884
1004
|
}
|
|
885
1005
|
|
|
886
1006
|
connectedCallback() {
|
|
887
|
-
if (!this.#
|
|
888
|
-
this.#
|
|
1007
|
+
if (!this.#surface) this.#initialize();
|
|
1008
|
+
this.#syncSurface();
|
|
889
1009
|
this.#syncInputAttributes();
|
|
890
1010
|
this.#bindInputEvents();
|
|
891
1011
|
if (this.#initialValue === null) {
|
|
@@ -898,23 +1018,24 @@ class PropskitColor extends HTMLElement {
|
|
|
898
1018
|
|
|
899
1019
|
if (!this.#observer) {
|
|
900
1020
|
this.#observer = new MutationObserver((mutations) => {
|
|
901
|
-
let
|
|
1021
|
+
let syncSurface = false;
|
|
902
1022
|
let syncInput = false;
|
|
903
1023
|
|
|
904
1024
|
for (const mutation of mutations) {
|
|
905
1025
|
if (mutation.type !== "attributes") continue;
|
|
906
1026
|
if (
|
|
907
1027
|
mutation.attributeName === "label" ||
|
|
908
|
-
mutation.attributeName === "direction" ||
|
|
909
1028
|
mutation.attributeName === "aria-label"
|
|
910
1029
|
) {
|
|
911
|
-
|
|
1030
|
+
syncSurface = true;
|
|
1031
|
+
} else if (mutation.attributeName === "direction") {
|
|
1032
|
+
continue;
|
|
912
1033
|
} else {
|
|
913
1034
|
syncInput = true;
|
|
914
1035
|
}
|
|
915
1036
|
}
|
|
916
1037
|
|
|
917
|
-
if (
|
|
1038
|
+
if (syncSurface) this.#syncSurface();
|
|
918
1039
|
if (syncInput) this.#syncInputAttributes();
|
|
919
1040
|
});
|
|
920
1041
|
}
|
|
@@ -930,9 +1051,9 @@ class PropskitColor extends HTMLElement {
|
|
|
930
1051
|
}
|
|
931
1052
|
|
|
932
1053
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
933
|
-
if (oldValue === newValue || !this.#
|
|
934
|
-
if (name === "label" || name === "
|
|
935
|
-
this.#
|
|
1054
|
+
if (oldValue === newValue || !this.#surface) return;
|
|
1055
|
+
if (name === "label" || name === "aria-label") {
|
|
1056
|
+
this.#syncSurface();
|
|
936
1057
|
}
|
|
937
1058
|
}
|
|
938
1059
|
|
|
@@ -944,7 +1065,9 @@ class PropskitColor extends HTMLElement {
|
|
|
944
1065
|
const customLabel = initialChildren.find(
|
|
945
1066
|
(node) => node.nodeType === Node.ELEMENT_NODE && node.matches("label"),
|
|
946
1067
|
);
|
|
947
|
-
const
|
|
1068
|
+
const surface = figLabCreateElement("div", {
|
|
1069
|
+
className: "propskit-color-surface",
|
|
1070
|
+
});
|
|
948
1071
|
const label = customLabel || document.createElement("label");
|
|
949
1072
|
const picker = document.createElement("fig-fill-picker");
|
|
950
1073
|
const swatch = document.createElement("fig-swatch");
|
|
@@ -954,42 +1077,24 @@ class PropskitColor extends HTMLElement {
|
|
|
954
1077
|
for (const node of initialChildren) {
|
|
955
1078
|
if (node !== customLabel) picker.appendChild(node);
|
|
956
1079
|
}
|
|
957
|
-
|
|
958
|
-
this.#
|
|
1080
|
+
surface.append(label, picker);
|
|
1081
|
+
this.#surface = surface;
|
|
959
1082
|
this.#label = label;
|
|
960
1083
|
this.#input = picker;
|
|
961
1084
|
this.#swatch = swatch;
|
|
962
1085
|
this.#hasCustomLabel = Boolean(customLabel);
|
|
963
|
-
this.replaceChildren(
|
|
1086
|
+
this.replaceChildren(surface);
|
|
964
1087
|
}
|
|
965
1088
|
|
|
966
|
-
#
|
|
967
|
-
if (!this.#
|
|
968
|
-
const
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
this.#label.remove();
|
|
974
|
-
} else {
|
|
975
|
-
if (!this.#hasCustomLabel) {
|
|
976
|
-
this.#label.textContent = hasLabelAttr ? (rawLabel ?? "") : "Label";
|
|
977
|
-
}
|
|
978
|
-
if (this.#label.parentElement !== this.#field) {
|
|
979
|
-
this.#field.prepend(this.#label);
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
this.#field.setAttribute(
|
|
984
|
-
"direction",
|
|
985
|
-
this.getAttribute("direction") || "horizontal",
|
|
986
|
-
);
|
|
987
|
-
this.#input.setAttribute(
|
|
988
|
-
"aria-label",
|
|
989
|
-
this.getAttribute("aria-label") ||
|
|
990
|
-
this.#label.textContent?.trim() ||
|
|
991
|
-
"Color",
|
|
1089
|
+
#syncSurface() {
|
|
1090
|
+
if (!this.#surface || !this.#label || !this.#input) return;
|
|
1091
|
+
const labelId = figLabSyncPropskitLabel(
|
|
1092
|
+
this,
|
|
1093
|
+
this.#surface,
|
|
1094
|
+
this.#label,
|
|
1095
|
+
this.#hasCustomLabel,
|
|
992
1096
|
);
|
|
1097
|
+
figLabSyncPropskitControlLabel(this, this.#input, labelId, "Color");
|
|
993
1098
|
}
|
|
994
1099
|
|
|
995
1100
|
#parsedHostColor() {
|
|
@@ -1127,14 +1232,7 @@ class PropskitColor extends HTMLElement {
|
|
|
1127
1232
|
this.#swatch.setAttribute("background", detail.color);
|
|
1128
1233
|
this.#swatch.setAttribute("alpha", String(detail.alpha));
|
|
1129
1234
|
}
|
|
1130
|
-
this
|
|
1131
|
-
new CustomEvent(type, {
|
|
1132
|
-
detail,
|
|
1133
|
-
bubbles: true,
|
|
1134
|
-
cancelable: true,
|
|
1135
|
-
composed: true,
|
|
1136
|
-
}),
|
|
1137
|
-
);
|
|
1235
|
+
figLabDispatchPropskitEvent(this, type);
|
|
1138
1236
|
}
|
|
1139
1237
|
|
|
1140
1238
|
#handleClick(event) {
|
|
@@ -1198,22 +1296,7 @@ class PropskitColor extends HTMLElement {
|
|
|
1198
1296
|
const next = this.defaultValue;
|
|
1199
1297
|
this.setAttribute("value", next);
|
|
1200
1298
|
this.#forceInputValue(next);
|
|
1201
|
-
this
|
|
1202
|
-
new CustomEvent("input", {
|
|
1203
|
-
detail: next,
|
|
1204
|
-
bubbles: true,
|
|
1205
|
-
cancelable: true,
|
|
1206
|
-
composed: true,
|
|
1207
|
-
}),
|
|
1208
|
-
);
|
|
1209
|
-
this.dispatchEvent(
|
|
1210
|
-
new CustomEvent("change", {
|
|
1211
|
-
detail: next,
|
|
1212
|
-
bubbles: true,
|
|
1213
|
-
cancelable: true,
|
|
1214
|
-
composed: true,
|
|
1215
|
-
}),
|
|
1216
|
-
);
|
|
1299
|
+
figLabEmitPropskitReset(this);
|
|
1217
1300
|
}
|
|
1218
1301
|
|
|
1219
1302
|
focus(options) {
|
|
@@ -1223,9 +1306,9 @@ class PropskitColor extends HTMLElement {
|
|
|
1223
1306
|
}
|
|
1224
1307
|
figLabDefineElement("propskit-color", PropskitColor);
|
|
1225
1308
|
|
|
1226
|
-
/*
|
|
1227
|
-
class PropskitFill extends
|
|
1228
|
-
#
|
|
1309
|
+
/* PropsKit fill surface — color-style swatch that opens the full fill picker */
|
|
1310
|
+
class PropskitFill extends FigLabPropskitElement {
|
|
1311
|
+
#surface = null;
|
|
1229
1312
|
#label = null;
|
|
1230
1313
|
#input = null;
|
|
1231
1314
|
#swatch = null;
|
|
@@ -1238,12 +1321,12 @@ class PropskitFill extends HTMLElement {
|
|
|
1238
1321
|
#initialValue = null;
|
|
1239
1322
|
|
|
1240
1323
|
static get observedAttributes() {
|
|
1241
|
-
return ["label", "
|
|
1324
|
+
return ["label", "aria-label"];
|
|
1242
1325
|
}
|
|
1243
1326
|
|
|
1244
1327
|
connectedCallback() {
|
|
1245
|
-
if (!this.#
|
|
1246
|
-
this.#
|
|
1328
|
+
if (!this.#surface) this.#initialize();
|
|
1329
|
+
this.#syncSurface();
|
|
1247
1330
|
this.#syncInputAttributes();
|
|
1248
1331
|
this.#bindInputEvents();
|
|
1249
1332
|
if (this.#initialValue === null) {
|
|
@@ -1256,23 +1339,24 @@ class PropskitFill extends HTMLElement {
|
|
|
1256
1339
|
|
|
1257
1340
|
if (!this.#observer) {
|
|
1258
1341
|
this.#observer = new MutationObserver((mutations) => {
|
|
1259
|
-
let
|
|
1342
|
+
let syncSurface = false;
|
|
1260
1343
|
let syncInput = false;
|
|
1261
1344
|
|
|
1262
1345
|
for (const mutation of mutations) {
|
|
1263
1346
|
if (mutation.type !== "attributes") continue;
|
|
1264
1347
|
if (
|
|
1265
1348
|
mutation.attributeName === "label" ||
|
|
1266
|
-
mutation.attributeName === "direction" ||
|
|
1267
1349
|
mutation.attributeName === "aria-label"
|
|
1268
1350
|
) {
|
|
1269
|
-
|
|
1351
|
+
syncSurface = true;
|
|
1352
|
+
} else if (mutation.attributeName === "direction") {
|
|
1353
|
+
continue;
|
|
1270
1354
|
} else {
|
|
1271
1355
|
syncInput = true;
|
|
1272
1356
|
}
|
|
1273
1357
|
}
|
|
1274
1358
|
|
|
1275
|
-
if (
|
|
1359
|
+
if (syncSurface) this.#syncSurface();
|
|
1276
1360
|
if (syncInput) this.#syncInputAttributes();
|
|
1277
1361
|
});
|
|
1278
1362
|
}
|
|
@@ -1288,9 +1372,9 @@ class PropskitFill extends HTMLElement {
|
|
|
1288
1372
|
}
|
|
1289
1373
|
|
|
1290
1374
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
1291
|
-
if (oldValue === newValue || !this.#
|
|
1292
|
-
if (name === "label" || name === "
|
|
1293
|
-
this.#
|
|
1375
|
+
if (oldValue === newValue || !this.#surface) return;
|
|
1376
|
+
if (name === "label" || name === "aria-label") {
|
|
1377
|
+
this.#syncSurface();
|
|
1294
1378
|
}
|
|
1295
1379
|
}
|
|
1296
1380
|
|
|
@@ -1302,7 +1386,9 @@ class PropskitFill extends HTMLElement {
|
|
|
1302
1386
|
const customLabel = initialChildren.find(
|
|
1303
1387
|
(node) => node.nodeType === Node.ELEMENT_NODE && node.matches("label"),
|
|
1304
1388
|
);
|
|
1305
|
-
const
|
|
1389
|
+
const surface = figLabCreateElement("div", {
|
|
1390
|
+
className: "propskit-fill-surface",
|
|
1391
|
+
});
|
|
1306
1392
|
const label = customLabel || document.createElement("label");
|
|
1307
1393
|
const picker = document.createElement("fig-fill-picker");
|
|
1308
1394
|
const swatch = document.createElement("fig-swatch");
|
|
@@ -1312,42 +1398,24 @@ class PropskitFill extends HTMLElement {
|
|
|
1312
1398
|
for (const node of initialChildren) {
|
|
1313
1399
|
if (node !== customLabel) picker.appendChild(node);
|
|
1314
1400
|
}
|
|
1315
|
-
|
|
1316
|
-
this.#
|
|
1401
|
+
surface.append(label, picker);
|
|
1402
|
+
this.#surface = surface;
|
|
1317
1403
|
this.#label = label;
|
|
1318
1404
|
this.#input = picker;
|
|
1319
1405
|
this.#swatch = swatch;
|
|
1320
1406
|
this.#hasCustomLabel = Boolean(customLabel);
|
|
1321
|
-
this.replaceChildren(
|
|
1407
|
+
this.replaceChildren(surface);
|
|
1322
1408
|
}
|
|
1323
1409
|
|
|
1324
|
-
#
|
|
1325
|
-
if (!this.#
|
|
1326
|
-
const
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
this.#label.remove();
|
|
1332
|
-
} else {
|
|
1333
|
-
if (!this.#hasCustomLabel) {
|
|
1334
|
-
this.#label.textContent = hasLabelAttr ? (rawLabel ?? "") : "Label";
|
|
1335
|
-
}
|
|
1336
|
-
if (this.#label.parentElement !== this.#field) {
|
|
1337
|
-
this.#field.prepend(this.#label);
|
|
1338
|
-
}
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
this.#field.setAttribute(
|
|
1342
|
-
"direction",
|
|
1343
|
-
this.getAttribute("direction") || "horizontal",
|
|
1344
|
-
);
|
|
1345
|
-
this.#input.setAttribute(
|
|
1346
|
-
"aria-label",
|
|
1347
|
-
this.getAttribute("aria-label") ||
|
|
1348
|
-
this.#label.textContent?.trim() ||
|
|
1349
|
-
"Fill",
|
|
1410
|
+
#syncSurface() {
|
|
1411
|
+
if (!this.#surface || !this.#label || !this.#input) return;
|
|
1412
|
+
const labelId = figLabSyncPropskitLabel(
|
|
1413
|
+
this,
|
|
1414
|
+
this.#surface,
|
|
1415
|
+
this.#label,
|
|
1416
|
+
this.#hasCustomLabel,
|
|
1350
1417
|
);
|
|
1418
|
+
figLabSyncPropskitControlLabel(this, this.#input, labelId, "Fill");
|
|
1351
1419
|
}
|
|
1352
1420
|
|
|
1353
1421
|
#parsedHostFill() {
|
|
@@ -1494,14 +1562,7 @@ class PropskitFill extends HTMLElement {
|
|
|
1494
1562
|
this.#input.setAttribute("value", serialized);
|
|
1495
1563
|
}
|
|
1496
1564
|
this.#syncSwatch(fill);
|
|
1497
|
-
this
|
|
1498
|
-
new CustomEvent(type, {
|
|
1499
|
-
detail: fill,
|
|
1500
|
-
bubbles: true,
|
|
1501
|
-
cancelable: true,
|
|
1502
|
-
composed: true,
|
|
1503
|
-
}),
|
|
1504
|
-
);
|
|
1565
|
+
figLabDispatchPropskitEvent(this, type);
|
|
1505
1566
|
}
|
|
1506
1567
|
|
|
1507
1568
|
#handleClick(event) {
|
|
@@ -1567,23 +1628,7 @@ class PropskitFill extends HTMLElement {
|
|
|
1567
1628
|
const next = this.defaultValue;
|
|
1568
1629
|
this.setAttribute("value", next);
|
|
1569
1630
|
this.#forceInputValue(next);
|
|
1570
|
-
|
|
1571
|
-
this.dispatchEvent(
|
|
1572
|
-
new CustomEvent("input", {
|
|
1573
|
-
detail: fill,
|
|
1574
|
-
bubbles: true,
|
|
1575
|
-
cancelable: true,
|
|
1576
|
-
composed: true,
|
|
1577
|
-
}),
|
|
1578
|
-
);
|
|
1579
|
-
this.dispatchEvent(
|
|
1580
|
-
new CustomEvent("change", {
|
|
1581
|
-
detail: fill,
|
|
1582
|
-
bubbles: true,
|
|
1583
|
-
cancelable: true,
|
|
1584
|
-
composed: true,
|
|
1585
|
-
}),
|
|
1586
|
-
);
|
|
1631
|
+
figLabEmitPropskitReset(this);
|
|
1587
1632
|
}
|
|
1588
1633
|
|
|
1589
1634
|
focus(options) {
|
|
@@ -1593,9 +1638,9 @@ class PropskitFill extends HTMLElement {
|
|
|
1593
1638
|
}
|
|
1594
1639
|
figLabDefineElement("propskit-fill", PropskitFill);
|
|
1595
1640
|
|
|
1596
|
-
/*
|
|
1597
|
-
class PropskitGradient extends
|
|
1598
|
-
#
|
|
1641
|
+
/* PropsKit gradient surface */
|
|
1642
|
+
class PropskitGradient extends FigLabPropskitElement {
|
|
1643
|
+
#surface = null;
|
|
1599
1644
|
#label = null;
|
|
1600
1645
|
#input = null;
|
|
1601
1646
|
#hasCustomLabel = false;
|
|
@@ -1607,12 +1652,12 @@ class PropskitGradient extends HTMLElement {
|
|
|
1607
1652
|
#initialValue = null;
|
|
1608
1653
|
|
|
1609
1654
|
static get observedAttributes() {
|
|
1610
|
-
return ["label", "
|
|
1655
|
+
return ["label", "aria-label"];
|
|
1611
1656
|
}
|
|
1612
1657
|
|
|
1613
1658
|
connectedCallback() {
|
|
1614
|
-
if (!this.#
|
|
1615
|
-
this.#
|
|
1659
|
+
if (!this.#surface) this.#initialize();
|
|
1660
|
+
this.#syncSurface();
|
|
1616
1661
|
this.#syncInputAttributes();
|
|
1617
1662
|
this.#bindInputEvents();
|
|
1618
1663
|
if (this.#initialValue === null) {
|
|
@@ -1627,23 +1672,24 @@ class PropskitGradient extends HTMLElement {
|
|
|
1627
1672
|
|
|
1628
1673
|
if (!this.#observer) {
|
|
1629
1674
|
this.#observer = new MutationObserver((mutations) => {
|
|
1630
|
-
let
|
|
1675
|
+
let syncSurface = false;
|
|
1631
1676
|
let syncInput = false;
|
|
1632
1677
|
|
|
1633
1678
|
for (const mutation of mutations) {
|
|
1634
1679
|
if (mutation.type !== "attributes") continue;
|
|
1635
1680
|
if (
|
|
1636
1681
|
mutation.attributeName === "label" ||
|
|
1637
|
-
mutation.attributeName === "direction" ||
|
|
1638
1682
|
mutation.attributeName === "aria-label"
|
|
1639
1683
|
) {
|
|
1640
|
-
|
|
1684
|
+
syncSurface = true;
|
|
1685
|
+
} else if (mutation.attributeName === "direction") {
|
|
1686
|
+
continue;
|
|
1641
1687
|
} else {
|
|
1642
1688
|
syncInput = true;
|
|
1643
1689
|
}
|
|
1644
1690
|
}
|
|
1645
1691
|
|
|
1646
|
-
if (
|
|
1692
|
+
if (syncSurface) this.#syncSurface();
|
|
1647
1693
|
if (syncInput) this.#syncInputAttributes();
|
|
1648
1694
|
});
|
|
1649
1695
|
}
|
|
@@ -1659,9 +1705,9 @@ class PropskitGradient extends HTMLElement {
|
|
|
1659
1705
|
}
|
|
1660
1706
|
|
|
1661
1707
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
1662
|
-
if (oldValue === newValue || !this.#
|
|
1663
|
-
if (name === "label" || name === "
|
|
1664
|
-
this.#
|
|
1708
|
+
if (oldValue === newValue || !this.#surface) return;
|
|
1709
|
+
if (name === "label" || name === "aria-label") {
|
|
1710
|
+
this.#syncSurface();
|
|
1665
1711
|
}
|
|
1666
1712
|
}
|
|
1667
1713
|
|
|
@@ -1673,46 +1719,30 @@ class PropskitGradient extends HTMLElement {
|
|
|
1673
1719
|
const customLabel = initialChildren.find(
|
|
1674
1720
|
(node) => node.nodeType === Node.ELEMENT_NODE && node.matches("label"),
|
|
1675
1721
|
);
|
|
1676
|
-
const
|
|
1722
|
+
const surface = figLabCreateElement("div", {
|
|
1723
|
+
className: "propskit-gradient-surface",
|
|
1724
|
+
});
|
|
1677
1725
|
const label = customLabel || document.createElement("label");
|
|
1678
1726
|
const input = document.createElement("fig-input-gradient");
|
|
1679
1727
|
input.anchorElement = this;
|
|
1680
1728
|
|
|
1681
|
-
|
|
1682
|
-
this.#
|
|
1729
|
+
surface.append(label, input);
|
|
1730
|
+
this.#surface = surface;
|
|
1683
1731
|
this.#label = label;
|
|
1684
1732
|
this.#input = input;
|
|
1685
1733
|
this.#hasCustomLabel = Boolean(customLabel);
|
|
1686
|
-
this.replaceChildren(
|
|
1734
|
+
this.replaceChildren(surface);
|
|
1687
1735
|
}
|
|
1688
1736
|
|
|
1689
|
-
#
|
|
1690
|
-
if (!this.#
|
|
1691
|
-
const
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
this.#label.remove();
|
|
1697
|
-
} else {
|
|
1698
|
-
if (!this.#hasCustomLabel) {
|
|
1699
|
-
this.#label.textContent = hasLabelAttr ? (rawLabel ?? "") : "Label";
|
|
1700
|
-
}
|
|
1701
|
-
if (this.#label.parentElement !== this.#field) {
|
|
1702
|
-
this.#field.prepend(this.#label);
|
|
1703
|
-
}
|
|
1704
|
-
}
|
|
1705
|
-
|
|
1706
|
-
this.#field.setAttribute(
|
|
1707
|
-
"direction",
|
|
1708
|
-
this.getAttribute("direction") || "horizontal",
|
|
1709
|
-
);
|
|
1710
|
-
this.#input.setAttribute(
|
|
1711
|
-
"aria-label",
|
|
1712
|
-
this.getAttribute("aria-label") ||
|
|
1713
|
-
this.#label.textContent?.trim() ||
|
|
1714
|
-
"Gradient",
|
|
1737
|
+
#syncSurface() {
|
|
1738
|
+
if (!this.#surface || !this.#label || !this.#input) return;
|
|
1739
|
+
const labelId = figLabSyncPropskitLabel(
|
|
1740
|
+
this,
|
|
1741
|
+
this.#surface,
|
|
1742
|
+
this.#label,
|
|
1743
|
+
this.#hasCustomLabel,
|
|
1715
1744
|
);
|
|
1745
|
+
figLabSyncPropskitControlLabel(this, this.#input, labelId, "Gradient");
|
|
1716
1746
|
}
|
|
1717
1747
|
|
|
1718
1748
|
#getForwardedInputAttrNames() {
|
|
@@ -1789,18 +1819,7 @@ class PropskitGradient extends HTMLElement {
|
|
|
1789
1819
|
if (figLabBooleanAttribute(this, "disabled")) return;
|
|
1790
1820
|
const value = this.#valueFromGradientEvent(event);
|
|
1791
1821
|
this.setAttribute("value", value);
|
|
1792
|
-
|
|
1793
|
-
event instanceof CustomEvent && event.detail !== undefined
|
|
1794
|
-
? event.detail
|
|
1795
|
-
: this.#input?.value;
|
|
1796
|
-
this.dispatchEvent(
|
|
1797
|
-
new CustomEvent(type, {
|
|
1798
|
-
detail,
|
|
1799
|
-
bubbles: true,
|
|
1800
|
-
cancelable: true,
|
|
1801
|
-
composed: true,
|
|
1802
|
-
}),
|
|
1803
|
-
);
|
|
1822
|
+
figLabDispatchPropskitEvent(this, type);
|
|
1804
1823
|
}
|
|
1805
1824
|
|
|
1806
1825
|
#handleClick(event) {
|
|
@@ -1851,8 +1870,7 @@ class PropskitGradient extends HTMLElement {
|
|
|
1851
1870
|
const next = this.defaultValue;
|
|
1852
1871
|
if (!next) return;
|
|
1853
1872
|
this.value = next;
|
|
1854
|
-
|
|
1855
|
-
figLabEmitPropskitReset(this, detail);
|
|
1873
|
+
figLabEmitPropskitReset(this);
|
|
1856
1874
|
}
|
|
1857
1875
|
|
|
1858
1876
|
focus(options) {
|
|
@@ -1861,74 +1879,36 @@ class PropskitGradient extends HTMLElement {
|
|
|
1861
1879
|
}
|
|
1862
1880
|
figLabDefineElement("propskit-gradient", PropskitGradient);
|
|
1863
1881
|
|
|
1864
|
-
/*
|
|
1865
|
-
class
|
|
1866
|
-
#
|
|
1882
|
+
/* PropsKit palette select surface */
|
|
1883
|
+
class PropskitPalette extends FigLabPropskitElement {
|
|
1884
|
+
#surface = null;
|
|
1867
1885
|
#label = null;
|
|
1868
1886
|
#select = null;
|
|
1869
|
-
#
|
|
1887
|
+
#palettes = [];
|
|
1870
1888
|
#hasCustomLabel = false;
|
|
1871
1889
|
#observer = null;
|
|
1872
|
-
#
|
|
1890
|
+
#initialValue = [];
|
|
1891
|
+
#eventValue = undefined;
|
|
1892
|
+
#syncing = false;
|
|
1893
|
+
#syncVersion = 0;
|
|
1894
|
+
#buttonPreview = null;
|
|
1873
1895
|
#boundHandleInput = null;
|
|
1874
1896
|
#boundHandleChange = null;
|
|
1875
1897
|
#boundHandleOptionHover = null;
|
|
1876
1898
|
#boundHandleClick = this.#handleClick.bind(this);
|
|
1877
1899
|
#boundHandlePointerDown = this.#handlePointerDown.bind(this);
|
|
1878
|
-
/** True when pointerdown saw the menu open — skip click-to-open after light-dismiss. */
|
|
1879
1900
|
#closeGesture = false;
|
|
1880
|
-
#initialValue = null;
|
|
1881
1901
|
|
|
1882
1902
|
static get observedAttributes() {
|
|
1883
|
-
return ["label", "
|
|
1884
|
-
}
|
|
1885
|
-
|
|
1886
|
-
static #canUseFigSelect() {
|
|
1887
|
-
return ["fig-select", "fig-select-options", "fig-select-option"].every(
|
|
1888
|
-
(tag) => {
|
|
1889
|
-
const Constructor = customElements.get(tag);
|
|
1890
|
-
return (
|
|
1891
|
-
typeof Constructor === "function" &&
|
|
1892
|
-
Constructor.prototype instanceof HTMLElement
|
|
1893
|
-
);
|
|
1894
|
-
},
|
|
1895
|
-
);
|
|
1896
|
-
}
|
|
1897
|
-
|
|
1898
|
-
static #parseOptionsAttribute(raw) {
|
|
1899
|
-
const text = raw || "";
|
|
1900
|
-
if (text.startsWith("[")) {
|
|
1901
|
-
try {
|
|
1902
|
-
const parsed = JSON.parse(text);
|
|
1903
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
1904
|
-
} catch {
|
|
1905
|
-
/* fall through */
|
|
1906
|
-
}
|
|
1907
|
-
}
|
|
1908
|
-
const delimiter = text.includes("\n") ? "\n" : ",";
|
|
1909
|
-
return text
|
|
1910
|
-
.split(delimiter)
|
|
1911
|
-
.map((s) => s.trim())
|
|
1912
|
-
.filter(Boolean);
|
|
1913
|
-
}
|
|
1914
|
-
|
|
1915
|
-
static #optionEntryValue(opt) {
|
|
1916
|
-
if (opt && typeof opt === "object") {
|
|
1917
|
-
return String(opt.value ?? opt.label ?? "");
|
|
1918
|
-
}
|
|
1919
|
-
return String(opt ?? "");
|
|
1920
|
-
}
|
|
1921
|
-
|
|
1922
|
-
static #optionEntryLabel(opt) {
|
|
1923
|
-
if (opt && typeof opt === "object") {
|
|
1924
|
-
return String(opt.label ?? opt.value ?? "");
|
|
1925
|
-
}
|
|
1926
|
-
return String(opt ?? "");
|
|
1903
|
+
return ["label", "aria-label", "options", "value", "disabled"];
|
|
1927
1904
|
}
|
|
1928
1905
|
|
|
1929
1906
|
connectedCallback() {
|
|
1930
|
-
if (!this.#
|
|
1931
|
-
|
|
1907
|
+
if (!this.#surface) {
|
|
1908
|
+
this.#initialize();
|
|
1909
|
+
this.#initialValue = this.#clonePalette(this.value);
|
|
1910
|
+
}
|
|
1911
|
+
this.#syncSurface();
|
|
1932
1912
|
this.#syncSelectAttributes();
|
|
1933
1913
|
this.#bindSelectEvents();
|
|
1934
1914
|
this.removeEventListener("click", this.#boundHandleClick);
|
|
@@ -1939,28 +1919,31 @@ class PropskitSelect extends HTMLElement {
|
|
|
1939
1919
|
|
|
1940
1920
|
if (!this.#observer) {
|
|
1941
1921
|
this.#observer = new MutationObserver((mutations) => {
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1922
|
+
if (
|
|
1923
|
+
mutations.some(
|
|
1924
|
+
({ type, attributeName }) =>
|
|
1925
|
+
type === "attributes" &&
|
|
1926
|
+
attributeName &&
|
|
1927
|
+
!PropskitPalette.observedAttributes.includes(attributeName) &&
|
|
1928
|
+
![
|
|
1929
|
+
"label",
|
|
1930
|
+
"name",
|
|
1931
|
+
"options",
|
|
1932
|
+
"value",
|
|
1933
|
+
"default",
|
|
1934
|
+
"size",
|
|
1935
|
+
"variant",
|
|
1936
|
+
"class",
|
|
1937
|
+
"style",
|
|
1938
|
+
"id",
|
|
1939
|
+
].includes(attributeName) &&
|
|
1940
|
+
!attributeName.startsWith("data-"),
|
|
1941
|
+
)
|
|
1942
|
+
) {
|
|
1943
|
+
this.#syncSelectAttributes();
|
|
1957
1944
|
}
|
|
1958
|
-
|
|
1959
|
-
if (syncField) this.#syncField();
|
|
1960
|
-
if (syncSelect) this.#syncSelectAttributes();
|
|
1961
1945
|
});
|
|
1962
1946
|
}
|
|
1963
|
-
|
|
1964
1947
|
this.#observer.observe(this, { attributes: true });
|
|
1965
1948
|
}
|
|
1966
1949
|
|
|
@@ -1973,157 +1956,204 @@ class PropskitSelect extends HTMLElement {
|
|
|
1973
1956
|
}
|
|
1974
1957
|
|
|
1975
1958
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
1976
|
-
if (oldValue === newValue || !this.#
|
|
1977
|
-
if (
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
}
|
|
1981
|
-
|
|
1959
|
+
if (oldValue === newValue || !this.#surface) return;
|
|
1960
|
+
if (this.#syncing && name === "value") return;
|
|
1961
|
+
if (name === "label" || name === "aria-label") {
|
|
1962
|
+
this.#syncSurface();
|
|
1963
|
+
} else if (name === "options") {
|
|
1964
|
+
this.#syncOptions();
|
|
1965
|
+
} else if (name === "value") {
|
|
1966
|
+
this.#syncSelection();
|
|
1967
|
+
} else if (name === "disabled") {
|
|
1982
1968
|
this.#syncSelectAttributes();
|
|
1983
1969
|
}
|
|
1984
1970
|
}
|
|
1985
1971
|
|
|
1986
1972
|
#initialize() {
|
|
1987
|
-
this.#initialValue = this.getAttribute("value") ?? "";
|
|
1988
1973
|
const customLabel = this.querySelector(":scope > label");
|
|
1989
|
-
const
|
|
1990
|
-
|
|
1974
|
+
const surface = figLabCreateElement("div", {
|
|
1975
|
+
className: "propskit-palette-surface",
|
|
1976
|
+
});
|
|
1991
1977
|
const label = customLabel || document.createElement("label");
|
|
1992
|
-
|
|
1993
|
-
const
|
|
1994
|
-
|
|
1995
|
-
);
|
|
1996
|
-
|
|
1978
|
+
const select = document.createElement("fig-select");
|
|
1979
|
+
const buttonPreview = this.#createPalettePreview([]);
|
|
1980
|
+
buttonPreview.setAttribute("slot", "trigger");
|
|
1981
|
+
buttonPreview.setAttribute("data-propskit-palette-trigger", "");
|
|
1982
|
+
buttonPreview.hidden = true;
|
|
1997
1983
|
select.setAttribute("full", "");
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
1984
|
+
select.setAttribute("subtle", "");
|
|
1985
|
+
select.append(buttonPreview);
|
|
1986
|
+
surface.append(label, select);
|
|
1987
|
+
this.#surface = surface;
|
|
2001
1988
|
this.#label = label;
|
|
2002
1989
|
this.#select = select;
|
|
1990
|
+
this.#buttonPreview = buttonPreview;
|
|
2003
1991
|
this.#hasCustomLabel = Boolean(customLabel);
|
|
2004
|
-
this.replaceChildren(
|
|
1992
|
+
this.replaceChildren(surface);
|
|
1993
|
+
this.#syncOptions();
|
|
2005
1994
|
}
|
|
2006
1995
|
|
|
2007
|
-
#
|
|
2008
|
-
if (!
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
const isBlankLabel = hasLabelAttr && (rawLabel ?? "").trim() === "";
|
|
1996
|
+
#clonePalette(palette) {
|
|
1997
|
+
if (!Array.isArray(palette)) return [];
|
|
1998
|
+
return palette.map((entry) => ({ ...entry }));
|
|
1999
|
+
}
|
|
2012
2000
|
|
|
2013
|
-
|
|
2014
|
-
|
|
2001
|
+
#normalizePalette(value) {
|
|
2002
|
+
const palette = document.createElement("fig-input-palette");
|
|
2003
|
+
if (typeof value === "string") {
|
|
2004
|
+
palette.setAttribute("value", value);
|
|
2015
2005
|
} else {
|
|
2016
|
-
|
|
2017
|
-
this.#label.textContent = hasLabelAttr ? (rawLabel ?? "") : "Label";
|
|
2018
|
-
}
|
|
2019
|
-
if (this.#label.parentElement !== this.#field) {
|
|
2020
|
-
this.#field.prepend(this.#label);
|
|
2021
|
-
}
|
|
2006
|
+
palette.setAttribute("value", JSON.stringify(value ?? []));
|
|
2022
2007
|
}
|
|
2008
|
+
return this.#clonePalette(palette.value);
|
|
2009
|
+
}
|
|
2023
2010
|
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
this.getAttribute("direction") || "horizontal",
|
|
2027
|
-
);
|
|
2028
|
-
this.#select.setAttribute(
|
|
2029
|
-
"label",
|
|
2030
|
-
this.getAttribute("aria-label") ||
|
|
2031
|
-
this.#label.textContent?.trim() ||
|
|
2032
|
-
"Select",
|
|
2033
|
-
);
|
|
2034
|
-
// Always match menu width to the control surface.
|
|
2035
|
-
this.#select.setAttribute("full", "");
|
|
2011
|
+
#serializePalette(palette) {
|
|
2012
|
+
return JSON.stringify(this.#normalizePalette(palette));
|
|
2036
2013
|
}
|
|
2037
2014
|
|
|
2038
|
-
#
|
|
2039
|
-
|
|
2040
|
-
"
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
"aria-label",
|
|
2049
|
-
"full",
|
|
2050
|
-
"default",
|
|
2051
|
-
"variant",
|
|
2052
|
-
]);
|
|
2053
|
-
// fig-dropdown consumes light-DOM <option>s, not an options attribute.
|
|
2054
|
-
if (!this.#usesFigSelect) reserved.add("options");
|
|
2055
|
-
return this.getAttributeNames().filter(
|
|
2056
|
-
(name) => !reserved.has(name) && !name.startsWith("data-"),
|
|
2057
|
-
);
|
|
2015
|
+
#parseOptions() {
|
|
2016
|
+
try {
|
|
2017
|
+
const options = JSON.parse(this.getAttribute("options") || "[]");
|
|
2018
|
+
if (!Array.isArray(options)) return [];
|
|
2019
|
+
return options
|
|
2020
|
+
.filter((palette) => Array.isArray(palette))
|
|
2021
|
+
.map((palette) => this.#normalizePalette(palette));
|
|
2022
|
+
} catch {
|
|
2023
|
+
return [];
|
|
2024
|
+
}
|
|
2058
2025
|
}
|
|
2059
2026
|
|
|
2060
|
-
#
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
);
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
}
|
|
2070
|
-
}
|
|
2071
|
-
for (const entry of parsed) {
|
|
2072
|
-
const option = document.createElement("option");
|
|
2073
|
-
option.value = PropskitSelect.#optionEntryValue(entry);
|
|
2074
|
-
option.textContent = PropskitSelect.#optionEntryLabel(entry);
|
|
2075
|
-
this.#select.appendChild(option);
|
|
2076
|
-
}
|
|
2077
|
-
// Re-apply value after options land; fig-dropdown clones via async slotchange.
|
|
2078
|
-
const value = this.getAttribute("value");
|
|
2079
|
-
if (value != null) {
|
|
2080
|
-
const applyValue = () => {
|
|
2081
|
-
this.#select?.setAttribute("value", value);
|
|
2082
|
-
if (this.#select && "value" in this.#select) {
|
|
2083
|
-
this.#select.value = value;
|
|
2084
|
-
}
|
|
2085
|
-
};
|
|
2086
|
-
applyValue();
|
|
2087
|
-
queueMicrotask(applyValue);
|
|
2088
|
-
}
|
|
2027
|
+
#createPalettePreview(palette) {
|
|
2028
|
+
const preview = document.createElement("fig-input-palette");
|
|
2029
|
+
preview.className = "propskit-palette-preview";
|
|
2030
|
+
preview.setAttribute("value", JSON.stringify(palette));
|
|
2031
|
+
preview.setAttribute("fixed", "");
|
|
2032
|
+
preview.setAttribute("disabled", "");
|
|
2033
|
+
preview.setAttribute("full", "");
|
|
2034
|
+
preview.setAttribute("aria-hidden", "true");
|
|
2035
|
+
return preview;
|
|
2089
2036
|
}
|
|
2090
2037
|
|
|
2091
|
-
#
|
|
2038
|
+
#syncButtonPreview(palette) {
|
|
2039
|
+
if (!this.#buttonPreview) return;
|
|
2040
|
+
this.#buttonPreview.setAttribute("value", JSON.stringify(palette));
|
|
2041
|
+
this.#buttonPreview.hidden = !palette.length;
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
#syncOptions() {
|
|
2092
2045
|
if (!this.#select) return;
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2046
|
+
const syncVersion = ++this.#syncVersion;
|
|
2047
|
+
this.#palettes = this.#parseOptions();
|
|
2048
|
+
const panel = document.createElement("fig-select-options");
|
|
2049
|
+
panel.setAttribute("slot", "panel");
|
|
2050
|
+
panel.className = "propskit-palette-options";
|
|
2051
|
+
|
|
2052
|
+
this.#palettes.forEach((palette, index) => {
|
|
2053
|
+
const option = document.createElement("fig-select-option");
|
|
2054
|
+
const previewSlot = document.createElement("span");
|
|
2055
|
+
option.setAttribute("value", String(index));
|
|
2056
|
+
option.setAttribute("label", `Palette ${index + 1}`);
|
|
2057
|
+
option.setAttribute("aria-label", `Palette ${index + 1}`);
|
|
2058
|
+
previewSlot.setAttribute("slot", "prepend");
|
|
2059
|
+
previewSlot.className = "propskit-palette-option-preview";
|
|
2060
|
+
previewSlot.append(this.#createPalettePreview(palette));
|
|
2061
|
+
option.append(previewSlot);
|
|
2062
|
+
panel.append(option);
|
|
2101
2063
|
});
|
|
2102
|
-
const nextManaged = new Set(selectAttrs);
|
|
2103
2064
|
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2065
|
+
this.#syncing = true;
|
|
2066
|
+
this.#select.replaceChildren(this.#buttonPreview, panel);
|
|
2067
|
+
this.#syncSelection();
|
|
2068
|
+
queueMicrotask(() => {
|
|
2069
|
+
if (syncVersion !== this.#syncVersion) return;
|
|
2070
|
+
this.#syncSelection();
|
|
2071
|
+
this.#syncing = false;
|
|
2072
|
+
});
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
#paletteIndex(value) {
|
|
2076
|
+
const palette = this.#normalizePalette(value);
|
|
2077
|
+
return this.#palettes.findIndex((option) =>
|
|
2078
|
+
figLabPropskitJsonValuesEqual(option, palette),
|
|
2079
|
+
);
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
#reflectPaletteValue(palette) {
|
|
2083
|
+
const serialized = this.#serializePalette(palette);
|
|
2084
|
+
if (this.getAttribute("value") === serialized) return;
|
|
2085
|
+
const wasSyncing = this.#syncing;
|
|
2086
|
+
this.#syncing = true;
|
|
2087
|
+
this.setAttribute("value", serialized);
|
|
2088
|
+
this.#syncing = wasSyncing;
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
#syncSelection() {
|
|
2092
|
+
if (!this.#select) return;
|
|
2093
|
+
if (!this.#palettes.length) {
|
|
2094
|
+
this.#select.removeAttribute("value");
|
|
2095
|
+
this.#syncButtonPreview([]);
|
|
2096
|
+
const wasSyncing = this.#syncing;
|
|
2097
|
+
this.#syncing = true;
|
|
2098
|
+
this.removeAttribute("value");
|
|
2099
|
+
this.#syncing = wasSyncing;
|
|
2100
|
+
return;
|
|
2109
2101
|
}
|
|
2110
2102
|
|
|
2111
|
-
|
|
2103
|
+
const requested = this.hasAttribute("value")
|
|
2104
|
+
? this.getAttribute("value")
|
|
2105
|
+
: this.#palettes[0];
|
|
2106
|
+
const requestedIndex = this.#paletteIndex(requested);
|
|
2107
|
+
const index = requestedIndex >= 0 ? requestedIndex : 0;
|
|
2108
|
+
this.#select.setAttribute("value", String(index));
|
|
2109
|
+
this.#reflectPaletteValue(this.#palettes[index]);
|
|
2110
|
+
this.#syncButtonPreview(this.#palettes[index]);
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
#syncSurface() {
|
|
2114
|
+
if (!this.#surface || !this.#label || !this.#select) return;
|
|
2115
|
+
const labelId = figLabSyncPropskitLabel(
|
|
2116
|
+
this,
|
|
2117
|
+
this.#surface,
|
|
2118
|
+
this.#label,
|
|
2119
|
+
this.#hasCustomLabel,
|
|
2120
|
+
);
|
|
2121
|
+
figLabSyncPropskitControlLabel(this, this.#select, labelId, "Palette");
|
|
2122
|
+
this.#select.setAttribute(
|
|
2123
|
+
"label",
|
|
2124
|
+
this.getAttribute("aria-label") ||
|
|
2125
|
+
this.#label.textContent?.trim() ||
|
|
2126
|
+
"Palette",
|
|
2127
|
+
);
|
|
2128
|
+
this.#select.setAttribute("full", "");
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
#syncSelectAttributes() {
|
|
2132
|
+
if (!this.#select) return;
|
|
2133
|
+
this.#select.toggleAttribute(
|
|
2134
|
+
"disabled",
|
|
2135
|
+
figLabBooleanAttribute(this, "disabled"),
|
|
2136
|
+
);
|
|
2137
|
+
for (const name of ["position", "offset", "closedby"]) {
|
|
2138
|
+
if (this.hasAttribute(name)) {
|
|
2139
|
+
this.#select.setAttribute(name, this.getAttribute(name) ?? "");
|
|
2140
|
+
} else {
|
|
2141
|
+
this.#select.removeAttribute(name);
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2112
2144
|
}
|
|
2113
2145
|
|
|
2114
2146
|
#bindSelectEvents() {
|
|
2115
2147
|
if (!this.#select) return;
|
|
2116
2148
|
this.#boundHandleInput ??= this.#forwardSelectEvent.bind(this, "input");
|
|
2117
2149
|
this.#boundHandleChange ??= this.#forwardSelectEvent.bind(this, "change");
|
|
2150
|
+
this.#boundHandleOptionHover ??= this.#forwardSelectEvent.bind(
|
|
2151
|
+
this,
|
|
2152
|
+
"optionhover",
|
|
2153
|
+
);
|
|
2118
2154
|
this.#select.addEventListener("input", this.#boundHandleInput);
|
|
2119
2155
|
this.#select.addEventListener("change", this.#boundHandleChange);
|
|
2120
|
-
|
|
2121
|
-
this.#boundHandleOptionHover ??= this.#forwardSelectEvent.bind(
|
|
2122
|
-
this,
|
|
2123
|
-
"optionhover",
|
|
2124
|
-
);
|
|
2125
|
-
this.#select.addEventListener("optionhover", this.#boundHandleOptionHover);
|
|
2126
|
-
}
|
|
2156
|
+
this.#select.addEventListener("optionhover", this.#boundHandleOptionHover);
|
|
2127
2157
|
}
|
|
2128
2158
|
|
|
2129
2159
|
#unbindSelectEvents() {
|
|
@@ -2142,28 +2172,37 @@ class PropskitSelect extends HTMLElement {
|
|
|
2142
2172
|
}
|
|
2143
2173
|
}
|
|
2144
2174
|
|
|
2175
|
+
#paletteAt(index) {
|
|
2176
|
+
return this.#clonePalette(this.#palettes[index] ?? []);
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
#dispatchPropskitEvent(type, palette) {
|
|
2180
|
+
this.#eventValue = palette;
|
|
2181
|
+
try {
|
|
2182
|
+
figLabDispatchPropskitEvent(this, type, palette);
|
|
2183
|
+
} finally {
|
|
2184
|
+
this.#eventValue = undefined;
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2145
2188
|
#forwardSelectEvent(type, event) {
|
|
2146
2189
|
if (event.target !== this.#select) return;
|
|
2147
2190
|
event.stopImmediatePropagation();
|
|
2148
|
-
if (figLabBooleanAttribute(this, "disabled")) return;
|
|
2149
|
-
const
|
|
2150
|
-
|
|
2151
|
-
const detail =
|
|
2152
|
-
event instanceof CustomEvent && event.detail !== undefined
|
|
2191
|
+
if (this.#syncing || figLabBooleanAttribute(this, "disabled")) return;
|
|
2192
|
+
const rawIndex =
|
|
2193
|
+
type === "optionhover" && event instanceof CustomEvent
|
|
2153
2194
|
? event.detail
|
|
2154
|
-
: value;
|
|
2155
|
-
this
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
}),
|
|
2162
|
-
);
|
|
2195
|
+
: this.#select.getAttribute("value");
|
|
2196
|
+
const palette = this.#paletteAt(Number(rawIndex));
|
|
2197
|
+
if (type !== "optionhover") {
|
|
2198
|
+
this.#reflectPaletteValue(palette);
|
|
2199
|
+
this.#syncButtonPreview(palette);
|
|
2200
|
+
}
|
|
2201
|
+
this.#dispatchPropskitEvent(type, palette);
|
|
2163
2202
|
}
|
|
2164
2203
|
|
|
2165
2204
|
#isSelectMenuOpen() {
|
|
2166
|
-
if (!this.#select
|
|
2205
|
+
if (!this.#select) return false;
|
|
2167
2206
|
if (this.#select.open) return true;
|
|
2168
2207
|
const popup = this.#select.shadowRoot?.querySelector(
|
|
2169
2208
|
'dialog[is="fig-popup"]',
|
|
@@ -2174,24 +2213,18 @@ class PropskitSelect extends HTMLElement {
|
|
|
2174
2213
|
#handlePointerDown(event) {
|
|
2175
2214
|
if (!(event.target instanceof Element)) return;
|
|
2176
2215
|
if (event.target.closest("fig-menu")) return;
|
|
2177
|
-
|
|
2178
|
-
if (event.target.closest("fig-select, fig-dropdown")) {
|
|
2216
|
+
if (event.target.closest("fig-select")) {
|
|
2179
2217
|
this.#closeGesture = false;
|
|
2180
2218
|
return;
|
|
2181
2219
|
}
|
|
2182
|
-
if (!this.#usesFigSelect) return;
|
|
2183
|
-
// Light-dismiss closes on pointerdown; remember so click doesn't reopen.
|
|
2184
2220
|
this.#closeGesture = this.#isSelectMenuOpen();
|
|
2185
2221
|
}
|
|
2186
2222
|
|
|
2187
2223
|
#handleClick(event) {
|
|
2188
2224
|
if (figLabBooleanAttribute(this, "disabled")) return;
|
|
2189
|
-
if (event.target instanceof Element && event.target.closest("fig-menu")) {
|
|
2190
|
-
return;
|
|
2191
|
-
}
|
|
2192
2225
|
if (
|
|
2193
2226
|
event.target instanceof Element &&
|
|
2194
|
-
event.target.closest("fig-
|
|
2227
|
+
event.target.closest("fig-menu, fig-select")
|
|
2195
2228
|
) {
|
|
2196
2229
|
this.#closeGesture = false;
|
|
2197
2230
|
return;
|
|
@@ -2201,96 +2234,110 @@ class PropskitSelect extends HTMLElement {
|
|
|
2201
2234
|
return;
|
|
2202
2235
|
}
|
|
2203
2236
|
this.#select.focus();
|
|
2204
|
-
if (!this.#usesFigSelect) return;
|
|
2205
|
-
|
|
2206
2237
|
if (this.#closeGesture || this.#isSelectMenuOpen()) {
|
|
2207
2238
|
this.#closeGesture = false;
|
|
2208
2239
|
this.#select.open = false;
|
|
2209
2240
|
return;
|
|
2210
2241
|
}
|
|
2211
|
-
|
|
2212
2242
|
this.#select.open = true;
|
|
2213
2243
|
}
|
|
2214
2244
|
|
|
2215
2245
|
get value() {
|
|
2216
|
-
|
|
2246
|
+
if (this.#eventValue !== undefined) {
|
|
2247
|
+
return this.#eventValue;
|
|
2248
|
+
}
|
|
2249
|
+
const index = Number(this.#select?.getAttribute("value"));
|
|
2250
|
+
if (Number.isInteger(index) && index >= 0) return this.#paletteAt(index);
|
|
2251
|
+
if (this.hasAttribute("value")) {
|
|
2252
|
+
return this.#normalizePalette(this.getAttribute("value"));
|
|
2253
|
+
}
|
|
2254
|
+
return [];
|
|
2217
2255
|
}
|
|
2218
2256
|
|
|
2219
2257
|
set value(nextValue) {
|
|
2220
2258
|
if (nextValue === null || nextValue === undefined) {
|
|
2221
2259
|
this.removeAttribute("value");
|
|
2222
|
-
|
|
2223
|
-
this.setAttribute("value", String(nextValue));
|
|
2260
|
+
return;
|
|
2224
2261
|
}
|
|
2262
|
+
this.setAttribute("value", this.#serializePalette(nextValue));
|
|
2225
2263
|
}
|
|
2226
2264
|
|
|
2227
2265
|
get defaultValue() {
|
|
2228
|
-
|
|
2266
|
+
if (this.hasAttribute("default")) {
|
|
2267
|
+
return this.#normalizePalette(this.getAttribute("default"));
|
|
2268
|
+
}
|
|
2269
|
+
return this.#clonePalette(this.#initialValue);
|
|
2229
2270
|
}
|
|
2230
2271
|
|
|
2231
2272
|
get isDefault() {
|
|
2232
|
-
return
|
|
2273
|
+
return figLabPropskitJsonValuesEqual(this.value, this.defaultValue);
|
|
2233
2274
|
}
|
|
2234
2275
|
|
|
2235
2276
|
resetToDefault() {
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2277
|
+
this.value = this.defaultValue;
|
|
2278
|
+
for (const type of ["input", "change"]) {
|
|
2279
|
+
this.#dispatchPropskitEvent(type, this.value);
|
|
2280
|
+
}
|
|
2240
2281
|
}
|
|
2241
2282
|
|
|
2242
2283
|
focus(options) {
|
|
2243
2284
|
this.#select?.focus(options);
|
|
2244
2285
|
}
|
|
2245
2286
|
}
|
|
2246
|
-
figLabDefineElement("propskit-
|
|
2287
|
+
figLabDefineElement("propskit-palette", PropskitPalette);
|
|
2247
2288
|
|
|
2248
|
-
/*
|
|
2249
|
-
class
|
|
2250
|
-
#
|
|
2289
|
+
/* PropsKit select surface */
|
|
2290
|
+
class PropskitSelect extends FigLabPropskitElement {
|
|
2291
|
+
#surface = null;
|
|
2251
2292
|
#label = null;
|
|
2252
|
-
#
|
|
2293
|
+
#select = null;
|
|
2253
2294
|
#hasCustomLabel = false;
|
|
2254
2295
|
#observer = null;
|
|
2255
|
-
#
|
|
2296
|
+
#managedSelectAttrs = new Set();
|
|
2256
2297
|
#boundHandleInput = null;
|
|
2257
2298
|
#boundHandleChange = null;
|
|
2299
|
+
#boundHandleOptionHover = null;
|
|
2258
2300
|
#boundHandleClick = this.#handleClick.bind(this);
|
|
2301
|
+
#boundHandlePointerDown = this.#handlePointerDown.bind(this);
|
|
2302
|
+
/** True when pointerdown saw the menu open — skip click-to-open after light-dismiss. */
|
|
2303
|
+
#closeGesture = false;
|
|
2304
|
+
#eventValue = undefined;
|
|
2259
2305
|
#initialValue = null;
|
|
2260
2306
|
|
|
2261
2307
|
static get observedAttributes() {
|
|
2262
|
-
return ["label", "
|
|
2308
|
+
return ["label", "aria-label", "options", "value"];
|
|
2263
2309
|
}
|
|
2264
2310
|
|
|
2265
2311
|
connectedCallback() {
|
|
2266
|
-
if (!this.#
|
|
2267
|
-
this.#
|
|
2268
|
-
this.#
|
|
2269
|
-
this.#
|
|
2312
|
+
if (!this.#surface) this.#initialize();
|
|
2313
|
+
this.#syncSurface();
|
|
2314
|
+
this.#syncSelectAttributes();
|
|
2315
|
+
this.#bindSelectEvents();
|
|
2270
2316
|
this.removeEventListener("click", this.#boundHandleClick);
|
|
2271
2317
|
this.addEventListener("click", this.#boundHandleClick);
|
|
2318
|
+
this.removeEventListener("pointerdown", this.#boundHandlePointerDown, true);
|
|
2319
|
+
this.addEventListener("pointerdown", this.#boundHandlePointerDown, true);
|
|
2272
2320
|
figLabConnectPropskitResetMenu(this);
|
|
2273
2321
|
|
|
2274
2322
|
if (!this.#observer) {
|
|
2275
2323
|
this.#observer = new MutationObserver((mutations) => {
|
|
2276
|
-
let
|
|
2277
|
-
let
|
|
2324
|
+
let syncSurface = false;
|
|
2325
|
+
let syncSelect = false;
|
|
2278
2326
|
|
|
2279
2327
|
for (const mutation of mutations) {
|
|
2280
2328
|
if (mutation.type !== "attributes") continue;
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
syncField = true;
|
|
2329
|
+
const name = mutation.attributeName;
|
|
2330
|
+
if (name === "label" || name === "aria-label") {
|
|
2331
|
+
syncSurface = true;
|
|
2332
|
+
} else if (name === "direction") {
|
|
2333
|
+
continue;
|
|
2287
2334
|
} else {
|
|
2288
|
-
|
|
2335
|
+
syncSelect = true;
|
|
2289
2336
|
}
|
|
2290
2337
|
}
|
|
2291
2338
|
|
|
2292
|
-
if (
|
|
2293
|
-
if (
|
|
2339
|
+
if (syncSurface) this.#syncSurface();
|
|
2340
|
+
if (syncSelect) this.#syncSelectAttributes();
|
|
2294
2341
|
});
|
|
2295
2342
|
}
|
|
2296
2343
|
|
|
@@ -2299,72 +2346,63 @@ class PropskitText extends HTMLElement {
|
|
|
2299
2346
|
|
|
2300
2347
|
disconnectedCallback() {
|
|
2301
2348
|
this.#observer?.disconnect();
|
|
2302
|
-
this.#
|
|
2349
|
+
this.#unbindSelectEvents();
|
|
2303
2350
|
this.removeEventListener("click", this.#boundHandleClick);
|
|
2351
|
+
this.removeEventListener("pointerdown", this.#boundHandlePointerDown, true);
|
|
2304
2352
|
figLabDisconnectPropskitResetMenu(this);
|
|
2305
2353
|
}
|
|
2306
2354
|
|
|
2307
2355
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
2308
|
-
if (oldValue === newValue || !this.#
|
|
2309
|
-
if (name === "label" || name === "
|
|
2310
|
-
this.#
|
|
2356
|
+
if (oldValue === newValue || !this.#surface) return;
|
|
2357
|
+
if (name === "label" || name === "aria-label") {
|
|
2358
|
+
this.#syncSurface();
|
|
2359
|
+
return;
|
|
2360
|
+
}
|
|
2361
|
+
if (name === "options" || name === "value") {
|
|
2362
|
+
this.#syncSelectAttributes();
|
|
2311
2363
|
}
|
|
2312
2364
|
}
|
|
2313
2365
|
|
|
2314
2366
|
#initialize() {
|
|
2315
2367
|
this.#initialValue = this.getAttribute("value") ?? "";
|
|
2316
|
-
const
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
(node) => node.nodeType === Node.ELEMENT_NODE && node.matches("label"),
|
|
2322
|
-
);
|
|
2323
|
-
const field = document.createElement("fig-field");
|
|
2368
|
+
const customLabel = this.querySelector(":scope > label");
|
|
2369
|
+
const customOptions = this.querySelector(":scope > fig-select-options");
|
|
2370
|
+
const surface = figLabCreateElement("div", {
|
|
2371
|
+
className: "propskit-select-surface",
|
|
2372
|
+
});
|
|
2324
2373
|
const label = customLabel || document.createElement("label");
|
|
2325
|
-
const
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
this.#field = field;
|
|
2374
|
+
const select = document.createElement("fig-select");
|
|
2375
|
+
// Match menu/control width to the full surface.
|
|
2376
|
+
select.setAttribute("full", "");
|
|
2377
|
+
if (customOptions) select.append(customOptions);
|
|
2378
|
+
surface.append(label, select);
|
|
2379
|
+
this.#surface = surface;
|
|
2332
2380
|
this.#label = label;
|
|
2333
|
-
this.#
|
|
2381
|
+
this.#select = select;
|
|
2334
2382
|
this.#hasCustomLabel = Boolean(customLabel);
|
|
2335
|
-
this.replaceChildren(
|
|
2383
|
+
this.replaceChildren(surface);
|
|
2336
2384
|
}
|
|
2337
2385
|
|
|
2338
|
-
#
|
|
2339
|
-
if (!this.#
|
|
2340
|
-
const
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
this.#label.remove();
|
|
2346
|
-
} else {
|
|
2347
|
-
if (!this.#hasCustomLabel) {
|
|
2348
|
-
this.#label.textContent = hasLabelAttr ? (rawLabel ?? "") : "Label";
|
|
2349
|
-
}
|
|
2350
|
-
if (this.#label.parentElement !== this.#field) {
|
|
2351
|
-
this.#field.prepend(this.#label);
|
|
2352
|
-
}
|
|
2353
|
-
}
|
|
2354
|
-
|
|
2355
|
-
this.#field.setAttribute(
|
|
2356
|
-
"direction",
|
|
2357
|
-
this.getAttribute("direction") || "horizontal",
|
|
2386
|
+
#syncSurface() {
|
|
2387
|
+
if (!this.#surface || !this.#label || !this.#select) return;
|
|
2388
|
+
const labelId = figLabSyncPropskitLabel(
|
|
2389
|
+
this,
|
|
2390
|
+
this.#surface,
|
|
2391
|
+
this.#label,
|
|
2392
|
+
this.#hasCustomLabel,
|
|
2358
2393
|
);
|
|
2359
|
-
this.#
|
|
2360
|
-
|
|
2394
|
+
figLabSyncPropskitControlLabel(this, this.#select, labelId, "Select");
|
|
2395
|
+
this.#select.setAttribute(
|
|
2396
|
+
"label",
|
|
2361
2397
|
this.getAttribute("aria-label") ||
|
|
2362
2398
|
this.#label.textContent?.trim() ||
|
|
2363
|
-
"
|
|
2399
|
+
"Select",
|
|
2364
2400
|
);
|
|
2401
|
+
// Always match menu width to the control surface.
|
|
2402
|
+
this.#select.setAttribute("full", "");
|
|
2365
2403
|
}
|
|
2366
2404
|
|
|
2367
|
-
#
|
|
2405
|
+
#getForwardedSelectAttrNames() {
|
|
2368
2406
|
const reserved = new Set([
|
|
2369
2407
|
"label",
|
|
2370
2408
|
"direction",
|
|
@@ -2375,8 +2413,7 @@ class PropskitText extends HTMLElement {
|
|
|
2375
2413
|
"id",
|
|
2376
2414
|
"size",
|
|
2377
2415
|
"aria-label",
|
|
2378
|
-
"
|
|
2379
|
-
"resizable",
|
|
2416
|
+
"full",
|
|
2380
2417
|
"default",
|
|
2381
2418
|
"variant",
|
|
2382
2419
|
]);
|
|
@@ -2385,81 +2422,139 @@ class PropskitText extends HTMLElement {
|
|
|
2385
2422
|
);
|
|
2386
2423
|
}
|
|
2387
2424
|
|
|
2388
|
-
#
|
|
2389
|
-
if (!this.#
|
|
2390
|
-
const
|
|
2391
|
-
|
|
2425
|
+
#syncSelectAttributes() {
|
|
2426
|
+
if (!this.#select) return;
|
|
2427
|
+
const selectAttrs = this.#getForwardedSelectAttrNames().sort((a, b) => {
|
|
2428
|
+
// Build options before applying value so fig-select can resolve selection.
|
|
2429
|
+
if (a === "options") return -1;
|
|
2430
|
+
if (b === "options") return 1;
|
|
2431
|
+
if (a === "value") return 1;
|
|
2432
|
+
if (b === "value") return -1;
|
|
2433
|
+
return 0;
|
|
2434
|
+
});
|
|
2435
|
+
const nextManaged = new Set(selectAttrs);
|
|
2392
2436
|
|
|
2393
|
-
for (const attrName of this.#
|
|
2394
|
-
if (!nextManaged.has(attrName)) this.#
|
|
2437
|
+
for (const attrName of this.#managedSelectAttrs) {
|
|
2438
|
+
if (!nextManaged.has(attrName)) this.#select.removeAttribute(attrName);
|
|
2395
2439
|
}
|
|
2396
|
-
for (const attrName of
|
|
2397
|
-
this.#
|
|
2440
|
+
for (const attrName of selectAttrs) {
|
|
2441
|
+
this.#select.setAttribute(attrName, this.getAttribute(attrName) ?? "");
|
|
2398
2442
|
}
|
|
2399
2443
|
|
|
2400
|
-
this.#
|
|
2444
|
+
this.#managedSelectAttrs = nextManaged;
|
|
2401
2445
|
}
|
|
2402
2446
|
|
|
2403
|
-
#
|
|
2404
|
-
if (!this.#
|
|
2405
|
-
this.#boundHandleInput ??= this.#
|
|
2406
|
-
this.#boundHandleChange ??= this.#
|
|
2407
|
-
this.#
|
|
2408
|
-
this.#
|
|
2447
|
+
#bindSelectEvents() {
|
|
2448
|
+
if (!this.#select) return;
|
|
2449
|
+
this.#boundHandleInput ??= this.#forwardSelectEvent.bind(this, "input");
|
|
2450
|
+
this.#boundHandleChange ??= this.#forwardSelectEvent.bind(this, "change");
|
|
2451
|
+
this.#select.addEventListener("input", this.#boundHandleInput);
|
|
2452
|
+
this.#select.addEventListener("change", this.#boundHandleChange);
|
|
2453
|
+
this.#boundHandleOptionHover ??= this.#forwardSelectEvent.bind(
|
|
2454
|
+
this,
|
|
2455
|
+
"optionhover",
|
|
2456
|
+
);
|
|
2457
|
+
this.#select.addEventListener("optionhover", this.#boundHandleOptionHover);
|
|
2409
2458
|
}
|
|
2410
2459
|
|
|
2411
|
-
#
|
|
2412
|
-
if (!this.#
|
|
2460
|
+
#unbindSelectEvents() {
|
|
2461
|
+
if (!this.#select) return;
|
|
2413
2462
|
if (this.#boundHandleInput) {
|
|
2414
|
-
this.#
|
|
2463
|
+
this.#select.removeEventListener("input", this.#boundHandleInput);
|
|
2415
2464
|
}
|
|
2416
2465
|
if (this.#boundHandleChange) {
|
|
2417
|
-
this.#
|
|
2466
|
+
this.#select.removeEventListener("change", this.#boundHandleChange);
|
|
2467
|
+
}
|
|
2468
|
+
if (this.#boundHandleOptionHover) {
|
|
2469
|
+
this.#select.removeEventListener(
|
|
2470
|
+
"optionhover",
|
|
2471
|
+
this.#boundHandleOptionHover,
|
|
2472
|
+
);
|
|
2418
2473
|
}
|
|
2419
2474
|
}
|
|
2420
2475
|
|
|
2421
|
-
#
|
|
2476
|
+
#forwardSelectEvent(type, event) {
|
|
2477
|
+
if (event.target !== this.#select) return;
|
|
2422
2478
|
event.stopImmediatePropagation();
|
|
2423
2479
|
if (figLabBooleanAttribute(this, "disabled")) return;
|
|
2424
|
-
const value = this.#
|
|
2425
|
-
this.setAttribute("value", String(value));
|
|
2426
|
-
const
|
|
2427
|
-
|
|
2480
|
+
const value = this.#select?.value ?? "";
|
|
2481
|
+
if (type !== "optionhover") this.setAttribute("value", String(value));
|
|
2482
|
+
const eventValue =
|
|
2483
|
+
type === "optionhover" &&
|
|
2484
|
+
event instanceof CustomEvent &&
|
|
2485
|
+
event.detail !== undefined
|
|
2428
2486
|
? event.detail
|
|
2429
|
-
: value;
|
|
2430
|
-
this
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2487
|
+
: this.value;
|
|
2488
|
+
if (type === "optionhover") this.#eventValue = eventValue;
|
|
2489
|
+
try {
|
|
2490
|
+
figLabDispatchPropskitEvent(this, type, eventValue);
|
|
2491
|
+
} finally {
|
|
2492
|
+
this.#eventValue = undefined;
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
#isSelectMenuOpen() {
|
|
2497
|
+
if (!this.#select) return false;
|
|
2498
|
+
if (this.#select.open) return true;
|
|
2499
|
+
const popup = this.#select.shadowRoot?.querySelector(
|
|
2500
|
+
'dialog[is="fig-popup"]',
|
|
2437
2501
|
);
|
|
2502
|
+
return Boolean(popup?.open || popup?.matches?.(":open"));
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
#handlePointerDown(event) {
|
|
2506
|
+
if (!(event.target instanceof Element)) return;
|
|
2507
|
+
if (event.target.closest("fig-menu")) return;
|
|
2508
|
+
// fig-select owns its trigger clicks.
|
|
2509
|
+
if (event.target.closest("fig-select")) {
|
|
2510
|
+
this.#closeGesture = false;
|
|
2511
|
+
return;
|
|
2512
|
+
}
|
|
2513
|
+
// Light-dismiss closes on pointerdown; remember so click doesn't reopen.
|
|
2514
|
+
this.#closeGesture = this.#isSelectMenuOpen();
|
|
2438
2515
|
}
|
|
2439
2516
|
|
|
2440
2517
|
#handleClick(event) {
|
|
2441
2518
|
if (figLabBooleanAttribute(this, "disabled")) return;
|
|
2519
|
+
if (event.target instanceof Element && event.target.closest("fig-menu")) {
|
|
2520
|
+
return;
|
|
2521
|
+
}
|
|
2442
2522
|
if (
|
|
2443
2523
|
event.target instanceof Element &&
|
|
2444
|
-
event.target.closest("fig-
|
|
2524
|
+
event.target.closest("fig-select")
|
|
2445
2525
|
) {
|
|
2526
|
+
this.#closeGesture = false;
|
|
2446
2527
|
return;
|
|
2447
2528
|
}
|
|
2448
|
-
this
|
|
2529
|
+
if (!this.#select || figLabBooleanAttribute(this.#select, "disabled")) {
|
|
2530
|
+
this.#closeGesture = false;
|
|
2531
|
+
return;
|
|
2532
|
+
}
|
|
2533
|
+
this.#select.focus();
|
|
2534
|
+
|
|
2535
|
+
if (this.#closeGesture || this.#isSelectMenuOpen()) {
|
|
2536
|
+
this.#closeGesture = false;
|
|
2537
|
+
this.#select.open = false;
|
|
2538
|
+
return;
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2541
|
+
this.#select.open = true;
|
|
2449
2542
|
}
|
|
2450
2543
|
|
|
2451
2544
|
get value() {
|
|
2452
|
-
return
|
|
2545
|
+
return (
|
|
2546
|
+
this.#eventValue ??
|
|
2547
|
+
this.#select?.value ??
|
|
2548
|
+
this.getAttribute("value") ??
|
|
2549
|
+
""
|
|
2550
|
+
);
|
|
2453
2551
|
}
|
|
2454
2552
|
|
|
2455
2553
|
set value(nextValue) {
|
|
2456
2554
|
if (nextValue === null || nextValue === undefined) {
|
|
2457
2555
|
this.removeAttribute("value");
|
|
2458
|
-
if (this.#input) this.#input.value = "";
|
|
2459
2556
|
} else {
|
|
2460
|
-
|
|
2461
|
-
this.setAttribute("value", next);
|
|
2462
|
-
if (this.#input) this.#input.value = next;
|
|
2557
|
+
this.setAttribute("value", String(nextValue));
|
|
2463
2558
|
}
|
|
2464
2559
|
}
|
|
2465
2560
|
|
|
@@ -2474,18 +2569,19 @@ class PropskitText extends HTMLElement {
|
|
|
2474
2569
|
resetToDefault() {
|
|
2475
2570
|
const value = this.defaultValue;
|
|
2476
2571
|
this.value = value;
|
|
2477
|
-
|
|
2572
|
+
if (this.#select) this.#select.value = value;
|
|
2573
|
+
figLabEmitPropskitReset(this);
|
|
2478
2574
|
}
|
|
2479
2575
|
|
|
2480
2576
|
focus(options) {
|
|
2481
|
-
this.#
|
|
2577
|
+
this.#select?.focus(options);
|
|
2482
2578
|
}
|
|
2483
2579
|
}
|
|
2484
|
-
figLabDefineElement("propskit-
|
|
2580
|
+
figLabDefineElement("propskit-select", PropskitSelect);
|
|
2485
2581
|
|
|
2486
|
-
/*
|
|
2487
|
-
class
|
|
2488
|
-
#
|
|
2582
|
+
/* PropsKit text surface */
|
|
2583
|
+
class PropskitText extends FigLabPropskitElement {
|
|
2584
|
+
#surface = null;
|
|
2489
2585
|
#label = null;
|
|
2490
2586
|
#input = null;
|
|
2491
2587
|
#hasCustomLabel = false;
|
|
@@ -2497,12 +2593,12 @@ class PropskitNumber extends HTMLElement {
|
|
|
2497
2593
|
#initialValue = null;
|
|
2498
2594
|
|
|
2499
2595
|
static get observedAttributes() {
|
|
2500
|
-
return ["label", "
|
|
2596
|
+
return ["label", "aria-label"];
|
|
2501
2597
|
}
|
|
2502
2598
|
|
|
2503
2599
|
connectedCallback() {
|
|
2504
|
-
if (!this.#
|
|
2505
|
-
this.#
|
|
2600
|
+
if (!this.#surface) this.#initialize();
|
|
2601
|
+
this.#syncSurface();
|
|
2506
2602
|
this.#syncInputAttributes();
|
|
2507
2603
|
this.#bindInputEvents();
|
|
2508
2604
|
this.removeEventListener("click", this.#boundHandleClick);
|
|
@@ -2511,22 +2607,24 @@ class PropskitNumber extends HTMLElement {
|
|
|
2511
2607
|
|
|
2512
2608
|
if (!this.#observer) {
|
|
2513
2609
|
this.#observer = new MutationObserver((mutations) => {
|
|
2514
|
-
let
|
|
2610
|
+
let syncSurface = false;
|
|
2515
2611
|
let syncInput = false;
|
|
2516
2612
|
|
|
2517
2613
|
for (const mutation of mutations) {
|
|
2518
2614
|
if (mutation.type !== "attributes") continue;
|
|
2519
2615
|
if (
|
|
2520
2616
|
mutation.attributeName === "label" ||
|
|
2521
|
-
mutation.attributeName === "
|
|
2617
|
+
mutation.attributeName === "aria-label"
|
|
2522
2618
|
) {
|
|
2523
|
-
|
|
2619
|
+
syncSurface = true;
|
|
2620
|
+
} else if (mutation.attributeName === "direction") {
|
|
2621
|
+
continue;
|
|
2524
2622
|
} else {
|
|
2525
2623
|
syncInput = true;
|
|
2526
2624
|
}
|
|
2527
2625
|
}
|
|
2528
2626
|
|
|
2529
|
-
if (
|
|
2627
|
+
if (syncSurface) this.#syncSurface();
|
|
2530
2628
|
if (syncInput) this.#syncInputAttributes();
|
|
2531
2629
|
});
|
|
2532
2630
|
}
|
|
@@ -2542,13 +2640,14 @@ class PropskitNumber extends HTMLElement {
|
|
|
2542
2640
|
}
|
|
2543
2641
|
|
|
2544
2642
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
2545
|
-
if (oldValue === newValue || !this.#
|
|
2546
|
-
if (name === "label" || name === "
|
|
2643
|
+
if (oldValue === newValue || !this.#surface) return;
|
|
2644
|
+
if (name === "label" || name === "aria-label") {
|
|
2645
|
+
this.#syncSurface();
|
|
2646
|
+
}
|
|
2547
2647
|
}
|
|
2548
2648
|
|
|
2549
2649
|
#initialize() {
|
|
2550
|
-
this.#initialValue =
|
|
2551
|
-
this.getAttribute("value") ?? this.getAttribute("min") ?? "0";
|
|
2650
|
+
this.#initialValue = this.getAttribute("value") ?? "";
|
|
2552
2651
|
const initialChildren = Array.from(this.childNodes).filter(
|
|
2553
2652
|
(node) =>
|
|
2554
2653
|
node.nodeType !== Node.TEXT_NODE || Boolean(node.textContent?.trim()),
|
|
@@ -2556,55 +2655,49 @@ class PropskitNumber extends HTMLElement {
|
|
|
2556
2655
|
const customLabel = initialChildren.find(
|
|
2557
2656
|
(node) => node.nodeType === Node.ELEMENT_NODE && node.matches("label"),
|
|
2558
2657
|
);
|
|
2559
|
-
const
|
|
2658
|
+
const surface = figLabCreateElement("div", {
|
|
2659
|
+
className: "propskit-text-surface",
|
|
2660
|
+
});
|
|
2560
2661
|
const label = customLabel || document.createElement("label");
|
|
2561
|
-
const input = document.createElement("fig-input-
|
|
2562
|
-
|
|
2563
|
-
field.append(label, input);
|
|
2564
|
-
this.#field = field;
|
|
2565
|
-
this.#label = label;
|
|
2566
|
-
this.#input = input;
|
|
2567
|
-
this.#hasCustomLabel = Boolean(customLabel);
|
|
2568
|
-
this.replaceChildren(field);
|
|
2662
|
+
const input = document.createElement("fig-input-text");
|
|
2569
2663
|
|
|
2570
2664
|
for (const node of initialChildren) {
|
|
2571
2665
|
if (node !== customLabel) input.appendChild(node);
|
|
2572
2666
|
}
|
|
2667
|
+
surface.append(label, input);
|
|
2668
|
+
this.#surface = surface;
|
|
2669
|
+
this.#label = label;
|
|
2670
|
+
this.#input = input;
|
|
2671
|
+
this.#hasCustomLabel = Boolean(customLabel);
|
|
2672
|
+
this.replaceChildren(surface);
|
|
2573
2673
|
}
|
|
2574
2674
|
|
|
2575
|
-
#
|
|
2576
|
-
if (!this.#
|
|
2577
|
-
const
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
this.#label.remove();
|
|
2583
|
-
} else {
|
|
2584
|
-
if (!this.#hasCustomLabel) {
|
|
2585
|
-
this.#label.textContent = hasLabelAttr ? (rawLabel ?? "") : "Label";
|
|
2586
|
-
}
|
|
2587
|
-
if (this.#label.parentElement !== this.#field) {
|
|
2588
|
-
this.#field.prepend(this.#label);
|
|
2589
|
-
}
|
|
2590
|
-
}
|
|
2591
|
-
|
|
2592
|
-
this.#field.setAttribute(
|
|
2593
|
-
"direction",
|
|
2594
|
-
this.getAttribute("direction") || "horizontal",
|
|
2675
|
+
#syncSurface() {
|
|
2676
|
+
if (!this.#surface || !this.#label || !this.#input) return;
|
|
2677
|
+
const labelId = figLabSyncPropskitLabel(
|
|
2678
|
+
this,
|
|
2679
|
+
this.#surface,
|
|
2680
|
+
this.#label,
|
|
2681
|
+
this.#hasCustomLabel,
|
|
2595
2682
|
);
|
|
2683
|
+
figLabSyncPropskitControlLabel(this, this.#input, labelId, "Text");
|
|
2596
2684
|
}
|
|
2597
2685
|
|
|
2598
2686
|
#getForwardedInputAttrNames() {
|
|
2599
2687
|
const reserved = new Set([
|
|
2600
2688
|
"label",
|
|
2601
2689
|
"direction",
|
|
2602
|
-
"size",
|
|
2603
2690
|
"oninput",
|
|
2604
2691
|
"onchange",
|
|
2605
2692
|
"class",
|
|
2606
2693
|
"style",
|
|
2607
2694
|
"id",
|
|
2695
|
+
"size",
|
|
2696
|
+
"type",
|
|
2697
|
+
"aria-label",
|
|
2698
|
+
"multiline",
|
|
2699
|
+
"autoresize",
|
|
2700
|
+
"resizable",
|
|
2608
2701
|
"default",
|
|
2609
2702
|
"variant",
|
|
2610
2703
|
]);
|
|
@@ -2616,7 +2709,8 @@ class PropskitNumber extends HTMLElement {
|
|
|
2616
2709
|
#syncInputAttributes() {
|
|
2617
2710
|
if (!this.#input) return;
|
|
2618
2711
|
const inputAttrs = this.#getForwardedInputAttrNames();
|
|
2619
|
-
const
|
|
2712
|
+
const defaultEnabledAttrs = ["multiline", "autoresize"];
|
|
2713
|
+
const nextManaged = new Set([...inputAttrs, ...defaultEnabledAttrs, "type"]);
|
|
2620
2714
|
|
|
2621
2715
|
for (const attrName of this.#managedInputAttrs) {
|
|
2622
2716
|
if (!nextManaged.has(attrName)) this.#input.removeAttribute(attrName);
|
|
@@ -2624,6 +2718,10 @@ class PropskitNumber extends HTMLElement {
|
|
|
2624
2718
|
for (const attrName of inputAttrs) {
|
|
2625
2719
|
this.#input.setAttribute(attrName, this.getAttribute(attrName) ?? "");
|
|
2626
2720
|
}
|
|
2721
|
+
for (const attrName of defaultEnabledAttrs) {
|
|
2722
|
+
this.#input.setAttribute(attrName, this.getAttribute(attrName) ?? "");
|
|
2723
|
+
}
|
|
2724
|
+
this.#input.setAttribute("type", "text");
|
|
2627
2725
|
|
|
2628
2726
|
this.#managedInputAttrs = nextManaged;
|
|
2629
2727
|
}
|
|
@@ -2649,28 +2747,16 @@ class PropskitNumber extends HTMLElement {
|
|
|
2649
2747
|
#forwardInputEvent(type, event) {
|
|
2650
2748
|
event.stopImmediatePropagation();
|
|
2651
2749
|
if (figLabBooleanAttribute(this, "disabled")) return;
|
|
2652
|
-
const
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
: this.#input?.value;
|
|
2656
|
-
if (this.#input?.value !== undefined) {
|
|
2657
|
-
this.setAttribute("value", String(this.#input.value));
|
|
2658
|
-
}
|
|
2659
|
-
this.dispatchEvent(
|
|
2660
|
-
new CustomEvent(type, {
|
|
2661
|
-
detail,
|
|
2662
|
-
bubbles: true,
|
|
2663
|
-
cancelable: true,
|
|
2664
|
-
composed: true,
|
|
2665
|
-
}),
|
|
2666
|
-
);
|
|
2750
|
+
const value = this.#input?.value ?? "";
|
|
2751
|
+
this.setAttribute("value", String(value));
|
|
2752
|
+
figLabDispatchPropskitEvent(this, type);
|
|
2667
2753
|
}
|
|
2668
2754
|
|
|
2669
2755
|
#handleClick(event) {
|
|
2670
2756
|
if (figLabBooleanAttribute(this, "disabled")) return;
|
|
2671
2757
|
if (
|
|
2672
2758
|
event.target instanceof Element &&
|
|
2673
|
-
event.target.closest("fig-input-
|
|
2759
|
+
event.target.closest("fig-input-text, fig-menu")
|
|
2674
2760
|
) {
|
|
2675
2761
|
return;
|
|
2676
2762
|
}
|
|
@@ -2682,7 +2768,7 @@ class PropskitNumber extends HTMLElement {
|
|
|
2682
2768
|
}
|
|
2683
2769
|
|
|
2684
2770
|
set value(nextValue) {
|
|
2685
|
-
if (nextValue === null || nextValue === undefined
|
|
2771
|
+
if (nextValue === null || nextValue === undefined) {
|
|
2686
2772
|
this.removeAttribute("value");
|
|
2687
2773
|
if (this.#input) this.#input.value = "";
|
|
2688
2774
|
} else {
|
|
@@ -2693,7 +2779,7 @@ class PropskitNumber extends HTMLElement {
|
|
|
2693
2779
|
}
|
|
2694
2780
|
|
|
2695
2781
|
get defaultValue() {
|
|
2696
|
-
return this.getAttribute("default") ?? this.#initialValue ?? "
|
|
2782
|
+
return this.getAttribute("default") ?? this.#initialValue ?? "";
|
|
2697
2783
|
}
|
|
2698
2784
|
|
|
2699
2785
|
get isDefault() {
|
|
@@ -2703,119 +2789,353 @@ class PropskitNumber extends HTMLElement {
|
|
|
2703
2789
|
resetToDefault() {
|
|
2704
2790
|
const value = this.defaultValue;
|
|
2705
2791
|
this.value = value;
|
|
2706
|
-
figLabEmitPropskitReset(this
|
|
2792
|
+
figLabEmitPropskitReset(this);
|
|
2707
2793
|
}
|
|
2708
2794
|
|
|
2709
2795
|
focus(options) {
|
|
2710
2796
|
this.#input?.focus(options);
|
|
2711
2797
|
}
|
|
2712
2798
|
}
|
|
2713
|
-
figLabDefineElement("propskit-
|
|
2714
|
-
|
|
2715
|
-
/**
|
|
2716
|
-
* Compact X/Y editor.
|
|
2717
|
-
*
|
|
2718
|
-
* @attr {number} x - Horizontal value.
|
|
2719
|
-
* @attr {number} y - Vertical value.
|
|
2720
|
-
* @attr {string} default - JSON reset value with x and y.
|
|
2721
|
-
* @attr {string} label - Field label. Empty values use "Position".
|
|
2722
|
-
* @attr {string} units - Set to "percent" to show percentage units.
|
|
2723
|
-
* @attr {boolean|string} disabled - Disables both number inputs.
|
|
2724
|
-
* @attr {string} size - Defaults to the large row; set to "small" for compact.
|
|
2725
|
-
* @fires input - Composed event with { x, y }.
|
|
2726
|
-
* @fires change - Composed event with { x, y }.
|
|
2727
|
-
*/
|
|
2728
|
-
class PropskitPosition extends HTMLElement {
|
|
2729
|
-
static observedAttributes = [
|
|
2730
|
-
"x",
|
|
2731
|
-
"y",
|
|
2732
|
-
"default",
|
|
2733
|
-
"label",
|
|
2734
|
-
"units",
|
|
2735
|
-
"disabled",
|
|
2736
|
-
"size",
|
|
2737
|
-
];
|
|
2799
|
+
figLabDefineElement("propskit-text", PropskitText);
|
|
2738
2800
|
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
#
|
|
2742
|
-
#
|
|
2743
|
-
#
|
|
2744
|
-
#
|
|
2745
|
-
#
|
|
2746
|
-
#
|
|
2801
|
+
/* PropsKit number surface */
|
|
2802
|
+
class PropskitNumber extends FigLabPropskitElement {
|
|
2803
|
+
#surface = null;
|
|
2804
|
+
#label = null;
|
|
2805
|
+
#input = null;
|
|
2806
|
+
#hasCustomLabel = false;
|
|
2807
|
+
#observer = null;
|
|
2808
|
+
#managedInputAttrs = new Set();
|
|
2809
|
+
#boundHandleInput = null;
|
|
2810
|
+
#boundHandleChange = null;
|
|
2747
2811
|
#boundHandleClick = this.#handleClick.bind(this);
|
|
2812
|
+
#boundHandleKeyDown = this.#handleKeyDown.bind(this);
|
|
2813
|
+
#initialValue = null;
|
|
2814
|
+
|
|
2815
|
+
static get observedAttributes() {
|
|
2816
|
+
return ["label", "aria-label"];
|
|
2817
|
+
}
|
|
2748
2818
|
|
|
2749
2819
|
connectedCallback() {
|
|
2750
|
-
if (!this.#
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
}
|
|
2755
|
-
if (!this.#field) this.#render();
|
|
2756
|
-
this.#syncLabel();
|
|
2757
|
-
this.#syncInputs();
|
|
2758
|
-
this.#bindEvents();
|
|
2820
|
+
if (!this.#surface) this.#initialize();
|
|
2821
|
+
this.#syncSurface();
|
|
2822
|
+
this.#syncInputAttributes();
|
|
2823
|
+
this.#bindInputEvents();
|
|
2759
2824
|
this.removeEventListener("click", this.#boundHandleClick);
|
|
2760
2825
|
this.addEventListener("click", this.#boundHandleClick);
|
|
2826
|
+
this.removeEventListener("keydown", this.#boundHandleKeyDown);
|
|
2827
|
+
this.addEventListener("keydown", this.#boundHandleKeyDown);
|
|
2761
2828
|
figLabConnectPropskitResetMenu(this);
|
|
2829
|
+
|
|
2830
|
+
if (!this.#observer) {
|
|
2831
|
+
this.#observer = new MutationObserver((mutations) => {
|
|
2832
|
+
let syncSurface = false;
|
|
2833
|
+
let syncInput = false;
|
|
2834
|
+
|
|
2835
|
+
for (const mutation of mutations) {
|
|
2836
|
+
if (mutation.type !== "attributes") continue;
|
|
2837
|
+
if (
|
|
2838
|
+
mutation.attributeName === "label" ||
|
|
2839
|
+
mutation.attributeName === "aria-label"
|
|
2840
|
+
) {
|
|
2841
|
+
syncSurface = true;
|
|
2842
|
+
} else if (mutation.attributeName === "direction") {
|
|
2843
|
+
continue;
|
|
2844
|
+
} else {
|
|
2845
|
+
syncInput = true;
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
|
|
2849
|
+
if (syncSurface) this.#syncSurface();
|
|
2850
|
+
if (syncInput) this.#syncInputAttributes();
|
|
2851
|
+
});
|
|
2852
|
+
}
|
|
2853
|
+
|
|
2854
|
+
this.#observer.observe(this, { attributes: true });
|
|
2762
2855
|
}
|
|
2763
2856
|
|
|
2764
2857
|
disconnectedCallback() {
|
|
2765
|
-
this.#
|
|
2858
|
+
this.#observer?.disconnect();
|
|
2859
|
+
this.#unbindInputEvents();
|
|
2766
2860
|
this.removeEventListener("click", this.#boundHandleClick);
|
|
2861
|
+
this.removeEventListener("keydown", this.#boundHandleKeyDown);
|
|
2767
2862
|
figLabDisconnectPropskitResetMenu(this);
|
|
2768
2863
|
}
|
|
2769
2864
|
|
|
2770
2865
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
2771
|
-
if (oldValue === newValue || !this.#
|
|
2772
|
-
if (
|
|
2773
|
-
this.#syncInputs();
|
|
2774
|
-
} else if (name === "label") {
|
|
2775
|
-
this.#syncLabel();
|
|
2776
|
-
} else if (name === "units") {
|
|
2777
|
-
this.#syncUnits();
|
|
2778
|
-
} else if (name === "disabled") {
|
|
2779
|
-
this.#syncDisabled();
|
|
2780
|
-
}
|
|
2866
|
+
if (oldValue === newValue || !this.#surface) return;
|
|
2867
|
+
if (name === "label" || name === "aria-label") this.#syncSurface();
|
|
2781
2868
|
}
|
|
2782
2869
|
|
|
2783
|
-
#
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2870
|
+
#initialize() {
|
|
2871
|
+
this.#initialValue =
|
|
2872
|
+
this.getAttribute("value") ?? this.getAttribute("min") ?? "0";
|
|
2873
|
+
const initialChildren = Array.from(this.childNodes).filter(
|
|
2874
|
+
(node) =>
|
|
2875
|
+
node.nodeType !== Node.TEXT_NODE || Boolean(node.textContent?.trim()),
|
|
2876
|
+
);
|
|
2877
|
+
const customLabel = initialChildren.find(
|
|
2878
|
+
(node) => node.nodeType === Node.ELEMENT_NODE && node.matches("label"),
|
|
2879
|
+
);
|
|
2880
|
+
const surface = figLabCreateElement("div", {
|
|
2881
|
+
className: "propskit-number-surface",
|
|
2882
|
+
});
|
|
2883
|
+
const label = customLabel || document.createElement("label");
|
|
2884
|
+
const input = document.createElement("fig-input-number");
|
|
2787
2885
|
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
};
|
|
2795
|
-
}
|
|
2886
|
+
surface.append(label, input);
|
|
2887
|
+
this.#surface = surface;
|
|
2888
|
+
this.#label = label;
|
|
2889
|
+
this.#input = input;
|
|
2890
|
+
this.#hasCustomLabel = Boolean(customLabel);
|
|
2891
|
+
this.replaceChildren(surface);
|
|
2796
2892
|
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
try {
|
|
2800
|
-
const parsed = JSON.parse(value);
|
|
2801
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2802
|
-
return null;
|
|
2803
|
-
}
|
|
2804
|
-
return {
|
|
2805
|
-
x: this.#finiteNumber(parsed.x, this.#initialValue?.x ?? 50),
|
|
2806
|
-
y: this.#finiteNumber(parsed.y, this.#initialValue?.y ?? 50),
|
|
2807
|
-
};
|
|
2808
|
-
} catch {
|
|
2809
|
-
return null;
|
|
2893
|
+
for (const node of initialChildren) {
|
|
2894
|
+
if (node !== customLabel) input.appendChild(node);
|
|
2810
2895
|
}
|
|
2811
2896
|
}
|
|
2812
2897
|
|
|
2813
|
-
#
|
|
2814
|
-
const
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2898
|
+
#syncSurface() {
|
|
2899
|
+
const labelId = figLabSyncPropskitLabel(
|
|
2900
|
+
this,
|
|
2901
|
+
this.#surface,
|
|
2902
|
+
this.#label,
|
|
2903
|
+
this.#hasCustomLabel,
|
|
2904
|
+
);
|
|
2905
|
+
figLabSyncPropskitControlLabel(this, this.#input, labelId, "Number");
|
|
2906
|
+
}
|
|
2907
|
+
|
|
2908
|
+
#getForwardedInputAttrNames() {
|
|
2909
|
+
const reserved = new Set([
|
|
2910
|
+
"label",
|
|
2911
|
+
"direction",
|
|
2912
|
+
"size",
|
|
2913
|
+
"aria-label",
|
|
2914
|
+
"oninput",
|
|
2915
|
+
"onchange",
|
|
2916
|
+
"class",
|
|
2917
|
+
"style",
|
|
2918
|
+
"id",
|
|
2919
|
+
"default",
|
|
2920
|
+
"variant",
|
|
2921
|
+
]);
|
|
2922
|
+
return this.getAttributeNames().filter(
|
|
2923
|
+
(name) => !reserved.has(name) && !name.startsWith("data-"),
|
|
2924
|
+
);
|
|
2925
|
+
}
|
|
2926
|
+
|
|
2927
|
+
#syncInputAttributes() {
|
|
2928
|
+
if (!this.#input) return;
|
|
2929
|
+
const inputAttrs = this.#getForwardedInputAttrNames();
|
|
2930
|
+
const nextManaged = new Set(inputAttrs);
|
|
2931
|
+
|
|
2932
|
+
for (const attrName of this.#managedInputAttrs) {
|
|
2933
|
+
if (!nextManaged.has(attrName)) this.#input.removeAttribute(attrName);
|
|
2934
|
+
}
|
|
2935
|
+
for (const attrName of inputAttrs) {
|
|
2936
|
+
this.#input.setAttribute(attrName, this.getAttribute(attrName) ?? "");
|
|
2937
|
+
}
|
|
2938
|
+
|
|
2939
|
+
this.#managedInputAttrs = nextManaged;
|
|
2940
|
+
}
|
|
2941
|
+
|
|
2942
|
+
#bindInputEvents() {
|
|
2943
|
+
if (!this.#input) return;
|
|
2944
|
+
this.#boundHandleInput ??= this.#forwardInputEvent.bind(this, "input");
|
|
2945
|
+
this.#boundHandleChange ??= this.#forwardInputEvent.bind(this, "change");
|
|
2946
|
+
this.#input.addEventListener("input", this.#boundHandleInput);
|
|
2947
|
+
this.#input.addEventListener("change", this.#boundHandleChange);
|
|
2948
|
+
}
|
|
2949
|
+
|
|
2950
|
+
#unbindInputEvents() {
|
|
2951
|
+
if (!this.#input) return;
|
|
2952
|
+
if (this.#boundHandleInput) {
|
|
2953
|
+
this.#input.removeEventListener("input", this.#boundHandleInput);
|
|
2954
|
+
}
|
|
2955
|
+
if (this.#boundHandleChange) {
|
|
2956
|
+
this.#input.removeEventListener("change", this.#boundHandleChange);
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
|
|
2960
|
+
#forwardInputEvent(type, event) {
|
|
2961
|
+
event.stopImmediatePropagation();
|
|
2962
|
+
if (figLabBooleanAttribute(this, "disabled")) return;
|
|
2963
|
+
const value = figLabFiniteNumberOrNull(this.#input?.value);
|
|
2964
|
+
if (value === null) this.removeAttribute("value");
|
|
2965
|
+
else this.setAttribute("value", String(value));
|
|
2966
|
+
figLabDispatchPropskitEvent(this, type);
|
|
2967
|
+
}
|
|
2968
|
+
|
|
2969
|
+
#handleClick(event) {
|
|
2970
|
+
if (figLabBooleanAttribute(this, "disabled")) return;
|
|
2971
|
+
if (
|
|
2972
|
+
event.target instanceof Element &&
|
|
2973
|
+
event.target.closest("fig-input-number, fig-menu")
|
|
2974
|
+
) {
|
|
2975
|
+
return;
|
|
2976
|
+
}
|
|
2977
|
+
this.focus();
|
|
2978
|
+
}
|
|
2979
|
+
|
|
2980
|
+
#handleKeyDown(event) {
|
|
2981
|
+
if (
|
|
2982
|
+
!["ArrowUp", "ArrowDown"].includes(event.key) ||
|
|
2983
|
+
!(event.target instanceof Element) ||
|
|
2984
|
+
!event.target.closest("fig-input-number")
|
|
2985
|
+
) {
|
|
2986
|
+
return;
|
|
2987
|
+
}
|
|
2988
|
+
event.stopPropagation();
|
|
2989
|
+
const input = this.#input?.querySelector("input");
|
|
2990
|
+
queueMicrotask(() => {
|
|
2991
|
+
if (this.isConnected && !figLabBooleanAttribute(this, "disabled")) {
|
|
2992
|
+
input?.focus({ preventScroll: true });
|
|
2993
|
+
}
|
|
2994
|
+
});
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2997
|
+
get value() {
|
|
2998
|
+
return figLabFiniteNumberOrNull(
|
|
2999
|
+
this.#input?.value ?? this.getAttribute("value"),
|
|
3000
|
+
);
|
|
3001
|
+
}
|
|
3002
|
+
|
|
3003
|
+
set value(nextValue) {
|
|
3004
|
+
if (nextValue === null || nextValue === undefined || nextValue === "") {
|
|
3005
|
+
this.removeAttribute("value");
|
|
3006
|
+
if (this.#input) this.#input.value = "";
|
|
3007
|
+
} else {
|
|
3008
|
+
const next = String(nextValue);
|
|
3009
|
+
this.setAttribute("value", next);
|
|
3010
|
+
if (this.#input) this.#input.value = next;
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
|
|
3014
|
+
get defaultValue() {
|
|
3015
|
+
return this.getAttribute("default") ?? this.#initialValue ?? "0";
|
|
3016
|
+
}
|
|
3017
|
+
|
|
3018
|
+
get isDefault() {
|
|
3019
|
+
return figLabPropskitValuesEqual(this.value, this.defaultValue);
|
|
3020
|
+
}
|
|
3021
|
+
|
|
3022
|
+
resetToDefault() {
|
|
3023
|
+
const value = this.defaultValue;
|
|
3024
|
+
this.value = value;
|
|
3025
|
+
figLabEmitPropskitReset(this);
|
|
3026
|
+
}
|
|
3027
|
+
|
|
3028
|
+
focus(options) {
|
|
3029
|
+
this.#input?.focus(options);
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
figLabDefineElement("propskit-number", PropskitNumber);
|
|
3033
|
+
|
|
3034
|
+
/**
|
|
3035
|
+
* Compact X/Y editor.
|
|
3036
|
+
*
|
|
3037
|
+
* @attr {number} x - Horizontal value.
|
|
3038
|
+
* @attr {number} y - Vertical value.
|
|
3039
|
+
* @attr {string} default - JSON reset value with x and y.
|
|
3040
|
+
* @attr {string} label - Surface label. Omitted values use "Label"; empty hides it.
|
|
3041
|
+
* @attr {string} units - Set to "percent" to show percentage units.
|
|
3042
|
+
* @attr {boolean|string} disabled - Disables both number inputs.
|
|
3043
|
+
* @fires input - Composed event with { x, y }.
|
|
3044
|
+
* @fires change - Composed event with { x, y }.
|
|
3045
|
+
*/
|
|
3046
|
+
class PropskitPosition extends FigLabPropskitElement {
|
|
3047
|
+
static observedAttributes = [
|
|
3048
|
+
"x",
|
|
3049
|
+
"y",
|
|
3050
|
+
"default",
|
|
3051
|
+
"label",
|
|
3052
|
+
"aria-label",
|
|
3053
|
+
"units",
|
|
3054
|
+
"disabled",
|
|
3055
|
+
];
|
|
3056
|
+
|
|
3057
|
+
#surface = null;
|
|
3058
|
+
#label = null;
|
|
3059
|
+
#hasCustomLabel = false;
|
|
3060
|
+
#xInput = null;
|
|
3061
|
+
#yInput = null;
|
|
3062
|
+
#initialValue = null;
|
|
3063
|
+
#initialized = false;
|
|
3064
|
+
#reflecting = false;
|
|
3065
|
+
#boundHandleInput = this.#handleInputEvent.bind(this, "input");
|
|
3066
|
+
#boundHandleChange = this.#handleInputEvent.bind(this, "change");
|
|
3067
|
+
#boundHandleClick = this.#handleClick.bind(this);
|
|
3068
|
+
|
|
3069
|
+
connectedCallback() {
|
|
3070
|
+
if (!this.#initialized) {
|
|
3071
|
+
this.#initialValue = this.#readValue();
|
|
3072
|
+
this.#reflectValue(this.#initialValue);
|
|
3073
|
+
this.#initialized = true;
|
|
3074
|
+
}
|
|
3075
|
+
if (!this.#surface) this.#render();
|
|
3076
|
+
this.#syncLabel();
|
|
3077
|
+
this.#syncInputs();
|
|
3078
|
+
this.#bindEvents();
|
|
3079
|
+
this.removeEventListener("click", this.#boundHandleClick);
|
|
3080
|
+
this.addEventListener("click", this.#boundHandleClick);
|
|
3081
|
+
figLabConnectPropskitResetMenu(this);
|
|
3082
|
+
}
|
|
3083
|
+
|
|
3084
|
+
disconnectedCallback() {
|
|
3085
|
+
this.#unbindEvents();
|
|
3086
|
+
this.removeEventListener("click", this.#boundHandleClick);
|
|
3087
|
+
figLabDisconnectPropskitResetMenu(this);
|
|
3088
|
+
}
|
|
3089
|
+
|
|
3090
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
3091
|
+
if (oldValue === newValue || !this.#initialized) return;
|
|
3092
|
+
if ((name === "x" || name === "y") && !this.#reflecting) {
|
|
3093
|
+
this.#syncInputs();
|
|
3094
|
+
} else if (name === "label" || name === "aria-label") {
|
|
3095
|
+
this.#syncLabel();
|
|
3096
|
+
} else if (name === "units") {
|
|
3097
|
+
this.#syncUnits();
|
|
3098
|
+
} else if (name === "disabled") {
|
|
3099
|
+
this.#syncDisabled();
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
|
|
3103
|
+
#finiteNumber(value, fallback = 50) {
|
|
3104
|
+
const number = Number(value);
|
|
3105
|
+
return Number.isFinite(number) ? number : fallback;
|
|
3106
|
+
}
|
|
3107
|
+
|
|
3108
|
+
#readValue() {
|
|
3109
|
+
const x = this.getAttribute("x");
|
|
3110
|
+
const y = this.getAttribute("y");
|
|
3111
|
+
return {
|
|
3112
|
+
x: x === null || !x.trim() ? 50 : this.#finiteNumber(x, 50),
|
|
3113
|
+
y: y === null || !y.trim() ? 50 : this.#finiteNumber(y, 50),
|
|
3114
|
+
};
|
|
3115
|
+
}
|
|
3116
|
+
|
|
3117
|
+
#parseDefault(value) {
|
|
3118
|
+
if (typeof value !== "string" || !value.trim()) return null;
|
|
3119
|
+
try {
|
|
3120
|
+
const parsed = JSON.parse(value);
|
|
3121
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
3122
|
+
return null;
|
|
3123
|
+
}
|
|
3124
|
+
return {
|
|
3125
|
+
x: this.#finiteNumber(parsed.x, this.#initialValue?.x ?? 50),
|
|
3126
|
+
y: this.#finiteNumber(parsed.y, this.#initialValue?.y ?? 50),
|
|
3127
|
+
};
|
|
3128
|
+
} catch {
|
|
3129
|
+
return null;
|
|
3130
|
+
}
|
|
3131
|
+
}
|
|
3132
|
+
|
|
3133
|
+
#reflectValue(value) {
|
|
3134
|
+
const current = this.#readValue();
|
|
3135
|
+
const normalized = {
|
|
3136
|
+
x: this.#finiteNumber(value?.x, current.x),
|
|
3137
|
+
y: this.#finiteNumber(value?.y, current.y),
|
|
3138
|
+
};
|
|
2819
3139
|
this.#reflecting = true;
|
|
2820
3140
|
this.setAttribute("x", String(normalized.x));
|
|
2821
3141
|
this.setAttribute("y", String(normalized.y));
|
|
@@ -2840,142 +3160,1000 @@ class PropskitPosition extends HTMLElement {
|
|
|
2840
3160
|
if (figLabBooleanAttribute(this, "disabled")) {
|
|
2841
3161
|
input.setAttribute("disabled", "");
|
|
2842
3162
|
}
|
|
2843
|
-
return input;
|
|
3163
|
+
return input;
|
|
3164
|
+
}
|
|
3165
|
+
|
|
3166
|
+
#render() {
|
|
3167
|
+
const value = this.#readValue();
|
|
3168
|
+
const customLabel = this.querySelector(":scope > label");
|
|
3169
|
+
const surface = figLabCreateElement("div", {
|
|
3170
|
+
className: "propskit-position-surface",
|
|
3171
|
+
role: "group",
|
|
3172
|
+
});
|
|
3173
|
+
const label = customLabel || document.createElement("label");
|
|
3174
|
+
const xInput = this.#createNumber("x", value.x);
|
|
3175
|
+
const yInput = this.#createNumber("y", value.y);
|
|
3176
|
+
surface.append(label, xInput, yInput);
|
|
3177
|
+
this.#surface = surface;
|
|
3178
|
+
this.#label = label;
|
|
3179
|
+
this.#hasCustomLabel = Boolean(customLabel);
|
|
3180
|
+
this.#xInput = xInput;
|
|
3181
|
+
this.#yInput = yInput;
|
|
3182
|
+
this.replaceChildren(surface);
|
|
3183
|
+
figLabConnectPropskitResetMenu(this);
|
|
3184
|
+
}
|
|
3185
|
+
|
|
3186
|
+
#syncLabel() {
|
|
3187
|
+
const labelId = figLabSyncPropskitLabel(
|
|
3188
|
+
this,
|
|
3189
|
+
this.#surface,
|
|
3190
|
+
this.#label,
|
|
3191
|
+
this.#hasCustomLabel,
|
|
3192
|
+
);
|
|
3193
|
+
figLabSyncPropskitControlLabel(this, this.#surface, labelId, "Position");
|
|
3194
|
+
}
|
|
3195
|
+
|
|
3196
|
+
#syncInputs() {
|
|
3197
|
+
const value = this.#readValue();
|
|
3198
|
+
this.#xInput?.setAttribute("value", String(value.x));
|
|
3199
|
+
this.#yInput?.setAttribute("value", String(value.y));
|
|
3200
|
+
this.#syncUnits();
|
|
3201
|
+
this.#syncDisabled();
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
#syncUnits() {
|
|
3205
|
+
const units = this.units === "percent" ? "%" : null;
|
|
3206
|
+
for (const input of [this.#xInput, this.#yInput]) {
|
|
3207
|
+
if (!input) continue;
|
|
3208
|
+
if (units) input.setAttribute("units", units);
|
|
3209
|
+
else input.removeAttribute("units");
|
|
3210
|
+
}
|
|
3211
|
+
}
|
|
3212
|
+
|
|
3213
|
+
#syncDisabled() {
|
|
3214
|
+
const disabled = figLabBooleanAttribute(this, "disabled");
|
|
3215
|
+
this.#xInput?.toggleAttribute("disabled", disabled);
|
|
3216
|
+
this.#yInput?.toggleAttribute("disabled", disabled);
|
|
3217
|
+
}
|
|
3218
|
+
|
|
3219
|
+
#bindEvents() {
|
|
3220
|
+
this.#unbindEvents();
|
|
3221
|
+
this.#xInput?.addEventListener("input", this.#boundHandleInput);
|
|
3222
|
+
this.#xInput?.addEventListener("change", this.#boundHandleChange);
|
|
3223
|
+
this.#yInput?.addEventListener("input", this.#boundHandleInput);
|
|
3224
|
+
this.#yInput?.addEventListener("change", this.#boundHandleChange);
|
|
3225
|
+
}
|
|
3226
|
+
|
|
3227
|
+
#unbindEvents() {
|
|
3228
|
+
this.#xInput?.removeEventListener("input", this.#boundHandleInput);
|
|
3229
|
+
this.#xInput?.removeEventListener("change", this.#boundHandleChange);
|
|
3230
|
+
this.#yInput?.removeEventListener("input", this.#boundHandleInput);
|
|
3231
|
+
this.#yInput?.removeEventListener("change", this.#boundHandleChange);
|
|
3232
|
+
}
|
|
3233
|
+
|
|
3234
|
+
#handleInputEvent(type, event) {
|
|
3235
|
+
event.stopImmediatePropagation();
|
|
3236
|
+
if (figLabBooleanAttribute(this, "disabled")) return;
|
|
3237
|
+
const axis = event.currentTarget?.getAttribute("data-propskit-position-axis");
|
|
3238
|
+
if (axis !== "x" && axis !== "y") return;
|
|
3239
|
+
const value = this.value;
|
|
3240
|
+
value[axis] = this.#finiteNumber(event.currentTarget.value, value[axis]);
|
|
3241
|
+
this.#reflectValue(value);
|
|
3242
|
+
figLabDispatchPropskitEvent(this, type);
|
|
3243
|
+
}
|
|
3244
|
+
|
|
3245
|
+
#handleClick(event) {
|
|
3246
|
+
if (figLabBooleanAttribute(this, "disabled")) return;
|
|
3247
|
+
if (
|
|
3248
|
+
event.target instanceof Element &&
|
|
3249
|
+
event.target.closest("fig-input-number, fig-menu")
|
|
3250
|
+
) {
|
|
3251
|
+
return;
|
|
3252
|
+
}
|
|
3253
|
+
this.focus();
|
|
3254
|
+
}
|
|
3255
|
+
|
|
3256
|
+
get x() {
|
|
3257
|
+
return this.#readValue().x;
|
|
3258
|
+
}
|
|
3259
|
+
|
|
3260
|
+
set x(value) {
|
|
3261
|
+
this.setAttribute("x", String(this.#finiteNumber(value, 50)));
|
|
3262
|
+
}
|
|
3263
|
+
|
|
3264
|
+
get y() {
|
|
3265
|
+
return this.#readValue().y;
|
|
3266
|
+
}
|
|
3267
|
+
|
|
3268
|
+
set y(value) {
|
|
3269
|
+
this.setAttribute("y", String(this.#finiteNumber(value, 50)));
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3272
|
+
get units() {
|
|
3273
|
+
return this.getAttribute("units")?.trim().toLowerCase() === "percent"
|
|
3274
|
+
? "percent"
|
|
3275
|
+
: "none";
|
|
3276
|
+
}
|
|
3277
|
+
|
|
3278
|
+
set units(value) {
|
|
3279
|
+
this.setAttribute(
|
|
3280
|
+
"units",
|
|
3281
|
+
String(value).trim().toLowerCase() === "none" ? "none" : "percent",
|
|
3282
|
+
);
|
|
3283
|
+
}
|
|
3284
|
+
|
|
3285
|
+
get value() {
|
|
3286
|
+
return { ...this.#readValue() };
|
|
3287
|
+
}
|
|
3288
|
+
|
|
3289
|
+
set value(value) {
|
|
3290
|
+
const normalized = this.#reflectValue(value);
|
|
3291
|
+
this.#syncInputs(normalized);
|
|
3292
|
+
}
|
|
3293
|
+
|
|
3294
|
+
get defaultValue() {
|
|
3295
|
+
return (
|
|
3296
|
+
this.#parseDefault(this.getAttribute("default")) || {
|
|
3297
|
+
...(this.#initialValue || { x: 50, y: 50 }),
|
|
3298
|
+
}
|
|
3299
|
+
);
|
|
3300
|
+
}
|
|
3301
|
+
|
|
3302
|
+
get isDefault() {
|
|
3303
|
+
return figLabPropskitJsonValuesEqual(this.value, this.defaultValue);
|
|
3304
|
+
}
|
|
3305
|
+
|
|
3306
|
+
resetToDefault() {
|
|
3307
|
+
const value = this.defaultValue;
|
|
3308
|
+
this.value = value;
|
|
3309
|
+
figLabEmitPropskitReset(this);
|
|
3310
|
+
}
|
|
3311
|
+
|
|
3312
|
+
focus(options) {
|
|
3313
|
+
this.#xInput?.focus(options);
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
figLabDefineElement("propskit-position", PropskitPosition);
|
|
3317
|
+
|
|
3318
|
+
/**
|
|
3319
|
+
* Labeled two-axis joystick.
|
|
3320
|
+
*
|
|
3321
|
+
* @attr {string} value - JSON object with percentage x and y coordinates.
|
|
3322
|
+
* @attr {string} default - JSON reset value with percentage x and y coordinates.
|
|
3323
|
+
* @attr {string} label - Surface label. Omitted values use "Label"; empty hides it.
|
|
3324
|
+
* @attr {string} axis-labels - Forwarded to the inner fig-joystick.
|
|
3325
|
+
* @attr {string} coordinates - Forwarded coordinate mode: "screen" or "math".
|
|
3326
|
+
* @attr {number} precision - Forwarded number-field display precision.
|
|
3327
|
+
* @attr {boolean|string} disabled - Disables the joystick and both fields.
|
|
3328
|
+
* @fires input - Shared PropsKit event with a typed { x, y } value.
|
|
3329
|
+
* @fires change - Shared PropsKit event with a typed { x, y } value.
|
|
3330
|
+
*/
|
|
3331
|
+
class PropskitJoystick extends FigLabPropskitElement {
|
|
3332
|
+
static observedAttributes = [
|
|
3333
|
+
"value",
|
|
3334
|
+
"default",
|
|
3335
|
+
"label",
|
|
3336
|
+
"aria-label",
|
|
3337
|
+
"axis-labels",
|
|
3338
|
+
"coordinates",
|
|
3339
|
+
"precision",
|
|
3340
|
+
"disabled",
|
|
3341
|
+
];
|
|
3342
|
+
|
|
3343
|
+
#surface = null;
|
|
3344
|
+
#label = null;
|
|
3345
|
+
#hasCustomLabel = false;
|
|
3346
|
+
#joystick = null;
|
|
3347
|
+
#observer = null;
|
|
3348
|
+
#initialValue = { x: 50, y: 50 };
|
|
3349
|
+
#reflecting = false;
|
|
3350
|
+
#boundInput = this.#handlePrimitiveEvent.bind(this, "input");
|
|
3351
|
+
#boundChange = this.#handlePrimitiveEvent.bind(this, "change");
|
|
3352
|
+
|
|
3353
|
+
connectedCallback() {
|
|
3354
|
+
if (!this.#surface) {
|
|
3355
|
+
this.#initialValue = this.#normalizeValue(
|
|
3356
|
+
this.getAttribute("value"),
|
|
3357
|
+
this.#initialValue,
|
|
3358
|
+
);
|
|
3359
|
+
this.#reflectValue(this.#initialValue);
|
|
3360
|
+
this.#render();
|
|
3361
|
+
}
|
|
3362
|
+
this.#syncLabel();
|
|
3363
|
+
this.#syncPrimitive();
|
|
3364
|
+
this.#bindEvents();
|
|
3365
|
+
this.#observer?.observe(this.#joystick, { childList: true, subtree: true });
|
|
3366
|
+
figLabConnectPropskitResetMenu(this);
|
|
3367
|
+
}
|
|
3368
|
+
|
|
3369
|
+
disconnectedCallback() {
|
|
3370
|
+
this.#unbindEvents();
|
|
3371
|
+
this.#observer?.disconnect();
|
|
3372
|
+
figLabDisconnectPropskitResetMenu(this);
|
|
3373
|
+
}
|
|
3374
|
+
|
|
3375
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
3376
|
+
if (oldValue === newValue || !this.#surface) return;
|
|
3377
|
+
if (name === "value" && !this.#reflecting) {
|
|
3378
|
+
this.#reflectValue(this.#normalizeValue(newValue, this.value));
|
|
3379
|
+
this.#syncPrimitiveValue();
|
|
3380
|
+
} else if (name === "label" || name === "aria-label") {
|
|
3381
|
+
this.#syncLabel();
|
|
3382
|
+
} else if (
|
|
3383
|
+
name === "axis-labels" ||
|
|
3384
|
+
name === "coordinates" ||
|
|
3385
|
+
name === "precision"
|
|
3386
|
+
) {
|
|
3387
|
+
this.#syncForwardedAttributes();
|
|
3388
|
+
} else if (name === "disabled") {
|
|
3389
|
+
this.#syncDisabled();
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
|
|
3393
|
+
#finitePercent(value, fallback = 50) {
|
|
3394
|
+
const number = Number(value);
|
|
3395
|
+
return Number.isFinite(number)
|
|
3396
|
+
? Math.max(0, Math.min(100, number))
|
|
3397
|
+
: fallback;
|
|
3398
|
+
}
|
|
3399
|
+
|
|
3400
|
+
#normalizeValue(value, fallback = { x: 50, y: 50 }) {
|
|
3401
|
+
let parsed = value;
|
|
3402
|
+
if (typeof value === "string") {
|
|
3403
|
+
const trimmed = value.trim();
|
|
3404
|
+
if (!trimmed) return { ...fallback };
|
|
3405
|
+
try {
|
|
3406
|
+
parsed = JSON.parse(trimmed);
|
|
3407
|
+
} catch {
|
|
3408
|
+
const parts = trimmed.split(/[\s,]+/).filter(Boolean);
|
|
3409
|
+
const parseAxis = (token, axisFallback) => {
|
|
3410
|
+
if (!token) return axisFallback;
|
|
3411
|
+
const numeric = Number.parseFloat(token.replace(/%/g, ""));
|
|
3412
|
+
if (!Number.isFinite(numeric)) return axisFallback;
|
|
3413
|
+
return token.includes("%") || Math.abs(numeric) > 1
|
|
3414
|
+
? numeric
|
|
3415
|
+
: numeric * 100;
|
|
3416
|
+
};
|
|
3417
|
+
parsed = {
|
|
3418
|
+
x: parseAxis(parts[0], fallback.x),
|
|
3419
|
+
y: parseAxis(parts[1] ?? parts[0], fallback.y),
|
|
3420
|
+
};
|
|
3421
|
+
}
|
|
3422
|
+
}
|
|
3423
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
3424
|
+
return { ...fallback };
|
|
3425
|
+
}
|
|
3426
|
+
return {
|
|
3427
|
+
x: this.#finitePercent(parsed.x, fallback.x),
|
|
3428
|
+
y: this.#finitePercent(parsed.y, fallback.y),
|
|
3429
|
+
};
|
|
3430
|
+
}
|
|
3431
|
+
|
|
3432
|
+
#reflectValue(value) {
|
|
3433
|
+
const normalized = this.#normalizeValue(value, this.#initialValue);
|
|
3434
|
+
const serialized = JSON.stringify(normalized);
|
|
3435
|
+
if (this.getAttribute("value") !== serialized) {
|
|
3436
|
+
this.#reflecting = true;
|
|
3437
|
+
this.setAttribute("value", serialized);
|
|
3438
|
+
this.#reflecting = false;
|
|
3439
|
+
}
|
|
3440
|
+
return normalized;
|
|
3441
|
+
}
|
|
3442
|
+
|
|
3443
|
+
#render() {
|
|
3444
|
+
const customLabel = this.querySelector(":scope > label");
|
|
3445
|
+
const surface = figLabCreateElement("div", {
|
|
3446
|
+
className: "propskit-joystick-surface",
|
|
3447
|
+
role: "group",
|
|
3448
|
+
});
|
|
3449
|
+
const label = customLabel || document.createElement("label");
|
|
3450
|
+
const joystick = document.createElement("fig-joystick");
|
|
3451
|
+
joystick.setAttribute("fields", "true");
|
|
3452
|
+
joystick.setAttribute("aspect-ratio", "1 / 1");
|
|
3453
|
+
surface.append(label, joystick);
|
|
3454
|
+
this.#surface = surface;
|
|
3455
|
+
this.#label = label;
|
|
3456
|
+
this.#hasCustomLabel = Boolean(customLabel);
|
|
3457
|
+
this.#joystick = joystick;
|
|
3458
|
+
this.replaceChildren(surface);
|
|
3459
|
+
this.#observer = new MutationObserver(() => this.#syncDescendantState());
|
|
3460
|
+
this.#observer.observe(joystick, { childList: true, subtree: true });
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3463
|
+
#syncLabel() {
|
|
3464
|
+
const labelId = figLabSyncPropskitLabel(
|
|
3465
|
+
this,
|
|
3466
|
+
this.#surface,
|
|
3467
|
+
this.#label,
|
|
3468
|
+
this.#hasCustomLabel,
|
|
3469
|
+
);
|
|
3470
|
+
figLabSyncPropskitControlLabel(this, this.#joystick, labelId, "Joystick");
|
|
3471
|
+
this.#syncDescendantState();
|
|
3472
|
+
}
|
|
3473
|
+
|
|
3474
|
+
#syncPrimitive() {
|
|
3475
|
+
this.#syncForwardedAttributes();
|
|
3476
|
+
this.#syncPrimitiveValue();
|
|
3477
|
+
this.#syncDisabled();
|
|
3478
|
+
}
|
|
3479
|
+
|
|
3480
|
+
#syncForwardedAttributes() {
|
|
3481
|
+
if (!this.#joystick) return;
|
|
3482
|
+
this.#joystick.setAttribute("fields", "true");
|
|
3483
|
+
this.#joystick.setAttribute("aspect-ratio", "1 / 1");
|
|
3484
|
+
for (const name of ["axis-labels", "coordinates", "precision"]) {
|
|
3485
|
+
if (this.hasAttribute(name)) {
|
|
3486
|
+
this.#joystick.setAttribute(name, this.getAttribute(name) ?? "");
|
|
3487
|
+
} else {
|
|
3488
|
+
this.#joystick.removeAttribute(name);
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
}
|
|
3492
|
+
|
|
3493
|
+
#syncPrimitiveValue() {
|
|
3494
|
+
if (!this.#joystick) return;
|
|
3495
|
+
const value = this.value;
|
|
3496
|
+
const serialized = `${value.x}% ${value.y}%`;
|
|
3497
|
+
if (this.#joystick.getAttribute("value") !== serialized) {
|
|
3498
|
+
this.#joystick.setAttribute("value", serialized);
|
|
3499
|
+
}
|
|
3500
|
+
}
|
|
3501
|
+
|
|
3502
|
+
#syncDisabled() {
|
|
3503
|
+
if (!this.#joystick || !this.#surface) return;
|
|
3504
|
+
const disabled = figLabBooleanAttribute(this, "disabled");
|
|
3505
|
+
this.#surface.setAttribute("aria-disabled", String(disabled));
|
|
3506
|
+
this.#joystick.toggleAttribute("disabled", disabled);
|
|
3507
|
+
this.#joystick.inert = disabled;
|
|
3508
|
+
this.#syncDescendantState();
|
|
3509
|
+
}
|
|
3510
|
+
|
|
3511
|
+
#syncDescendantState() {
|
|
3512
|
+
if (!this.#joystick) return;
|
|
3513
|
+
const disabled = figLabBooleanAttribute(this, "disabled");
|
|
3514
|
+
const labelId = this.#label?.isConnected ? this.#label.id : "";
|
|
3515
|
+
const explicitLabel = this.getAttribute("aria-label")?.trim();
|
|
3516
|
+
const handle = this.#joystick.querySelector("fig-handle");
|
|
3517
|
+
if (handle) {
|
|
3518
|
+
handle.toggleAttribute("disabled", disabled);
|
|
3519
|
+
if (explicitLabel) {
|
|
3520
|
+
handle.setAttribute("aria-label", explicitLabel);
|
|
3521
|
+
handle.removeAttribute("aria-labelledby");
|
|
3522
|
+
} else if (labelId) {
|
|
3523
|
+
handle.setAttribute("aria-labelledby", labelId);
|
|
3524
|
+
handle.removeAttribute("aria-label");
|
|
3525
|
+
} else {
|
|
3526
|
+
handle.setAttribute("aria-label", "Joystick");
|
|
3527
|
+
handle.removeAttribute("aria-labelledby");
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3530
|
+
for (const input of this.#joystick.querySelectorAll("fig-input-number")) {
|
|
3531
|
+
input.toggleAttribute("disabled", disabled);
|
|
3532
|
+
}
|
|
3533
|
+
}
|
|
3534
|
+
|
|
3535
|
+
#bindEvents() {
|
|
3536
|
+
this.#unbindEvents();
|
|
3537
|
+
this.#joystick?.addEventListener("input", this.#boundInput);
|
|
3538
|
+
this.#joystick?.addEventListener("change", this.#boundChange);
|
|
3539
|
+
}
|
|
3540
|
+
|
|
3541
|
+
#unbindEvents() {
|
|
3542
|
+
this.#joystick?.removeEventListener("input", this.#boundInput);
|
|
3543
|
+
this.#joystick?.removeEventListener("change", this.#boundChange);
|
|
3544
|
+
}
|
|
3545
|
+
|
|
3546
|
+
#handlePrimitiveEvent(type, event) {
|
|
3547
|
+
event.stopImmediatePropagation();
|
|
3548
|
+
if (
|
|
3549
|
+
event.target !== this.#joystick ||
|
|
3550
|
+
figLabBooleanAttribute(this, "disabled")
|
|
3551
|
+
) {
|
|
3552
|
+
return;
|
|
3553
|
+
}
|
|
3554
|
+
const value = this.#normalizeValue(event.detail?.value, this.value);
|
|
3555
|
+
this.#reflectValue(value);
|
|
3556
|
+
this.#syncPrimitiveValue();
|
|
3557
|
+
figLabDispatchPropskitEvent(this, type);
|
|
3558
|
+
}
|
|
3559
|
+
|
|
3560
|
+
get value() {
|
|
3561
|
+
return this.#normalizeValue(
|
|
3562
|
+
this.getAttribute("value"),
|
|
3563
|
+
this.#initialValue,
|
|
3564
|
+
);
|
|
3565
|
+
}
|
|
3566
|
+
|
|
3567
|
+
set value(value) {
|
|
3568
|
+
this.#reflectValue(value);
|
|
3569
|
+
this.#syncPrimitiveValue();
|
|
3570
|
+
}
|
|
3571
|
+
|
|
3572
|
+
get defaultValue() {
|
|
3573
|
+
return this.#normalizeValue(
|
|
3574
|
+
this.getAttribute("default"),
|
|
3575
|
+
this.#initialValue,
|
|
3576
|
+
);
|
|
3577
|
+
}
|
|
3578
|
+
|
|
3579
|
+
get isDefault() {
|
|
3580
|
+
return figLabPropskitJsonValuesEqual(this.value, this.defaultValue);
|
|
3581
|
+
}
|
|
3582
|
+
|
|
3583
|
+
resetToDefault() {
|
|
3584
|
+
this.value = this.defaultValue;
|
|
3585
|
+
figLabEmitPropskitReset(this);
|
|
3586
|
+
}
|
|
3587
|
+
|
|
3588
|
+
focus(options) {
|
|
3589
|
+
if (figLabBooleanAttribute(this, "disabled")) return;
|
|
3590
|
+
this.#joystick?.focus(options);
|
|
3591
|
+
}
|
|
3592
|
+
}
|
|
3593
|
+
figLabDefineElement("propskit-joystick", PropskitJoystick);
|
|
3594
|
+
|
|
3595
|
+
/**
|
|
3596
|
+
* Labeled transform-origin grid.
|
|
3597
|
+
*
|
|
3598
|
+
* @attr {string} value - JSON object with percentage x and y coordinates.
|
|
3599
|
+
* @attr {string} default - JSON reset value with percentage x and y coordinates.
|
|
3600
|
+
* @attr {string} label - Surface label. Omitted values use "Label"; empty hides it.
|
|
3601
|
+
* @attr {number} precision - Forwarded number-field display precision.
|
|
3602
|
+
* @attr {boolean|string} drag - Forwarded drag behavior.
|
|
3603
|
+
* @attr {boolean|string} disabled - Disables the origin grid and both fields.
|
|
3604
|
+
* @fires input - Shared PropsKit event with a typed { x, y } value.
|
|
3605
|
+
* @fires change - Shared PropsKit event with a typed { x, y } value.
|
|
3606
|
+
*/
|
|
3607
|
+
class PropskitOrigin extends FigLabPropskitElement {
|
|
3608
|
+
static observedAttributes = [
|
|
3609
|
+
"value",
|
|
3610
|
+
"default",
|
|
3611
|
+
"label",
|
|
3612
|
+
"aria-label",
|
|
3613
|
+
"precision",
|
|
3614
|
+
"drag",
|
|
3615
|
+
"disabled",
|
|
3616
|
+
];
|
|
3617
|
+
|
|
3618
|
+
#surface = null;
|
|
3619
|
+
#label = null;
|
|
3620
|
+
#hasCustomLabel = false;
|
|
3621
|
+
#origin = null;
|
|
3622
|
+
#observer = null;
|
|
3623
|
+
#initialValue = { x: 50, y: 50 };
|
|
3624
|
+
#reflecting = false;
|
|
3625
|
+
#boundInput = this.#handlePrimitiveEvent.bind(this, "input");
|
|
3626
|
+
#boundChange = this.#handlePrimitiveEvent.bind(this, "change");
|
|
3627
|
+
|
|
3628
|
+
connectedCallback() {
|
|
3629
|
+
if (!this.#surface) {
|
|
3630
|
+
this.#initialValue = this.#normalizeValue(
|
|
3631
|
+
this.getAttribute("value"),
|
|
3632
|
+
this.#initialValue,
|
|
3633
|
+
);
|
|
3634
|
+
this.#reflectValue(this.#initialValue);
|
|
3635
|
+
this.#render();
|
|
3636
|
+
}
|
|
3637
|
+
this.#syncLabel();
|
|
3638
|
+
this.#syncPrimitive();
|
|
3639
|
+
this.#bindEvents();
|
|
3640
|
+
this.#observer?.observe(this.#origin, { childList: true, subtree: true });
|
|
3641
|
+
figLabConnectPropskitResetMenu(this);
|
|
3642
|
+
}
|
|
3643
|
+
|
|
3644
|
+
disconnectedCallback() {
|
|
3645
|
+
this.#unbindEvents();
|
|
3646
|
+
this.#observer?.disconnect();
|
|
3647
|
+
figLabDisconnectPropskitResetMenu(this);
|
|
3648
|
+
}
|
|
3649
|
+
|
|
3650
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
3651
|
+
if (oldValue === newValue || !this.#surface) return;
|
|
3652
|
+
if (name === "value" && !this.#reflecting) {
|
|
3653
|
+
this.#reflectValue(this.#normalizeValue(newValue, this.value));
|
|
3654
|
+
this.#syncPrimitiveValue();
|
|
3655
|
+
} else if (name === "label" || name === "aria-label") {
|
|
3656
|
+
this.#syncLabel();
|
|
3657
|
+
} else if (name === "precision" || name === "drag") {
|
|
3658
|
+
this.#syncForwardedAttributes();
|
|
3659
|
+
} else if (name === "disabled") {
|
|
3660
|
+
this.#syncDisabled();
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
|
|
3664
|
+
#finiteNumber(value, fallback = 50) {
|
|
3665
|
+
const number = Number(value);
|
|
3666
|
+
return Number.isFinite(number) ? number : fallback;
|
|
3667
|
+
}
|
|
3668
|
+
|
|
3669
|
+
#parseAxisToken(token, axis, fallback) {
|
|
3670
|
+
const normalized = String(token ?? "").trim().toLowerCase();
|
|
3671
|
+
const keywords =
|
|
3672
|
+
axis === "x"
|
|
3673
|
+
? { left: 0, center: 50, right: 100 }
|
|
3674
|
+
: { top: 0, center: 50, bottom: 100 };
|
|
3675
|
+
if (normalized in keywords) return keywords[normalized];
|
|
3676
|
+
const number = Number.parseFloat(normalized.replace(/%/g, ""));
|
|
3677
|
+
return Number.isFinite(number) ? number : fallback;
|
|
3678
|
+
}
|
|
3679
|
+
|
|
3680
|
+
#normalizeValue(value, fallback = { x: 50, y: 50 }) {
|
|
3681
|
+
let parsed = value;
|
|
3682
|
+
if (typeof value === "string") {
|
|
3683
|
+
const trimmed = value.trim();
|
|
3684
|
+
if (!trimmed) return { ...fallback };
|
|
3685
|
+
try {
|
|
3686
|
+
parsed = JSON.parse(trimmed);
|
|
3687
|
+
} catch {
|
|
3688
|
+
const parts = trimmed.replace(/,/g, " ").split(/\s+/).filter(Boolean);
|
|
3689
|
+
parsed = {
|
|
3690
|
+
x: this.#parseAxisToken(parts[0], "x", fallback.x),
|
|
3691
|
+
y: this.#parseAxisToken(parts[1] ?? parts[0], "y", fallback.y),
|
|
3692
|
+
};
|
|
3693
|
+
}
|
|
3694
|
+
}
|
|
3695
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
3696
|
+
return { ...fallback };
|
|
3697
|
+
}
|
|
3698
|
+
return {
|
|
3699
|
+
x: this.#finiteNumber(parsed.x, fallback.x),
|
|
3700
|
+
y: this.#finiteNumber(parsed.y, fallback.y),
|
|
3701
|
+
};
|
|
3702
|
+
}
|
|
3703
|
+
|
|
3704
|
+
#reflectValue(value) {
|
|
3705
|
+
const normalized = this.#normalizeValue(value, this.#initialValue);
|
|
3706
|
+
const serialized = JSON.stringify(normalized);
|
|
3707
|
+
if (this.getAttribute("value") !== serialized) {
|
|
3708
|
+
this.#reflecting = true;
|
|
3709
|
+
this.setAttribute("value", serialized);
|
|
3710
|
+
this.#reflecting = false;
|
|
3711
|
+
}
|
|
3712
|
+
return normalized;
|
|
3713
|
+
}
|
|
3714
|
+
|
|
3715
|
+
#render() {
|
|
3716
|
+
const customLabel = this.querySelector(":scope > label");
|
|
3717
|
+
const surface = figLabCreateElement("div", {
|
|
3718
|
+
className: "propskit-origin-surface",
|
|
3719
|
+
role: "group",
|
|
3720
|
+
});
|
|
3721
|
+
const label = customLabel || document.createElement("label");
|
|
3722
|
+
const origin = document.createElement("fig-origin-grid");
|
|
3723
|
+
origin.setAttribute("fields", "true");
|
|
3724
|
+
origin.setAttribute("aspect-ratio", "1 / 1");
|
|
3725
|
+
surface.append(label, origin);
|
|
3726
|
+
this.#surface = surface;
|
|
3727
|
+
this.#label = label;
|
|
3728
|
+
this.#hasCustomLabel = Boolean(customLabel);
|
|
3729
|
+
this.#origin = origin;
|
|
3730
|
+
this.replaceChildren(surface);
|
|
3731
|
+
this.#observer = new MutationObserver(() => this.#syncDescendantState());
|
|
3732
|
+
this.#observer.observe(origin, { childList: true, subtree: true });
|
|
3733
|
+
}
|
|
3734
|
+
|
|
3735
|
+
#syncLabel() {
|
|
3736
|
+
const labelId = figLabSyncPropskitLabel(
|
|
3737
|
+
this,
|
|
3738
|
+
this.#surface,
|
|
3739
|
+
this.#label,
|
|
3740
|
+
this.#hasCustomLabel,
|
|
3741
|
+
);
|
|
3742
|
+
figLabSyncPropskitControlLabel(this, this.#origin, labelId, "Origin");
|
|
3743
|
+
this.#syncDescendantState();
|
|
3744
|
+
}
|
|
3745
|
+
|
|
3746
|
+
#syncPrimitive() {
|
|
3747
|
+
this.#syncForwardedAttributes();
|
|
3748
|
+
this.#syncPrimitiveValue();
|
|
3749
|
+
this.#syncDisabled();
|
|
3750
|
+
}
|
|
3751
|
+
|
|
3752
|
+
#syncForwardedAttributes() {
|
|
3753
|
+
if (!this.#origin) return;
|
|
3754
|
+
this.#origin.setAttribute("fields", "true");
|
|
3755
|
+
this.#origin.setAttribute("aspect-ratio", "1 / 1");
|
|
3756
|
+
for (const name of ["precision", "drag"]) {
|
|
3757
|
+
if (this.hasAttribute(name)) {
|
|
3758
|
+
this.#origin.setAttribute(name, this.getAttribute(name) ?? "");
|
|
3759
|
+
} else {
|
|
3760
|
+
this.#origin.removeAttribute(name);
|
|
3761
|
+
}
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
|
|
3765
|
+
#syncPrimitiveValue() {
|
|
3766
|
+
if (!this.#origin) return;
|
|
3767
|
+
const value = this.value;
|
|
3768
|
+
const serialized = `${value.x}% ${value.y}%`;
|
|
3769
|
+
if (this.#origin.getAttribute("value") !== serialized) {
|
|
3770
|
+
this.#origin.setAttribute("value", serialized);
|
|
3771
|
+
}
|
|
3772
|
+
}
|
|
3773
|
+
|
|
3774
|
+
#syncDisabled() {
|
|
3775
|
+
if (!this.#origin || !this.#surface) return;
|
|
3776
|
+
const disabled = figLabBooleanAttribute(this, "disabled");
|
|
3777
|
+
this.#surface.setAttribute("aria-disabled", String(disabled));
|
|
3778
|
+
this.#origin.toggleAttribute("disabled", disabled);
|
|
3779
|
+
this.#origin.inert = disabled;
|
|
3780
|
+
this.#syncDescendantState();
|
|
3781
|
+
}
|
|
3782
|
+
|
|
3783
|
+
#syncDescendantState() {
|
|
3784
|
+
if (!this.#origin) return;
|
|
3785
|
+
const disabled = figLabBooleanAttribute(this, "disabled");
|
|
3786
|
+
const labelId = this.#label?.isConnected ? this.#label.id : "";
|
|
3787
|
+
const explicitLabel = this.getAttribute("aria-label")?.trim();
|
|
3788
|
+
const handle = this.#origin.querySelector("fig-handle");
|
|
3789
|
+
if (handle) {
|
|
3790
|
+
handle.toggleAttribute("disabled", disabled);
|
|
3791
|
+
if (explicitLabel) {
|
|
3792
|
+
handle.setAttribute("aria-label", explicitLabel);
|
|
3793
|
+
handle.removeAttribute("aria-labelledby");
|
|
3794
|
+
} else if (labelId) {
|
|
3795
|
+
handle.setAttribute("aria-labelledby", labelId);
|
|
3796
|
+
handle.removeAttribute("aria-label");
|
|
3797
|
+
} else {
|
|
3798
|
+
handle.setAttribute("aria-label", "Origin");
|
|
3799
|
+
handle.removeAttribute("aria-labelledby");
|
|
3800
|
+
}
|
|
3801
|
+
}
|
|
3802
|
+
for (const input of this.#origin.querySelectorAll("fig-input-number")) {
|
|
3803
|
+
input.toggleAttribute("disabled", disabled);
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
3806
|
+
|
|
3807
|
+
#bindEvents() {
|
|
3808
|
+
this.#unbindEvents();
|
|
3809
|
+
this.#origin?.addEventListener("input", this.#boundInput);
|
|
3810
|
+
this.#origin?.addEventListener("change", this.#boundChange);
|
|
3811
|
+
}
|
|
3812
|
+
|
|
3813
|
+
#unbindEvents() {
|
|
3814
|
+
this.#origin?.removeEventListener("input", this.#boundInput);
|
|
3815
|
+
this.#origin?.removeEventListener("change", this.#boundChange);
|
|
3816
|
+
}
|
|
3817
|
+
|
|
3818
|
+
#handlePrimitiveEvent(type, event) {
|
|
3819
|
+
event.stopImmediatePropagation();
|
|
3820
|
+
if (
|
|
3821
|
+
event.target !== this.#origin ||
|
|
3822
|
+
figLabBooleanAttribute(this, "disabled")
|
|
3823
|
+
) {
|
|
3824
|
+
return;
|
|
3825
|
+
}
|
|
3826
|
+
const value = this.#normalizeValue(event.detail, this.value);
|
|
3827
|
+
this.#reflectValue(value);
|
|
3828
|
+
this.#syncPrimitiveValue();
|
|
3829
|
+
figLabDispatchPropskitEvent(this, type);
|
|
3830
|
+
}
|
|
3831
|
+
|
|
3832
|
+
get value() {
|
|
3833
|
+
return this.#normalizeValue(
|
|
3834
|
+
this.getAttribute("value"),
|
|
3835
|
+
this.#initialValue,
|
|
3836
|
+
);
|
|
3837
|
+
}
|
|
3838
|
+
|
|
3839
|
+
set value(value) {
|
|
3840
|
+
this.#reflectValue(value);
|
|
3841
|
+
this.#syncPrimitiveValue();
|
|
3842
|
+
}
|
|
3843
|
+
|
|
3844
|
+
get defaultValue() {
|
|
3845
|
+
return this.#normalizeValue(
|
|
3846
|
+
this.getAttribute("default"),
|
|
3847
|
+
this.#initialValue,
|
|
3848
|
+
);
|
|
3849
|
+
}
|
|
3850
|
+
|
|
3851
|
+
get isDefault() {
|
|
3852
|
+
return figLabPropskitJsonValuesEqual(this.value, this.defaultValue);
|
|
3853
|
+
}
|
|
3854
|
+
|
|
3855
|
+
resetToDefault() {
|
|
3856
|
+
this.value = this.defaultValue;
|
|
3857
|
+
figLabEmitPropskitReset(this);
|
|
3858
|
+
}
|
|
3859
|
+
|
|
3860
|
+
focus(options) {
|
|
3861
|
+
if (figLabBooleanAttribute(this, "disabled")) return;
|
|
3862
|
+
this.#origin?.querySelector("fig-handle")?.focus(options);
|
|
3863
|
+
}
|
|
3864
|
+
}
|
|
3865
|
+
figLabDefineElement("propskit-origin", PropskitOrigin);
|
|
3866
|
+
|
|
3867
|
+
/**
|
|
3868
|
+
* Shared labeled curve editor for PropsKit easing and spring controls.
|
|
3869
|
+
*/
|
|
3870
|
+
class PropskitCurve extends FigLabPropskitElement {
|
|
3871
|
+
static observedAttributes = [
|
|
3872
|
+
"value",
|
|
3873
|
+
"default",
|
|
3874
|
+
"label",
|
|
3875
|
+
"aria-label",
|
|
3876
|
+
"precision",
|
|
3877
|
+
"edit",
|
|
3878
|
+
"disabled",
|
|
3879
|
+
];
|
|
3880
|
+
|
|
3881
|
+
#surface = null;
|
|
3882
|
+
#label = null;
|
|
3883
|
+
#hasCustomLabel = false;
|
|
3884
|
+
#curve = null;
|
|
3885
|
+
#observer = null;
|
|
3886
|
+
#initialValue = null;
|
|
3887
|
+
#reflecting = false;
|
|
3888
|
+
#boundInput = this.#handlePrimitiveEvent.bind(this, "input");
|
|
3889
|
+
#boundChange = this.#handlePrimitiveEvent.bind(this, "change");
|
|
3890
|
+
|
|
3891
|
+
connectedCallback() {
|
|
3892
|
+
if (!this.#surface) {
|
|
3893
|
+
this.#initialValue = this.#normalizeValue(
|
|
3894
|
+
this.getAttribute("value"),
|
|
3895
|
+
this.#fallbackValue(),
|
|
3896
|
+
);
|
|
3897
|
+
this.#reflectValue(this.#initialValue);
|
|
3898
|
+
this.#render();
|
|
3899
|
+
}
|
|
3900
|
+
this.#syncLabel();
|
|
3901
|
+
this.#syncPrimitive();
|
|
3902
|
+
this.#bindEvents();
|
|
3903
|
+
this.#observer?.observe(this.#curve, { childList: true, subtree: true });
|
|
3904
|
+
figLabConnectPropskitResetMenu(this);
|
|
3905
|
+
}
|
|
3906
|
+
|
|
3907
|
+
disconnectedCallback() {
|
|
3908
|
+
this.#unbindEvents();
|
|
3909
|
+
this.#observer?.disconnect();
|
|
3910
|
+
figLabDisconnectPropskitResetMenu(this);
|
|
3911
|
+
}
|
|
3912
|
+
|
|
3913
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
3914
|
+
if (oldValue === newValue || !this.#surface) return;
|
|
3915
|
+
if (name === "value" && !this.#reflecting) {
|
|
3916
|
+
this.#reflectValue(this.#normalizeValue(newValue, this.value));
|
|
3917
|
+
this.#syncPrimitiveValue();
|
|
3918
|
+
} else if (name === "label" || name === "aria-label") {
|
|
3919
|
+
this.#syncLabel();
|
|
3920
|
+
} else if (name === "precision" || name === "edit") {
|
|
3921
|
+
this.#syncForwardedAttributes();
|
|
3922
|
+
} else if (name === "disabled") {
|
|
3923
|
+
this.#syncDisabled();
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3926
|
+
|
|
3927
|
+
#isSpring() {
|
|
3928
|
+
return this.localName === "propskit-spring";
|
|
3929
|
+
}
|
|
3930
|
+
|
|
3931
|
+
#fallbackValue() {
|
|
3932
|
+
return this.#isSpring()
|
|
3933
|
+
? { stiffness: 200, damping: 15, mass: 1 }
|
|
3934
|
+
: { x1: 0.42, y1: 0, x2: 0.58, y2: 1 };
|
|
3935
|
+
}
|
|
3936
|
+
|
|
3937
|
+
#finiteNumber(value, fallback) {
|
|
3938
|
+
const number = Number(value);
|
|
3939
|
+
return Number.isFinite(number) ? number : fallback;
|
|
3940
|
+
}
|
|
3941
|
+
|
|
3942
|
+
#positiveNumber(value, fallback) {
|
|
3943
|
+
const number = this.#finiteNumber(value, fallback);
|
|
3944
|
+
return number > 0 ? number : fallback;
|
|
3945
|
+
}
|
|
3946
|
+
|
|
3947
|
+
#normalizeValue(value, fallback = this.#fallbackValue()) {
|
|
3948
|
+
let parsed = value;
|
|
3949
|
+
if (typeof value === "string") {
|
|
3950
|
+
const trimmed = value.trim();
|
|
3951
|
+
if (!trimmed) return { ...fallback };
|
|
3952
|
+
try {
|
|
3953
|
+
parsed = JSON.parse(trimmed);
|
|
3954
|
+
} catch {
|
|
3955
|
+
if (this.#isSpring()) {
|
|
3956
|
+
const match = trimmed.match(
|
|
3957
|
+
/^spring\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*\)$/,
|
|
3958
|
+
);
|
|
3959
|
+
parsed = match
|
|
3960
|
+
? {
|
|
3961
|
+
stiffness: Number(match[1]),
|
|
3962
|
+
damping: Number(match[2]),
|
|
3963
|
+
mass: Number(match[3]),
|
|
3964
|
+
}
|
|
3965
|
+
: null;
|
|
3966
|
+
} else {
|
|
3967
|
+
const raw = trimmed
|
|
3968
|
+
.replace(/^cubic-bezier\(\s*/i, "")
|
|
3969
|
+
.replace(/\s*\)$/, "");
|
|
3970
|
+
const parts = raw.split(",").map((part) => Number(part.trim()));
|
|
3971
|
+
parsed =
|
|
3972
|
+
parts.length === 4 && parts.every(Number.isFinite)
|
|
3973
|
+
? { x1: parts[0], y1: parts[1], x2: parts[2], y2: parts[3] }
|
|
3974
|
+
: null;
|
|
3975
|
+
}
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
|
|
3979
|
+
if (this.#isSpring()) {
|
|
3980
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
3981
|
+
return { ...fallback };
|
|
3982
|
+
}
|
|
3983
|
+
return {
|
|
3984
|
+
stiffness: this.#positiveNumber(parsed.stiffness, fallback.stiffness),
|
|
3985
|
+
damping: this.#positiveNumber(parsed.damping, fallback.damping),
|
|
3986
|
+
mass: this.#positiveNumber(parsed.mass, fallback.mass),
|
|
3987
|
+
};
|
|
3988
|
+
}
|
|
3989
|
+
|
|
3990
|
+
if (Array.isArray(parsed)) {
|
|
3991
|
+
parsed = {
|
|
3992
|
+
x1: parsed[0],
|
|
3993
|
+
y1: parsed[1],
|
|
3994
|
+
x2: parsed[2],
|
|
3995
|
+
y2: parsed[3],
|
|
3996
|
+
};
|
|
3997
|
+
}
|
|
3998
|
+
if (!parsed || typeof parsed !== "object") return { ...fallback };
|
|
3999
|
+
return {
|
|
4000
|
+
x1: Math.max(
|
|
4001
|
+
0,
|
|
4002
|
+
Math.min(1, this.#finiteNumber(parsed.x1, fallback.x1)),
|
|
4003
|
+
),
|
|
4004
|
+
y1: this.#finiteNumber(parsed.y1, fallback.y1),
|
|
4005
|
+
x2: Math.max(
|
|
4006
|
+
0,
|
|
4007
|
+
Math.min(1, this.#finiteNumber(parsed.x2, fallback.x2)),
|
|
4008
|
+
),
|
|
4009
|
+
y2: this.#finiteNumber(parsed.y2, fallback.y2),
|
|
4010
|
+
};
|
|
4011
|
+
}
|
|
4012
|
+
|
|
4013
|
+
#reflectValue(value) {
|
|
4014
|
+
const fallback = this.#initialValue || this.#fallbackValue();
|
|
4015
|
+
const normalized = this.#normalizeValue(value, fallback);
|
|
4016
|
+
const serialized = JSON.stringify(normalized);
|
|
4017
|
+
if (this.getAttribute("value") !== serialized) {
|
|
4018
|
+
this.#reflecting = true;
|
|
4019
|
+
this.setAttribute("value", serialized);
|
|
4020
|
+
this.#reflecting = false;
|
|
4021
|
+
}
|
|
4022
|
+
return normalized;
|
|
2844
4023
|
}
|
|
2845
4024
|
|
|
2846
4025
|
#render() {
|
|
2847
|
-
const
|
|
2848
|
-
const
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
const
|
|
2853
|
-
const
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
this.#
|
|
2858
|
-
this
|
|
2859
|
-
|
|
4026
|
+
const customLabel = this.querySelector(":scope > label");
|
|
4027
|
+
const surface = figLabCreateElement("div", {
|
|
4028
|
+
className: `${this.localName}-surface`,
|
|
4029
|
+
role: "group",
|
|
4030
|
+
});
|
|
4031
|
+
const label = customLabel || document.createElement("label");
|
|
4032
|
+
const curve = document.createElement("fig-easing-curve");
|
|
4033
|
+
curve.setAttribute("mode", this.#isSpring() ? "spring" : "bezier");
|
|
4034
|
+
curve.setAttribute("aspect-ratio", "1 / 1");
|
|
4035
|
+
surface.append(label, curve);
|
|
4036
|
+
this.#surface = surface;
|
|
4037
|
+
this.#label = label;
|
|
4038
|
+
this.#hasCustomLabel = Boolean(customLabel);
|
|
4039
|
+
this.#curve = curve;
|
|
4040
|
+
this.replaceChildren(surface);
|
|
4041
|
+
this.#observer = new MutationObserver(() => this.#syncDescendantState());
|
|
4042
|
+
this.#observer.observe(curve, { childList: true, subtree: true });
|
|
2860
4043
|
}
|
|
2861
4044
|
|
|
2862
4045
|
#syncLabel() {
|
|
2863
|
-
const
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
4046
|
+
const labelId = figLabSyncPropskitLabel(
|
|
4047
|
+
this,
|
|
4048
|
+
this.#surface,
|
|
4049
|
+
this.#label,
|
|
4050
|
+
this.#hasCustomLabel,
|
|
4051
|
+
);
|
|
4052
|
+
figLabSyncPropskitControlLabel(
|
|
4053
|
+
this,
|
|
4054
|
+
this.#curve,
|
|
4055
|
+
labelId,
|
|
4056
|
+
this.#isSpring() ? "Spring" : "Easing",
|
|
4057
|
+
);
|
|
4058
|
+
this.#syncDescendantState();
|
|
2867
4059
|
}
|
|
2868
4060
|
|
|
2869
|
-
#
|
|
2870
|
-
|
|
2871
|
-
this.#
|
|
2872
|
-
this.#yInput?.setAttribute("value", String(value.y));
|
|
2873
|
-
this.#syncUnits();
|
|
4061
|
+
#syncPrimitive() {
|
|
4062
|
+
this.#syncForwardedAttributes();
|
|
4063
|
+
this.#syncPrimitiveValue();
|
|
2874
4064
|
this.#syncDisabled();
|
|
2875
4065
|
}
|
|
2876
4066
|
|
|
2877
|
-
#
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
4067
|
+
#syncForwardedAttributes() {
|
|
4068
|
+
if (!this.#curve) return;
|
|
4069
|
+
this.#curve.setAttribute("mode", this.#isSpring() ? "spring" : "bezier");
|
|
4070
|
+
this.#curve.setAttribute("aspect-ratio", "1 / 1");
|
|
4071
|
+
for (const name of ["precision", "edit"]) {
|
|
4072
|
+
if (this.hasAttribute(name)) {
|
|
4073
|
+
this.#curve.setAttribute(name, this.getAttribute(name) ?? "");
|
|
4074
|
+
} else {
|
|
4075
|
+
this.#curve.removeAttribute(name);
|
|
4076
|
+
}
|
|
4077
|
+
}
|
|
4078
|
+
}
|
|
4079
|
+
|
|
4080
|
+
#syncPrimitiveValue() {
|
|
4081
|
+
if (!this.#curve) return;
|
|
4082
|
+
const value = this.value;
|
|
4083
|
+
const serialized = this.#isSpring()
|
|
4084
|
+
? `spring(${value.stiffness}, ${value.damping}, ${value.mass})`
|
|
4085
|
+
: `${value.x1}, ${value.y1}, ${value.x2}, ${value.y2}`;
|
|
4086
|
+
if (this.#curve.getAttribute("value") !== serialized) {
|
|
4087
|
+
this.#curve.setAttribute("value", serialized);
|
|
2883
4088
|
}
|
|
2884
4089
|
}
|
|
2885
4090
|
|
|
2886
4091
|
#syncDisabled() {
|
|
4092
|
+
if (!this.#curve || !this.#surface) return;
|
|
2887
4093
|
const disabled = figLabBooleanAttribute(this, "disabled");
|
|
2888
|
-
this.#
|
|
2889
|
-
this.#
|
|
4094
|
+
this.#surface.setAttribute("aria-disabled", String(disabled));
|
|
4095
|
+
this.#curve.toggleAttribute("disabled", disabled);
|
|
4096
|
+
this.#curve.inert = disabled;
|
|
4097
|
+
this.#syncDescendantState();
|
|
4098
|
+
}
|
|
4099
|
+
|
|
4100
|
+
#syncDescendantState() {
|
|
4101
|
+
if (!this.#curve) return;
|
|
4102
|
+
const disabled = figLabBooleanAttribute(this, "disabled");
|
|
4103
|
+
this.#curve
|
|
4104
|
+
.querySelector("fig-select")
|
|
4105
|
+
?.setAttribute("variant", "ghost");
|
|
4106
|
+
for (const control of this.#curve.querySelectorAll(
|
|
4107
|
+
"fig-handle, fig-select, fig-dropdown, fig-input-text",
|
|
4108
|
+
)) {
|
|
4109
|
+
control.toggleAttribute("disabled", disabled);
|
|
4110
|
+
}
|
|
4111
|
+
for (const control of this.#curve.querySelectorAll("input, select")) {
|
|
4112
|
+
control.disabled = disabled;
|
|
4113
|
+
}
|
|
2890
4114
|
}
|
|
2891
4115
|
|
|
2892
4116
|
#bindEvents() {
|
|
2893
4117
|
this.#unbindEvents();
|
|
2894
|
-
this.#
|
|
2895
|
-
this.#
|
|
2896
|
-
this.#yInput?.addEventListener("input", this.#boundHandleInput);
|
|
2897
|
-
this.#yInput?.addEventListener("change", this.#boundHandleChange);
|
|
4118
|
+
this.#curve?.addEventListener("input", this.#boundInput);
|
|
4119
|
+
this.#curve?.addEventListener("change", this.#boundChange);
|
|
2898
4120
|
}
|
|
2899
4121
|
|
|
2900
4122
|
#unbindEvents() {
|
|
2901
|
-
this.#
|
|
2902
|
-
this.#
|
|
2903
|
-
this.#yInput?.removeEventListener("input", this.#boundHandleInput);
|
|
2904
|
-
this.#yInput?.removeEventListener("change", this.#boundHandleChange);
|
|
4123
|
+
this.#curve?.removeEventListener("input", this.#boundInput);
|
|
4124
|
+
this.#curve?.removeEventListener("change", this.#boundChange);
|
|
2905
4125
|
}
|
|
2906
4126
|
|
|
2907
|
-
#
|
|
4127
|
+
#handlePrimitiveEvent(type, event) {
|
|
2908
4128
|
event.stopImmediatePropagation();
|
|
2909
|
-
if (figLabBooleanAttribute(this, "disabled")) return;
|
|
2910
|
-
const axis = event.currentTarget?.getAttribute("data-propskit-position-axis");
|
|
2911
|
-
if (axis !== "x" && axis !== "y") return;
|
|
2912
|
-
const value = this.value;
|
|
2913
|
-
value[axis] = this.#finiteNumber(event.currentTarget.value, value[axis]);
|
|
2914
|
-
const detail = this.#reflectValue(value);
|
|
2915
|
-
this.dispatchEvent(
|
|
2916
|
-
new CustomEvent(type, {
|
|
2917
|
-
detail: { ...detail, units: this.units },
|
|
2918
|
-
bubbles: true,
|
|
2919
|
-
cancelable: true,
|
|
2920
|
-
composed: true,
|
|
2921
|
-
}),
|
|
2922
|
-
);
|
|
2923
|
-
}
|
|
2924
|
-
|
|
2925
|
-
#handleClick(event) {
|
|
2926
|
-
if (figLabBooleanAttribute(this, "disabled")) return;
|
|
2927
4129
|
if (
|
|
2928
|
-
event.target
|
|
2929
|
-
|
|
4130
|
+
event.target !== this.#curve ||
|
|
4131
|
+
figLabBooleanAttribute(this, "disabled")
|
|
2930
4132
|
) {
|
|
2931
4133
|
return;
|
|
2932
4134
|
}
|
|
2933
|
-
this.
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
return this.#readValue().x;
|
|
2938
|
-
}
|
|
2939
|
-
|
|
2940
|
-
set x(value) {
|
|
2941
|
-
this.setAttribute("x", String(this.#finiteNumber(value, 50)));
|
|
2942
|
-
}
|
|
2943
|
-
|
|
2944
|
-
get y() {
|
|
2945
|
-
return this.#readValue().y;
|
|
2946
|
-
}
|
|
2947
|
-
|
|
2948
|
-
set y(value) {
|
|
2949
|
-
this.setAttribute("y", String(this.#finiteNumber(value, 50)));
|
|
2950
|
-
}
|
|
2951
|
-
|
|
2952
|
-
get units() {
|
|
2953
|
-
return this.getAttribute("units")?.trim().toLowerCase() === "percent"
|
|
2954
|
-
? "percent"
|
|
2955
|
-
: "none";
|
|
2956
|
-
}
|
|
2957
|
-
|
|
2958
|
-
set units(value) {
|
|
2959
|
-
this.setAttribute(
|
|
2960
|
-
"units",
|
|
2961
|
-
String(value).trim().toLowerCase() === "none" ? "none" : "percent",
|
|
2962
|
-
);
|
|
4135
|
+
const value = this.#normalizeValue(event.detail?.value, this.value);
|
|
4136
|
+
this.#reflectValue(value);
|
|
4137
|
+
this.#syncPrimitiveValue();
|
|
4138
|
+
figLabDispatchPropskitEvent(this, type);
|
|
2963
4139
|
}
|
|
2964
4140
|
|
|
2965
4141
|
get value() {
|
|
2966
|
-
return
|
|
4142
|
+
return this.#normalizeValue(
|
|
4143
|
+
this.getAttribute("value"),
|
|
4144
|
+
this.#initialValue || this.#fallbackValue(),
|
|
4145
|
+
);
|
|
2967
4146
|
}
|
|
2968
4147
|
|
|
2969
4148
|
set value(value) {
|
|
2970
|
-
|
|
2971
|
-
this.#
|
|
4149
|
+
this.#reflectValue(value);
|
|
4150
|
+
this.#syncPrimitiveValue();
|
|
2972
4151
|
}
|
|
2973
4152
|
|
|
2974
4153
|
get defaultValue() {
|
|
2975
|
-
return (
|
|
2976
|
-
this
|
|
2977
|
-
|
|
2978
|
-
}
|
|
4154
|
+
return this.#normalizeValue(
|
|
4155
|
+
this.getAttribute("default"),
|
|
4156
|
+
this.#initialValue || this.#fallbackValue(),
|
|
2979
4157
|
);
|
|
2980
4158
|
}
|
|
2981
4159
|
|
|
@@ -2984,16 +4162,29 @@ class PropskitPosition extends HTMLElement {
|
|
|
2984
4162
|
}
|
|
2985
4163
|
|
|
2986
4164
|
resetToDefault() {
|
|
2987
|
-
|
|
2988
|
-
this
|
|
2989
|
-
figLabEmitPropskitReset(this, { ...value, units: this.units });
|
|
4165
|
+
this.value = this.defaultValue;
|
|
4166
|
+
figLabEmitPropskitReset(this);
|
|
2990
4167
|
}
|
|
2991
4168
|
|
|
2992
4169
|
focus(options) {
|
|
2993
|
-
this
|
|
4170
|
+
if (figLabBooleanAttribute(this, "disabled")) return;
|
|
4171
|
+
this.#curve
|
|
4172
|
+
?.querySelector("fig-select, fig-dropdown, fig-handle, fig-input-text")
|
|
4173
|
+
?.focus(options);
|
|
2994
4174
|
}
|
|
2995
4175
|
}
|
|
2996
|
-
|
|
4176
|
+
|
|
4177
|
+
/**
|
|
4178
|
+
* Labeled cubic-bezier easing editor with a typed { x1, y1, x2, y2 } value.
|
|
4179
|
+
*/
|
|
4180
|
+
class PropskitEasing extends PropskitCurve {}
|
|
4181
|
+
figLabDefineElement("propskit-easing", PropskitEasing);
|
|
4182
|
+
|
|
4183
|
+
/**
|
|
4184
|
+
* Labeled spring editor with a typed { stiffness, damping, mass } value.
|
|
4185
|
+
*/
|
|
4186
|
+
class PropskitSpring extends PropskitCurve {}
|
|
4187
|
+
figLabDefineElement("propskit-spring", PropskitSpring);
|
|
2997
4188
|
|
|
2998
4189
|
/**
|
|
2999
4190
|
* Collapsible color-point group composed from color and position controls.
|
|
@@ -3001,19 +4192,17 @@ figLabDefineElement("propskit-position", PropskitPosition);
|
|
|
3001
4192
|
* @attr {string} label - Passed to the internal fig-group name.
|
|
3002
4193
|
* @attr {boolean|string} collapsible - Internal group collapsibility; defaults true.
|
|
3003
4194
|
* @attr {boolean|string} open - Internal group expanded state; defaults true.
|
|
3004
|
-
* @attr {string} size - Passed to both internal PropsKit controls.
|
|
3005
4195
|
* @attr {boolean|string} disabled - Disables both internal controls.
|
|
3006
4196
|
* @attr {string} value - JSON object with x, y, and color.
|
|
3007
4197
|
* @fires input - Composed event with { x, y, color }.
|
|
3008
4198
|
* @fires change - Composed event with { x, y, color }.
|
|
3009
4199
|
* @fires openchange - Composed event mirroring the internal fig-group.
|
|
3010
4200
|
*/
|
|
3011
|
-
class PropskitColorPoint extends
|
|
4201
|
+
class PropskitColorPoint extends FigLabPropskitElement {
|
|
3012
4202
|
static observedAttributes = [
|
|
3013
4203
|
"label",
|
|
3014
4204
|
"collapsible",
|
|
3015
4205
|
"open",
|
|
3016
|
-
"size",
|
|
3017
4206
|
"disabled",
|
|
3018
4207
|
"value",
|
|
3019
4208
|
];
|
|
@@ -3035,7 +4224,6 @@ class PropskitColorPoint extends HTMLElement {
|
|
|
3035
4224
|
}
|
|
3036
4225
|
if (!this.#group) this.#render();
|
|
3037
4226
|
this.#syncGroupAttributes();
|
|
3038
|
-
this.#syncSize();
|
|
3039
4227
|
this.#syncDisabled();
|
|
3040
4228
|
this.#syncControls(this.#readValue());
|
|
3041
4229
|
this.removeEventListener("input", this.#boundHandleInput);
|
|
@@ -3061,10 +4249,6 @@ class PropskitColorPoint extends HTMLElement {
|
|
|
3061
4249
|
}
|
|
3062
4250
|
return;
|
|
3063
4251
|
}
|
|
3064
|
-
if (name === "size") {
|
|
3065
|
-
this.#syncSize();
|
|
3066
|
-
return;
|
|
3067
|
-
}
|
|
3068
4252
|
if (name === "disabled") {
|
|
3069
4253
|
this.#syncDisabled();
|
|
3070
4254
|
return;
|
|
@@ -3131,7 +4315,6 @@ class PropskitColorPoint extends HTMLElement {
|
|
|
3131
4315
|
this.#colorControl = color;
|
|
3132
4316
|
this.#positionControl = position;
|
|
3133
4317
|
this.#syncGroupAttributes();
|
|
3134
|
-
this.#syncSize();
|
|
3135
4318
|
this.#syncDisabled();
|
|
3136
4319
|
group.append(color, position);
|
|
3137
4320
|
this.replaceChildren(group);
|
|
@@ -3151,15 +4334,6 @@ class PropskitColorPoint extends HTMLElement {
|
|
|
3151
4334
|
}
|
|
3152
4335
|
}
|
|
3153
4336
|
|
|
3154
|
-
#syncSize() {
|
|
3155
|
-
const size = this.getAttribute("size");
|
|
3156
|
-
for (const control of [this.#colorControl, this.#positionControl]) {
|
|
3157
|
-
if (!control) continue;
|
|
3158
|
-
if (size === null) control.removeAttribute("size");
|
|
3159
|
-
else control.setAttribute("size", size);
|
|
3160
|
-
}
|
|
3161
|
-
}
|
|
3162
|
-
|
|
3163
4337
|
#syncDisabled() {
|
|
3164
4338
|
figLabSyncDisabledControls(this, [
|
|
3165
4339
|
this.#colorControl,
|
|
@@ -3196,15 +4370,8 @@ class PropskitColorPoint extends HTMLElement {
|
|
|
3196
4370
|
} else {
|
|
3197
4371
|
return;
|
|
3198
4372
|
}
|
|
3199
|
-
|
|
3200
|
-
this
|
|
3201
|
-
new CustomEvent(type, {
|
|
3202
|
-
detail: { ...detail, units: "percent" },
|
|
3203
|
-
bubbles: true,
|
|
3204
|
-
cancelable: true,
|
|
3205
|
-
composed: true,
|
|
3206
|
-
}),
|
|
3207
|
-
);
|
|
4373
|
+
this.#reflectValue(value);
|
|
4374
|
+
figLabDispatchPropskitEvent(this, type);
|
|
3208
4375
|
}
|
|
3209
4376
|
|
|
3210
4377
|
#handleOpenChange(event) {
|
|
@@ -3262,7 +4429,6 @@ figLabDefineElement("propskit-color-point", PropskitColorPoint);
|
|
|
3262
4429
|
* @attr {string} label - Passed to the internal fig-group name.
|
|
3263
4430
|
* @attr {boolean|string} collapsible - Internal group collapsibility; defaults true.
|
|
3264
4431
|
* @attr {boolean|string} open - Internal group expanded state; defaults true.
|
|
3265
|
-
* @attr {string} size - Passed to both internal PropsKit controls.
|
|
3266
4432
|
* @attr {string} units - Passed to the internal position and radius controls.
|
|
3267
4433
|
* @attr {boolean|string} disabled - Disables both internal controls.
|
|
3268
4434
|
* @attr {string} value - JSON object with x, y, and radius.
|
|
@@ -3270,12 +4436,11 @@ figLabDefineElement("propskit-color-point", PropskitColorPoint);
|
|
|
3270
4436
|
* @fires change - Composed event with { x, y, radius }.
|
|
3271
4437
|
* @fires openchange - Composed event mirroring the internal fig-group.
|
|
3272
4438
|
*/
|
|
3273
|
-
class PropskitPointRadius extends
|
|
4439
|
+
class PropskitPointRadius extends FigLabPropskitElement {
|
|
3274
4440
|
static observedAttributes = [
|
|
3275
4441
|
"label",
|
|
3276
4442
|
"collapsible",
|
|
3277
4443
|
"open",
|
|
3278
|
-
"size",
|
|
3279
4444
|
"units",
|
|
3280
4445
|
"disabled",
|
|
3281
4446
|
"value",
|
|
@@ -3298,7 +4463,6 @@ class PropskitPointRadius extends HTMLElement {
|
|
|
3298
4463
|
}
|
|
3299
4464
|
if (!this.#group) this.#render();
|
|
3300
4465
|
this.#syncGroupAttributes();
|
|
3301
|
-
this.#syncSize();
|
|
3302
4466
|
this.#syncUnits();
|
|
3303
4467
|
this.#syncDisabled();
|
|
3304
4468
|
this.#syncControls(this.#readValue());
|
|
@@ -3325,10 +4489,6 @@ class PropskitPointRadius extends HTMLElement {
|
|
|
3325
4489
|
}
|
|
3326
4490
|
return;
|
|
3327
4491
|
}
|
|
3328
|
-
if (name === "size") {
|
|
3329
|
-
this.#syncSize();
|
|
3330
|
-
return;
|
|
3331
|
-
}
|
|
3332
4492
|
if (name === "units") {
|
|
3333
4493
|
this.#syncUnits();
|
|
3334
4494
|
return;
|
|
@@ -3418,7 +4578,6 @@ class PropskitPointRadius extends HTMLElement {
|
|
|
3418
4578
|
this.#positionControl = position;
|
|
3419
4579
|
this.#radiusControl = radius;
|
|
3420
4580
|
this.#syncGroupAttributes();
|
|
3421
|
-
this.#syncSize();
|
|
3422
4581
|
this.#syncDisabled();
|
|
3423
4582
|
group.append(position, radius);
|
|
3424
4583
|
this.replaceChildren(group);
|
|
@@ -3438,15 +4597,6 @@ class PropskitPointRadius extends HTMLElement {
|
|
|
3438
4597
|
}
|
|
3439
4598
|
}
|
|
3440
4599
|
|
|
3441
|
-
#syncSize() {
|
|
3442
|
-
const size = this.getAttribute("size");
|
|
3443
|
-
for (const control of [this.#positionControl, this.#radiusControl]) {
|
|
3444
|
-
if (!control) continue;
|
|
3445
|
-
if (size === null) control.removeAttribute("size");
|
|
3446
|
-
else control.setAttribute("size", size);
|
|
3447
|
-
}
|
|
3448
|
-
}
|
|
3449
|
-
|
|
3450
4600
|
#syncUnits() {
|
|
3451
4601
|
if (this.hasAttribute("units")) {
|
|
3452
4602
|
this.#positionControl?.setAttribute("units", this.units);
|
|
@@ -3508,19 +4658,8 @@ class PropskitPointRadius extends HTMLElement {
|
|
|
3508
4658
|
} else {
|
|
3509
4659
|
return;
|
|
3510
4660
|
}
|
|
3511
|
-
|
|
3512
|
-
this
|
|
3513
|
-
new CustomEvent(type, {
|
|
3514
|
-
detail: {
|
|
3515
|
-
...detail,
|
|
3516
|
-
radius: this.#radiusParts(detail.radius).value,
|
|
3517
|
-
units: this.units,
|
|
3518
|
-
},
|
|
3519
|
-
bubbles: true,
|
|
3520
|
-
cancelable: true,
|
|
3521
|
-
composed: true,
|
|
3522
|
-
}),
|
|
3523
|
-
);
|
|
4661
|
+
this.#reflectValue(value);
|
|
4662
|
+
figLabDispatchPropskitEvent(this, type);
|
|
3524
4663
|
}
|
|
3525
4664
|
|
|
3526
4665
|
#handleOpenChange(event) {
|
|
@@ -3591,7 +4730,6 @@ figLabDefineElement("propskit-point-radius", PropskitPointRadius);
|
|
|
3591
4730
|
* @attr {string} label - Passed to the internal fig-group name.
|
|
3592
4731
|
* @attr {boolean|string} collapsible - Internal group collapsibility; defaults true.
|
|
3593
4732
|
* @attr {boolean|string} open - Internal group expanded state; defaults true.
|
|
3594
|
-
* @attr {string} size - Passed to every internal PropsKit control.
|
|
3595
4733
|
* @attr {string} units - Passed to the internal position and radius controls.
|
|
3596
4734
|
* @attr {boolean|string} disabled - Disables every internal control.
|
|
3597
4735
|
* @attr {string} value - JSON object with x, y, radius, and angle.
|
|
@@ -3599,12 +4737,11 @@ figLabDefineElement("propskit-point-radius", PropskitPointRadius);
|
|
|
3599
4737
|
* @fires change - Composed event with { x, y, radius, angle }.
|
|
3600
4738
|
* @fires openchange - Composed event mirroring the internal fig-group.
|
|
3601
4739
|
*/
|
|
3602
|
-
class PropskitPointRadiusAngle extends
|
|
4740
|
+
class PropskitPointRadiusAngle extends FigLabPropskitElement {
|
|
3603
4741
|
static observedAttributes = [
|
|
3604
4742
|
"label",
|
|
3605
4743
|
"collapsible",
|
|
3606
4744
|
"open",
|
|
3607
|
-
"size",
|
|
3608
4745
|
"units",
|
|
3609
4746
|
"disabled",
|
|
3610
4747
|
"value",
|
|
@@ -3628,7 +4765,6 @@ class PropskitPointRadiusAngle extends HTMLElement {
|
|
|
3628
4765
|
}
|
|
3629
4766
|
if (!this.#group) this.#render();
|
|
3630
4767
|
this.#syncGroupAttributes();
|
|
3631
|
-
this.#syncSize();
|
|
3632
4768
|
this.#syncUnits();
|
|
3633
4769
|
this.#syncDisabled();
|
|
3634
4770
|
this.#syncControls(this.#readValue());
|
|
@@ -3655,10 +4791,6 @@ class PropskitPointRadiusAngle extends HTMLElement {
|
|
|
3655
4791
|
}
|
|
3656
4792
|
return;
|
|
3657
4793
|
}
|
|
3658
|
-
if (name === "size") {
|
|
3659
|
-
this.#syncSize();
|
|
3660
|
-
return;
|
|
3661
|
-
}
|
|
3662
4794
|
if (name === "units") {
|
|
3663
4795
|
this.#syncUnits();
|
|
3664
4796
|
return;
|
|
@@ -3760,7 +4892,6 @@ class PropskitPointRadiusAngle extends HTMLElement {
|
|
|
3760
4892
|
this.#radiusControl = radius;
|
|
3761
4893
|
this.#angleControl = angle;
|
|
3762
4894
|
this.#syncGroupAttributes();
|
|
3763
|
-
this.#syncSize();
|
|
3764
4895
|
this.#syncDisabled();
|
|
3765
4896
|
group.append(position, radius, angle);
|
|
3766
4897
|
this.replaceChildren(group);
|
|
@@ -3780,19 +4911,6 @@ class PropskitPointRadiusAngle extends HTMLElement {
|
|
|
3780
4911
|
}
|
|
3781
4912
|
}
|
|
3782
4913
|
|
|
3783
|
-
#syncSize() {
|
|
3784
|
-
const size = this.getAttribute("size");
|
|
3785
|
-
for (const control of [
|
|
3786
|
-
this.#positionControl,
|
|
3787
|
-
this.#radiusControl,
|
|
3788
|
-
this.#angleControl,
|
|
3789
|
-
]) {
|
|
3790
|
-
if (!control) continue;
|
|
3791
|
-
if (size === null) control.removeAttribute("size");
|
|
3792
|
-
else control.setAttribute("size", size);
|
|
3793
|
-
}
|
|
3794
|
-
}
|
|
3795
|
-
|
|
3796
4914
|
#syncUnits() {
|
|
3797
4915
|
if (this.hasAttribute("units")) {
|
|
3798
4916
|
this.#positionControl?.setAttribute("units", this.units);
|
|
@@ -3862,19 +4980,8 @@ class PropskitPointRadiusAngle extends HTMLElement {
|
|
|
3862
4980
|
} else {
|
|
3863
4981
|
return;
|
|
3864
4982
|
}
|
|
3865
|
-
|
|
3866
|
-
this
|
|
3867
|
-
new CustomEvent(type, {
|
|
3868
|
-
detail: {
|
|
3869
|
-
...detail,
|
|
3870
|
-
radius: this.#radiusParts(detail.radius).value,
|
|
3871
|
-
units: this.units,
|
|
3872
|
-
},
|
|
3873
|
-
bubbles: true,
|
|
3874
|
-
cancelable: true,
|
|
3875
|
-
composed: true,
|
|
3876
|
-
}),
|
|
3877
|
-
);
|
|
4983
|
+
this.#reflectValue(value);
|
|
4984
|
+
figLabDispatchPropskitEvent(this, type);
|
|
3878
4985
|
}
|
|
3879
4986
|
|
|
3880
4987
|
#handleOpenChange(event) {
|
|
@@ -3948,7 +5055,6 @@ figLabDefineElement(
|
|
|
3948
5055
|
* @attr {string} label - Passed to the internal fig-group name.
|
|
3949
5056
|
* @attr {boolean|string} collapsible - Internal group collapsibility; defaults true.
|
|
3950
5057
|
* @attr {boolean|string} open - Internal group expanded state; defaults true.
|
|
3951
|
-
* @attr {string} size - Passed to both internal position controls.
|
|
3952
5058
|
* @attr {string} units - Passed to both internal position controls.
|
|
3953
5059
|
* @attr {boolean|string} disabled - Disables both internal position controls.
|
|
3954
5060
|
* @attr {string} value - JSON object with x, y, x2, and y2.
|
|
@@ -3956,12 +5062,11 @@ figLabDefineElement(
|
|
|
3956
5062
|
* @fires change - Composed event with { x, y, x2, y2 }.
|
|
3957
5063
|
* @fires openchange - Composed event mirroring the internal fig-group.
|
|
3958
5064
|
*/
|
|
3959
|
-
class PropskitPointPoint extends
|
|
5065
|
+
class PropskitPointPoint extends FigLabPropskitElement {
|
|
3960
5066
|
static observedAttributes = [
|
|
3961
5067
|
"label",
|
|
3962
5068
|
"collapsible",
|
|
3963
5069
|
"open",
|
|
3964
|
-
"size",
|
|
3965
5070
|
"units",
|
|
3966
5071
|
"disabled",
|
|
3967
5072
|
"value",
|
|
@@ -3984,7 +5089,6 @@ class PropskitPointPoint extends HTMLElement {
|
|
|
3984
5089
|
}
|
|
3985
5090
|
if (!this.#group) this.#render();
|
|
3986
5091
|
this.#syncGroupAttributes();
|
|
3987
|
-
this.#syncSize();
|
|
3988
5092
|
this.#syncUnits();
|
|
3989
5093
|
this.#syncDisabled();
|
|
3990
5094
|
this.#syncControls(this.#readValue());
|
|
@@ -4011,10 +5115,6 @@ class PropskitPointPoint extends HTMLElement {
|
|
|
4011
5115
|
}
|
|
4012
5116
|
return;
|
|
4013
5117
|
}
|
|
4014
|
-
if (name === "size") {
|
|
4015
|
-
this.#syncSize();
|
|
4016
|
-
return;
|
|
4017
|
-
}
|
|
4018
5118
|
if (name === "units") {
|
|
4019
5119
|
this.#syncUnits();
|
|
4020
5120
|
return;
|
|
@@ -4085,7 +5185,6 @@ class PropskitPointPoint extends HTMLElement {
|
|
|
4085
5185
|
this.#startControl = start;
|
|
4086
5186
|
this.#endControl = end;
|
|
4087
5187
|
this.#syncGroupAttributes();
|
|
4088
|
-
this.#syncSize();
|
|
4089
5188
|
this.#syncDisabled();
|
|
4090
5189
|
group.append(start, end);
|
|
4091
5190
|
this.replaceChildren(group);
|
|
@@ -4105,15 +5204,6 @@ class PropskitPointPoint extends HTMLElement {
|
|
|
4105
5204
|
}
|
|
4106
5205
|
}
|
|
4107
5206
|
|
|
4108
|
-
#syncSize() {
|
|
4109
|
-
const size = this.getAttribute("size");
|
|
4110
|
-
for (const control of [this.#startControl, this.#endControl]) {
|
|
4111
|
-
if (!control) continue;
|
|
4112
|
-
if (size === null) control.removeAttribute("size");
|
|
4113
|
-
else control.setAttribute("size", size);
|
|
4114
|
-
}
|
|
4115
|
-
}
|
|
4116
|
-
|
|
4117
5207
|
#syncUnits() {
|
|
4118
5208
|
for (const control of [this.#startControl, this.#endControl]) {
|
|
4119
5209
|
if (!control) continue;
|
|
@@ -4165,15 +5255,8 @@ class PropskitPointPoint extends HTMLElement {
|
|
|
4165
5255
|
} else {
|
|
4166
5256
|
return;
|
|
4167
5257
|
}
|
|
4168
|
-
|
|
4169
|
-
this
|
|
4170
|
-
new CustomEvent(type, {
|
|
4171
|
-
detail: { ...detail, units: this.units },
|
|
4172
|
-
bubbles: true,
|
|
4173
|
-
cancelable: true,
|
|
4174
|
-
composed: true,
|
|
4175
|
-
}),
|
|
4176
|
-
);
|
|
5258
|
+
this.#reflectValue(value);
|
|
5259
|
+
figLabDispatchPropskitEvent(this, type);
|
|
4177
5260
|
}
|
|
4178
5261
|
|
|
4179
5262
|
#handleOpenChange(event) {
|
|
@@ -4239,14 +5322,17 @@ class PropskitPointPoint extends HTMLElement {
|
|
|
4239
5322
|
figLabDefineElement("propskit-point-point", PropskitPointPoint);
|
|
4240
5323
|
|
|
4241
5324
|
/* Collapsible property group — always collapsible (no collapsible attr). */
|
|
4242
|
-
class PropskitGroup extends
|
|
4243
|
-
static observedAttributes = ["name", "open", "show-reset", "disabled"
|
|
5325
|
+
class PropskitGroup extends FigLabPropskitElement {
|
|
5326
|
+
static observedAttributes = ["name", "open", "show-reset", "disabled"];
|
|
4244
5327
|
|
|
4245
5328
|
static #CONTROL_SELECTOR = [
|
|
4246
5329
|
"propskit-color",
|
|
4247
5330
|
"propskit-fill",
|
|
4248
5331
|
"propskit-gradient",
|
|
5332
|
+
"propskit-easing",
|
|
5333
|
+
"propskit-joystick",
|
|
4249
5334
|
"propskit-number",
|
|
5335
|
+
"propskit-origin",
|
|
4250
5336
|
"propskit-position",
|
|
4251
5337
|
"propskit-color-point",
|
|
4252
5338
|
"propskit-point-radius",
|
|
@@ -4254,6 +5340,7 @@ class PropskitGroup extends HTMLElement {
|
|
|
4254
5340
|
"propskit-point-point",
|
|
4255
5341
|
"propskit-select",
|
|
4256
5342
|
"propskit-slider",
|
|
5343
|
+
"propskit-spring",
|
|
4257
5344
|
"propskit-switch",
|
|
4258
5345
|
"propskit-text",
|
|
4259
5346
|
"propskit-wheel",
|
|
@@ -4270,7 +5357,6 @@ class PropskitGroup extends HTMLElement {
|
|
|
4270
5357
|
connectedCallback() {
|
|
4271
5358
|
this.#render();
|
|
4272
5359
|
this.#syncDisabled();
|
|
4273
|
-
this.#syncSize();
|
|
4274
5360
|
this.#bindDirtyListeners();
|
|
4275
5361
|
requestAnimationFrame(() => {
|
|
4276
5362
|
this.#syncDirtyState();
|
|
@@ -4304,10 +5390,6 @@ class PropskitGroup extends HTMLElement {
|
|
|
4304
5390
|
this.#syncDisabled();
|
|
4305
5391
|
return;
|
|
4306
5392
|
}
|
|
4307
|
-
if (name === "size") {
|
|
4308
|
-
this.#syncSize();
|
|
4309
|
-
return;
|
|
4310
|
-
}
|
|
4311
5393
|
this.#render();
|
|
4312
5394
|
}
|
|
4313
5395
|
|
|
@@ -4364,7 +5446,6 @@ class PropskitGroup extends HTMLElement {
|
|
|
4364
5446
|
if (!this.#childObserver) {
|
|
4365
5447
|
this.#childObserver = new MutationObserver(() => {
|
|
4366
5448
|
this.#syncDisabled();
|
|
4367
|
-
this.#syncSize();
|
|
4368
5449
|
this.#queueDirtySync();
|
|
4369
5450
|
});
|
|
4370
5451
|
}
|
|
@@ -4455,26 +5536,6 @@ class PropskitGroup extends HTMLElement {
|
|
|
4455
5536
|
?.toggleAttribute("disabled", disabled);
|
|
4456
5537
|
}
|
|
4457
5538
|
|
|
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
|
-
|
|
4478
5539
|
#controlIsDirty(el) {
|
|
4479
5540
|
return el.isDefault === false;
|
|
4480
5541
|
}
|
|
@@ -4627,13 +5688,13 @@ class PropskitGroup extends HTMLElement {
|
|
|
4627
5688
|
}
|
|
4628
5689
|
figLabDefineElement("propskit-group", PropskitGroup);
|
|
4629
5690
|
|
|
4630
|
-
/*
|
|
4631
|
-
class PropskitSlider extends
|
|
5691
|
+
/* PropsKit slider surface */
|
|
5692
|
+
class PropskitSlider extends FigLabPropskitElement {
|
|
4632
5693
|
static #DRAG_THRESHOLD_PX = 4;
|
|
4633
5694
|
static #DRAGGING_BODY_CLASS = "propskit-slider-dragging";
|
|
4634
5695
|
static #TEXT_MEASURE_CANVAS = null;
|
|
4635
5696
|
|
|
4636
|
-
#
|
|
5697
|
+
#surface = null;
|
|
4637
5698
|
#label = null;
|
|
4638
5699
|
#slider = null;
|
|
4639
5700
|
#hasCustomLabel = false;
|
|
@@ -4693,15 +5754,15 @@ class PropskitSlider extends HTMLElement {
|
|
|
4693
5754
|
]);
|
|
4694
5755
|
|
|
4695
5756
|
static get observedAttributes() {
|
|
4696
|
-
return ["label", "
|
|
5757
|
+
return ["label", "aria-label"];
|
|
4697
5758
|
}
|
|
4698
5759
|
|
|
4699
5760
|
connectedCallback() {
|
|
4700
|
-
if (!this.#
|
|
5761
|
+
if (!this.#surface) {
|
|
4701
5762
|
this.#initialize();
|
|
4702
5763
|
}
|
|
4703
5764
|
|
|
4704
|
-
this.#
|
|
5765
|
+
this.#syncSurface();
|
|
4705
5766
|
this.#syncSliderAttributes();
|
|
4706
5767
|
this.#bindSliderEvents();
|
|
4707
5768
|
this.#queueFocusDelegationSync();
|
|
@@ -4735,7 +5796,7 @@ class PropskitSlider extends HTMLElement {
|
|
|
4735
5796
|
|
|
4736
5797
|
if (!this.#observer) {
|
|
4737
5798
|
this.#observer = new MutationObserver((mutations) => {
|
|
4738
|
-
let
|
|
5799
|
+
let syncSurface = false;
|
|
4739
5800
|
let syncSlider = false;
|
|
4740
5801
|
|
|
4741
5802
|
for (const mutation of mutations) {
|
|
@@ -4756,16 +5817,21 @@ class PropskitSlider extends HTMLElement {
|
|
|
4756
5817
|
}
|
|
4757
5818
|
if (
|
|
4758
5819
|
mutation.attributeName === "label" ||
|
|
4759
|
-
mutation.attributeName === "
|
|
5820
|
+
mutation.attributeName === "aria-label"
|
|
4760
5821
|
) {
|
|
4761
|
-
|
|
5822
|
+
syncSurface = true;
|
|
5823
|
+
} else if (mutation.attributeName === "direction") {
|
|
5824
|
+
continue;
|
|
4762
5825
|
} else {
|
|
4763
5826
|
syncSlider = true;
|
|
4764
5827
|
}
|
|
4765
5828
|
}
|
|
4766
5829
|
}
|
|
4767
5830
|
|
|
4768
|
-
if (
|
|
5831
|
+
if (syncSurface) {
|
|
5832
|
+
this.#syncSurface();
|
|
5833
|
+
this.#queueFocusDelegationSync();
|
|
5834
|
+
}
|
|
4769
5835
|
if (syncSlider) {
|
|
4770
5836
|
this.#syncSliderAttributes();
|
|
4771
5837
|
this.#queueFocusDelegationSync();
|
|
@@ -4811,9 +5877,10 @@ class PropskitSlider extends HTMLElement {
|
|
|
4811
5877
|
}
|
|
4812
5878
|
|
|
4813
5879
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
4814
|
-
if (oldValue === newValue || !this.#
|
|
4815
|
-
if (name === "label" || name === "
|
|
4816
|
-
this.#
|
|
5880
|
+
if (oldValue === newValue || !this.#surface) return;
|
|
5881
|
+
if (name === "label" || name === "aria-label") {
|
|
5882
|
+
this.#syncSurface();
|
|
5883
|
+
this.#queueFocusDelegationSync();
|
|
4817
5884
|
}
|
|
4818
5885
|
}
|
|
4819
5886
|
|
|
@@ -4832,7 +5899,9 @@ class PropskitSlider extends HTMLElement {
|
|
|
4832
5899
|
const customLabel = initialChildren.find(
|
|
4833
5900
|
(node) => node.nodeType === Node.ELEMENT_NODE && node.matches("label"),
|
|
4834
5901
|
);
|
|
4835
|
-
const
|
|
5902
|
+
const surface = figLabCreateElement("div", {
|
|
5903
|
+
className: "propskit-slider-surface",
|
|
5904
|
+
});
|
|
4836
5905
|
const label = customLabel || document.createElement("label");
|
|
4837
5906
|
const slider = document.createElement("fig-slider");
|
|
4838
5907
|
slider.setAttribute("text", "true");
|
|
@@ -4841,14 +5910,14 @@ class PropskitSlider extends HTMLElement {
|
|
|
4841
5910
|
slider.setAttribute(attrName, value ?? "");
|
|
4842
5911
|
}
|
|
4843
5912
|
|
|
4844
|
-
|
|
5913
|
+
surface.append(label, slider);
|
|
4845
5914
|
|
|
4846
|
-
this.#
|
|
5915
|
+
this.#surface = surface;
|
|
4847
5916
|
this.#label = label;
|
|
4848
5917
|
this.#slider = slider;
|
|
4849
5918
|
this.#hasCustomLabel = Boolean(customLabel);
|
|
4850
5919
|
|
|
4851
|
-
this.replaceChildren(
|
|
5920
|
+
this.replaceChildren(surface);
|
|
4852
5921
|
this.#setupContextMenu();
|
|
4853
5922
|
|
|
4854
5923
|
for (const node of initialChildren) {
|
|
@@ -4872,28 +5941,12 @@ class PropskitSlider extends HTMLElement {
|
|
|
4872
5941
|
this.appendChild(menu);
|
|
4873
5942
|
}
|
|
4874
5943
|
|
|
4875
|
-
#
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
if (isBlankLabel) {
|
|
4882
|
-
if (this.#label.parentElement === this.#field) {
|
|
4883
|
-
this.#label.remove();
|
|
4884
|
-
}
|
|
4885
|
-
} else {
|
|
4886
|
-
if (!this.#hasCustomLabel) {
|
|
4887
|
-
this.#label.textContent = hasLabelAttr ? (rawLabel ?? "") : "Label";
|
|
4888
|
-
}
|
|
4889
|
-
if (this.#label.parentElement !== this.#field) {
|
|
4890
|
-
this.#field.prepend(this.#label);
|
|
4891
|
-
}
|
|
4892
|
-
}
|
|
4893
|
-
|
|
4894
|
-
this.#field.setAttribute(
|
|
4895
|
-
"direction",
|
|
4896
|
-
this.getAttribute("direction") || "horizontal",
|
|
5944
|
+
#syncSurface() {
|
|
5945
|
+
figLabSyncPropskitLabel(
|
|
5946
|
+
this,
|
|
5947
|
+
this.#surface,
|
|
5948
|
+
this.#label,
|
|
5949
|
+
this.#hasCustomLabel,
|
|
4897
5950
|
);
|
|
4898
5951
|
this.#queueHandleProximitySync();
|
|
4899
5952
|
}
|
|
@@ -5094,7 +6147,7 @@ class PropskitSlider extends HTMLElement {
|
|
|
5094
6147
|
const handleRadius = this.#handleRadius;
|
|
5095
6148
|
const distances = [];
|
|
5096
6149
|
const labelRect =
|
|
5097
|
-
this.#label?.parentElement === this.#
|
|
6150
|
+
this.#label?.parentElement === this.#surface
|
|
5098
6151
|
? this.#contentRect(this.#label)
|
|
5099
6152
|
: null;
|
|
5100
6153
|
const numberInput = this.#slider.querySelector("fig-input-number input");
|
|
@@ -5281,15 +6334,19 @@ class PropskitSlider extends HTMLElement {
|
|
|
5281
6334
|
if (rangeInput) {
|
|
5282
6335
|
rangeInput.removeAttribute("tabindex");
|
|
5283
6336
|
rangeInput.removeAttribute("aria-hidden");
|
|
5284
|
-
const
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
"
|
|
5288
|
-
if (
|
|
5289
|
-
|
|
5290
|
-
|
|
6337
|
+
const explicitLabel = this.getAttribute("aria-label")?.trim();
|
|
6338
|
+
if (explicitLabel) {
|
|
6339
|
+
rangeInput.setAttribute("aria-label", explicitLabel);
|
|
6340
|
+
rangeInput.removeAttribute("aria-labelledby");
|
|
6341
|
+
} else if (
|
|
6342
|
+
this.#label?.parentElement === this.#surface &&
|
|
6343
|
+
this.#label.id
|
|
5291
6344
|
) {
|
|
5292
|
-
rangeInput.setAttribute("aria-
|
|
6345
|
+
rangeInput.setAttribute("aria-labelledby", this.#label.id);
|
|
6346
|
+
rangeInput.removeAttribute("aria-label");
|
|
6347
|
+
} else {
|
|
6348
|
+
rangeInput.setAttribute("aria-label", "Slider");
|
|
6349
|
+
rangeInput.removeAttribute("aria-labelledby");
|
|
5293
6350
|
}
|
|
5294
6351
|
}
|
|
5295
6352
|
if (numberInput) {
|
|
@@ -5668,14 +6725,7 @@ class PropskitSlider extends HTMLElement {
|
|
|
5668
6725
|
if (!this.#slider || value === null || value === undefined) return;
|
|
5669
6726
|
this.#slider.value = value;
|
|
5670
6727
|
this.setAttribute("value", String(this.#slider.value));
|
|
5671
|
-
this
|
|
5672
|
-
new CustomEvent(eventType, {
|
|
5673
|
-
detail: this.#slider.value,
|
|
5674
|
-
bubbles: true,
|
|
5675
|
-
cancelable: true,
|
|
5676
|
-
composed: true,
|
|
5677
|
-
}),
|
|
5678
|
-
);
|
|
6728
|
+
figLabDispatchPropskitEvent(this, eventType);
|
|
5679
6729
|
}
|
|
5680
6730
|
|
|
5681
6731
|
#syncSteppersToNumberInput() {
|
|
@@ -5729,10 +6779,6 @@ class PropskitSlider extends HTMLElement {
|
|
|
5729
6779
|
if (type === "change") {
|
|
5730
6780
|
this.#resetElasticPull();
|
|
5731
6781
|
}
|
|
5732
|
-
const detail =
|
|
5733
|
-
event instanceof CustomEvent && event.detail !== undefined
|
|
5734
|
-
? event.detail
|
|
5735
|
-
: this.#slider?.value;
|
|
5736
6782
|
if (this.#slider?.value !== undefined) {
|
|
5737
6783
|
const next = String(this.#slider.value);
|
|
5738
6784
|
if (this.getAttribute("value") !== next) {
|
|
@@ -5740,14 +6786,7 @@ class PropskitSlider extends HTMLElement {
|
|
|
5740
6786
|
}
|
|
5741
6787
|
}
|
|
5742
6788
|
this.#syncProgressStyles();
|
|
5743
|
-
this
|
|
5744
|
-
new CustomEvent(type, {
|
|
5745
|
-
detail,
|
|
5746
|
-
bubbles: true,
|
|
5747
|
-
cancelable: true,
|
|
5748
|
-
composed: true,
|
|
5749
|
-
}),
|
|
5750
|
-
);
|
|
6789
|
+
figLabDispatchPropskitEvent(this, type);
|
|
5751
6790
|
}
|
|
5752
6791
|
|
|
5753
6792
|
focus(options) {
|
|
@@ -5758,7 +6797,9 @@ class PropskitSlider extends HTMLElement {
|
|
|
5758
6797
|
}
|
|
5759
6798
|
|
|
5760
6799
|
get value() {
|
|
5761
|
-
return
|
|
6800
|
+
return figLabFiniteNumberOrNull(
|
|
6801
|
+
this.getAttribute("value") ?? this.#slider?.value,
|
|
6802
|
+
);
|
|
5762
6803
|
}
|
|
5763
6804
|
|
|
5764
6805
|
set value(nextValue) {
|
|
@@ -6592,18 +7633,16 @@ figLabDefineElement("fig-input-wheel", FigInputWheel);
|
|
|
6592
7633
|
/**
|
|
6593
7634
|
* Labeled numeric wheel with an optional editable number field.
|
|
6594
7635
|
*
|
|
6595
|
-
* @attr {string} label -
|
|
7636
|
+
* @attr {string} label - Surface label. Defaults to "Label"; empty hides it.
|
|
6596
7637
|
* @attr {string} default - Reset target.
|
|
6597
7638
|
* @attr {number} precision - Number field display decimals.
|
|
6598
7639
|
* @attr {boolean|string} elastic - Enables resisted row stretching. Defaults to true.
|
|
6599
7640
|
* @attr {boolean|string} spin - Keeps wheel ticks synchronized to value. Defaults to true.
|
|
6600
7641
|
* @attr {boolean|string} text - Shows the number field. Defaults to true.
|
|
6601
|
-
* @attr {string} size - Set to "small" for compact sizing.
|
|
6602
|
-
* @attr {string} variant - Set to "minimal" for compact chrome.
|
|
6603
7642
|
* @fires input - Retargeted numeric composed event.
|
|
6604
7643
|
* @fires change - Retargeted numeric composed event.
|
|
6605
7644
|
*/
|
|
6606
|
-
class PropskitWheel extends
|
|
7645
|
+
class PropskitWheel extends FigLabPropskitElement {
|
|
6607
7646
|
static #RESERVED_ATTRS = new Set([
|
|
6608
7647
|
"label",
|
|
6609
7648
|
"size",
|
|
@@ -6623,6 +7662,7 @@ class PropskitWheel extends HTMLElement {
|
|
|
6623
7662
|
static get observedAttributes() {
|
|
6624
7663
|
return [
|
|
6625
7664
|
"label",
|
|
7665
|
+
"aria-label",
|
|
6626
7666
|
"units",
|
|
6627
7667
|
"value",
|
|
6628
7668
|
"disabled",
|
|
@@ -6712,7 +7752,7 @@ class PropskitWheel extends HTMLElement {
|
|
|
6712
7752
|
|
|
6713
7753
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
6714
7754
|
if (oldValue === newValue || !this.#surface) return;
|
|
6715
|
-
if (name === "label") this.#syncLabel();
|
|
7755
|
+
if (name === "label" || name === "aria-label") this.#syncLabel();
|
|
6716
7756
|
if (name === "text") this.#syncText();
|
|
6717
7757
|
if (name === "elastic" && newValue === "false") {
|
|
6718
7758
|
this.#stopElasticTracking();
|
|
@@ -6755,10 +7795,6 @@ class PropskitWheel extends HTMLElement {
|
|
|
6755
7795
|
const label = customLabel || document.createElement("label");
|
|
6756
7796
|
const wheel = document.createElement("fig-input-wheel");
|
|
6757
7797
|
const input = document.createElement("fig-input-number");
|
|
6758
|
-
const labelId = figLabUniqueId("propskit-wheel-label");
|
|
6759
|
-
label.id = labelId;
|
|
6760
|
-
wheel.setAttribute("aria-labelledby", labelId);
|
|
6761
|
-
input.setAttribute("aria-labelledby", labelId);
|
|
6762
7798
|
surface.append(label, wheel, input);
|
|
6763
7799
|
this.#surface = surface;
|
|
6764
7800
|
this.#wheel = wheel;
|
|
@@ -6772,16 +7808,14 @@ class PropskitWheel extends HTMLElement {
|
|
|
6772
7808
|
}
|
|
6773
7809
|
|
|
6774
7810
|
#syncLabel() {
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
this.#
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
}
|
|
6781
|
-
this.toggleAttribute(
|
|
6782
|
-
"data-label-empty",
|
|
6783
|
-
!(this.#label.textContent ?? "").trim(),
|
|
7811
|
+
const labelId = figLabSyncPropskitLabel(
|
|
7812
|
+
this,
|
|
7813
|
+
this.#surface,
|
|
7814
|
+
this.#label,
|
|
7815
|
+
this.#hasCustomLabel,
|
|
6784
7816
|
);
|
|
7817
|
+
figLabSyncPropskitControlLabel(this, this.#wheel, labelId, "Wheel");
|
|
7818
|
+
figLabSyncPropskitControlLabel(this, this.#input, labelId, "Number");
|
|
6785
7819
|
}
|
|
6786
7820
|
|
|
6787
7821
|
#syncText() {
|
|
@@ -6813,13 +7847,15 @@ class PropskitWheel extends HTMLElement {
|
|
|
6813
7847
|
"disabled",
|
|
6814
7848
|
figLabBooleanAttribute(this, "disabled"),
|
|
6815
7849
|
);
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
7850
|
+
const publicValue = figLabFiniteNumberOrNull(this.getAttribute("value"));
|
|
7851
|
+
this.#wheel.value = publicValue ?? 0;
|
|
7852
|
+
const normalized = String(this.#wheel.value);
|
|
7853
|
+
if (publicValue !== null && this.getAttribute("value") !== normalized) {
|
|
6819
7854
|
this.setAttribute("value", normalized);
|
|
6820
7855
|
}
|
|
6821
|
-
|
|
6822
|
-
|
|
7856
|
+
const inputValue = publicValue === null ? "" : normalized;
|
|
7857
|
+
if (this.#input?.getAttribute("value") !== inputValue) {
|
|
7858
|
+
this.#input?.setAttribute("value", inputValue);
|
|
6823
7859
|
}
|
|
6824
7860
|
this.#syncPrimitiveAria();
|
|
6825
7861
|
}
|
|
@@ -6909,7 +7945,7 @@ class PropskitWheel extends HTMLElement {
|
|
|
6909
7945
|
"disabled",
|
|
6910
7946
|
figLabBooleanAttribute(this, "disabled"),
|
|
6911
7947
|
);
|
|
6912
|
-
this.#input.setAttribute("value", this
|
|
7948
|
+
this.#input.setAttribute("value", String(this.value ?? ""));
|
|
6913
7949
|
this.#managedInputAttrs = nextManaged;
|
|
6914
7950
|
}
|
|
6915
7951
|
|
|
@@ -6952,15 +7988,20 @@ class PropskitWheel extends HTMLElement {
|
|
|
6952
7988
|
value,
|
|
6953
7989
|
{ spin = false, animateHandle = false } = {},
|
|
6954
7990
|
) {
|
|
6955
|
-
const parsed =
|
|
7991
|
+
const parsed = figLabFiniteNumberOrNull(value);
|
|
7992
|
+
if (parsed === null) {
|
|
7993
|
+
this.removeAttribute("value");
|
|
7994
|
+
if (this.#wheel) this.#wheel.value = 0;
|
|
7995
|
+
this.#input?.setAttribute("value", "");
|
|
7996
|
+
this.#syncPrimitiveAria();
|
|
7997
|
+
return null;
|
|
7998
|
+
}
|
|
6956
7999
|
if (!this.#wheel) {
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
return normalized;
|
|
8000
|
+
this.setAttribute("value", String(parsed));
|
|
8001
|
+
return parsed;
|
|
6960
8002
|
}
|
|
6961
|
-
|
|
6962
|
-
|
|
6963
|
-
else this.#wheel.value = nextValue;
|
|
8003
|
+
if (spin) this.#wheel.spinTo(parsed, { animateHandle });
|
|
8004
|
+
else this.#wheel.value = parsed;
|
|
6964
8005
|
const normalized = this.#wheel.value;
|
|
6965
8006
|
if (this.getAttribute("value") !== normalized) {
|
|
6966
8007
|
this.setAttribute("value", normalized);
|
|
@@ -6972,21 +8013,14 @@ class PropskitWheel extends HTMLElement {
|
|
|
6972
8013
|
return Number(normalized);
|
|
6973
8014
|
}
|
|
6974
8015
|
|
|
6975
|
-
#emit(type
|
|
6976
|
-
this
|
|
6977
|
-
new CustomEvent(type, {
|
|
6978
|
-
detail: value,
|
|
6979
|
-
bubbles: true,
|
|
6980
|
-
cancelable: true,
|
|
6981
|
-
composed: true,
|
|
6982
|
-
}),
|
|
6983
|
-
);
|
|
8016
|
+
#emit(type) {
|
|
8017
|
+
figLabDispatchPropskitEvent(this, type);
|
|
6984
8018
|
}
|
|
6985
8019
|
|
|
6986
8020
|
#handlePrimitiveEvent(type, event) {
|
|
6987
8021
|
event.stopImmediatePropagation();
|
|
6988
|
-
|
|
6989
|
-
this.#emit(type
|
|
8022
|
+
this.#setSynchronizedValue(event.detail);
|
|
8023
|
+
this.#emit(type);
|
|
6990
8024
|
}
|
|
6991
8025
|
|
|
6992
8026
|
#handleNumberEvent(type, event) {
|
|
@@ -6996,12 +8030,12 @@ class PropskitWheel extends HTMLElement {
|
|
|
6996
8030
|
event instanceof CustomEvent && event.detail !== undefined
|
|
6997
8031
|
? event.detail
|
|
6998
8032
|
: this.#input?.value;
|
|
6999
|
-
|
|
8033
|
+
this.#setSynchronizedValue(raw, {
|
|
7000
8034
|
spin: type === "input",
|
|
7001
8035
|
animateHandle: type === "input" && this.#animateNumberInputHandle,
|
|
7002
8036
|
});
|
|
7003
8037
|
if (type === "input") this.#animateNumberInputHandle = false;
|
|
7004
|
-
this.#emit(type
|
|
8038
|
+
this.#emit(type);
|
|
7005
8039
|
}
|
|
7006
8040
|
|
|
7007
8041
|
#handleNumberKeyDown(event) {
|
|
@@ -7292,12 +8326,11 @@ class PropskitWheel extends HTMLElement {
|
|
|
7292
8326
|
}
|
|
7293
8327
|
|
|
7294
8328
|
get value() {
|
|
7295
|
-
return this.getAttribute("value")
|
|
8329
|
+
return figLabFiniteNumberOrNull(this.getAttribute("value"));
|
|
7296
8330
|
}
|
|
7297
8331
|
|
|
7298
8332
|
set value(nextValue) {
|
|
7299
|
-
|
|
7300
|
-
this.#setSynchronizedValue(Number.isFinite(parsed) ? parsed : 0);
|
|
8333
|
+
this.#setSynchronizedValue(nextValue);
|
|
7301
8334
|
}
|
|
7302
8335
|
|
|
7303
8336
|
get min() {
|
|
@@ -7337,11 +8370,8 @@ class PropskitWheel extends HTMLElement {
|
|
|
7337
8370
|
}
|
|
7338
8371
|
|
|
7339
8372
|
resetToDefault() {
|
|
7340
|
-
|
|
7341
|
-
|
|
7342
|
-
Number.isFinite(parsed) ? parsed : 0,
|
|
7343
|
-
);
|
|
7344
|
-
figLabEmitPropskitReset(this, value);
|
|
8373
|
+
this.#setSynchronizedValue(this.defaultValue);
|
|
8374
|
+
figLabEmitPropskitReset(this);
|
|
7345
8375
|
}
|
|
7346
8376
|
|
|
7347
8377
|
focus(options) {
|
|
@@ -8802,7 +9832,7 @@ figLabDefineElement("fig-canvas-control", FigCanvasControl);
|
|
|
8802
9832
|
* @attr {string} aspect-ratio - SVG editor aspect ratio.
|
|
8803
9833
|
* @attr {boolean} edit - Whether to show the editor and number fields. Defaults to true.
|
|
8804
9834
|
*/
|
|
8805
|
-
class PropskitOscillator extends
|
|
9835
|
+
class PropskitOscillator extends FigLabPropskitElement {
|
|
8806
9836
|
#waves = [PropskitOscillator.#defaultWave()];
|
|
8807
9837
|
#activeWaveIndex = 0;
|
|
8808
9838
|
#precision = 2;
|
|
@@ -9304,7 +10334,6 @@ class PropskitOscillator extends HTMLElement {
|
|
|
9304
10334
|
return figLabCreateElement("propskit-slider", {
|
|
9305
10335
|
className: "propskit-oscillator-field",
|
|
9306
10336
|
label,
|
|
9307
|
-
direction: "horizontal",
|
|
9308
10337
|
name,
|
|
9309
10338
|
"data-wave-index": index,
|
|
9310
10339
|
value: this.#round(wave[name]),
|
|
@@ -9538,7 +10567,9 @@ class PropskitOscillator extends HTMLElement {
|
|
|
9538
10567
|
this.#applyFieldValue(
|
|
9539
10568
|
this.#indexFromElement(field),
|
|
9540
10569
|
field.getAttribute("name"),
|
|
9541
|
-
event.detail
|
|
10570
|
+
event.detail?.value ??
|
|
10571
|
+
event.currentTarget?.value ??
|
|
10572
|
+
event.target?.value,
|
|
9542
10573
|
"input",
|
|
9543
10574
|
);
|
|
9544
10575
|
} finally {
|
|
@@ -9550,7 +10581,9 @@ class PropskitOscillator extends HTMLElement {
|
|
|
9550
10581
|
this.#applyFieldValue(
|
|
9551
10582
|
this.#indexFromElement(field),
|
|
9552
10583
|
field.getAttribute("name"),
|
|
9553
|
-
event.detail
|
|
10584
|
+
event.detail?.value ??
|
|
10585
|
+
event.currentTarget?.value ??
|
|
10586
|
+
event.target?.value,
|
|
9554
10587
|
"change",
|
|
9555
10588
|
);
|
|
9556
10589
|
});
|
|
@@ -9886,16 +10919,7 @@ class PropskitOscillator extends HTMLElement {
|
|
|
9886
10919
|
}
|
|
9887
10920
|
|
|
9888
10921
|
#emit(type) {
|
|
9889
|
-
this
|
|
9890
|
-
new CustomEvent(type, {
|
|
9891
|
-
bubbles: true,
|
|
9892
|
-
detail: {
|
|
9893
|
-
value: this.value,
|
|
9894
|
-
data: this.data,
|
|
9895
|
-
preset: PropskitOscillator.#labelForType(this.#activeWave.type),
|
|
9896
|
-
},
|
|
9897
|
-
}),
|
|
9898
|
-
);
|
|
10922
|
+
figLabDispatchPropskitEvent(this, type);
|
|
9899
10923
|
}
|
|
9900
10924
|
}
|
|
9901
10925
|
figLabDefineElement("propskit-oscillator", PropskitOscillator);
|
|
@@ -10588,7 +11612,8 @@ class FigReorder extends HTMLElement {
|
|
|
10588
11612
|
"fig-easing-curve",
|
|
10589
11613
|
"fig-input-angle",
|
|
10590
11614
|
"fig-input-wheel",
|
|
10591
|
-
"fig-
|
|
11615
|
+
"fig-joystick",
|
|
11616
|
+
"fig-origin-grid",
|
|
10592
11617
|
"fig-canvas-control",
|
|
10593
11618
|
"propskit-color-point",
|
|
10594
11619
|
"propskit-number",
|