@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/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 FigFieldSlider extends HTMLElement {
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("--fig-field-slider-elastic-distance")
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(--fig-field-slider-elastic-distance)",
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("--fig-field-slider-elastic-size", `${stretch}px`);
493
- this.style.setProperty("--fig-field-slider-elastic-scale", `${scale}`);
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
- "--fig-field-slider-elastic-origin",
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("--fig-field-slider-elastic-size");
511
- this.style.removeProperty("--fig-field-slider-elastic-scale");
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("fig-field-slider", FigFieldSlider);
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 FigInputOscillator extends HTMLElement {
1832
- #waves = [FigInputOscillator.#defaultWave()];
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 FigInputOscillator.TYPES.find((type) => type.value === wave.type)?.name;
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 = [FigInputOscillator.#defaultWave()];
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] || FigInputOscillator.#defaultWave();
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 FigInputOscillator.TYPES.some((item) => item.value === normalized)
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 FigInputOscillator.TYPES.find((item) => item.value === type)?.name || "Wave";
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 = FigInputOscillator.#waveValue(type, t);
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="fig-input-oscillator-svg-container">
2103
- <svg viewBox="0 0 ${this.#drawWidth} ${this.#drawHeight}" class="fig-input-oscillator-svg">
2104
- <rect class="fig-input-oscillator-bounds" x="0" y="0" width="${this.#drawWidth}" height="${this.#drawHeight}"></rect>
2105
- <line class="fig-input-oscillator-baseline"></line>
2106
- <path class="fig-input-oscillator-path"></path>
2107
- <circle class="fig-input-oscillator-playhead"></circle>
2108
- <foreignObject class="fig-input-oscillator-handle fig-input-oscillator-amplitude-handle" data-handle="amplitude" width="20" height="20"><div class="fig-input-oscillator-handle-inner"><fig-tooltip text="Amplitude"><fig-handle size="small" aria-label="Oscillator amplitude handle"${disabled}></fig-handle></fig-tooltip></div></foreignObject>
2109
- <foreignObject class="fig-input-oscillator-handle fig-input-oscillator-frequency-handle" data-handle="frequency" width="20" height="20"><div class="fig-input-oscillator-handle-inner"><fig-tooltip text="Frequency"><fig-handle size="small" aria-label="Oscillator frequency handle"${disabled}></fig-handle></fig-tooltip></div></foreignObject>
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="fig-input-oscillator-waves">
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 = FigInputOscillator.#labelForType(wave.type);
3179
+ const label = PropskitOscillator.#labelForType(wave.type);
2125
3180
  const open = this.#expandedWaveIndices.has(index) ? ' open="true"' : ' open="false"';
2126
- return `<fig-group class="fig-input-oscillator-wave" collapsible borderless compact="true"${open} data-wave-index="${index}">
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="fig-input-oscillator-remove-button" variant="ghost" icon data-wave-index="${index}" aria-label="Remove form"${removeDisabled}><fig-icon name="minus"></fig-icon></fig-button>
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="fig-input-oscillator-add-type-button" type="select" variant="ghost" icon data-wave-index="${index}" aria-label="Add form"${disabled}>
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("fig-input-oscillator-add-type", "sine", disabled, index)}
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="fig-input-oscillator-fields" data-wave-index="${index}"${active}>
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 = FigInputOscillator.TYPES.map((type) => {
3204
+ const options = PropskitOscillator.TYPES.map((type) => {
2150
3205
  const selected = type.value === value ? " selected" : "";
2151
3206
  return `<option value="${type.value}"${selected}>
2152
- ${FigInputOscillator.waveIcon(type.value, 24)}
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="${FigInputOscillator.#escapeAttribute(String(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="${FigInputOscillator.#escapeAttribute(units)}"`
3219
+ ? ` units="${PropskitOscillator.#escapeAttribute(units)}"`
2165
3220
  : "";
2166
- const wave = this.#waves[index] || FigInputOscillator.#defaultWave();
2167
- return `<fig-field-slider class="fig-input-oscillator-field" label="${label}" direction="horizontal" name="${name}" data-wave-index="${index}" value="${this.#round(wave[name])}" min="${min}" max="${max}" step="${step}" precision="${this.#precision}" elastic="false"${unitsAttr}${disabled}></fig-field-slider>`;
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(".fig-input-oscillator-svg");
2172
- this.#path = this.querySelector(".fig-input-oscillator-path");
2173
- this.#playhead = this.querySelector(".fig-input-oscillator-playhead");
2174
- this.#baseline = this.querySelector(".fig-input-oscillator-baseline");
2175
- this.#bounds = this.querySelector(".fig-input-oscillator-bounds");
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(".fig-input-oscillator-wave-type"),
3234
+ this.querySelectorAll(".propskit-oscillator-wave-type"),
2180
3235
  );
2181
- this.#fields = Array.from(this.querySelectorAll("fig-field-slider[name]"));
3236
+ this.#fields = Array.from(this.querySelectorAll("propskit-slider[name]"));
2182
3237
  this.#waveGroups = Array.from(
2183
- this.querySelectorAll("fig-group.fig-input-oscillator-wave"),
3238
+ this.querySelectorAll("fig-group.propskit-oscillator-wave"),
2184
3239
  );
2185
3240
  this.#waveRows = Array.from(
2186
- this.querySelectorAll(".fig-input-oscillator-fields"),
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 = FigInputOscillator.#waveValue(wave.type, cycleT, wave.phase);
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(".fig-input-oscillator-add-type")) {
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, FigInputOscillator.#defaultWave(type));
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(".fig-input-oscillator-add-type-button")) {
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(".fig-input-oscillator-remove-button")) {
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(".fig-input-oscillator-svg-container");
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?.(".fig-input-oscillator-handle, fig-handle")) {
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 = FigInputOscillator.#waveValue(wave.type, cycleT, wave.phase);
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 FigInputOscillator.#waveValue(
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: FigInputOscillator.#labelForType(this.#activeWave.type),
3772
+ preset: PropskitOscillator.#labelForType(this.#activeWave.type),
2718
3773
  },
2719
3774
  }),
2720
3775
  );
2721
3776
  }
2722
3777
  }
2723
- customElements.define("fig-input-oscillator", FigInputOscillator);
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
- "fig-field-slider",
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",