@rogieking/figui3 6.25.0 → 6.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/skills/figui3/SKILL.md +3 -1
- package/.cursor/skills/propkit/SKILL.md +2 -0
- package/README.md +42 -12
- package/components.css +55 -25
- package/dist/components.css +1 -1
- package/dist/fig-editor.css +1 -1
- package/dist/fig-lab.css +1 -1
- package/dist/fig-lab.js +22 -19
- package/dist/fig.css +1 -1
- package/dist/fig.js +25 -25
- package/fig-lab.css +46 -13
- package/fig-lab.js +386 -145
- package/fig.js +206 -25
- package/package.json +1 -1
package/fig-lab.js
CHANGED
|
@@ -18,6 +18,139 @@ function figLabBooleanAttribute(element, name) {
|
|
|
18
18
|
return element.hasAttribute(name) && element.getAttribute(name) !== "false";
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
function figLabColorEventAliases(color, alpha, opacity) {
|
|
22
|
+
const numericAlpha = Number(alpha);
|
|
23
|
+
const numericOpacity = Number(opacity);
|
|
24
|
+
const normalizedAlpha = Number.isFinite(numericAlpha)
|
|
25
|
+
? Math.max(0, Math.min(1, numericAlpha))
|
|
26
|
+
: Number.isFinite(numericOpacity)
|
|
27
|
+
? Math.max(0, Math.min(100, numericOpacity)) / 100
|
|
28
|
+
: 1;
|
|
29
|
+
const normalizedOpacity = Number.isFinite(numericOpacity)
|
|
30
|
+
? Math.max(0, Math.min(100, numericOpacity))
|
|
31
|
+
: Math.round(normalizedAlpha * 100);
|
|
32
|
+
return { color, alpha: normalizedAlpha, opacity: normalizedOpacity };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const figLabPropskitResetMenus = new WeakMap();
|
|
36
|
+
|
|
37
|
+
function figLabConnectPropskitResetMenu(host) {
|
|
38
|
+
let state = figLabPropskitResetMenus.get(host);
|
|
39
|
+
if (!state) {
|
|
40
|
+
const menu = document.createElement("fig-menu");
|
|
41
|
+
menu.setAttribute("position", "bottom left");
|
|
42
|
+
menu.setAttribute("offset", "0 0");
|
|
43
|
+
const resetItem = document.createElement("fig-menu-item");
|
|
44
|
+
resetItem.setAttribute("value", "reset-default");
|
|
45
|
+
resetItem.textContent = "Reset";
|
|
46
|
+
menu.appendChild(resetItem);
|
|
47
|
+
|
|
48
|
+
let fallbackTimer = 0;
|
|
49
|
+
const clearPendingOpen = () => {
|
|
50
|
+
if (fallbackTimer) {
|
|
51
|
+
clearTimeout(fallbackTimer);
|
|
52
|
+
fallbackTimer = 0;
|
|
53
|
+
}
|
|
54
|
+
window.removeEventListener("pointerup", openMenu, true);
|
|
55
|
+
window.removeEventListener("pointercancel", openMenu, true);
|
|
56
|
+
};
|
|
57
|
+
const openMenu = () => {
|
|
58
|
+
clearPendingOpen();
|
|
59
|
+
const point = state?.point;
|
|
60
|
+
if (point) menu.showAt?.(point.x, point.y);
|
|
61
|
+
};
|
|
62
|
+
const handleContextMenu = (event) => {
|
|
63
|
+
if (
|
|
64
|
+
figLabBooleanAttribute(host, "disabled") ||
|
|
65
|
+
event.target?.closest?.("fig-menu")
|
|
66
|
+
) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
event.preventDefault();
|
|
70
|
+
event.stopPropagation();
|
|
71
|
+
event.stopImmediatePropagation?.();
|
|
72
|
+
state.point = { x: event.clientX, y: event.clientY };
|
|
73
|
+
clearPendingOpen();
|
|
74
|
+
window.addEventListener("pointerup", openMenu, {
|
|
75
|
+
once: true,
|
|
76
|
+
capture: true,
|
|
77
|
+
});
|
|
78
|
+
window.addEventListener("pointercancel", openMenu, {
|
|
79
|
+
once: true,
|
|
80
|
+
capture: true,
|
|
81
|
+
});
|
|
82
|
+
fallbackTimer = window.setTimeout(openMenu, 180);
|
|
83
|
+
};
|
|
84
|
+
const handleChange = (event) => {
|
|
85
|
+
event.stopPropagation();
|
|
86
|
+
if (event.detail?.value === "reset-default") host.resetToDefault?.();
|
|
87
|
+
};
|
|
88
|
+
state = {
|
|
89
|
+
menu,
|
|
90
|
+
point: null,
|
|
91
|
+
clearPendingOpen,
|
|
92
|
+
handleContextMenu,
|
|
93
|
+
handleChange,
|
|
94
|
+
};
|
|
95
|
+
figLabPropskitResetMenus.set(host, state);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (state.menu.parentElement !== host) host.appendChild(state.menu);
|
|
99
|
+
host.removeEventListener("contextmenu", state.handleContextMenu);
|
|
100
|
+
host.addEventListener("contextmenu", state.handleContextMenu);
|
|
101
|
+
state.menu.removeEventListener("change", state.handleChange);
|
|
102
|
+
state.menu.addEventListener("change", state.handleChange);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function figLabDisconnectPropskitResetMenu(host) {
|
|
106
|
+
const state = figLabPropskitResetMenus.get(host);
|
|
107
|
+
if (!state) return;
|
|
108
|
+
state.clearPendingOpen();
|
|
109
|
+
host.removeEventListener("contextmenu", state.handleContextMenu);
|
|
110
|
+
state.menu.removeEventListener("change", state.handleChange);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function figLabEmitPropskitReset(host, detail) {
|
|
114
|
+
for (const type of ["input", "change"]) {
|
|
115
|
+
host.dispatchEvent(
|
|
116
|
+
new CustomEvent(type, {
|
|
117
|
+
detail,
|
|
118
|
+
bubbles: true,
|
|
119
|
+
cancelable: true,
|
|
120
|
+
composed: true,
|
|
121
|
+
}),
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function figLabPropskitValuesEqual(value, defaultValue) {
|
|
127
|
+
if (typeof value === "boolean" || typeof defaultValue === "boolean") {
|
|
128
|
+
return Boolean(value) === Boolean(defaultValue);
|
|
129
|
+
}
|
|
130
|
+
return String(value ?? "") === String(defaultValue ?? "");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function figLabPerceivedColorTheme(context, color) {
|
|
134
|
+
if (!context || !color) return null;
|
|
135
|
+
const probe = document.createElement("span");
|
|
136
|
+
probe.style.cssText =
|
|
137
|
+
"position:absolute;visibility:hidden;pointer-events:none;color:" + color;
|
|
138
|
+
context.appendChild(probe);
|
|
139
|
+
const resolved = getComputedStyle(probe).color;
|
|
140
|
+
probe.remove();
|
|
141
|
+
if (!resolved.startsWith("rgb")) return null;
|
|
142
|
+
const channels = resolved.match(/[\d.]+/g)?.slice(0, 3).map(Number);
|
|
143
|
+
if (!channels || channels.length < 3 || channels.some(Number.isNaN)) return null;
|
|
144
|
+
const [r, g, b] = channels.map((channel) => {
|
|
145
|
+
const value = channel / 255;
|
|
146
|
+
return value <= 0.04045
|
|
147
|
+
? value / 12.92
|
|
148
|
+
: ((value + 0.055) / 1.055) ** 2.4;
|
|
149
|
+
});
|
|
150
|
+
const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
151
|
+
return luminance <= 0.179 ? "light" : "dark";
|
|
152
|
+
}
|
|
153
|
+
|
|
21
154
|
/* Unique IDs for lab components such as propskit-group. */
|
|
22
155
|
let figLabUniqueIdCounter = 0;
|
|
23
156
|
function figLabUniqueId(prefix = "fig-lab") {
|
|
@@ -44,6 +177,7 @@ class PropskitSwitch extends HTMLElement {
|
|
|
44
177
|
#boundHandleInput = null;
|
|
45
178
|
#boundHandleChange = null;
|
|
46
179
|
#boundHandleClick = this.#handleClick.bind(this);
|
|
180
|
+
#initialChecked = false;
|
|
47
181
|
|
|
48
182
|
static get observedAttributes() {
|
|
49
183
|
return ["label", "direction"];
|
|
@@ -56,6 +190,7 @@ class PropskitSwitch extends HTMLElement {
|
|
|
56
190
|
this.#bindSwitchEvents();
|
|
57
191
|
this.removeEventListener("click", this.#boundHandleClick);
|
|
58
192
|
this.addEventListener("click", this.#boundHandleClick);
|
|
193
|
+
figLabConnectPropskitResetMenu(this);
|
|
59
194
|
|
|
60
195
|
if (!this.#observer) {
|
|
61
196
|
this.#observer = new MutationObserver((mutations) => {
|
|
@@ -86,6 +221,7 @@ class PropskitSwitch extends HTMLElement {
|
|
|
86
221
|
this.#observer?.disconnect();
|
|
87
222
|
this.#unbindSwitchEvents();
|
|
88
223
|
this.removeEventListener("click", this.#boundHandleClick);
|
|
224
|
+
figLabDisconnectPropskitResetMenu(this);
|
|
89
225
|
}
|
|
90
226
|
|
|
91
227
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
@@ -94,6 +230,7 @@ class PropskitSwitch extends HTMLElement {
|
|
|
94
230
|
}
|
|
95
231
|
|
|
96
232
|
#initialize() {
|
|
233
|
+
this.#initialChecked = figLabBooleanAttribute(this, "checked");
|
|
97
234
|
const initialChildren = Array.from(this.childNodes).filter(
|
|
98
235
|
(node) =>
|
|
99
236
|
node.nodeType !== Node.TEXT_NODE || Boolean(node.textContent?.trim()),
|
|
@@ -157,6 +294,7 @@ class PropskitSwitch extends HTMLElement {
|
|
|
157
294
|
"id",
|
|
158
295
|
"checked",
|
|
159
296
|
"value",
|
|
297
|
+
"default",
|
|
160
298
|
]);
|
|
161
299
|
return this.getAttributeNames().filter(
|
|
162
300
|
(name) => !reserved.has(name) && !name.startsWith("data-"),
|
|
@@ -219,7 +357,10 @@ class PropskitSwitch extends HTMLElement {
|
|
|
219
357
|
}
|
|
220
358
|
|
|
221
359
|
#handleClick(event) {
|
|
222
|
-
if (
|
|
360
|
+
if (
|
|
361
|
+
event.target instanceof Element &&
|
|
362
|
+
event.target.closest("fig-segmented-control, fig-menu")
|
|
363
|
+
) {
|
|
223
364
|
return;
|
|
224
365
|
}
|
|
225
366
|
const value = this.#switch?.value === "on" ? "off" : "on";
|
|
@@ -246,6 +387,25 @@ class PropskitSwitch extends HTMLElement {
|
|
|
246
387
|
this.setAttribute("value", nextValue ?? "");
|
|
247
388
|
}
|
|
248
389
|
|
|
390
|
+
get defaultValue() {
|
|
391
|
+
return this.hasAttribute("default")
|
|
392
|
+
? figLabBooleanAttribute(this, "default")
|
|
393
|
+
: this.#initialChecked;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
get isDefault() {
|
|
397
|
+
return this.checked === this.defaultValue;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
resetToDefault() {
|
|
401
|
+
const checked = this.defaultValue;
|
|
402
|
+
this.checked = checked;
|
|
403
|
+
figLabEmitPropskitReset(this, {
|
|
404
|
+
checked,
|
|
405
|
+
value: this.getAttribute("value") ?? "",
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
249
409
|
focus(options) {
|
|
250
410
|
const selected =
|
|
251
411
|
this.#switch?.querySelector("fig-segment[selected]") ||
|
|
@@ -283,6 +443,7 @@ class PropskitColor extends HTMLElement {
|
|
|
283
443
|
}
|
|
284
444
|
this.removeEventListener("click", this.#boundHandleClick);
|
|
285
445
|
this.addEventListener("click", this.#boundHandleClick);
|
|
446
|
+
figLabConnectPropskitResetMenu(this);
|
|
286
447
|
|
|
287
448
|
if (!this.#observer) {
|
|
288
449
|
this.#observer = new MutationObserver((mutations) => {
|
|
@@ -314,6 +475,7 @@ class PropskitColor extends HTMLElement {
|
|
|
314
475
|
this.#observer?.disconnect();
|
|
315
476
|
this.#unbindInputEvents();
|
|
316
477
|
this.removeEventListener("click", this.#boundHandleClick);
|
|
478
|
+
figLabDisconnectPropskitResetMenu(this);
|
|
317
479
|
}
|
|
318
480
|
|
|
319
481
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
@@ -477,7 +639,10 @@ class PropskitColor extends HTMLElement {
|
|
|
477
639
|
}
|
|
478
640
|
|
|
479
641
|
#handleClick(event) {
|
|
480
|
-
if (
|
|
642
|
+
if (
|
|
643
|
+
event.target instanceof Element &&
|
|
644
|
+
event.target.closest("fig-input-color, fig-menu")
|
|
645
|
+
) {
|
|
481
646
|
return;
|
|
482
647
|
}
|
|
483
648
|
this.focus();
|
|
@@ -513,8 +678,16 @@ class PropskitColor extends HTMLElement {
|
|
|
513
678
|
return this.getAttribute("default") ?? this.#initialValue ?? "";
|
|
514
679
|
}
|
|
515
680
|
|
|
681
|
+
get defaultValue() {
|
|
682
|
+
return String(this.#defaultValue());
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
get isDefault() {
|
|
686
|
+
return figLabPropskitValuesEqual(this.value, this.defaultValue);
|
|
687
|
+
}
|
|
688
|
+
|
|
516
689
|
resetToDefault() {
|
|
517
|
-
const next =
|
|
690
|
+
const next = this.defaultValue;
|
|
518
691
|
this.setAttribute("value", next);
|
|
519
692
|
this.#forceInputValue(next);
|
|
520
693
|
this.dispatchEvent(
|
|
@@ -555,6 +728,7 @@ class PropskitSelect extends HTMLElement {
|
|
|
555
728
|
#boundHandlePointerDown = this.#handlePointerDown.bind(this);
|
|
556
729
|
/** True when pointerdown saw the menu open — skip click-to-open after light-dismiss. */
|
|
557
730
|
#closeGesture = false;
|
|
731
|
+
#initialValue = null;
|
|
558
732
|
|
|
559
733
|
static get observedAttributes() {
|
|
560
734
|
return ["label", "direction", "aria-label", "options", "value"];
|
|
@@ -569,6 +743,7 @@ class PropskitSelect extends HTMLElement {
|
|
|
569
743
|
this.addEventListener("click", this.#boundHandleClick);
|
|
570
744
|
this.removeEventListener("pointerdown", this.#boundHandlePointerDown, true);
|
|
571
745
|
this.addEventListener("pointerdown", this.#boundHandlePointerDown, true);
|
|
746
|
+
figLabConnectPropskitResetMenu(this);
|
|
572
747
|
|
|
573
748
|
if (!this.#observer) {
|
|
574
749
|
this.#observer = new MutationObserver((mutations) => {
|
|
@@ -602,6 +777,7 @@ class PropskitSelect extends HTMLElement {
|
|
|
602
777
|
this.#unbindSelectEvents();
|
|
603
778
|
this.removeEventListener("click", this.#boundHandleClick);
|
|
604
779
|
this.removeEventListener("pointerdown", this.#boundHandlePointerDown, true);
|
|
780
|
+
figLabDisconnectPropskitResetMenu(this);
|
|
605
781
|
}
|
|
606
782
|
|
|
607
783
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
@@ -616,6 +792,7 @@ class PropskitSelect extends HTMLElement {
|
|
|
616
792
|
}
|
|
617
793
|
|
|
618
794
|
#initialize() {
|
|
795
|
+
this.#initialValue = this.getAttribute("value") ?? "";
|
|
619
796
|
const customLabel = this.querySelector(":scope > label");
|
|
620
797
|
const field = document.createElement("fig-field");
|
|
621
798
|
const label = customLabel || document.createElement("label");
|
|
@@ -673,6 +850,7 @@ class PropskitSelect extends HTMLElement {
|
|
|
673
850
|
"size",
|
|
674
851
|
"aria-label",
|
|
675
852
|
"full",
|
|
853
|
+
"default",
|
|
676
854
|
]);
|
|
677
855
|
return this.getAttributeNames().filter(
|
|
678
856
|
(name) => !reserved.has(name) && !name.startsWith("data-"),
|
|
@@ -749,6 +927,7 @@ class PropskitSelect extends HTMLElement {
|
|
|
749
927
|
|
|
750
928
|
#handlePointerDown(event) {
|
|
751
929
|
if (!(event.target instanceof Element)) return;
|
|
930
|
+
if (event.target.closest("fig-menu")) return;
|
|
752
931
|
// fig-select owns its trigger/option clicks.
|
|
753
932
|
if (event.target.closest("fig-select")) {
|
|
754
933
|
this.#closeGesture = false;
|
|
@@ -759,6 +938,9 @@ class PropskitSelect extends HTMLElement {
|
|
|
759
938
|
}
|
|
760
939
|
|
|
761
940
|
#handleClick(event) {
|
|
941
|
+
if (event.target instanceof Element && event.target.closest("fig-menu")) {
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
762
944
|
if (event.target instanceof Element && event.target.closest("fig-select")) {
|
|
763
945
|
this.#closeGesture = false;
|
|
764
946
|
return;
|
|
@@ -790,6 +972,21 @@ class PropskitSelect extends HTMLElement {
|
|
|
790
972
|
}
|
|
791
973
|
}
|
|
792
974
|
|
|
975
|
+
get defaultValue() {
|
|
976
|
+
return this.getAttribute("default") ?? this.#initialValue ?? "";
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
get isDefault() {
|
|
980
|
+
return figLabPropskitValuesEqual(this.value, this.defaultValue);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
resetToDefault() {
|
|
984
|
+
const value = this.defaultValue;
|
|
985
|
+
this.value = value;
|
|
986
|
+
if (this.#select) this.#select.value = value;
|
|
987
|
+
figLabEmitPropskitReset(this, value);
|
|
988
|
+
}
|
|
989
|
+
|
|
793
990
|
focus(options) {
|
|
794
991
|
this.#select?.focus(options);
|
|
795
992
|
}
|
|
@@ -807,6 +1004,7 @@ class PropskitText extends HTMLElement {
|
|
|
807
1004
|
#boundHandleInput = null;
|
|
808
1005
|
#boundHandleChange = null;
|
|
809
1006
|
#boundHandleClick = this.#handleClick.bind(this);
|
|
1007
|
+
#initialValue = null;
|
|
810
1008
|
|
|
811
1009
|
static get observedAttributes() {
|
|
812
1010
|
return ["label", "direction", "aria-label"];
|
|
@@ -819,6 +1017,7 @@ class PropskitText extends HTMLElement {
|
|
|
819
1017
|
this.#bindInputEvents();
|
|
820
1018
|
this.removeEventListener("click", this.#boundHandleClick);
|
|
821
1019
|
this.addEventListener("click", this.#boundHandleClick);
|
|
1020
|
+
figLabConnectPropskitResetMenu(this);
|
|
822
1021
|
|
|
823
1022
|
if (!this.#observer) {
|
|
824
1023
|
this.#observer = new MutationObserver((mutations) => {
|
|
@@ -850,6 +1049,7 @@ class PropskitText extends HTMLElement {
|
|
|
850
1049
|
this.#observer?.disconnect();
|
|
851
1050
|
this.#unbindInputEvents();
|
|
852
1051
|
this.removeEventListener("click", this.#boundHandleClick);
|
|
1052
|
+
figLabDisconnectPropskitResetMenu(this);
|
|
853
1053
|
}
|
|
854
1054
|
|
|
855
1055
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
@@ -860,6 +1060,7 @@ class PropskitText extends HTMLElement {
|
|
|
860
1060
|
}
|
|
861
1061
|
|
|
862
1062
|
#initialize() {
|
|
1063
|
+
this.#initialValue = this.getAttribute("value") ?? "";
|
|
863
1064
|
const initialChildren = Array.from(this.childNodes).filter(
|
|
864
1065
|
(node) =>
|
|
865
1066
|
node.nodeType !== Node.TEXT_NODE || Boolean(node.textContent?.trim()),
|
|
@@ -924,6 +1125,7 @@ class PropskitText extends HTMLElement {
|
|
|
924
1125
|
"aria-label",
|
|
925
1126
|
"multiline",
|
|
926
1127
|
"resizable",
|
|
1128
|
+
"default",
|
|
927
1129
|
]);
|
|
928
1130
|
return this.getAttributeNames().filter(
|
|
929
1131
|
(name) => !reserved.has(name) && !name.startsWith("data-"),
|
|
@@ -982,7 +1184,10 @@ class PropskitText extends HTMLElement {
|
|
|
982
1184
|
}
|
|
983
1185
|
|
|
984
1186
|
#handleClick(event) {
|
|
985
|
-
if (
|
|
1187
|
+
if (
|
|
1188
|
+
event.target instanceof Element &&
|
|
1189
|
+
event.target.closest("fig-input-text, fig-menu")
|
|
1190
|
+
) {
|
|
986
1191
|
return;
|
|
987
1192
|
}
|
|
988
1193
|
this.focus();
|
|
@@ -1003,6 +1208,20 @@ class PropskitText extends HTMLElement {
|
|
|
1003
1208
|
}
|
|
1004
1209
|
}
|
|
1005
1210
|
|
|
1211
|
+
get defaultValue() {
|
|
1212
|
+
return this.getAttribute("default") ?? this.#initialValue ?? "";
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
get isDefault() {
|
|
1216
|
+
return figLabPropskitValuesEqual(this.value, this.defaultValue);
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
resetToDefault() {
|
|
1220
|
+
const value = this.defaultValue;
|
|
1221
|
+
this.value = value;
|
|
1222
|
+
figLabEmitPropskitReset(this, value);
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1006
1225
|
focus(options) {
|
|
1007
1226
|
this.#input?.focus(options);
|
|
1008
1227
|
}
|
|
@@ -1020,6 +1239,7 @@ class PropskitNumber extends HTMLElement {
|
|
|
1020
1239
|
#boundHandleInput = null;
|
|
1021
1240
|
#boundHandleChange = null;
|
|
1022
1241
|
#boundHandleClick = this.#handleClick.bind(this);
|
|
1242
|
+
#initialValue = null;
|
|
1023
1243
|
|
|
1024
1244
|
static get observedAttributes() {
|
|
1025
1245
|
return ["label", "direction"];
|
|
@@ -1032,6 +1252,7 @@ class PropskitNumber extends HTMLElement {
|
|
|
1032
1252
|
this.#bindInputEvents();
|
|
1033
1253
|
this.removeEventListener("click", this.#boundHandleClick);
|
|
1034
1254
|
this.addEventListener("click", this.#boundHandleClick);
|
|
1255
|
+
figLabConnectPropskitResetMenu(this);
|
|
1035
1256
|
|
|
1036
1257
|
if (!this.#observer) {
|
|
1037
1258
|
this.#observer = new MutationObserver((mutations) => {
|
|
@@ -1062,6 +1283,7 @@ class PropskitNumber extends HTMLElement {
|
|
|
1062
1283
|
this.#observer?.disconnect();
|
|
1063
1284
|
this.#unbindInputEvents();
|
|
1064
1285
|
this.removeEventListener("click", this.#boundHandleClick);
|
|
1286
|
+
figLabDisconnectPropskitResetMenu(this);
|
|
1065
1287
|
}
|
|
1066
1288
|
|
|
1067
1289
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
@@ -1070,6 +1292,8 @@ class PropskitNumber extends HTMLElement {
|
|
|
1070
1292
|
}
|
|
1071
1293
|
|
|
1072
1294
|
#initialize() {
|
|
1295
|
+
this.#initialValue =
|
|
1296
|
+
this.getAttribute("value") ?? this.getAttribute("min") ?? "0";
|
|
1073
1297
|
const initialChildren = Array.from(this.childNodes).filter(
|
|
1074
1298
|
(node) =>
|
|
1075
1299
|
node.nodeType !== Node.TEXT_NODE || Boolean(node.textContent?.trim()),
|
|
@@ -1126,6 +1350,7 @@ class PropskitNumber extends HTMLElement {
|
|
|
1126
1350
|
"class",
|
|
1127
1351
|
"style",
|
|
1128
1352
|
"id",
|
|
1353
|
+
"default",
|
|
1129
1354
|
]);
|
|
1130
1355
|
return this.getAttributeNames().filter(
|
|
1131
1356
|
(name) => !reserved.has(name) && !name.startsWith("data-"),
|
|
@@ -1185,7 +1410,10 @@ class PropskitNumber extends HTMLElement {
|
|
|
1185
1410
|
}
|
|
1186
1411
|
|
|
1187
1412
|
#handleClick(event) {
|
|
1188
|
-
if (
|
|
1413
|
+
if (
|
|
1414
|
+
event.target instanceof Element &&
|
|
1415
|
+
event.target.closest("fig-input-number, fig-menu")
|
|
1416
|
+
) {
|
|
1189
1417
|
return;
|
|
1190
1418
|
}
|
|
1191
1419
|
this.focus();
|
|
@@ -1206,6 +1434,20 @@ class PropskitNumber extends HTMLElement {
|
|
|
1206
1434
|
}
|
|
1207
1435
|
}
|
|
1208
1436
|
|
|
1437
|
+
get defaultValue() {
|
|
1438
|
+
return this.getAttribute("default") ?? this.#initialValue ?? "0";
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
get isDefault() {
|
|
1442
|
+
return figLabPropskitValuesEqual(this.value, this.defaultValue);
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
resetToDefault() {
|
|
1446
|
+
const value = this.defaultValue;
|
|
1447
|
+
this.value = value;
|
|
1448
|
+
figLabEmitPropskitReset(this, this.#input?.value ?? value);
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1209
1451
|
focus(options) {
|
|
1210
1452
|
this.#input?.focus(options);
|
|
1211
1453
|
}
|
|
@@ -1223,12 +1465,12 @@ class PropskitGroup extends HTMLElement {
|
|
|
1223
1465
|
"propskit-slider",
|
|
1224
1466
|
"propskit-switch",
|
|
1225
1467
|
"propskit-text",
|
|
1468
|
+
"propskit-oscillator",
|
|
1226
1469
|
].join(",");
|
|
1227
1470
|
|
|
1228
1471
|
#header = null;
|
|
1229
1472
|
#chevron = null;
|
|
1230
1473
|
#resetTooltip = null;
|
|
1231
|
-
#defaults = new WeakMap();
|
|
1232
1474
|
#childObserver = null;
|
|
1233
1475
|
#dirtyFrame = 0;
|
|
1234
1476
|
#boundOnControlEvent = () => this.#queueDirtySync();
|
|
@@ -1237,7 +1479,6 @@ class PropskitGroup extends HTMLElement {
|
|
|
1237
1479
|
this.#render();
|
|
1238
1480
|
this.#bindDirtyListeners();
|
|
1239
1481
|
requestAnimationFrame(() => {
|
|
1240
|
-
this.#ensureDefaults();
|
|
1241
1482
|
this.#syncDirtyState();
|
|
1242
1483
|
});
|
|
1243
1484
|
}
|
|
@@ -1320,12 +1561,16 @@ class PropskitGroup extends HTMLElement {
|
|
|
1320
1561
|
|
|
1321
1562
|
if (!this.#childObserver) {
|
|
1322
1563
|
this.#childObserver = new MutationObserver(() => {
|
|
1323
|
-
this.#ensureDefaults();
|
|
1324
1564
|
this.#queueDirtySync();
|
|
1325
1565
|
});
|
|
1326
1566
|
}
|
|
1327
1567
|
this.#childObserver.disconnect();
|
|
1328
|
-
this.#childObserver.observe(this, {
|
|
1568
|
+
this.#childObserver.observe(this, {
|
|
1569
|
+
childList: true,
|
|
1570
|
+
subtree: true,
|
|
1571
|
+
attributes: true,
|
|
1572
|
+
attributeFilter: ["value", "checked", "default"],
|
|
1573
|
+
});
|
|
1329
1574
|
}
|
|
1330
1575
|
|
|
1331
1576
|
#unbindDirtyListeners() {
|
|
@@ -1338,7 +1583,6 @@ class PropskitGroup extends HTMLElement {
|
|
|
1338
1583
|
if (this.#dirtyFrame) return;
|
|
1339
1584
|
this.#dirtyFrame = requestAnimationFrame(() => {
|
|
1340
1585
|
this.#dirtyFrame = 0;
|
|
1341
|
-
this.#ensureDefaults();
|
|
1342
1586
|
this.#syncDirtyState();
|
|
1343
1587
|
});
|
|
1344
1588
|
}
|
|
@@ -1377,48 +1621,15 @@ class PropskitGroup extends HTMLElement {
|
|
|
1377
1621
|
#controls() {
|
|
1378
1622
|
return [
|
|
1379
1623
|
...this.querySelectorAll(PropskitGroup.#CONTROL_SELECTOR),
|
|
1380
|
-
].filter(
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
return { kind: "checked", value: Boolean(el.checked) };
|
|
1386
|
-
}
|
|
1387
|
-
if ("value" in el) {
|
|
1388
|
-
return { kind: "value", value: el.value };
|
|
1389
|
-
}
|
|
1390
|
-
return { kind: "value", value: el.getAttribute("value") ?? "" };
|
|
1391
|
-
}
|
|
1392
|
-
|
|
1393
|
-
#ensureDefaults() {
|
|
1394
|
-
for (const el of this.#controls()) {
|
|
1395
|
-
if (this.#defaults.has(el)) continue;
|
|
1396
|
-
if (el.hasAttribute("default")) {
|
|
1397
|
-
if (el.localName === "propskit-switch") {
|
|
1398
|
-
this.#defaults.set(el, {
|
|
1399
|
-
kind: "checked",
|
|
1400
|
-
value: figLabBooleanAttribute(el, "default"),
|
|
1401
|
-
});
|
|
1402
|
-
continue;
|
|
1403
|
-
}
|
|
1404
|
-
this.#defaults.set(el, {
|
|
1405
|
-
kind: "value",
|
|
1406
|
-
value: el.getAttribute("default") ?? "",
|
|
1407
|
-
});
|
|
1408
|
-
continue;
|
|
1409
|
-
}
|
|
1410
|
-
this.#defaults.set(el, this.#snapshotControl(el));
|
|
1411
|
-
}
|
|
1624
|
+
].filter(
|
|
1625
|
+
(el) =>
|
|
1626
|
+
el.closest("propskit-group") === this &&
|
|
1627
|
+
!el.parentElement?.closest(PropskitGroup.#CONTROL_SELECTOR),
|
|
1628
|
+
);
|
|
1412
1629
|
}
|
|
1413
1630
|
|
|
1414
1631
|
#controlIsDirty(el) {
|
|
1415
|
-
|
|
1416
|
-
if (!snap) return false;
|
|
1417
|
-
const cur = this.#snapshotControl(el);
|
|
1418
|
-
if (snap.kind === "checked") {
|
|
1419
|
-
return Boolean(cur.value) !== Boolean(snap.value);
|
|
1420
|
-
}
|
|
1421
|
-
return String(cur.value ?? "") !== String(snap.value ?? "");
|
|
1632
|
+
return el.isDefault === false;
|
|
1422
1633
|
}
|
|
1423
1634
|
|
|
1424
1635
|
#computeDirty() {
|
|
@@ -1436,67 +1647,12 @@ class PropskitGroup extends HTMLElement {
|
|
|
1436
1647
|
}
|
|
1437
1648
|
|
|
1438
1649
|
#restoreControl(el) {
|
|
1439
|
-
const snap = this.#defaults.get(el);
|
|
1440
|
-
if (!snap) return;
|
|
1441
|
-
|
|
1442
|
-
if (snap.kind === "checked") {
|
|
1443
|
-
el.checked = snap.value;
|
|
1444
|
-
const detail = {
|
|
1445
|
-
checked: Boolean(snap.value),
|
|
1446
|
-
value: el.getAttribute("value") ?? "",
|
|
1447
|
-
};
|
|
1448
|
-
el.dispatchEvent(
|
|
1449
|
-
new CustomEvent("input", {
|
|
1450
|
-
detail,
|
|
1451
|
-
bubbles: true,
|
|
1452
|
-
cancelable: true,
|
|
1453
|
-
composed: true,
|
|
1454
|
-
}),
|
|
1455
|
-
);
|
|
1456
|
-
el.dispatchEvent(
|
|
1457
|
-
new CustomEvent("change", {
|
|
1458
|
-
detail,
|
|
1459
|
-
bubbles: true,
|
|
1460
|
-
cancelable: true,
|
|
1461
|
-
composed: true,
|
|
1462
|
-
}),
|
|
1463
|
-
);
|
|
1464
|
-
return;
|
|
1465
|
-
}
|
|
1466
|
-
|
|
1467
1650
|
if (typeof el.resetToDefault === "function") {
|
|
1468
|
-
const prevDefault = el.getAttribute("default");
|
|
1469
|
-
el.setAttribute("default", String(snap.value ?? ""));
|
|
1470
1651
|
el.resetToDefault();
|
|
1471
|
-
if (prevDefault === null) el.removeAttribute("default");
|
|
1472
|
-
else el.setAttribute("default", prevDefault);
|
|
1473
|
-
return;
|
|
1474
1652
|
}
|
|
1475
|
-
|
|
1476
|
-
el.value = snap.value;
|
|
1477
|
-
const detail =
|
|
1478
|
-
el.getAttribute?.("value") ??
|
|
1479
|
-
("value" in el ? el.value : snap.value);
|
|
1480
|
-
el.dispatchEvent(
|
|
1481
|
-
new CustomEvent("input", {
|
|
1482
|
-
detail,
|
|
1483
|
-
bubbles: true,
|
|
1484
|
-
cancelable: true,
|
|
1485
|
-
composed: true,
|
|
1486
|
-
}),
|
|
1487
|
-
);
|
|
1488
|
-
el.dispatchEvent(
|
|
1489
|
-
new CustomEvent("change", {
|
|
1490
|
-
detail,
|
|
1491
|
-
bubbles: true,
|
|
1492
|
-
cancelable: true,
|
|
1493
|
-
composed: true,
|
|
1494
|
-
}),
|
|
1495
|
-
);
|
|
1496
1653
|
}
|
|
1497
1654
|
|
|
1498
1655
|
#resetControls() {
|
|
1499
|
-
this.#ensureDefaults();
|
|
1500
1656
|
for (const el of this.#controls()) this.#restoreControl(el);
|
|
1501
1657
|
this.#syncDirtyState();
|
|
1502
1658
|
this.dispatchEvent(
|
|
@@ -1638,6 +1794,7 @@ class PropskitSlider extends HTMLElement {
|
|
|
1638
1794
|
#contextMenu = null;
|
|
1639
1795
|
#pendingClickTimer = 0;
|
|
1640
1796
|
#pendingClickValue = null;
|
|
1797
|
+
#initialValue = null;
|
|
1641
1798
|
#isElasticTracking = false;
|
|
1642
1799
|
#elasticMaxPx = 0;
|
|
1643
1800
|
#elasticRangeRect = null;
|
|
@@ -1711,6 +1868,10 @@ class PropskitSlider extends HTMLElement {
|
|
|
1711
1868
|
this.#pushExternalValueToSlider();
|
|
1712
1869
|
continue;
|
|
1713
1870
|
}
|
|
1871
|
+
if (mutation.attributeName === "color") {
|
|
1872
|
+
syncSlider = true;
|
|
1873
|
+
continue;
|
|
1874
|
+
}
|
|
1714
1875
|
if (
|
|
1715
1876
|
mutation.attributeName &&
|
|
1716
1877
|
this.#ignoredSliderAttrs.has(mutation.attributeName)
|
|
@@ -1770,6 +1931,8 @@ class PropskitSlider extends HTMLElement {
|
|
|
1770
1931
|
}
|
|
1771
1932
|
|
|
1772
1933
|
#initialize() {
|
|
1934
|
+
this.#initialValue =
|
|
1935
|
+
this.getAttribute("value") ?? this.getAttribute("min") ?? "0";
|
|
1773
1936
|
const initialChildren = Array.from(this.childNodes).filter((node) => {
|
|
1774
1937
|
return (
|
|
1775
1938
|
node.nodeType !== Node.TEXT_NODE || Boolean(node.textContent?.trim())
|
|
@@ -1811,7 +1974,7 @@ class PropskitSlider extends HTMLElement {
|
|
|
1811
1974
|
|
|
1812
1975
|
const resetItem = document.createElement("fig-menu-item");
|
|
1813
1976
|
resetItem.setAttribute("value", "reset-default");
|
|
1814
|
-
resetItem.textContent = "Reset
|
|
1977
|
+
resetItem.textContent = "Reset";
|
|
1815
1978
|
menu.appendChild(resetItem);
|
|
1816
1979
|
menu.addEventListener("change", this.#boundHandleContextMenuChange);
|
|
1817
1980
|
|
|
@@ -1889,17 +2052,33 @@ class PropskitSlider extends HTMLElement {
|
|
|
1889
2052
|
if (sliderType === "opacity") {
|
|
1890
2053
|
this.#slider.style.setProperty(
|
|
1891
2054
|
"--color",
|
|
1892
|
-
"
|
|
2055
|
+
this.getAttribute("color") || "var(--figma-color-bg-secondary)",
|
|
1893
2056
|
);
|
|
1894
2057
|
} else {
|
|
1895
2058
|
this.#slider.style.removeProperty("--color");
|
|
1896
2059
|
}
|
|
2060
|
+
this.#syncNumberTheme(sliderType);
|
|
1897
2061
|
|
|
1898
2062
|
this.#managedSliderAttrs = nextManaged;
|
|
1899
2063
|
this.#pushExternalValueToSlider();
|
|
1900
2064
|
this.#queueSteppersSync();
|
|
1901
2065
|
}
|
|
1902
2066
|
|
|
2067
|
+
#syncNumberTheme(sliderType) {
|
|
2068
|
+
const numberInput = this.#slider?.querySelector("fig-input-number");
|
|
2069
|
+
if (!numberInput) return;
|
|
2070
|
+
if (sliderType !== "opacity" || !this.hasAttribute("color")) {
|
|
2071
|
+
numberInput.removeAttribute("theme");
|
|
2072
|
+
return;
|
|
2073
|
+
}
|
|
2074
|
+
const theme = figLabPerceivedColorTheme(
|
|
2075
|
+
this.#slider,
|
|
2076
|
+
this.getAttribute("color"),
|
|
2077
|
+
);
|
|
2078
|
+
if (theme) numberInput.setAttribute("theme", theme);
|
|
2079
|
+
else numberInput.removeAttribute("theme");
|
|
2080
|
+
}
|
|
2081
|
+
|
|
1903
2082
|
#pushExternalValueToSlider() {
|
|
1904
2083
|
if (!this.#slider) return;
|
|
1905
2084
|
const next = this.getAttribute("value") ?? "";
|
|
@@ -1963,8 +2142,8 @@ class PropskitSlider extends HTMLElement {
|
|
|
1963
2142
|
}
|
|
1964
2143
|
}
|
|
1965
2144
|
if (numberInput) {
|
|
1966
|
-
numberInput.
|
|
1967
|
-
numberInput.
|
|
2145
|
+
numberInput.removeAttribute("tabindex");
|
|
2146
|
+
numberInput.removeAttribute("aria-hidden");
|
|
1968
2147
|
}
|
|
1969
2148
|
}
|
|
1970
2149
|
|
|
@@ -2214,8 +2393,7 @@ class PropskitSlider extends HTMLElement {
|
|
|
2214
2393
|
return (
|
|
2215
2394
|
this.getAttribute("default") ??
|
|
2216
2395
|
this.#slider?.getAttribute("default") ??
|
|
2217
|
-
this
|
|
2218
|
-
this.#slider?.getAttribute("value") ??
|
|
2396
|
+
this.#initialValue ??
|
|
2219
2397
|
this.#rangeInput?.min ??
|
|
2220
2398
|
"0"
|
|
2221
2399
|
);
|
|
@@ -2333,6 +2511,14 @@ class PropskitSlider extends HTMLElement {
|
|
|
2333
2511
|
if (this.#slider) this.#slider.value = next;
|
|
2334
2512
|
}
|
|
2335
2513
|
|
|
2514
|
+
get defaultValue() {
|
|
2515
|
+
return this.#defaultValue();
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
get isDefault() {
|
|
2519
|
+
return figLabPropskitValuesEqual(this.value, this.defaultValue);
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2336
2522
|
resetToDefault() {
|
|
2337
2523
|
this.#resetToDefault();
|
|
2338
2524
|
}
|
|
@@ -3243,18 +3429,29 @@ class FigCanvasControl extends HTMLElement {
|
|
|
3243
3429
|
this.#syncPointPointCursors();
|
|
3244
3430
|
}
|
|
3245
3431
|
|
|
3246
|
-
#emitInput() {
|
|
3432
|
+
#emitInput(detail = this.value) {
|
|
3247
3433
|
this.dispatchEvent(
|
|
3248
|
-
new CustomEvent("input", { bubbles: true, detail
|
|
3434
|
+
new CustomEvent("input", { bubbles: true, detail }),
|
|
3249
3435
|
);
|
|
3250
3436
|
}
|
|
3251
3437
|
|
|
3252
|
-
#emitChange() {
|
|
3438
|
+
#emitChange(detail = this.value) {
|
|
3253
3439
|
this.dispatchEvent(
|
|
3254
|
-
new CustomEvent("change", { bubbles: true, detail
|
|
3440
|
+
new CustomEvent("change", { bubbles: true, detail }),
|
|
3255
3441
|
);
|
|
3256
3442
|
}
|
|
3257
3443
|
|
|
3444
|
+
#colorEventDetail(detail) {
|
|
3445
|
+
return {
|
|
3446
|
+
...this.value,
|
|
3447
|
+
...figLabColorEventAliases(
|
|
3448
|
+
detail.color,
|
|
3449
|
+
detail.alpha,
|
|
3450
|
+
detail.opacity,
|
|
3451
|
+
),
|
|
3452
|
+
};
|
|
3453
|
+
}
|
|
3454
|
+
|
|
3258
3455
|
#syncValueAttribute() {
|
|
3259
3456
|
this.setAttribute("value", JSON.stringify(this.value));
|
|
3260
3457
|
}
|
|
@@ -3265,8 +3462,11 @@ class FigCanvasControl extends HTMLElement {
|
|
|
3265
3462
|
this.#pointHandle.addEventListener("input", (e) => {
|
|
3266
3463
|
e.stopPropagation();
|
|
3267
3464
|
if (e.detail?.color) {
|
|
3268
|
-
this.setAttribute(
|
|
3269
|
-
|
|
3465
|
+
this.setAttribute(
|
|
3466
|
+
"color",
|
|
3467
|
+
this.#pointHandle.getAttribute("color") || e.detail.color,
|
|
3468
|
+
);
|
|
3469
|
+
this.#emitInput(this.#colorEventDetail(e.detail));
|
|
3270
3470
|
return;
|
|
3271
3471
|
}
|
|
3272
3472
|
this.#isDragging = true;
|
|
@@ -3285,8 +3485,11 @@ class FigCanvasControl extends HTMLElement {
|
|
|
3285
3485
|
this.#pointHandle.addEventListener("change", (e) => {
|
|
3286
3486
|
e.stopPropagation();
|
|
3287
3487
|
if (e.detail?.color) {
|
|
3288
|
-
this.setAttribute(
|
|
3289
|
-
|
|
3488
|
+
this.setAttribute(
|
|
3489
|
+
"color",
|
|
3490
|
+
this.#pointHandle.getAttribute("color") || e.detail.color,
|
|
3491
|
+
);
|
|
3492
|
+
this.#emitChange(this.#colorEventDetail(e.detail));
|
|
3290
3493
|
return;
|
|
3291
3494
|
}
|
|
3292
3495
|
const px = e.detail?.px ?? this.#x / 100;
|
|
@@ -3718,6 +3921,7 @@ class PropskitOscillator extends HTMLElement {
|
|
|
3718
3921
|
#activeFieldInput = null;
|
|
3719
3922
|
#dragController = null;
|
|
3720
3923
|
#dragCleanup = null;
|
|
3924
|
+
#initialValue = null;
|
|
3721
3925
|
|
|
3722
3926
|
static TYPES = [
|
|
3723
3927
|
{ name: "Wave", value: "sine" },
|
|
@@ -3741,11 +3945,18 @@ class PropskitOscillator extends HTMLElement {
|
|
|
3741
3945
|
}
|
|
3742
3946
|
|
|
3743
3947
|
connectedCallback() {
|
|
3948
|
+
if (this.#initialValue === null) {
|
|
3949
|
+
this.#initialValue =
|
|
3950
|
+
this.getAttribute("default") ??
|
|
3951
|
+
this.getAttribute("value") ??
|
|
3952
|
+
JSON.stringify({ waves: [PropskitOscillator.#defaultWave()] });
|
|
3953
|
+
}
|
|
3744
3954
|
this.#precision = this.#readInteger("precision", 2);
|
|
3745
3955
|
this.#parseValue(this.getAttribute("value"));
|
|
3746
3956
|
this.#syncAspectRatio();
|
|
3747
3957
|
this.#render();
|
|
3748
3958
|
this.#setupResizeObserver();
|
|
3959
|
+
figLabConnectPropskitResetMenu(this);
|
|
3749
3960
|
}
|
|
3750
3961
|
|
|
3751
3962
|
disconnectedCallback() {
|
|
@@ -3755,6 +3966,7 @@ class PropskitOscillator extends HTMLElement {
|
|
|
3755
3966
|
this.#resizeObserver.disconnect();
|
|
3756
3967
|
this.#resizeObserver = null;
|
|
3757
3968
|
}
|
|
3969
|
+
figLabDisconnectPropskitResetMenu(this);
|
|
3758
3970
|
}
|
|
3759
3971
|
|
|
3760
3972
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
@@ -3794,6 +4006,27 @@ class PropskitOscillator extends HTMLElement {
|
|
|
3794
4006
|
);
|
|
3795
4007
|
}
|
|
3796
4008
|
|
|
4009
|
+
get defaultValue() {
|
|
4010
|
+
return this.getAttribute("default") ?? this.#initialValue ?? "";
|
|
4011
|
+
}
|
|
4012
|
+
|
|
4013
|
+
get isDefault() {
|
|
4014
|
+
const defaultWaves = this.#normalizedWaves(this.defaultValue);
|
|
4015
|
+
if (!defaultWaves) return false;
|
|
4016
|
+
return (
|
|
4017
|
+
JSON.stringify(this.data.waves) ===
|
|
4018
|
+
JSON.stringify(defaultWaves.map((wave) => this.#roundWave(wave)))
|
|
4019
|
+
);
|
|
4020
|
+
}
|
|
4021
|
+
|
|
4022
|
+
resetToDefault() {
|
|
4023
|
+
const value = this.defaultValue;
|
|
4024
|
+
if (!value) return;
|
|
4025
|
+
this.value = value;
|
|
4026
|
+
this.#emit("input");
|
|
4027
|
+
this.#emit("change");
|
|
4028
|
+
}
|
|
4029
|
+
|
|
3797
4030
|
get data() {
|
|
3798
4031
|
return {
|
|
3799
4032
|
waves: this.#waves.map((wave) => this.#roundWave(wave)),
|
|
@@ -3835,33 +4068,35 @@ class PropskitOscillator extends HTMLElement {
|
|
|
3835
4068
|
}
|
|
3836
4069
|
|
|
3837
4070
|
#parseValue(value) {
|
|
3838
|
-
|
|
4071
|
+
const nextWaves = this.#normalizedWaves(value);
|
|
4072
|
+
if (!nextWaves) return false;
|
|
4073
|
+
this.#waves = nextWaves;
|
|
4074
|
+
if (!this.#waves.length) {
|
|
4075
|
+
this.#waves = [PropskitOscillator.#defaultWave()];
|
|
4076
|
+
}
|
|
4077
|
+
this.#activeWaveIndex = Math.min(this.#activeWaveIndex, this.#waves.length - 1);
|
|
4078
|
+
return true;
|
|
4079
|
+
}
|
|
3839
4080
|
|
|
4081
|
+
#normalizedWaves(value) {
|
|
4082
|
+
if (!value) return null;
|
|
3840
4083
|
let parsed = null;
|
|
3841
4084
|
if (typeof value === "string") {
|
|
3842
4085
|
try {
|
|
3843
4086
|
parsed = JSON.parse(value);
|
|
3844
4087
|
} catch {
|
|
3845
|
-
|
|
4088
|
+
return null;
|
|
3846
4089
|
}
|
|
3847
4090
|
} else if (typeof value === "object") {
|
|
3848
4091
|
parsed = value;
|
|
3849
4092
|
}
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
const nextWaves = Array.isArray(parsed)
|
|
4093
|
+
if (!parsed) return null;
|
|
4094
|
+
const waves = Array.isArray(parsed)
|
|
3854
4095
|
? parsed
|
|
3855
4096
|
: Array.isArray(parsed.waves)
|
|
3856
4097
|
? parsed.waves
|
|
3857
4098
|
: [parsed];
|
|
3858
|
-
|
|
3859
|
-
this.#waves = nextWaves.map((wave) => this.#normalizeWave(wave));
|
|
3860
|
-
if (!this.#waves.length) {
|
|
3861
|
-
this.#waves = [PropskitOscillator.#defaultWave()];
|
|
3862
|
-
}
|
|
3863
|
-
this.#activeWaveIndex = Math.min(this.#activeWaveIndex, this.#waves.length - 1);
|
|
3864
|
-
return true;
|
|
4099
|
+
return waves.map((wave) => this.#normalizeWave(wave));
|
|
3865
4100
|
}
|
|
3866
4101
|
|
|
3867
4102
|
#normalizeWave(state = {}) {
|
|
@@ -3962,6 +4197,7 @@ class PropskitOscillator extends HTMLElement {
|
|
|
3962
4197
|
this.#updateWaveform();
|
|
3963
4198
|
this.#setupEvents();
|
|
3964
4199
|
this.#startPlayhead();
|
|
4200
|
+
figLabConnectPropskitResetMenu(this);
|
|
3965
4201
|
}
|
|
3966
4202
|
|
|
3967
4203
|
#getInnerHTML() {
|
|
@@ -3998,9 +4234,7 @@ class PropskitOscillator extends HTMLElement {
|
|
|
3998
4234
|
<fig-tooltip text="Remove form">
|
|
3999
4235
|
<fig-button class="propskit-oscillator-remove-button" variant="ghost" icon data-wave-index="${index}" aria-label="Remove form"${removeDisabled}><fig-icon name="minus"></fig-icon></fig-button>
|
|
4000
4236
|
</fig-tooltip>
|
|
4001
|
-
|
|
4002
|
-
${this.#getWaveTypeSelectHTML("propskit-oscillator-add-type propskit-oscillator-add-type-button", "sine", disabled, index)}
|
|
4003
|
-
</fig-tooltip>
|
|
4237
|
+
${this.#getWaveTypeMenuHTML(disabled, index)}
|
|
4004
4238
|
</fig-header>
|
|
4005
4239
|
<div class="propskit-oscillator-fields" data-wave-index="${index}"${active}>
|
|
4006
4240
|
${this.#getNumberFieldHTML(index, "frequency", "Frequency", 0.1, 16, 0.1, "")}
|
|
@@ -4011,26 +4245,33 @@ class PropskitOscillator extends HTMLElement {
|
|
|
4011
4245
|
</fig-group>`;
|
|
4012
4246
|
}
|
|
4013
4247
|
|
|
4014
|
-
#
|
|
4015
|
-
const
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
<span slot="prepend">${PropskitOscillator.waveIcon(type.value, 24)}</span>
|
|
4248
|
+
#getWaveTypeMenuHTML(disabled, index) {
|
|
4249
|
+
const items = PropskitOscillator.TYPES.map((type) => {
|
|
4250
|
+
return `<fig-menu-item value="${type.value}">
|
|
4251
|
+
${PropskitOscillator.waveIcon(type.value, 24)}
|
|
4019
4252
|
<span>${type.name}</span>
|
|
4020
|
-
</fig-
|
|
4253
|
+
</fig-menu-item>`;
|
|
4021
4254
|
}).join("");
|
|
4022
|
-
const indexAttr =
|
|
4023
|
-
|
|
4024
|
-
|
|
4255
|
+
const indexAttr = ` data-wave-index="${PropskitOscillator.#escapeAttribute(String(index))}"`;
|
|
4256
|
+
return `<fig-menu class="propskit-oscillator-add-type" position="bottom right"${indexAttr}${disabled}>
|
|
4257
|
+
<fig-tooltip text="Add form">
|
|
4258
|
+
<fig-button class="propskit-oscillator-add-type-button" variant="ghost" icon fig-menu-trigger aria-label="Add form"><fig-icon name="plus"></fig-icon></fig-button>
|
|
4259
|
+
</fig-tooltip>
|
|
4260
|
+
${items}
|
|
4261
|
+
</fig-menu>`;
|
|
4025
4262
|
}
|
|
4026
4263
|
|
|
4027
4264
|
#getNumberFieldHTML(index, name, label, min, max, step, units) {
|
|
4028
4265
|
const disabled = this.#isDisabled() ? " disabled" : "";
|
|
4266
|
+
const typeAttrs =
|
|
4267
|
+
name === "amplitude" || name === "offset"
|
|
4268
|
+
? ' type="delta" default="0"'
|
|
4269
|
+
: "";
|
|
4029
4270
|
const unitsAttr = units
|
|
4030
4271
|
? ` units="${PropskitOscillator.#escapeAttribute(units)}"`
|
|
4031
4272
|
: "";
|
|
4032
4273
|
const wave = this.#waves[index] || PropskitOscillator.#defaultWave();
|
|
4033
|
-
return `<propskit-slider class="propskit-oscillator-field" label="${label}" direction="horizontal" name="${name}" data-wave-index="${index}" value="${this.#round(wave[name])}" min="${min}" max="${max}" step="${step}" precision="${this.#precision}" elastic="false"${unitsAttr}${disabled}></propskit-slider>`;
|
|
4274
|
+
return `<propskit-slider class="propskit-oscillator-field" label="${label}" direction="horizontal" name="${name}" data-wave-index="${index}" value="${this.#round(wave[name])}" min="${min}" max="${max}" step="${step}" precision="${this.#precision}" elastic="false"${typeAttrs}${unitsAttr}${disabled}></propskit-slider>`;
|
|
4034
4275
|
}
|
|
4035
4276
|
|
|
4036
4277
|
#cacheRefs() {
|
|
@@ -4297,7 +4538,7 @@ class PropskitOscillator extends HTMLElement {
|
|
|
4297
4538
|
if (this.#isDisabled()) return;
|
|
4298
4539
|
const insertAfter = this.#indexFromElement(control);
|
|
4299
4540
|
const insertIndex = insertAfter + 1;
|
|
4300
|
-
const type = this.#normalizeType(event.detail ?? control.value);
|
|
4541
|
+
const type = this.#normalizeType(event.detail?.value ?? control.value);
|
|
4301
4542
|
this.#waves.splice(insertIndex, 0, PropskitOscillator.#defaultWave(type));
|
|
4302
4543
|
this.#reindexExpandedWavesAfterInsert(insertIndex);
|
|
4303
4544
|
this.#activeWaveIndex = insertIndex;
|