@rogieking/figui3 6.14.1 → 6.16.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/README.md +102 -9
- package/components.css +0 -1
- package/dist/fig-editor.css +1 -1
- package/dist/fig-lab.css +1 -1
- package/dist/fig-lab.js +16 -16
- package/fig-lab.css +548 -59
- package/fig-lab.js +1119 -59
- package/package.json +1 -1
package/fig-lab.js
CHANGED
|
@@ -12,8 +12,1063 @@ function figLabBooleanAttribute(element, name) {
|
|
|
12
12
|
return element.hasAttribute(name) && element.getAttribute(name) !== "false";
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/* Field + Switch wrapper */
|
|
16
|
+
class PropskitSwitch extends HTMLElement {
|
|
17
|
+
#field = null;
|
|
18
|
+
#label = null;
|
|
19
|
+
#switch = null;
|
|
20
|
+
#hasCustomLabel = false;
|
|
21
|
+
#observer = null;
|
|
22
|
+
#managedSwitchAttrs = new Set();
|
|
23
|
+
#boundHandleInput = null;
|
|
24
|
+
#boundHandleChange = null;
|
|
25
|
+
#boundHandleClick = this.#handleClick.bind(this);
|
|
26
|
+
|
|
27
|
+
static get observedAttributes() {
|
|
28
|
+
return ["label", "direction"];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
connectedCallback() {
|
|
32
|
+
if (!this.#field) this.#initialize();
|
|
33
|
+
this.#syncField();
|
|
34
|
+
this.#syncSwitchAttributes();
|
|
35
|
+
this.#bindSwitchEvents();
|
|
36
|
+
this.removeEventListener("click", this.#boundHandleClick);
|
|
37
|
+
this.addEventListener("click", this.#boundHandleClick);
|
|
38
|
+
|
|
39
|
+
if (!this.#observer) {
|
|
40
|
+
this.#observer = new MutationObserver((mutations) => {
|
|
41
|
+
let syncField = false;
|
|
42
|
+
let syncSwitch = false;
|
|
43
|
+
|
|
44
|
+
for (const mutation of mutations) {
|
|
45
|
+
if (mutation.type !== "attributes") continue;
|
|
46
|
+
if (
|
|
47
|
+
mutation.attributeName === "label" ||
|
|
48
|
+
mutation.attributeName === "direction"
|
|
49
|
+
) {
|
|
50
|
+
syncField = true;
|
|
51
|
+
} else {
|
|
52
|
+
syncSwitch = true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (syncField) this.#syncField();
|
|
57
|
+
if (syncSwitch) this.#syncSwitchAttributes();
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
this.#observer.observe(this, { attributes: true });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
disconnectedCallback() {
|
|
65
|
+
this.#observer?.disconnect();
|
|
66
|
+
this.#unbindSwitchEvents();
|
|
67
|
+
this.removeEventListener("click", this.#boundHandleClick);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
71
|
+
if (oldValue === newValue || !this.#field) return;
|
|
72
|
+
if (name === "label" || name === "direction") this.#syncField();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
#initialize() {
|
|
76
|
+
const initialChildren = Array.from(this.childNodes).filter(
|
|
77
|
+
(node) =>
|
|
78
|
+
node.nodeType !== Node.TEXT_NODE || Boolean(node.textContent?.trim()),
|
|
79
|
+
);
|
|
80
|
+
const customLabel = initialChildren.find(
|
|
81
|
+
(node) => node.nodeType === Node.ELEMENT_NODE && node.matches("label"),
|
|
82
|
+
);
|
|
83
|
+
const field = document.createElement("fig-field");
|
|
84
|
+
const label = customLabel || document.createElement("label");
|
|
85
|
+
const switchControl = document.createElement("fig-segmented-control");
|
|
86
|
+
const offSegment = document.createElement("fig-segment");
|
|
87
|
+
const onSegment = document.createElement("fig-segment");
|
|
88
|
+
switchControl.setAttribute("sizing", "equal");
|
|
89
|
+
offSegment.setAttribute("value", "off");
|
|
90
|
+
offSegment.textContent = "Off";
|
|
91
|
+
onSegment.setAttribute("value", "on");
|
|
92
|
+
onSegment.textContent = "On";
|
|
93
|
+
switchControl.append(offSegment, onSegment);
|
|
94
|
+
|
|
95
|
+
field.append(label, switchControl);
|
|
96
|
+
this.#field = field;
|
|
97
|
+
this.#label = label;
|
|
98
|
+
this.#switch = switchControl;
|
|
99
|
+
this.#hasCustomLabel = Boolean(customLabel);
|
|
100
|
+
this.replaceChildren(field);
|
|
101
|
+
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
#syncField() {
|
|
105
|
+
if (!this.#field || !this.#label) return;
|
|
106
|
+
const hasLabelAttr = this.hasAttribute("label");
|
|
107
|
+
const rawLabel = this.getAttribute("label");
|
|
108
|
+
const isBlankLabel = hasLabelAttr && (rawLabel ?? "").trim() === "";
|
|
109
|
+
|
|
110
|
+
if (isBlankLabel) {
|
|
111
|
+
this.#label.remove();
|
|
112
|
+
} else {
|
|
113
|
+
if (!this.#hasCustomLabel) {
|
|
114
|
+
this.#label.textContent = hasLabelAttr ? (rawLabel ?? "") : "Label";
|
|
115
|
+
}
|
|
116
|
+
if (this.#label.parentElement !== this.#field) {
|
|
117
|
+
this.#field.prepend(this.#label);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
this.#field.setAttribute(
|
|
122
|
+
"direction",
|
|
123
|
+
this.getAttribute("direction") || "horizontal",
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
#getForwardedSwitchAttrNames() {
|
|
128
|
+
const reserved = new Set([
|
|
129
|
+
"label",
|
|
130
|
+
"direction",
|
|
131
|
+
"oninput",
|
|
132
|
+
"onchange",
|
|
133
|
+
"class",
|
|
134
|
+
"style",
|
|
135
|
+
"id",
|
|
136
|
+
"size",
|
|
137
|
+
"checked",
|
|
138
|
+
"value",
|
|
139
|
+
]);
|
|
140
|
+
return this.getAttributeNames().filter(
|
|
141
|
+
(name) => !reserved.has(name) && !name.startsWith("data-"),
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
#syncSwitchAttributes() {
|
|
146
|
+
if (!this.#switch) return;
|
|
147
|
+
const switchAttrs = this.#getForwardedSwitchAttrNames();
|
|
148
|
+
const nextManaged = new Set(switchAttrs);
|
|
149
|
+
|
|
150
|
+
for (const attrName of this.#managedSwitchAttrs) {
|
|
151
|
+
if (!nextManaged.has(attrName)) this.#switch.removeAttribute(attrName);
|
|
152
|
+
}
|
|
153
|
+
for (const attrName of switchAttrs) {
|
|
154
|
+
this.#switch.setAttribute(attrName, this.getAttribute(attrName) ?? "");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
this.#switch.setAttribute(
|
|
158
|
+
"value",
|
|
159
|
+
figLabBooleanAttribute(this, "checked") ? "on" : "off",
|
|
160
|
+
);
|
|
161
|
+
this.#managedSwitchAttrs = nextManaged;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
#bindSwitchEvents() {
|
|
165
|
+
if (!this.#switch) return;
|
|
166
|
+
this.#boundHandleInput ??= this.#forwardSwitchEvent.bind(this, "input");
|
|
167
|
+
this.#boundHandleChange ??= this.#forwardSwitchEvent.bind(this, "change");
|
|
168
|
+
this.#switch.addEventListener("input", this.#boundHandleInput);
|
|
169
|
+
this.#switch.addEventListener("change", this.#boundHandleChange);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
#unbindSwitchEvents() {
|
|
173
|
+
if (!this.#switch) return;
|
|
174
|
+
if (this.#boundHandleInput) {
|
|
175
|
+
this.#switch.removeEventListener("input", this.#boundHandleInput);
|
|
176
|
+
}
|
|
177
|
+
if (this.#boundHandleChange) {
|
|
178
|
+
this.#switch.removeEventListener("change", this.#boundHandleChange);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
#forwardSwitchEvent(type, event) {
|
|
183
|
+
event.stopImmediatePropagation();
|
|
184
|
+
const checked = this.#switch?.value === "on";
|
|
185
|
+
this.toggleAttribute("checked", checked);
|
|
186
|
+
const detail = {
|
|
187
|
+
checked,
|
|
188
|
+
value: this.getAttribute("value") ?? "",
|
|
189
|
+
};
|
|
190
|
+
this.dispatchEvent(
|
|
191
|
+
new CustomEvent(type, {
|
|
192
|
+
detail,
|
|
193
|
+
bubbles: true,
|
|
194
|
+
cancelable: true,
|
|
195
|
+
composed: true,
|
|
196
|
+
}),
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
#handleClick(event) {
|
|
201
|
+
if (event.target instanceof Element && event.target.closest("fig-segmented-control")) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const value = this.#switch?.value === "on" ? "off" : "on";
|
|
205
|
+
this.#switch?.querySelector(`fig-segment[value="${value}"]`)?.click();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
get checked() {
|
|
209
|
+
return this.#switch
|
|
210
|
+
? this.#switch.value === "on"
|
|
211
|
+
: figLabBooleanAttribute(this, "checked");
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
set checked(nextChecked) {
|
|
215
|
+
this.toggleAttribute("checked", Boolean(nextChecked));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
get value() {
|
|
219
|
+
return this.#switch?.value ?? this.getAttribute("value") ?? "";
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
set value(nextValue) {
|
|
223
|
+
this.setAttribute("value", nextValue ?? "");
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
focus(options) {
|
|
227
|
+
const selected =
|
|
228
|
+
this.#switch?.querySelector("fig-segment[selected]") ||
|
|
229
|
+
this.#switch?.querySelector("fig-segment");
|
|
230
|
+
selected?.focus(options);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
customElements.define("propskit-switch", PropskitSwitch);
|
|
234
|
+
|
|
235
|
+
/* Field + Color wrapper */
|
|
236
|
+
class PropskitColor extends HTMLElement {
|
|
237
|
+
#field = null;
|
|
238
|
+
#label = null;
|
|
239
|
+
#input = null;
|
|
240
|
+
#hasCustomLabel = false;
|
|
241
|
+
#observer = null;
|
|
242
|
+
#managedInputAttrs = new Set();
|
|
243
|
+
#boundHandleInput = null;
|
|
244
|
+
#boundHandleChange = null;
|
|
245
|
+
#boundHandleClick = this.#handleClick.bind(this);
|
|
246
|
+
|
|
247
|
+
static get observedAttributes() {
|
|
248
|
+
return ["label", "direction", "aria-label"];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
connectedCallback() {
|
|
252
|
+
if (!this.#field) this.#initialize();
|
|
253
|
+
this.#syncField();
|
|
254
|
+
this.#syncInputAttributes();
|
|
255
|
+
this.#bindInputEvents();
|
|
256
|
+
this.removeEventListener("click", this.#boundHandleClick);
|
|
257
|
+
this.addEventListener("click", this.#boundHandleClick);
|
|
258
|
+
|
|
259
|
+
if (!this.#observer) {
|
|
260
|
+
this.#observer = new MutationObserver((mutations) => {
|
|
261
|
+
let syncField = false;
|
|
262
|
+
let syncInput = false;
|
|
263
|
+
|
|
264
|
+
for (const mutation of mutations) {
|
|
265
|
+
if (mutation.type !== "attributes") continue;
|
|
266
|
+
if (
|
|
267
|
+
mutation.attributeName === "label" ||
|
|
268
|
+
mutation.attributeName === "direction" ||
|
|
269
|
+
mutation.attributeName === "aria-label"
|
|
270
|
+
) {
|
|
271
|
+
syncField = true;
|
|
272
|
+
} else {
|
|
273
|
+
syncInput = true;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (syncField) this.#syncField();
|
|
278
|
+
if (syncInput) this.#syncInputAttributes();
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
this.#observer.observe(this, { attributes: true });
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
disconnectedCallback() {
|
|
286
|
+
this.#observer?.disconnect();
|
|
287
|
+
this.#unbindInputEvents();
|
|
288
|
+
this.removeEventListener("click", this.#boundHandleClick);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
292
|
+
if (oldValue === newValue || !this.#field) return;
|
|
293
|
+
if (name === "label" || name === "direction" || name === "aria-label") {
|
|
294
|
+
this.#syncField();
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
#initialize() {
|
|
299
|
+
const initialChildren = Array.from(this.childNodes).filter(
|
|
300
|
+
(node) =>
|
|
301
|
+
node.nodeType !== Node.TEXT_NODE || Boolean(node.textContent?.trim()),
|
|
302
|
+
);
|
|
303
|
+
const customLabel = initialChildren.find(
|
|
304
|
+
(node) => node.nodeType === Node.ELEMENT_NODE && node.matches("label"),
|
|
305
|
+
);
|
|
306
|
+
const field = document.createElement("fig-field");
|
|
307
|
+
const label = customLabel || document.createElement("label");
|
|
308
|
+
const input = document.createElement("fig-input-color");
|
|
309
|
+
|
|
310
|
+
for (const node of initialChildren) {
|
|
311
|
+
if (node !== customLabel) input.appendChild(node);
|
|
312
|
+
}
|
|
313
|
+
field.append(label, input);
|
|
314
|
+
this.#field = field;
|
|
315
|
+
this.#label = label;
|
|
316
|
+
this.#input = input;
|
|
317
|
+
this.#hasCustomLabel = Boolean(customLabel);
|
|
318
|
+
this.replaceChildren(field);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
#syncField() {
|
|
322
|
+
if (!this.#field || !this.#label || !this.#input) return;
|
|
323
|
+
const hasLabelAttr = this.hasAttribute("label");
|
|
324
|
+
const rawLabel = this.getAttribute("label");
|
|
325
|
+
const isBlankLabel = hasLabelAttr && (rawLabel ?? "").trim() === "";
|
|
326
|
+
|
|
327
|
+
if (isBlankLabel) {
|
|
328
|
+
this.#label.remove();
|
|
329
|
+
} else {
|
|
330
|
+
if (!this.#hasCustomLabel) {
|
|
331
|
+
this.#label.textContent = hasLabelAttr ? (rawLabel ?? "") : "Label";
|
|
332
|
+
}
|
|
333
|
+
if (this.#label.parentElement !== this.#field) {
|
|
334
|
+
this.#field.prepend(this.#label);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
this.#field.setAttribute(
|
|
339
|
+
"direction",
|
|
340
|
+
this.getAttribute("direction") || "horizontal",
|
|
341
|
+
);
|
|
342
|
+
this.#input.setAttribute(
|
|
343
|
+
"aria-label",
|
|
344
|
+
this.getAttribute("aria-label") ||
|
|
345
|
+
this.#label.textContent?.trim() ||
|
|
346
|
+
"Color",
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
#getForwardedInputAttrNames() {
|
|
351
|
+
const reserved = new Set([
|
|
352
|
+
"label",
|
|
353
|
+
"direction",
|
|
354
|
+
"oninput",
|
|
355
|
+
"onchange",
|
|
356
|
+
"class",
|
|
357
|
+
"style",
|
|
358
|
+
"id",
|
|
359
|
+
"size",
|
|
360
|
+
"aria-label",
|
|
361
|
+
"text",
|
|
362
|
+
]);
|
|
363
|
+
return this.getAttributeNames().filter(
|
|
364
|
+
(name) => !reserved.has(name) && !name.startsWith("data-"),
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
#syncInputAttributes() {
|
|
369
|
+
if (!this.#input) return;
|
|
370
|
+
const inputAttrs = this.#getForwardedInputAttrNames();
|
|
371
|
+
const nextManaged = new Set(inputAttrs);
|
|
372
|
+
|
|
373
|
+
for (const attrName of this.#managedInputAttrs) {
|
|
374
|
+
if (!nextManaged.has(attrName)) this.#input.removeAttribute(attrName);
|
|
375
|
+
}
|
|
376
|
+
for (const attrName of inputAttrs) {
|
|
377
|
+
this.#input.setAttribute(attrName, this.getAttribute(attrName) ?? "");
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
this.#input.setAttribute("text", "true");
|
|
381
|
+
this.#managedInputAttrs = nextManaged;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
#bindInputEvents() {
|
|
385
|
+
if (!this.#input) return;
|
|
386
|
+
this.#boundHandleInput ??= this.#forwardInputEvent.bind(this, "input");
|
|
387
|
+
this.#boundHandleChange ??= this.#forwardInputEvent.bind(this, "change");
|
|
388
|
+
this.#input.addEventListener("input", this.#boundHandleInput);
|
|
389
|
+
this.#input.addEventListener("change", this.#boundHandleChange);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
#unbindInputEvents() {
|
|
393
|
+
if (!this.#input) return;
|
|
394
|
+
if (this.#boundHandleInput) {
|
|
395
|
+
this.#input.removeEventListener("input", this.#boundHandleInput);
|
|
396
|
+
}
|
|
397
|
+
if (this.#boundHandleChange) {
|
|
398
|
+
this.#input.removeEventListener("change", this.#boundHandleChange);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
#forwardInputEvent(type, event) {
|
|
403
|
+
event.stopImmediatePropagation();
|
|
404
|
+
const value = this.#input?.getAttribute("value") ?? "";
|
|
405
|
+
this.setAttribute("value", value);
|
|
406
|
+
const detail =
|
|
407
|
+
event instanceof CustomEvent && event.detail !== undefined
|
|
408
|
+
? event.detail
|
|
409
|
+
: value;
|
|
410
|
+
this.dispatchEvent(
|
|
411
|
+
new CustomEvent(type, {
|
|
412
|
+
detail,
|
|
413
|
+
bubbles: true,
|
|
414
|
+
cancelable: true,
|
|
415
|
+
composed: true,
|
|
416
|
+
}),
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
#handleClick(event) {
|
|
421
|
+
if (event.target instanceof Element && event.target.closest("fig-input-color")) {
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
this.focus();
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
get value() {
|
|
428
|
+
return this.#input?.getAttribute("value") ?? this.getAttribute("value") ?? "";
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
set value(nextValue) {
|
|
432
|
+
if (nextValue === null || nextValue === undefined || nextValue === "") {
|
|
433
|
+
this.removeAttribute("value");
|
|
434
|
+
} else {
|
|
435
|
+
this.setAttribute("value", String(nextValue));
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
focus(options) {
|
|
440
|
+
this.#input?.querySelector("input:not([tabindex='-1'])")?.focus(options);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
customElements.define("propskit-color", PropskitColor);
|
|
444
|
+
|
|
445
|
+
/* Field + Select wrapper */
|
|
446
|
+
class PropskitSelect extends HTMLElement {
|
|
447
|
+
#field = null;
|
|
448
|
+
#label = null;
|
|
449
|
+
#select = null;
|
|
450
|
+
#hasCustomLabel = false;
|
|
451
|
+
#observer = null;
|
|
452
|
+
#managedSelectAttrs = new Set();
|
|
453
|
+
#boundHandleInput = null;
|
|
454
|
+
#boundHandleChange = null;
|
|
455
|
+
#boundHandleClick = this.#handleClick.bind(this);
|
|
456
|
+
|
|
457
|
+
static get observedAttributes() {
|
|
458
|
+
return ["label", "direction", "aria-label"];
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
connectedCallback() {
|
|
462
|
+
if (!this.#field) this.#initialize();
|
|
463
|
+
this.#syncField();
|
|
464
|
+
this.#syncSelectAttributes();
|
|
465
|
+
this.#bindSelectEvents();
|
|
466
|
+
this.removeEventListener("click", this.#boundHandleClick);
|
|
467
|
+
this.addEventListener("click", this.#boundHandleClick);
|
|
468
|
+
|
|
469
|
+
if (!this.#observer) {
|
|
470
|
+
this.#observer = new MutationObserver((mutations) => {
|
|
471
|
+
let syncField = false;
|
|
472
|
+
let syncSelect = false;
|
|
473
|
+
|
|
474
|
+
for (const mutation of mutations) {
|
|
475
|
+
if (mutation.type !== "attributes") continue;
|
|
476
|
+
if (
|
|
477
|
+
mutation.attributeName === "label" ||
|
|
478
|
+
mutation.attributeName === "direction" ||
|
|
479
|
+
mutation.attributeName === "aria-label"
|
|
480
|
+
) {
|
|
481
|
+
syncField = true;
|
|
482
|
+
} else {
|
|
483
|
+
syncSelect = true;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (syncField) this.#syncField();
|
|
488
|
+
if (syncSelect) this.#syncSelectAttributes();
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
this.#observer.observe(this, { attributes: true });
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
disconnectedCallback() {
|
|
496
|
+
this.#observer?.disconnect();
|
|
497
|
+
this.#unbindSelectEvents();
|
|
498
|
+
this.removeEventListener("click", this.#boundHandleClick);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
502
|
+
if (oldValue === newValue || !this.#field) return;
|
|
503
|
+
if (name === "label" || name === "direction" || name === "aria-label") {
|
|
504
|
+
this.#syncField();
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
#initialize() {
|
|
509
|
+
const initialChildren = Array.from(this.childNodes).filter(
|
|
510
|
+
(node) =>
|
|
511
|
+
node.nodeType !== Node.TEXT_NODE || Boolean(node.textContent?.trim()),
|
|
512
|
+
);
|
|
513
|
+
const customLabel = initialChildren.find(
|
|
514
|
+
(node) => node.nodeType === Node.ELEMENT_NODE && node.matches("label"),
|
|
515
|
+
);
|
|
516
|
+
const field = document.createElement("fig-field");
|
|
517
|
+
const label = customLabel || document.createElement("label");
|
|
518
|
+
const select = document.createElement("fig-dropdown");
|
|
519
|
+
|
|
520
|
+
for (const node of initialChildren) {
|
|
521
|
+
if (node !== customLabel) select.appendChild(node);
|
|
522
|
+
}
|
|
523
|
+
field.append(label, select);
|
|
524
|
+
this.#field = field;
|
|
525
|
+
this.#label = label;
|
|
526
|
+
this.#select = select;
|
|
527
|
+
this.#hasCustomLabel = Boolean(customLabel);
|
|
528
|
+
this.replaceChildren(field);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
#syncField() {
|
|
532
|
+
if (!this.#field || !this.#label || !this.#select) return;
|
|
533
|
+
const hasLabelAttr = this.hasAttribute("label");
|
|
534
|
+
const rawLabel = this.getAttribute("label");
|
|
535
|
+
const isBlankLabel = hasLabelAttr && (rawLabel ?? "").trim() === "";
|
|
536
|
+
|
|
537
|
+
if (isBlankLabel) {
|
|
538
|
+
this.#label.remove();
|
|
539
|
+
} else {
|
|
540
|
+
if (!this.#hasCustomLabel) {
|
|
541
|
+
this.#label.textContent = hasLabelAttr ? (rawLabel ?? "") : "Label";
|
|
542
|
+
}
|
|
543
|
+
if (this.#label.parentElement !== this.#field) {
|
|
544
|
+
this.#field.prepend(this.#label);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
this.#field.setAttribute(
|
|
549
|
+
"direction",
|
|
550
|
+
this.getAttribute("direction") || "horizontal",
|
|
551
|
+
);
|
|
552
|
+
this.#select.setAttribute(
|
|
553
|
+
"label",
|
|
554
|
+
this.getAttribute("aria-label") ||
|
|
555
|
+
this.#label.textContent?.trim() ||
|
|
556
|
+
"Select",
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
#getForwardedSelectAttrNames() {
|
|
561
|
+
const reserved = new Set([
|
|
562
|
+
"label",
|
|
563
|
+
"direction",
|
|
564
|
+
"oninput",
|
|
565
|
+
"onchange",
|
|
566
|
+
"class",
|
|
567
|
+
"style",
|
|
568
|
+
"id",
|
|
569
|
+
"size",
|
|
570
|
+
"aria-label",
|
|
571
|
+
]);
|
|
572
|
+
return this.getAttributeNames().filter(
|
|
573
|
+
(name) => !reserved.has(name) && !name.startsWith("data-"),
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
#syncSelectAttributes() {
|
|
578
|
+
if (!this.#select) return;
|
|
579
|
+
const selectAttrs = this.#getForwardedSelectAttrNames();
|
|
580
|
+
const nextManaged = new Set(selectAttrs);
|
|
581
|
+
|
|
582
|
+
for (const attrName of this.#managedSelectAttrs) {
|
|
583
|
+
if (!nextManaged.has(attrName)) this.#select.removeAttribute(attrName);
|
|
584
|
+
}
|
|
585
|
+
for (const attrName of selectAttrs) {
|
|
586
|
+
this.#select.setAttribute(attrName, this.getAttribute(attrName) ?? "");
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
this.#managedSelectAttrs = nextManaged;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
#bindSelectEvents() {
|
|
593
|
+
if (!this.#select) return;
|
|
594
|
+
this.#boundHandleInput ??= this.#forwardSelectEvent.bind(this, "input");
|
|
595
|
+
this.#boundHandleChange ??= this.#forwardSelectEvent.bind(this, "change");
|
|
596
|
+
this.#select.addEventListener("input", this.#boundHandleInput);
|
|
597
|
+
this.#select.addEventListener("change", this.#boundHandleChange);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
#unbindSelectEvents() {
|
|
601
|
+
if (!this.#select) return;
|
|
602
|
+
if (this.#boundHandleInput) {
|
|
603
|
+
this.#select.removeEventListener("input", this.#boundHandleInput);
|
|
604
|
+
}
|
|
605
|
+
if (this.#boundHandleChange) {
|
|
606
|
+
this.#select.removeEventListener("change", this.#boundHandleChange);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
#forwardSelectEvent(type, event) {
|
|
611
|
+
event.stopImmediatePropagation();
|
|
612
|
+
const value = this.#select?.value ?? "";
|
|
613
|
+
this.setAttribute("value", String(value));
|
|
614
|
+
const detail =
|
|
615
|
+
event instanceof CustomEvent && event.detail !== undefined
|
|
616
|
+
? event.detail
|
|
617
|
+
: value;
|
|
618
|
+
this.dispatchEvent(
|
|
619
|
+
new CustomEvent(type, {
|
|
620
|
+
detail,
|
|
621
|
+
bubbles: true,
|
|
622
|
+
cancelable: true,
|
|
623
|
+
composed: true,
|
|
624
|
+
}),
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
#handleClick(event) {
|
|
629
|
+
if (event.target instanceof Element && event.target.closest("fig-dropdown")) {
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
const select = this.#select?.querySelector("select");
|
|
633
|
+
select?.focus();
|
|
634
|
+
if (typeof select?.showPicker === "function") {
|
|
635
|
+
try {
|
|
636
|
+
select.showPicker();
|
|
637
|
+
} catch {
|
|
638
|
+
// Browser may reject showPicker when no user activation is available.
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
get value() {
|
|
644
|
+
return this.#select?.value ?? this.getAttribute("value") ?? "";
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
set value(nextValue) {
|
|
648
|
+
if (nextValue === null || nextValue === undefined) {
|
|
649
|
+
this.removeAttribute("value");
|
|
650
|
+
} else {
|
|
651
|
+
this.setAttribute("value", String(nextValue));
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
focus(options) {
|
|
656
|
+
this.#select?.focus(options);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
customElements.define("propskit-select", PropskitSelect);
|
|
660
|
+
|
|
661
|
+
/* Field + Text wrapper */
|
|
662
|
+
class PropskitText extends HTMLElement {
|
|
663
|
+
#field = null;
|
|
664
|
+
#label = null;
|
|
665
|
+
#input = null;
|
|
666
|
+
#hasCustomLabel = false;
|
|
667
|
+
#observer = null;
|
|
668
|
+
#managedInputAttrs = new Set();
|
|
669
|
+
#boundHandleInput = null;
|
|
670
|
+
#boundHandleChange = null;
|
|
671
|
+
#boundHandleClick = this.#handleClick.bind(this);
|
|
672
|
+
|
|
673
|
+
static get observedAttributes() {
|
|
674
|
+
return ["label", "direction", "aria-label"];
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
connectedCallback() {
|
|
678
|
+
if (!this.#field) this.#initialize();
|
|
679
|
+
this.#syncField();
|
|
680
|
+
this.#syncInputAttributes();
|
|
681
|
+
this.#bindInputEvents();
|
|
682
|
+
this.removeEventListener("click", this.#boundHandleClick);
|
|
683
|
+
this.addEventListener("click", this.#boundHandleClick);
|
|
684
|
+
|
|
685
|
+
if (!this.#observer) {
|
|
686
|
+
this.#observer = new MutationObserver((mutations) => {
|
|
687
|
+
let syncField = false;
|
|
688
|
+
let syncInput = false;
|
|
689
|
+
|
|
690
|
+
for (const mutation of mutations) {
|
|
691
|
+
if (mutation.type !== "attributes") continue;
|
|
692
|
+
if (
|
|
693
|
+
mutation.attributeName === "label" ||
|
|
694
|
+
mutation.attributeName === "direction" ||
|
|
695
|
+
mutation.attributeName === "aria-label"
|
|
696
|
+
) {
|
|
697
|
+
syncField = true;
|
|
698
|
+
} else {
|
|
699
|
+
syncInput = true;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
if (syncField) this.#syncField();
|
|
704
|
+
if (syncInput) this.#syncInputAttributes();
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
this.#observer.observe(this, { attributes: true });
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
disconnectedCallback() {
|
|
712
|
+
this.#observer?.disconnect();
|
|
713
|
+
this.#unbindInputEvents();
|
|
714
|
+
this.removeEventListener("click", this.#boundHandleClick);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
718
|
+
if (oldValue === newValue || !this.#field) return;
|
|
719
|
+
if (name === "label" || name === "direction" || name === "aria-label") {
|
|
720
|
+
this.#syncField();
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
#initialize() {
|
|
725
|
+
const initialChildren = Array.from(this.childNodes).filter(
|
|
726
|
+
(node) =>
|
|
727
|
+
node.nodeType !== Node.TEXT_NODE || Boolean(node.textContent?.trim()),
|
|
728
|
+
);
|
|
729
|
+
const customLabel = initialChildren.find(
|
|
730
|
+
(node) => node.nodeType === Node.ELEMENT_NODE && node.matches("label"),
|
|
731
|
+
);
|
|
732
|
+
const field = document.createElement("fig-field");
|
|
733
|
+
const label = customLabel || document.createElement("label");
|
|
734
|
+
const input = document.createElement("fig-input-text");
|
|
735
|
+
|
|
736
|
+
for (const node of initialChildren) {
|
|
737
|
+
if (node !== customLabel) input.appendChild(node);
|
|
738
|
+
}
|
|
739
|
+
field.append(label, input);
|
|
740
|
+
this.#field = field;
|
|
741
|
+
this.#label = label;
|
|
742
|
+
this.#input = input;
|
|
743
|
+
this.#hasCustomLabel = Boolean(customLabel);
|
|
744
|
+
this.replaceChildren(field);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
#syncField() {
|
|
748
|
+
if (!this.#field || !this.#label || !this.#input) return;
|
|
749
|
+
const hasLabelAttr = this.hasAttribute("label");
|
|
750
|
+
const rawLabel = this.getAttribute("label");
|
|
751
|
+
const isBlankLabel = hasLabelAttr && (rawLabel ?? "").trim() === "";
|
|
752
|
+
|
|
753
|
+
if (isBlankLabel) {
|
|
754
|
+
this.#label.remove();
|
|
755
|
+
} else {
|
|
756
|
+
if (!this.#hasCustomLabel) {
|
|
757
|
+
this.#label.textContent = hasLabelAttr ? (rawLabel ?? "") : "Label";
|
|
758
|
+
}
|
|
759
|
+
if (this.#label.parentElement !== this.#field) {
|
|
760
|
+
this.#field.prepend(this.#label);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
this.#field.setAttribute(
|
|
765
|
+
"direction",
|
|
766
|
+
this.getAttribute("direction") || "horizontal",
|
|
767
|
+
);
|
|
768
|
+
this.#input.setAttribute(
|
|
769
|
+
"aria-label",
|
|
770
|
+
this.getAttribute("aria-label") ||
|
|
771
|
+
this.#label.textContent?.trim() ||
|
|
772
|
+
"Text",
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
#getForwardedInputAttrNames() {
|
|
777
|
+
const reserved = new Set([
|
|
778
|
+
"label",
|
|
779
|
+
"direction",
|
|
780
|
+
"oninput",
|
|
781
|
+
"onchange",
|
|
782
|
+
"class",
|
|
783
|
+
"style",
|
|
784
|
+
"id",
|
|
785
|
+
"size",
|
|
786
|
+
"aria-label",
|
|
787
|
+
"multiline",
|
|
788
|
+
"resizable",
|
|
789
|
+
]);
|
|
790
|
+
return this.getAttributeNames().filter(
|
|
791
|
+
(name) => !reserved.has(name) && !name.startsWith("data-"),
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
#syncInputAttributes() {
|
|
796
|
+
if (!this.#input) return;
|
|
797
|
+
const inputAttrs = this.#getForwardedInputAttrNames();
|
|
798
|
+
const nextManaged = new Set(inputAttrs);
|
|
799
|
+
|
|
800
|
+
for (const attrName of this.#managedInputAttrs) {
|
|
801
|
+
if (!nextManaged.has(attrName)) this.#input.removeAttribute(attrName);
|
|
802
|
+
}
|
|
803
|
+
for (const attrName of inputAttrs) {
|
|
804
|
+
this.#input.setAttribute(attrName, this.getAttribute(attrName) ?? "");
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
this.#managedInputAttrs = nextManaged;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
#bindInputEvents() {
|
|
811
|
+
if (!this.#input) return;
|
|
812
|
+
this.#boundHandleInput ??= this.#forwardInputEvent.bind(this, "input");
|
|
813
|
+
this.#boundHandleChange ??= this.#forwardInputEvent.bind(this, "change");
|
|
814
|
+
this.#input.addEventListener("input", this.#boundHandleInput);
|
|
815
|
+
this.#input.addEventListener("change", this.#boundHandleChange);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
#unbindInputEvents() {
|
|
819
|
+
if (!this.#input) return;
|
|
820
|
+
if (this.#boundHandleInput) {
|
|
821
|
+
this.#input.removeEventListener("input", this.#boundHandleInput);
|
|
822
|
+
}
|
|
823
|
+
if (this.#boundHandleChange) {
|
|
824
|
+
this.#input.removeEventListener("change", this.#boundHandleChange);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
#forwardInputEvent(type, event) {
|
|
829
|
+
event.stopImmediatePropagation();
|
|
830
|
+
const value = this.#input?.value ?? "";
|
|
831
|
+
this.setAttribute("value", String(value));
|
|
832
|
+
const detail =
|
|
833
|
+
event instanceof CustomEvent && event.detail !== undefined
|
|
834
|
+
? event.detail
|
|
835
|
+
: value;
|
|
836
|
+
this.dispatchEvent(
|
|
837
|
+
new CustomEvent(type, {
|
|
838
|
+
detail,
|
|
839
|
+
bubbles: true,
|
|
840
|
+
cancelable: true,
|
|
841
|
+
composed: true,
|
|
842
|
+
}),
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
#handleClick(event) {
|
|
847
|
+
if (event.target instanceof Element && event.target.closest("fig-input-text")) {
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
this.focus();
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
get value() {
|
|
854
|
+
return this.#input?.value ?? this.getAttribute("value") ?? "";
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
set value(nextValue) {
|
|
858
|
+
if (nextValue === null || nextValue === undefined) {
|
|
859
|
+
this.removeAttribute("value");
|
|
860
|
+
} else {
|
|
861
|
+
this.setAttribute("value", String(nextValue));
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
focus(options) {
|
|
866
|
+
this.#input?.focus(options);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
customElements.define("propskit-text", PropskitText);
|
|
870
|
+
|
|
871
|
+
/* Field + Number wrapper */
|
|
872
|
+
class PropskitNumber extends HTMLElement {
|
|
873
|
+
#field = null;
|
|
874
|
+
#label = null;
|
|
875
|
+
#input = null;
|
|
876
|
+
#hasCustomLabel = false;
|
|
877
|
+
#observer = null;
|
|
878
|
+
#managedInputAttrs = new Set();
|
|
879
|
+
#boundHandleInput = null;
|
|
880
|
+
#boundHandleChange = null;
|
|
881
|
+
#boundHandleClick = this.#handleClick.bind(this);
|
|
882
|
+
|
|
883
|
+
static get observedAttributes() {
|
|
884
|
+
return ["label", "direction"];
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
connectedCallback() {
|
|
888
|
+
if (!this.#field) this.#initialize();
|
|
889
|
+
this.#syncField();
|
|
890
|
+
this.#syncInputAttributes();
|
|
891
|
+
this.#bindInputEvents();
|
|
892
|
+
this.removeEventListener("click", this.#boundHandleClick);
|
|
893
|
+
this.addEventListener("click", this.#boundHandleClick);
|
|
894
|
+
|
|
895
|
+
if (!this.#observer) {
|
|
896
|
+
this.#observer = new MutationObserver((mutations) => {
|
|
897
|
+
let syncField = false;
|
|
898
|
+
let syncInput = false;
|
|
899
|
+
|
|
900
|
+
for (const mutation of mutations) {
|
|
901
|
+
if (mutation.type !== "attributes") continue;
|
|
902
|
+
if (
|
|
903
|
+
mutation.attributeName === "label" ||
|
|
904
|
+
mutation.attributeName === "direction"
|
|
905
|
+
) {
|
|
906
|
+
syncField = true;
|
|
907
|
+
} else {
|
|
908
|
+
syncInput = true;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
if (syncField) this.#syncField();
|
|
913
|
+
if (syncInput) this.#syncInputAttributes();
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
this.#observer.observe(this, { attributes: true });
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
disconnectedCallback() {
|
|
921
|
+
this.#observer?.disconnect();
|
|
922
|
+
this.#unbindInputEvents();
|
|
923
|
+
this.removeEventListener("click", this.#boundHandleClick);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
927
|
+
if (oldValue === newValue || !this.#field) return;
|
|
928
|
+
if (name === "label" || name === "direction") this.#syncField();
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
#initialize() {
|
|
932
|
+
const initialChildren = Array.from(this.childNodes).filter(
|
|
933
|
+
(node) =>
|
|
934
|
+
node.nodeType !== Node.TEXT_NODE || Boolean(node.textContent?.trim()),
|
|
935
|
+
);
|
|
936
|
+
const customLabel = initialChildren.find(
|
|
937
|
+
(node) => node.nodeType === Node.ELEMENT_NODE && node.matches("label"),
|
|
938
|
+
);
|
|
939
|
+
const field = document.createElement("fig-field");
|
|
940
|
+
const label = customLabel || document.createElement("label");
|
|
941
|
+
const input = document.createElement("fig-input-number");
|
|
942
|
+
|
|
943
|
+
field.append(label, input);
|
|
944
|
+
this.#field = field;
|
|
945
|
+
this.#label = label;
|
|
946
|
+
this.#input = input;
|
|
947
|
+
this.#hasCustomLabel = Boolean(customLabel);
|
|
948
|
+
this.replaceChildren(field);
|
|
949
|
+
|
|
950
|
+
for (const node of initialChildren) {
|
|
951
|
+
if (node !== customLabel) input.appendChild(node);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
#syncField() {
|
|
956
|
+
if (!this.#field || !this.#label) return;
|
|
957
|
+
const hasLabelAttr = this.hasAttribute("label");
|
|
958
|
+
const rawLabel = this.getAttribute("label");
|
|
959
|
+
const isBlankLabel = hasLabelAttr && (rawLabel ?? "").trim() === "";
|
|
960
|
+
|
|
961
|
+
if (isBlankLabel) {
|
|
962
|
+
this.#label.remove();
|
|
963
|
+
} else {
|
|
964
|
+
if (!this.#hasCustomLabel) {
|
|
965
|
+
this.#label.textContent = hasLabelAttr ? (rawLabel ?? "") : "Label";
|
|
966
|
+
}
|
|
967
|
+
if (this.#label.parentElement !== this.#field) {
|
|
968
|
+
this.#field.prepend(this.#label);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
this.#field.setAttribute(
|
|
973
|
+
"direction",
|
|
974
|
+
this.getAttribute("direction") || "horizontal",
|
|
975
|
+
);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
#getForwardedInputAttrNames() {
|
|
979
|
+
const reserved = new Set([
|
|
980
|
+
"label",
|
|
981
|
+
"direction",
|
|
982
|
+
"oninput",
|
|
983
|
+
"onchange",
|
|
984
|
+
"class",
|
|
985
|
+
"style",
|
|
986
|
+
"id",
|
|
987
|
+
]);
|
|
988
|
+
return this.getAttributeNames().filter(
|
|
989
|
+
(name) => !reserved.has(name) && !name.startsWith("data-"),
|
|
990
|
+
);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
#syncInputAttributes() {
|
|
994
|
+
if (!this.#input) return;
|
|
995
|
+
const inputAttrs = this.#getForwardedInputAttrNames();
|
|
996
|
+
const nextManaged = new Set(inputAttrs);
|
|
997
|
+
|
|
998
|
+
for (const attrName of this.#managedInputAttrs) {
|
|
999
|
+
if (!nextManaged.has(attrName)) this.#input.removeAttribute(attrName);
|
|
1000
|
+
}
|
|
1001
|
+
for (const attrName of inputAttrs) {
|
|
1002
|
+
this.#input.setAttribute(attrName, this.getAttribute(attrName) ?? "");
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
this.#managedInputAttrs = nextManaged;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
#bindInputEvents() {
|
|
1009
|
+
if (!this.#input) return;
|
|
1010
|
+
this.#boundHandleInput ??= this.#forwardInputEvent.bind(this, "input");
|
|
1011
|
+
this.#boundHandleChange ??= this.#forwardInputEvent.bind(this, "change");
|
|
1012
|
+
this.#input.addEventListener("input", this.#boundHandleInput);
|
|
1013
|
+
this.#input.addEventListener("change", this.#boundHandleChange);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
#unbindInputEvents() {
|
|
1017
|
+
if (!this.#input) return;
|
|
1018
|
+
if (this.#boundHandleInput) {
|
|
1019
|
+
this.#input.removeEventListener("input", this.#boundHandleInput);
|
|
1020
|
+
}
|
|
1021
|
+
if (this.#boundHandleChange) {
|
|
1022
|
+
this.#input.removeEventListener("change", this.#boundHandleChange);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
#forwardInputEvent(type, event) {
|
|
1027
|
+
event.stopImmediatePropagation();
|
|
1028
|
+
const detail =
|
|
1029
|
+
event instanceof CustomEvent && event.detail !== undefined
|
|
1030
|
+
? event.detail
|
|
1031
|
+
: this.#input?.value;
|
|
1032
|
+
if (this.#input?.value !== undefined) {
|
|
1033
|
+
this.setAttribute("value", String(this.#input.value));
|
|
1034
|
+
}
|
|
1035
|
+
this.dispatchEvent(
|
|
1036
|
+
new CustomEvent(type, {
|
|
1037
|
+
detail,
|
|
1038
|
+
bubbles: true,
|
|
1039
|
+
cancelable: true,
|
|
1040
|
+
composed: true,
|
|
1041
|
+
}),
|
|
1042
|
+
);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
#handleClick(event) {
|
|
1046
|
+
if (event.target instanceof Element && event.target.closest("fig-input-number")) {
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
this.focus();
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
get value() {
|
|
1053
|
+
return this.#input?.value ?? this.getAttribute("value") ?? "";
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
set value(nextValue) {
|
|
1057
|
+
if (nextValue === null || nextValue === undefined || nextValue === "") {
|
|
1058
|
+
this.removeAttribute("value");
|
|
1059
|
+
} else {
|
|
1060
|
+
this.setAttribute("value", String(nextValue));
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
focus(options) {
|
|
1065
|
+
this.#input?.focus(options);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
customElements.define("propskit-number", PropskitNumber);
|
|
1069
|
+
|
|
15
1070
|
/* Field + Slider wrapper */
|
|
16
|
-
class
|
|
1071
|
+
class PropskitSlider extends HTMLElement {
|
|
17
1072
|
#field = null;
|
|
18
1073
|
#label = null;
|
|
19
1074
|
#slider = null;
|
|
@@ -446,7 +1501,7 @@ class FigFieldSlider extends HTMLElement {
|
|
|
446
1501
|
|
|
447
1502
|
#readElasticDistance() {
|
|
448
1503
|
let raw = getComputedStyle(this)
|
|
449
|
-
.getPropertyValue("--
|
|
1504
|
+
.getPropertyValue("--propskit-slider-elastic-distance")
|
|
450
1505
|
.trim();
|
|
451
1506
|
if (raw.includes("var(") || !raw.endsWith("px")) {
|
|
452
1507
|
const probe = document.createElement("div");
|
|
@@ -454,7 +1509,7 @@ class FigFieldSlider extends HTMLElement {
|
|
|
454
1509
|
position: "absolute",
|
|
455
1510
|
visibility: "hidden",
|
|
456
1511
|
pointerEvents: "none",
|
|
457
|
-
width: "var(--
|
|
1512
|
+
width: "var(--propskit-slider-elastic-distance)",
|
|
458
1513
|
});
|
|
459
1514
|
this.appendChild(probe);
|
|
460
1515
|
raw = getComputedStyle(probe).width;
|
|
@@ -489,10 +1544,10 @@ class FigFieldSlider extends HTMLElement {
|
|
|
489
1544
|
? (this.#elasticHostWidth + stretch) / this.#elasticHostWidth
|
|
490
1545
|
: 1;
|
|
491
1546
|
this.dataset.elasticDragging = "true";
|
|
492
|
-
this.style.setProperty("--
|
|
493
|
-
this.style.setProperty("--
|
|
1547
|
+
this.style.setProperty("--propskit-slider-elastic-size", `${stretch}px`);
|
|
1548
|
+
this.style.setProperty("--propskit-slider-elastic-scale", `${scale}`);
|
|
494
1549
|
this.style.setProperty(
|
|
495
|
-
"--
|
|
1550
|
+
"--propskit-slider-elastic-origin",
|
|
496
1551
|
offset < 0 ? "right center" : "left center",
|
|
497
1552
|
);
|
|
498
1553
|
}
|
|
@@ -507,8 +1562,8 @@ class FigFieldSlider extends HTMLElement {
|
|
|
507
1562
|
|
|
508
1563
|
#clearElasticPull() {
|
|
509
1564
|
this.removeAttribute("data-elastic-dragging");
|
|
510
|
-
this.style.removeProperty("--
|
|
511
|
-
this.style.removeProperty("--
|
|
1565
|
+
this.style.removeProperty("--propskit-slider-elastic-size");
|
|
1566
|
+
this.style.removeProperty("--propskit-slider-elastic-scale");
|
|
512
1567
|
}
|
|
513
1568
|
|
|
514
1569
|
#valueFromPointer(event) {
|
|
@@ -630,7 +1685,7 @@ class FigFieldSlider extends HTMLElement {
|
|
|
630
1685
|
this.#resetToDefault();
|
|
631
1686
|
}
|
|
632
1687
|
}
|
|
633
|
-
customElements.define("
|
|
1688
|
+
customElements.define("propskit-slider", PropskitSlider);
|
|
634
1689
|
|
|
635
1690
|
/* Canvas Control */
|
|
636
1691
|
class FigCanvasControl extends HTMLElement {
|
|
@@ -1828,8 +2883,8 @@ customElements.define("fig-canvas-control", FigCanvasControl);
|
|
|
1828
2883
|
* @attr {string} aspect-ratio - SVG editor aspect ratio.
|
|
1829
2884
|
* @attr {boolean} edit - Whether to show the editor and number fields. Defaults to true.
|
|
1830
2885
|
*/
|
|
1831
|
-
class
|
|
1832
|
-
#waves = [
|
|
2886
|
+
class PropskitOscillator extends HTMLElement {
|
|
2887
|
+
#waves = [PropskitOscillator.#defaultWave()];
|
|
1833
2888
|
#activeWaveIndex = 0;
|
|
1834
2889
|
#precision = 2;
|
|
1835
2890
|
#drawWidth = 240;
|
|
@@ -1935,7 +2990,7 @@ class FigInputOscillator extends HTMLElement {
|
|
|
1935
2990
|
|
|
1936
2991
|
get preset() {
|
|
1937
2992
|
const wave = this.#activeWave;
|
|
1938
|
-
return
|
|
2993
|
+
return PropskitOscillator.TYPES.find((type) => type.value === wave.type)?.name;
|
|
1939
2994
|
}
|
|
1940
2995
|
|
|
1941
2996
|
#readInteger(name, fallback) {
|
|
@@ -1991,7 +3046,7 @@ class FigInputOscillator extends HTMLElement {
|
|
|
1991
3046
|
|
|
1992
3047
|
this.#waves = nextWaves.map((wave) => this.#normalizeWave(wave));
|
|
1993
3048
|
if (!this.#waves.length) {
|
|
1994
|
-
this.#waves = [
|
|
3049
|
+
this.#waves = [PropskitOscillator.#defaultWave()];
|
|
1995
3050
|
}
|
|
1996
3051
|
this.#activeWaveIndex = Math.min(this.#activeWaveIndex, this.#waves.length - 1);
|
|
1997
3052
|
return true;
|
|
@@ -2021,12 +3076,12 @@ class FigInputOscillator extends HTMLElement {
|
|
|
2021
3076
|
if (!this.#waves[this.#activeWaveIndex]) {
|
|
2022
3077
|
this.#activeWaveIndex = 0;
|
|
2023
3078
|
}
|
|
2024
|
-
return this.#waves[this.#activeWaveIndex] ||
|
|
3079
|
+
return this.#waves[this.#activeWaveIndex] || PropskitOscillator.#defaultWave();
|
|
2025
3080
|
}
|
|
2026
3081
|
|
|
2027
3082
|
#normalizeType(type) {
|
|
2028
3083
|
const normalized = String(type || "").toLowerCase();
|
|
2029
|
-
return
|
|
3084
|
+
return PropskitOscillator.TYPES.some((item) => item.value === normalized)
|
|
2030
3085
|
? normalized
|
|
2031
3086
|
: "sine";
|
|
2032
3087
|
}
|
|
@@ -2051,7 +3106,7 @@ class FigInputOscillator extends HTMLElement {
|
|
|
2051
3106
|
}
|
|
2052
3107
|
|
|
2053
3108
|
static #labelForType(type) {
|
|
2054
|
-
return
|
|
3109
|
+
return PropskitOscillator.TYPES.find((item) => item.value === type)?.name || "Wave";
|
|
2055
3110
|
}
|
|
2056
3111
|
|
|
2057
3112
|
static waveIcon(type, size = 24) {
|
|
@@ -2061,7 +3116,7 @@ class FigInputOscillator extends HTMLElement {
|
|
|
2061
3116
|
let d = "";
|
|
2062
3117
|
for (let i = 0; i <= samples; i++) {
|
|
2063
3118
|
const t = i / samples;
|
|
2064
|
-
const value =
|
|
3119
|
+
const value = PropskitOscillator.#waveValue(type, t);
|
|
2065
3120
|
const x = pad + t * draw;
|
|
2066
3121
|
const y = pad + (1 - (value + 1) / 2) * draw;
|
|
2067
3122
|
d += `${i === 0 ? "M" : "L"}${x.toFixed(1)},${y.toFixed(1)}`;
|
|
@@ -2099,21 +3154,21 @@ class FigInputOscillator extends HTMLElement {
|
|
|
2099
3154
|
#getInnerHTML() {
|
|
2100
3155
|
const disabled = this.#isDisabled() ? " disabled" : "";
|
|
2101
3156
|
|
|
2102
|
-
return `<div class="
|
|
2103
|
-
<svg viewBox="0 0 ${this.#drawWidth} ${this.#drawHeight}" class="
|
|
2104
|
-
<rect class="
|
|
2105
|
-
<line class="
|
|
2106
|
-
<path class="
|
|
2107
|
-
<circle class="
|
|
2108
|
-
<foreignObject class="
|
|
2109
|
-
<foreignObject class="
|
|
3157
|
+
return `<div class="propskit-oscillator-svg-container">
|
|
3158
|
+
<svg viewBox="0 0 ${this.#drawWidth} ${this.#drawHeight}" class="propskit-oscillator-svg">
|
|
3159
|
+
<rect class="propskit-oscillator-bounds" x="0" y="0" width="${this.#drawWidth}" height="${this.#drawHeight}"></rect>
|
|
3160
|
+
<line class="propskit-oscillator-baseline"></line>
|
|
3161
|
+
<path class="propskit-oscillator-path"></path>
|
|
3162
|
+
<circle class="propskit-oscillator-playhead"></circle>
|
|
3163
|
+
<foreignObject class="propskit-oscillator-handle propskit-oscillator-amplitude-handle" data-handle="amplitude" width="20" height="20"><div class="propskit-oscillator-handle-inner"><fig-tooltip text="Amplitude"><fig-handle size="small" aria-label="Oscillator amplitude handle"${disabled}></fig-handle></fig-tooltip></div></foreignObject>
|
|
3164
|
+
<foreignObject class="propskit-oscillator-handle propskit-oscillator-frequency-handle" data-handle="frequency" width="20" height="20"><div class="propskit-oscillator-handle-inner"><fig-tooltip text="Frequency"><fig-handle size="small" aria-label="Oscillator frequency handle"${disabled}></fig-handle></fig-tooltip></div></foreignObject>
|
|
2110
3165
|
</svg>
|
|
2111
3166
|
</div>
|
|
2112
3167
|
${this.#isEditEnabled() ? this.#getWaveControlsHTML(disabled) : ""}`;
|
|
2113
3168
|
}
|
|
2114
3169
|
|
|
2115
3170
|
#getWaveControlsHTML(disabled) {
|
|
2116
|
-
return `<div class="
|
|
3171
|
+
return `<div class="propskit-oscillator-waves">
|
|
2117
3172
|
${this.#waves.map((wave, index) => this.#getWaveRowHTML(wave, index, disabled)).join("")}
|
|
2118
3173
|
</div>`;
|
|
2119
3174
|
}
|
|
@@ -2121,22 +3176,22 @@ class FigInputOscillator extends HTMLElement {
|
|
|
2121
3176
|
#getWaveRowHTML(wave, index, disabled) {
|
|
2122
3177
|
const removeDisabled = disabled || this.#waves.length <= 1 ? " disabled" : "";
|
|
2123
3178
|
const active = index === this.#activeWaveIndex ? " data-active" : "";
|
|
2124
|
-
const label =
|
|
3179
|
+
const label = PropskitOscillator.#labelForType(wave.type);
|
|
2125
3180
|
const open = this.#expandedWaveIndices.has(index) ? ' open="true"' : ' open="false"';
|
|
2126
|
-
return `<fig-group class="
|
|
3181
|
+
return `<fig-group class="propskit-oscillator-wave" collapsible borderless compact="true"${open} data-wave-index="${index}">
|
|
2127
3182
|
<fig-header borderless>
|
|
2128
3183
|
<h3>${label}</h3>
|
|
2129
3184
|
<fig-tooltip text="Remove form">
|
|
2130
|
-
<fig-button class="
|
|
3185
|
+
<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>
|
|
2131
3186
|
</fig-tooltip>
|
|
2132
3187
|
<fig-tooltip text="Add form">
|
|
2133
|
-
<fig-button class="
|
|
3188
|
+
<fig-button class="propskit-oscillator-add-type-button" type="select" variant="ghost" icon data-wave-index="${index}" aria-label="Add form"${disabled}>
|
|
2134
3189
|
<fig-icon name="add"></fig-icon>
|
|
2135
|
-
${this.#getWaveTypeDropdownHTML("
|
|
3190
|
+
${this.#getWaveTypeDropdownHTML("propskit-oscillator-add-type", "sine", disabled, index)}
|
|
2136
3191
|
</fig-button>
|
|
2137
3192
|
</fig-tooltip>
|
|
2138
3193
|
</fig-header>
|
|
2139
|
-
<div class="
|
|
3194
|
+
<div class="propskit-oscillator-fields" data-wave-index="${index}"${active}>
|
|
2140
3195
|
${this.#getNumberFieldHTML(index, "frequency", "Frequency", 0.1, 16, 0.1, "")}
|
|
2141
3196
|
${this.#getNumberFieldHTML(index, "amplitude", "Amplitude", -4, 4, 0.1, "")}
|
|
2142
3197
|
${this.#getNumberFieldHTML(index, "phase", "Phase", -360, 360, 1, "°")}
|
|
@@ -2146,44 +3201,44 @@ class FigInputOscillator extends HTMLElement {
|
|
|
2146
3201
|
}
|
|
2147
3202
|
|
|
2148
3203
|
#getWaveTypeDropdownHTML(className, value, disabled, index = null) {
|
|
2149
|
-
const options =
|
|
3204
|
+
const options = PropskitOscillator.TYPES.map((type) => {
|
|
2150
3205
|
const selected = type.value === value ? " selected" : "";
|
|
2151
3206
|
return `<option value="${type.value}"${selected}>
|
|
2152
|
-
${
|
|
3207
|
+
${PropskitOscillator.waveIcon(type.value, 24)}
|
|
2153
3208
|
<label>${type.name}</label>
|
|
2154
3209
|
</option>`;
|
|
2155
3210
|
}).join("");
|
|
2156
3211
|
const indexAttr =
|
|
2157
|
-
index === null ? "" : ` data-wave-index="${
|
|
3212
|
+
index === null ? "" : ` data-wave-index="${PropskitOscillator.#escapeAttribute(String(index))}"`;
|
|
2158
3213
|
return `<fig-dropdown class="${className}" value="${value}" experimental="modern" type="dropdown" label="Add form"${indexAttr}${disabled}>${options}</fig-dropdown>`;
|
|
2159
3214
|
}
|
|
2160
3215
|
|
|
2161
3216
|
#getNumberFieldHTML(index, name, label, min, max, step, units) {
|
|
2162
3217
|
const disabled = this.#isDisabled() ? " disabled" : "";
|
|
2163
3218
|
const unitsAttr = units
|
|
2164
|
-
? ` units="${
|
|
3219
|
+
? ` units="${PropskitOscillator.#escapeAttribute(units)}"`
|
|
2165
3220
|
: "";
|
|
2166
|
-
const wave = this.#waves[index] ||
|
|
2167
|
-
return `<
|
|
3221
|
+
const wave = this.#waves[index] || PropskitOscillator.#defaultWave();
|
|
3222
|
+
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>`;
|
|
2168
3223
|
}
|
|
2169
3224
|
|
|
2170
3225
|
#cacheRefs() {
|
|
2171
|
-
this.#svg = this.querySelector(".
|
|
2172
|
-
this.#path = this.querySelector(".
|
|
2173
|
-
this.#playhead = this.querySelector(".
|
|
2174
|
-
this.#baseline = this.querySelector(".
|
|
2175
|
-
this.#bounds = this.querySelector(".
|
|
3226
|
+
this.#svg = this.querySelector(".propskit-oscillator-svg");
|
|
3227
|
+
this.#path = this.querySelector(".propskit-oscillator-path");
|
|
3228
|
+
this.#playhead = this.querySelector(".propskit-oscillator-playhead");
|
|
3229
|
+
this.#baseline = this.querySelector(".propskit-oscillator-baseline");
|
|
3230
|
+
this.#bounds = this.querySelector(".propskit-oscillator-bounds");
|
|
2176
3231
|
this.#handleAmplitude = this.querySelector('[data-handle="amplitude"]');
|
|
2177
3232
|
this.#handleFrequency = this.querySelector('[data-handle="frequency"]');
|
|
2178
3233
|
this.#typeControls = Array.from(
|
|
2179
|
-
this.querySelectorAll(".
|
|
3234
|
+
this.querySelectorAll(".propskit-oscillator-wave-type"),
|
|
2180
3235
|
);
|
|
2181
|
-
this.#fields = Array.from(this.querySelectorAll("
|
|
3236
|
+
this.#fields = Array.from(this.querySelectorAll("propskit-slider[name]"));
|
|
2182
3237
|
this.#waveGroups = Array.from(
|
|
2183
|
-
this.querySelectorAll("fig-group.
|
|
3238
|
+
this.querySelectorAll("fig-group.propskit-oscillator-wave"),
|
|
2184
3239
|
);
|
|
2185
3240
|
this.#waveRows = Array.from(
|
|
2186
|
-
this.querySelectorAll(".
|
|
3241
|
+
this.querySelectorAll(".propskit-oscillator-fields"),
|
|
2187
3242
|
);
|
|
2188
3243
|
}
|
|
2189
3244
|
|
|
@@ -2250,7 +3305,7 @@ class FigInputOscillator extends HTMLElement {
|
|
|
2250
3305
|
#sampleAt(t) {
|
|
2251
3306
|
return this.#waves.reduce((sum, wave) => {
|
|
2252
3307
|
const cycleT = t * wave.frequency;
|
|
2253
|
-
const value =
|
|
3308
|
+
const value = PropskitOscillator.#waveValue(wave.type, cycleT, wave.phase);
|
|
2254
3309
|
return sum + wave.offset + value * wave.amplitude;
|
|
2255
3310
|
}, 0);
|
|
2256
3311
|
}
|
|
@@ -2419,7 +3474,7 @@ class FigInputOscillator extends HTMLElement {
|
|
|
2419
3474
|
});
|
|
2420
3475
|
}
|
|
2421
3476
|
|
|
2422
|
-
for (const control of this.querySelectorAll(".
|
|
3477
|
+
for (const control of this.querySelectorAll(".propskit-oscillator-add-type")) {
|
|
2423
3478
|
const stopHeaderToggle = (event) => {
|
|
2424
3479
|
event.stopPropagation();
|
|
2425
3480
|
};
|
|
@@ -2431,7 +3486,7 @@ class FigInputOscillator extends HTMLElement {
|
|
|
2431
3486
|
const insertAfter = this.#indexFromElement(control);
|
|
2432
3487
|
const insertIndex = insertAfter + 1;
|
|
2433
3488
|
const type = this.#normalizeType(event.detail ?? control.value);
|
|
2434
|
-
this.#waves.splice(insertIndex, 0,
|
|
3489
|
+
this.#waves.splice(insertIndex, 0, PropskitOscillator.#defaultWave(type));
|
|
2435
3490
|
this.#reindexExpandedWavesAfterInsert(insertIndex);
|
|
2436
3491
|
this.#activeWaveIndex = insertIndex;
|
|
2437
3492
|
this.#render();
|
|
@@ -2440,7 +3495,7 @@ class FigInputOscillator extends HTMLElement {
|
|
|
2440
3495
|
});
|
|
2441
3496
|
}
|
|
2442
3497
|
|
|
2443
|
-
for (const button of this.querySelectorAll(".
|
|
3498
|
+
for (const button of this.querySelectorAll(".propskit-oscillator-add-type-button")) {
|
|
2444
3499
|
const stopHeaderToggle = (event) => {
|
|
2445
3500
|
event.stopPropagation();
|
|
2446
3501
|
};
|
|
@@ -2450,7 +3505,7 @@ class FigInputOscillator extends HTMLElement {
|
|
|
2450
3505
|
button.closest("fig-tooltip")?.addEventListener("click", stopHeaderToggle);
|
|
2451
3506
|
}
|
|
2452
3507
|
|
|
2453
|
-
for (const button of this.querySelectorAll(".
|
|
3508
|
+
for (const button of this.querySelectorAll(".propskit-oscillator-remove-button")) {
|
|
2454
3509
|
button.addEventListener("pointerdown", (event) => {
|
|
2455
3510
|
event.stopPropagation();
|
|
2456
3511
|
});
|
|
@@ -2474,10 +3529,10 @@ class FigInputOscillator extends HTMLElement {
|
|
|
2474
3529
|
this.#setupHandle(handle);
|
|
2475
3530
|
}
|
|
2476
3531
|
|
|
2477
|
-
const surface = this.querySelector(".
|
|
3532
|
+
const surface = this.querySelector(".propskit-oscillator-svg-container");
|
|
2478
3533
|
surface?.addEventListener("pointerdown", (event) => {
|
|
2479
3534
|
if (this.#isDisabled()) return;
|
|
2480
|
-
if (event.target?.closest?.(".
|
|
3535
|
+
if (event.target?.closest?.(".propskit-oscillator-handle, fig-handle")) {
|
|
2481
3536
|
return;
|
|
2482
3537
|
}
|
|
2483
3538
|
this.#startDrag(event, "offset");
|
|
@@ -2655,13 +3710,13 @@ class FigInputOscillator extends HTMLElement {
|
|
|
2655
3710
|
return this.#waves.reduce((sum, wave, index) => {
|
|
2656
3711
|
if (index === excludedIndex) return sum;
|
|
2657
3712
|
const cycleT = t * wave.frequency;
|
|
2658
|
-
const value =
|
|
3713
|
+
const value = PropskitOscillator.#waveValue(wave.type, cycleT, wave.phase);
|
|
2659
3714
|
return sum + wave.offset + value * wave.amplitude;
|
|
2660
3715
|
}, 0);
|
|
2661
3716
|
}
|
|
2662
3717
|
|
|
2663
3718
|
#activeWaveValueAt(wave, t) {
|
|
2664
|
-
return
|
|
3719
|
+
return PropskitOscillator.#waveValue(
|
|
2665
3720
|
wave.type,
|
|
2666
3721
|
t * wave.frequency,
|
|
2667
3722
|
wave.phase,
|
|
@@ -2714,13 +3769,13 @@ class FigInputOscillator extends HTMLElement {
|
|
|
2714
3769
|
detail: {
|
|
2715
3770
|
value: this.value,
|
|
2716
3771
|
data: this.data,
|
|
2717
|
-
preset:
|
|
3772
|
+
preset: PropskitOscillator.#labelForType(this.#activeWave.type),
|
|
2718
3773
|
},
|
|
2719
3774
|
}),
|
|
2720
3775
|
);
|
|
2721
3776
|
}
|
|
2722
3777
|
}
|
|
2723
|
-
customElements.define("
|
|
3778
|
+
customElements.define("propskit-oscillator", PropskitOscillator);
|
|
2724
3779
|
|
|
2725
3780
|
/* Angle Input */
|
|
2726
3781
|
/**
|
|
@@ -3259,7 +4314,12 @@ class FigReorder extends HTMLElement {
|
|
|
3259
4314
|
"fig-slider",
|
|
3260
4315
|
"fig-input-number",
|
|
3261
4316
|
"fig-input-text",
|
|
3262
|
-
"
|
|
4317
|
+
"propskit-color",
|
|
4318
|
+
"propskit-number",
|
|
4319
|
+
"propskit-select",
|
|
4320
|
+
"propskit-slider",
|
|
4321
|
+
"propskit-switch",
|
|
4322
|
+
"propskit-text",
|
|
3263
4323
|
"fig-checkbox",
|
|
3264
4324
|
"fig-switch",
|
|
3265
4325
|
"fig-combo-input",
|