@rogieking/figui3 1.0.26 → 1.0.27

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.
Files changed (2) hide show
  1. package/fig.js +918 -902
  2. package/package.json +1 -1
package/fig.js CHANGED
@@ -1,25 +1,24 @@
1
1
  function uniqueId() {
2
- return Date.now().toString(36) + Math.random().toString(36).substring(2)
2
+ return Date.now().toString(36) + Math.random().toString(36).substring(2);
3
3
  }
4
4
  function supportsPopover() {
5
- return HTMLElement.prototype.hasOwnProperty("popover");
5
+ return HTMLElement.prototype.hasOwnProperty("popover");
6
6
  }
7
7
 
8
-
9
8
  /* Button */
10
9
  class FigButton extends HTMLElement {
11
- #type
12
- #selected
13
- constructor() {
14
- super()
15
- //this.attachShadow({ mode: 'open' })
16
- }
17
- connectedCallback() {
18
- this.render()
19
- }
20
- render() {
21
- this.#type = this.getAttribute("type") || "button"
22
- /*this.shadowRoot.innerHTML = `
10
+ #type;
11
+ #selected;
12
+ constructor() {
13
+ super();
14
+ //this.attachShadow({ mode: 'open' })
15
+ }
16
+ connectedCallback() {
17
+ this.render();
18
+ }
19
+ render() {
20
+ this.#type = this.getAttribute("type") || "button";
21
+ /*this.shadowRoot.innerHTML = `
23
22
  <style>
24
23
  button, button:hover, button:active {
25
24
  padding: 0;
@@ -38,293 +37,293 @@ class FigButton extends HTMLElement {
38
37
  </button>
39
38
  `;*/
40
39
 
41
- this.#selected = this.hasAttribute("selected") && this.getAttribute("selected") !== "false"
42
- this.addEventListener("click", this.handleClick.bind(this))
43
-
44
-
45
- //child nodes hack
46
- requestAnimationFrame(() => {
47
- this.innerHTML = `<button type="${this.#type}">${this.innerHTML}</button>`
48
- this.button = this.querySelector('button')
49
- })
50
-
51
- }
52
- get type() {
53
- return this.#type
54
- }
55
- set type(value) {
56
- this.#type = value
57
- this.button.type = value
58
- this.setAttribute("type", value)
59
- }
60
- get selected() {
61
- return this.#selected
62
- }
63
- set selected(value) {
64
- this.#selected = value
65
- this.setAttribute("selected", value)
66
- }
67
-
68
- handleClick(event) {
69
- if (this.#type === "toggle") {
70
- this.selected = !this.#selected
71
- }
72
- if (this.#type === "submit") {
73
- this.button.click()
74
- }
75
- }
76
- static get observedAttributes() {
77
- return ['disabled'];
78
- }
79
- attributeChangedCallback(name, oldValue, newValue) {
80
- if (this.button) {
81
- this.button[name] = newValue
82
- if (newValue === "false") {
83
- this.button.removeAttribute(name)
84
- }
85
- }
86
- }
40
+ this.#selected =
41
+ this.hasAttribute("selected") &&
42
+ this.getAttribute("selected") !== "false";
43
+ this.addEventListener("click", this.handleClick.bind(this));
44
+
45
+ //child nodes hack
46
+ requestAnimationFrame(() => {
47
+ this.innerHTML = `<button type="${this.#type}">${
48
+ this.innerHTML
49
+ }</button>`;
50
+ this.button = this.querySelector("button");
51
+ });
52
+ }
53
+ get type() {
54
+ return this.#type;
55
+ }
56
+ set type(value) {
57
+ this.#type = value;
58
+ this.button.type = value;
59
+ this.setAttribute("type", value);
60
+ }
61
+ get selected() {
62
+ return this.#selected;
63
+ }
64
+ set selected(value) {
65
+ this.#selected = value;
66
+ this.setAttribute("selected", value);
67
+ }
68
+
69
+ handleClick(event) {
70
+ if (this.#type === "toggle") {
71
+ this.selected = !this.#selected;
72
+ }
73
+ if (this.#type === "submit") {
74
+ this.button.click();
75
+ }
76
+ }
77
+ static get observedAttributes() {
78
+ return ["disabled"];
79
+ }
80
+ attributeChangedCallback(name, oldValue, newValue) {
81
+ if (this.button) {
82
+ this.button[name] = newValue;
83
+ if (newValue === "false") {
84
+ this.button.removeAttribute(name);
85
+ }
86
+ }
87
+ }
87
88
  }
88
- window.customElements.define('fig-button', FigButton);
89
+ window.customElements.define("fig-button", FigButton);
89
90
 
90
91
  /* Dropdown */
91
92
  class FigDropdown extends HTMLElement {
92
- constructor() {
93
- super();
94
- this.attachShadow({ mode: 'open' });
95
- }
96
-
97
- connectedCallback() {
98
- this.render()
99
- }
100
-
101
- render() {
102
- this.select = document.createElement('select')
103
- this.optionsSlot = document.createElement('slot')
104
-
105
- this.appendChild(this.select)
106
- this.shadowRoot.appendChild(this.optionsSlot)
107
-
108
- // Move slotted options into the select element
109
- this.optionsSlot.addEventListener('slotchange', this.slotChange.bind(this));
110
- }
111
- slotChange() {
112
- while (this.select.firstChild) {
113
- this.select.firstChild.remove()
114
- }
115
- this.optionsSlot.assignedNodes().forEach(node => {
116
- if (node.nodeName === 'OPTION') {
117
- this.select.appendChild(node.cloneNode(true))
118
- }
119
- })
120
- }
93
+ constructor() {
94
+ super();
95
+ this.attachShadow({ mode: "open" });
96
+ }
97
+
98
+ connectedCallback() {
99
+ this.render();
100
+ }
101
+
102
+ render() {
103
+ this.select = document.createElement("select");
104
+ this.optionsSlot = document.createElement("slot");
105
+
106
+ this.appendChild(this.select);
107
+ this.shadowRoot.appendChild(this.optionsSlot);
108
+
109
+ // Move slotted options into the select element
110
+ this.optionsSlot.addEventListener("slotchange", this.slotChange.bind(this));
111
+ }
112
+ slotChange() {
113
+ while (this.select.firstChild) {
114
+ this.select.firstChild.remove();
115
+ }
116
+ this.optionsSlot.assignedNodes().forEach((node) => {
117
+ if (node.nodeName === "OPTION") {
118
+ this.select.appendChild(node.cloneNode(true));
119
+ }
120
+ });
121
+ }
121
122
  }
122
123
 
123
- customElements.define('fig-dropdown', FigDropdown);
124
+ customElements.define("fig-dropdown", FigDropdown);
124
125
 
125
126
  /* Tooltip */
126
127
  class FigTooltip extends HTMLElement {
127
- constructor() {
128
- super()
129
- this.action = this.getAttribute("action") || "hover"
130
- this.delay = parseInt(this.getAttribute("delay")) || 500
131
- this.isOpen = false
132
- }
133
- connectedCallback() {
134
- this.setup()
135
- this.setupEventListeners()
136
- }
137
-
138
- disconnectedCallback() {
139
- this.destroy()
140
- }
141
-
142
- setup() {
143
-
144
- }
145
-
146
- render() {
147
- this.destroy()
148
- this.popup = document.createElement('span');
149
- this.popup.setAttribute("class", "fig-tooltip")
150
- this.popup.style.position = "fixed"
151
- this.popup.style.visibility = "hidden"
152
- this.popup.style.pointerEvents = "none"
153
- this.popup.innerText = this.getAttribute("text")
154
- document.body.append(this.popup)
155
- }
156
-
157
- destroy() {
158
- if (this.popup) {
159
- this.popup.remove()
160
- }
161
- document.body.addEventListener("click", this.hidePopupOutsideClick)
162
- }
163
-
164
- setupEventListeners() {
165
- if (this.action === "hover") {
166
- this.addEventListener("mouseenter", this.showDelayedPopup.bind(this));
167
- this.addEventListener("mouseleave", this.hidePopup.bind(this));
168
- } else if (this.action === "click") {
169
- this.addEventListener("click", this.showDelayedPopup.bind(this));
170
- document.body.addEventListener("click", this.hidePopupOutsideClick.bind(this))
171
- }
172
- }
173
-
174
- getOffset() {
175
- const defaultOffset = { left: 8, top: 4, right: 8, bottom: 4 };
176
- const offsetAttr = this.getAttribute("offset");
177
- if (!offsetAttr) return defaultOffset;
178
-
179
- const [left, top, right, bottom] = offsetAttr.split(",").map(Number);
180
- return {
181
- left: isNaN(left) ? defaultOffset.left : left,
182
- top: isNaN(top) ? defaultOffset.top : top,
183
- right: isNaN(right) ? defaultOffset.right : right,
184
- bottom: isNaN(bottom) ? defaultOffset.bottom : bottom
185
- };
186
- }
187
-
188
- showDelayedPopup() {
189
- this.render()
190
- clearTimeout(this.timeout)
191
- this.timeout = setTimeout(this.showPopup.bind(this), this.delay)
192
- }
193
-
194
- showPopup() {
195
- const rect = this.getBoundingClientRect()
196
- const popupRect = this.popup.getBoundingClientRect()
197
- const offset = this.getOffset()
198
-
199
- // Position the tooltip above the element
200
- let top = rect.top - popupRect.height - offset.top
201
- let left = rect.left + (rect.width - popupRect.width) / 2
202
- this.popup.setAttribute("position", "top")
203
-
204
- // Adjust if tooltip would go off-screen
205
- if (top < 0) {
206
- this.popup.setAttribute("position", "bottom")
207
- top = rect.bottom + offset.bottom; // Position below instead
208
- }
209
- if (left < offset.left) {
210
- left = offset.left;
211
- } else if (left + popupRect.width > window.innerWidth - offset.right) {
212
-
213
- left = window.innerWidth - popupRect.width - offset.right;
214
- }
215
-
216
- this.popup.style.top = `${top}px`;
217
- this.popup.style.left = `${left}px`;
218
- this.popup.style.opacity = "1";
219
- this.popup.style.visibility = "visible"
220
- this.popup.style.display = "block"
221
- this.popup.style.pointerEvents = "all"
222
- this.popup.style.zIndex = parseInt((new Date()).getTime() / 1000)
223
-
224
- this.isOpen = true
225
- }
226
-
227
- hidePopup() {
228
- clearTimeout(this.timeout)
229
- this.popup.style.opacity = "0"
230
- this.popup.style.display = "block"
231
- this.popup.style.pointerEvents = "none"
232
- this.destroy()
233
- this.isOpen = false
234
- }
235
-
236
- hidePopupOutsideClick(event) {
237
- if (this.isOpen && !this.popup.contains(event.target)) {
238
- this.hidePopup()
239
- }
240
- }
128
+ constructor() {
129
+ super();
130
+ this.action = this.getAttribute("action") || "hover";
131
+ this.delay = parseInt(this.getAttribute("delay")) || 500;
132
+ this.isOpen = false;
133
+ }
134
+ connectedCallback() {
135
+ this.setup();
136
+ this.setupEventListeners();
137
+ }
138
+
139
+ disconnectedCallback() {
140
+ this.destroy();
141
+ }
142
+
143
+ setup() {}
144
+
145
+ render() {
146
+ this.destroy();
147
+ this.popup = document.createElement("span");
148
+ this.popup.setAttribute("class", "fig-tooltip");
149
+ this.popup.style.position = "fixed";
150
+ this.popup.style.visibility = "hidden";
151
+ this.popup.style.pointerEvents = "none";
152
+ this.popup.innerText = this.getAttribute("text");
153
+ document.body.append(this.popup);
154
+ }
155
+
156
+ destroy() {
157
+ if (this.popup) {
158
+ this.popup.remove();
159
+ }
160
+ document.body.addEventListener("click", this.hidePopupOutsideClick);
161
+ }
162
+
163
+ setupEventListeners() {
164
+ if (this.action === "hover") {
165
+ this.addEventListener("mouseenter", this.showDelayedPopup.bind(this));
166
+ this.addEventListener("mouseleave", this.hidePopup.bind(this));
167
+ } else if (this.action === "click") {
168
+ this.addEventListener("click", this.showDelayedPopup.bind(this));
169
+ document.body.addEventListener(
170
+ "click",
171
+ this.hidePopupOutsideClick.bind(this)
172
+ );
173
+ }
174
+ }
175
+
176
+ getOffset() {
177
+ const defaultOffset = { left: 8, top: 4, right: 8, bottom: 4 };
178
+ const offsetAttr = this.getAttribute("offset");
179
+ if (!offsetAttr) return defaultOffset;
180
+
181
+ const [left, top, right, bottom] = offsetAttr.split(",").map(Number);
182
+ return {
183
+ left: isNaN(left) ? defaultOffset.left : left,
184
+ top: isNaN(top) ? defaultOffset.top : top,
185
+ right: isNaN(right) ? defaultOffset.right : right,
186
+ bottom: isNaN(bottom) ? defaultOffset.bottom : bottom,
187
+ };
188
+ }
189
+
190
+ showDelayedPopup() {
191
+ this.render();
192
+ clearTimeout(this.timeout);
193
+ this.timeout = setTimeout(this.showPopup.bind(this), this.delay);
194
+ }
195
+
196
+ showPopup() {
197
+ const rect = this.getBoundingClientRect();
198
+ const popupRect = this.popup.getBoundingClientRect();
199
+ const offset = this.getOffset();
200
+
201
+ // Position the tooltip above the element
202
+ let top = rect.top - popupRect.height - offset.top;
203
+ let left = rect.left + (rect.width - popupRect.width) / 2;
204
+ this.popup.setAttribute("position", "top");
205
+
206
+ // Adjust if tooltip would go off-screen
207
+ if (top < 0) {
208
+ this.popup.setAttribute("position", "bottom");
209
+ top = rect.bottom + offset.bottom; // Position below instead
210
+ }
211
+ if (left < offset.left) {
212
+ left = offset.left;
213
+ } else if (left + popupRect.width > window.innerWidth - offset.right) {
214
+ left = window.innerWidth - popupRect.width - offset.right;
215
+ }
216
+
217
+ this.popup.style.top = `${top}px`;
218
+ this.popup.style.left = `${left}px`;
219
+ this.popup.style.opacity = "1";
220
+ this.popup.style.visibility = "visible";
221
+ this.popup.style.display = "block";
222
+ this.popup.style.pointerEvents = "all";
223
+ this.popup.style.zIndex = parseInt(new Date().getTime() / 1000);
224
+
225
+ this.isOpen = true;
226
+ }
227
+
228
+ hidePopup() {
229
+ clearTimeout(this.timeout);
230
+ this.popup.style.opacity = "0";
231
+ this.popup.style.display = "block";
232
+ this.popup.style.pointerEvents = "none";
233
+ this.destroy();
234
+ this.isOpen = false;
235
+ }
236
+
237
+ hidePopupOutsideClick(event) {
238
+ if (this.isOpen && !this.popup.contains(event.target)) {
239
+ this.hidePopup();
240
+ }
241
+ }
241
242
  }
242
243
 
243
244
  customElements.define("fig-tooltip", FigTooltip);
244
245
 
245
246
  /* Popover */
246
247
  class FigPopover extends FigTooltip {
247
- static observedAttributes = ["action", "size"];
248
-
249
- constructor() {
250
- super()
251
- this.action = this.getAttribute("action") || "click"
252
- this.delay = parseInt(this.getAttribute("delay")) || 0
253
- }
254
- render() {
255
- //this.destroy()
256
- //if (!this.popup) {
257
- this.popup = this.popup || this.querySelector("[popover]")
258
- this.popup.setAttribute("class", "fig-popover")
259
- this.popup.style.position = "fixed"
260
- this.popup.style.display = "block"
261
- this.popup.style.pointerEvents = "none"
262
- document.body.append(this.popup)
263
- //}
264
- }
265
-
266
- destroy() {
267
-
268
- }
248
+ static observedAttributes = ["action", "size"];
249
+
250
+ constructor() {
251
+ super();
252
+ this.action = this.getAttribute("action") || "click";
253
+ this.delay = parseInt(this.getAttribute("delay")) || 0;
254
+ }
255
+ render() {
256
+ //this.destroy()
257
+ //if (!this.popup) {
258
+ this.popup = this.popup || this.querySelector("[popover]");
259
+ this.popup.setAttribute("class", "fig-popover");
260
+ this.popup.style.position = "fixed";
261
+ this.popup.style.display = "block";
262
+ this.popup.style.pointerEvents = "none";
263
+ document.body.append(this.popup);
264
+ //}
265
+ }
266
+
267
+ destroy() {}
269
268
  }
270
269
  customElements.define("fig-popover", FigPopover);
271
270
 
272
271
  /* Dialog */
273
272
  class FigDialog extends HTMLElement {
274
- constructor() {
275
- super();
276
- this.attachShadow({ mode: 'open' })
277
- this.dialog = document.createElement('dialog')
278
- this.contentSlot = document.createElement('slot')
279
- }
280
-
281
- connectedCallback() {
282
- this.render()
283
- }
284
-
285
- disconnectedCallback() {
286
- this.contentSlot.removeEventListener('slotchange', this.slotChange)
287
- }
288
-
289
- render() {
290
- this.appendChild(this.dialog)
291
- this.shadowRoot.appendChild(this.contentSlot)
292
- this.contentSlot.addEventListener('slotchange', this.slotChange.bind(this))
293
- }
294
-
295
- slotChange() {
296
- while (this.dialog.firstChild) {
297
- this.dialog.firstChild.remove()
273
+ constructor() {
274
+ super();
275
+ this.attachShadow({ mode: "open" });
276
+ this.dialog = document.createElement("dialog");
277
+ this.contentSlot = document.createElement("slot");
278
+ }
279
+
280
+ connectedCallback() {
281
+ this.render();
282
+ }
283
+
284
+ disconnectedCallback() {
285
+ this.contentSlot.removeEventListener("slotchange", this.slotChange);
286
+ }
287
+
288
+ render() {
289
+ this.appendChild(this.dialog);
290
+ this.shadowRoot.appendChild(this.contentSlot);
291
+ this.contentSlot.addEventListener("slotchange", this.slotChange.bind(this));
292
+ }
293
+
294
+ slotChange() {
295
+ while (this.dialog.firstChild) {
296
+ this.dialog.firstChild.remove();
297
+ }
298
+ this.contentSlot.assignedNodes().forEach((node) => {
299
+ if (node !== this.dialog) {
300
+ this.dialog.appendChild(node.cloneNode(true));
301
+ }
302
+ });
303
+ }
304
+
305
+ static get observedAttributes() {
306
+ return ["open"];
307
+ }
308
+
309
+ attributeChangedCallback(name, oldValue, newValue) {
310
+ switch (name) {
311
+ case "open":
312
+ if (this?.show) {
313
+ this[newValue === "true" ? "show" : "close"]();
298
314
  }
299
- this.contentSlot.assignedNodes().forEach(node => {
300
- if (node !== this.dialog) {
301
- this.dialog.appendChild(node.cloneNode(true))
302
- }
303
- })
304
- }
305
-
306
- static get observedAttributes() {
307
- return ['open'];
308
- }
309
-
310
- attributeChangedCallback(name, oldValue, newValue) {
311
- switch (name) {
312
- case "open":
313
- if (this?.show) {
314
- this[newValue === "true" ? "show" : "close"]()
315
- }
316
- break;
317
- }
318
- }
319
-
320
- /* Public methods */
321
- show() {
322
- console.log("show dialog", this.dialog, this.dialog?.show)
323
- this.dialog.show()
324
- }
325
- close() {
326
- this.dialog.close()
327
- }
315
+ break;
316
+ }
317
+ }
318
+
319
+ /* Public methods */
320
+ show() {
321
+ console.log("show dialog", this.dialog, this.dialog?.show);
322
+ this.dialog.show();
323
+ }
324
+ close() {
325
+ this.dialog.close();
326
+ }
328
327
  }
329
328
  customElements.define("fig-dialog", FigDialog);
330
329
 
@@ -376,184 +375,184 @@ class FigDialog extends FigTooltip {
376
375
  customElements.define("fig-dialog", FigDialog);
377
376
  */
378
377
 
379
-
380
378
  class FigPopover2 extends HTMLElement {
381
- #popover
382
- #trigger
383
- #id
384
- #delay
385
- #timeout
386
- #action
387
-
388
- constructor() {
389
- super()
390
- }
391
- connectedCallback() {
392
- this.#popover = this.querySelector('[popover]')
393
- this.#trigger = this
394
- this.#delay = Number(this.getAttribute("delay")) || 0
395
- this.#action = this.getAttribute("trigger-action") || "click"
396
- this.#id = `tooltip-${uniqueId()}`
397
- if (this.#popover) {
398
- this.#popover.setAttribute("id", this.#id)
399
- this.#popover.setAttribute("role", "tooltip")
400
- this.#popover.setAttribute("popover", "manual")
401
- this.#popover.style['position-anchor'] = `--${this.#id}`
402
-
403
- this.#trigger.setAttribute("popovertarget", this.#id)
404
- this.#trigger.setAttribute("popovertargetaction", "toggle")
405
- this.#trigger.style['anchor-name'] = `--${this.#id}`
406
-
407
- if (this.#action === "hover") {
408
- this.#trigger.addEventListener("mouseover", this.handleOpen.bind(this))
409
- this.#trigger.addEventListener("mouseout", this.handleClose.bind(this))
410
- } else {
411
- this.#trigger.addEventListener("click", this.handleToggle.bind(this))
412
- }
413
-
414
- document.body.append(this.#popover)
415
- }
416
- }
417
-
418
- handleClose() {
419
- clearTimeout(this.#timeout)
420
- this.#popover.hidePopover()
421
- }
422
- handleToggle() {
423
- if (this.#popover.matches(':popover-open')) {
424
- this.handleClose()
425
- } else {
426
- this.handleOpen()
427
- }
428
- }
429
- handleOpen() {
430
- clearTimeout(this.#timeout)
431
- this.#timeout = setTimeout(() => {
432
- this.#popover.showPopover()
433
- }, this.#delay)
434
- }
379
+ #popover;
380
+ #trigger;
381
+ #id;
382
+ #delay;
383
+ #timeout;
384
+ #action;
385
+
386
+ constructor() {
387
+ super();
388
+ }
389
+ connectedCallback() {
390
+ this.#popover = this.querySelector("[popover]");
391
+ this.#trigger = this;
392
+ this.#delay = Number(this.getAttribute("delay")) || 0;
393
+ this.#action = this.getAttribute("trigger-action") || "click";
394
+ this.#id = `tooltip-${uniqueId()}`;
395
+ if (this.#popover) {
396
+ this.#popover.setAttribute("id", this.#id);
397
+ this.#popover.setAttribute("role", "tooltip");
398
+ this.#popover.setAttribute("popover", "manual");
399
+ this.#popover.style["position-anchor"] = `--${this.#id}`;
400
+
401
+ this.#trigger.setAttribute("popovertarget", this.#id);
402
+ this.#trigger.setAttribute("popovertargetaction", "toggle");
403
+ this.#trigger.style["anchor-name"] = `--${this.#id}`;
404
+
405
+ if (this.#action === "hover") {
406
+ this.#trigger.addEventListener("mouseover", this.handleOpen.bind(this));
407
+ this.#trigger.addEventListener("mouseout", this.handleClose.bind(this));
408
+ } else {
409
+ this.#trigger.addEventListener("click", this.handleToggle.bind(this));
410
+ }
411
+
412
+ document.body.append(this.#popover);
413
+ }
414
+ }
415
+
416
+ handleClose() {
417
+ clearTimeout(this.#timeout);
418
+ this.#popover.hidePopover();
419
+ }
420
+ handleToggle() {
421
+ if (this.#popover.matches(":popover-open")) {
422
+ this.handleClose();
423
+ } else {
424
+ this.handleOpen();
425
+ }
426
+ }
427
+ handleOpen() {
428
+ clearTimeout(this.#timeout);
429
+ this.#timeout = setTimeout(() => {
430
+ this.#popover.showPopover();
431
+ }, this.#delay);
432
+ }
435
433
  }
436
- window.customElements.define('fig-popover-2', FigPopover2);
437
-
434
+ window.customElements.define("fig-popover-2", FigPopover2);
438
435
 
439
436
  /* Tabs */
440
437
  class FigTab extends HTMLElement {
441
- constructor() {
442
- super()
443
- }
444
- connectedCallback() {
445
- this.setAttribute("label", this.innerText)
446
- this.addEventListener("click", this.handleClick.bind(this))
447
- }
448
- disconnectedCallback() {
449
- this.removeEventListener("click", this.handleClick)
450
- }
451
- handleClick() {
452
- this.setAttribute("selected", "true")
453
- }
438
+ constructor() {
439
+ super();
440
+ }
441
+ connectedCallback() {
442
+ this.setAttribute("label", this.innerText);
443
+ this.addEventListener("click", this.handleClick.bind(this));
444
+ }
445
+ disconnectedCallback() {
446
+ this.removeEventListener("click", this.handleClick);
447
+ }
448
+ handleClick() {
449
+ this.setAttribute("selected", "true");
450
+ }
454
451
  }
455
- window.customElements.define('fig-tab', FigTab);
452
+ window.customElements.define("fig-tab", FigTab);
456
453
 
457
454
  class FigTabs extends HTMLElement {
458
- constructor() {
459
- super()
460
- }
461
- connectedCallback() {
462
- this.name = this.getAttribute("name") || "tabs"
463
- this.addEventListener("click", this.handleClick.bind(this))
464
- }
465
- disconnectedCallback() {
466
- this.removeEventListener("click", this.handleClick)
467
- }
468
- handleClick(event) {
469
- const target = event.target;
470
- if (target.nodeName.toLowerCase() === "fig-tab") {
471
- const tabs = this.querySelectorAll("fig-tab")
472
- for (const tab of tabs) {
473
- if (tab === target) {
474
- this.selectedTab = tab
475
- } else {
476
- tab.removeAttribute("selected")
477
- }
478
- }
455
+ constructor() {
456
+ super();
457
+ }
458
+ connectedCallback() {
459
+ this.name = this.getAttribute("name") || "tabs";
460
+ this.addEventListener("click", this.handleClick.bind(this));
461
+ }
462
+ disconnectedCallback() {
463
+ this.removeEventListener("click", this.handleClick);
464
+ }
465
+ handleClick(event) {
466
+ const target = event.target;
467
+ if (target.nodeName.toLowerCase() === "fig-tab") {
468
+ const tabs = this.querySelectorAll("fig-tab");
469
+ for (const tab of tabs) {
470
+ if (tab === target) {
471
+ this.selectedTab = tab;
472
+ } else {
473
+ tab.removeAttribute("selected");
479
474
  }
475
+ }
480
476
  }
477
+ }
481
478
  }
482
- window.customElements.define('fig-tabs', FigTabs);
479
+ window.customElements.define("fig-tabs", FigTabs);
483
480
 
484
481
  /* Segmented Control */
485
482
  class FigSegment extends HTMLElement {
486
- constructor() {
487
- super()
488
- }
489
- connectedCallback() {
490
- this.addEventListener("click", this.handleClick.bind(this))
491
- }
492
- disconnectedCallback() {
493
- this.removeEventListener("click", this.handleClick)
494
- }
495
- handleClick() {
496
- this.setAttribute("selected", "true")
497
- }
483
+ #value;
484
+ #selected;
485
+ constructor() {
486
+ super();
487
+ }
488
+ connectedCallback() {
489
+ this.addEventListener("click", this.handleClick.bind(this));
490
+ }
491
+ disconnectedCallback() {
492
+ this.removeEventListener("click", this.handleClick);
493
+ }
494
+ handleClick() {
495
+ this.setAttribute("selected", "true");
496
+ }
497
+ get value() {
498
+ return this.#value;
499
+ }
500
+ set value(value) {
501
+ this.#value = value;
502
+ this.setAttribute("value", value);
503
+ }
504
+ get selected() {
505
+ return this.#selected;
506
+ }
507
+ set selected(value) {
508
+ this.#selected = value;
509
+ this.setAttribute("selected", value);
510
+ }
511
+ static get observedAttributes() {
512
+ return ["selected", "value"];
513
+ }
514
+ attributeChangedCallback(name, oldValue, newValue) {
515
+ switch (name) {
516
+ case "value":
517
+ this.#value = newValue;
518
+ break;
519
+ case "selected":
520
+ this.#selected = newValue;
521
+ break;
522
+ }
523
+ }
498
524
  }
499
- window.customElements.define('fig-segment', FigSegment);
500
-
501
- class FigSegmentedControl extends HTMLElement {
502
- constructor() {
503
- super()
504
- }
505
- connectedCallback() {
506
- this.name = this.getAttribute("name") || "segmented-control"
507
- this.addEventListener("click", this.handleClick.bind(this))
508
- }
509
- handleClick(event) {
510
- const target = event.target;
511
- if (target.nodeName.toLowerCase() === "fig-segment") {
512
- const segments = this.querySelectorAll("fig-segment")
513
- for (const segment of segments) {
514
- if (segment === target) {
515
- this.selectedSegment = segment
516
- } else {
517
- segment.removeAttribute("selected")
518
- }
519
- }
520
- }
521
- }
522
- }
523
- window.customElements.define('fig-segmented-control', FigSegmentedControl);
524
-
525
-
525
+ window.customElements.define("fig-segment", FigSegment);
526
526
 
527
527
  /* Slider */
528
528
  class FigSlider extends HTMLElement {
529
- #typeDefaults = {
530
- range: { min: 0, max: 100, step: 1 },
531
- hue: { min: 0, max: 255, step: 1 },
532
- delta: { min: -100, max: 100, step: 1 },
533
- opacity: { min: 0, max: 1, step: 0.01, color: "#FF0000" }
534
- }
535
- constructor() {
536
- super()
537
- }
538
- connectedCallback() {
539
-
540
- this.value = this.getAttribute("value")
541
- this.default = this.getAttribute("default") || null
542
- this.type = this.getAttribute("type") || "range"
543
-
544
- const defaults = this.#typeDefaults[this.type]
545
- this.min = this.getAttribute("min") || defaults.min
546
- this.max = this.getAttribute("max") || defaults.max
547
- this.step = this.getAttribute("step") || defaults.step
548
- this.color = this.getAttribute("color") || defaults?.color
549
- this.disabled = this.getAttribute("disabled") ? true : false
550
-
551
- if (this.color) {
552
- this.style.setProperty("--color", this.color)
553
- }
554
-
555
- let html = ''
556
- let slider = `<div class="fig-slider-input-container">
529
+ #typeDefaults = {
530
+ range: { min: 0, max: 100, step: 1 },
531
+ hue: { min: 0, max: 255, step: 1 },
532
+ delta: { min: -100, max: 100, step: 1 },
533
+ opacity: { min: 0, max: 1, step: 0.01, color: "#FF0000" },
534
+ };
535
+ constructor() {
536
+ super();
537
+ }
538
+ connectedCallback() {
539
+ this.value = this.getAttribute("value");
540
+ this.default = this.getAttribute("default") || null;
541
+ this.type = this.getAttribute("type") || "range";
542
+
543
+ const defaults = this.#typeDefaults[this.type];
544
+ this.min = this.getAttribute("min") || defaults.min;
545
+ this.max = this.getAttribute("max") || defaults.max;
546
+ this.step = this.getAttribute("step") || defaults.step;
547
+ this.color = this.getAttribute("color") || defaults?.color;
548
+ this.disabled = this.getAttribute("disabled") ? true : false;
549
+
550
+ if (this.color) {
551
+ this.style.setProperty("--color", this.color);
552
+ }
553
+
554
+ let html = "";
555
+ let slider = `<div class="fig-slider-input-container">
557
556
  <input
558
557
  type="range"
559
558
  ${this.disabled ? "disabled" : ""}
@@ -563,9 +562,9 @@ class FigSlider extends HTMLElement {
563
562
  class="${this.type}"
564
563
  value="${this.value}">
565
564
  ${this.innerHTML}
566
- </div>`
567
- if (this.getAttribute("text")) {
568
- html = `${slider}
565
+ </div>`;
566
+ if (this.getAttribute("text")) {
567
+ html = `${slider}
569
568
  <fig-input-text
570
569
  placeholder="##"
571
570
  type="number"
@@ -573,205 +572,212 @@ class FigSlider extends HTMLElement {
573
572
  max="${this.max}"
574
573
  step="${this.step}"
575
574
  value="${this.value}">
576
- </fig-input-text>`
577
- } else {
578
- html = slider
579
- }
580
-
581
- this.innerHTML = html
582
-
583
- this.textInput = this.querySelector("input[type=number]")
584
-
585
- this.input = this.querySelector('[type=range]')
586
- this.input.addEventListener("input", this.handleInput.bind(this))
587
- this.handleInput()
588
-
589
- if (this.textInput) {
590
- this.textInput.addEventListener("input", this.handleTextInput.bind(this))
591
- }
592
-
593
- if (this.default) {
594
- this.style.setProperty("--default", this.calculateNormal(this.default))
595
- }
596
- //child nodes hack
597
- requestAnimationFrame(() => {
598
- this.datalist = this.querySelector('datalist')
599
- if (this.datalist) {
600
- this.datalist.setAttribute("id", this.datalist.getAttribute("id") || uniqueId())
601
- this.input.setAttribute('list', this.datalist.getAttribute("id"))
602
- }
603
- })
604
- }
605
- static get observedAttributes() {
606
- return ['value', 'step', 'min', 'max', 'type', 'disabled']
607
- }
608
-
609
- focus() {
610
- this.input.focus()
611
- }
612
-
613
- attributeChangedCallback(name, oldValue, newValue) {
614
- if (this.input) {
615
- console.log('attributeChangedCallback:', name, oldValue, newValue)
616
- switch (name) {
617
- case "color":
618
- this.color = newValue
619
- this.style.setProperty("--color", this.color)
620
- break;
621
- case "type":
622
- this.input.className = newValue
623
- break;
624
- case "disabled":
625
- this.disabled = this.input.disabled = (newValue === "true" || newValue === undefined && newValue !== null)
626
- if (this.textInput) {
627
- this.textInput.disabled = this.disabled
628
- }
629
- break;
630
- default:
631
- this[name] = this.input[name] = newValue
632
- if (this.textInput) {
633
- this.textInput[name] = newValue
634
- }
635
- this.handleInput()
636
- break;
637
- }
638
- }
639
- }
640
- handleTextInput() {
641
- if (this.textInput) {
642
- this.input.value = Number(this.textInput.value)
643
- this.handleInput()
644
- }
645
- }
646
- calculateNormal(value) {
647
- let min = Number(this.input.min)
648
- let max = Number(this.input.max)
649
- let val = Number(value)
650
- return (val - min) / (max - min)
651
- }
652
-
653
- handleInput() {
654
- let val = Number(this.input.value)
655
- this.value = val
656
- let complete = this.calculateNormal(val)
657
- this.style.setProperty("--slider-complete", complete)
658
- if (this.textInput) {
659
- this.textInput.value = val
660
- }
661
- }
575
+ </fig-input-text>`;
576
+ } else {
577
+ html = slider;
578
+ }
579
+
580
+ this.innerHTML = html;
581
+
582
+ this.textInput = this.querySelector("input[type=number]");
583
+
584
+ this.input = this.querySelector("[type=range]");
585
+ this.input.addEventListener("input", this.handleInput.bind(this));
586
+ this.handleInput();
587
+
588
+ if (this.textInput) {
589
+ this.textInput.addEventListener("input", this.handleTextInput.bind(this));
590
+ }
591
+
592
+ if (this.default) {
593
+ this.style.setProperty("--default", this.calculateNormal(this.default));
594
+ }
595
+ //child nodes hack
596
+ requestAnimationFrame(() => {
597
+ this.datalist = this.querySelector("datalist");
598
+ if (this.datalist) {
599
+ this.datalist.setAttribute(
600
+ "id",
601
+ this.datalist.getAttribute("id") || uniqueId()
602
+ );
603
+ this.input.setAttribute("list", this.datalist.getAttribute("id"));
604
+ }
605
+ });
606
+ }
607
+ static get observedAttributes() {
608
+ return ["value", "step", "min", "max", "type", "disabled"];
609
+ }
610
+
611
+ focus() {
612
+ this.input.focus();
613
+ }
614
+
615
+ attributeChangedCallback(name, oldValue, newValue) {
616
+ if (this.input) {
617
+ console.log("attributeChangedCallback:", name, oldValue, newValue);
618
+ switch (name) {
619
+ case "color":
620
+ this.color = newValue;
621
+ this.style.setProperty("--color", this.color);
622
+ break;
623
+ case "type":
624
+ this.input.className = newValue;
625
+ break;
626
+ case "disabled":
627
+ this.disabled = this.input.disabled =
628
+ newValue === "true" ||
629
+ (newValue === undefined && newValue !== null);
630
+ if (this.textInput) {
631
+ this.textInput.disabled = this.disabled;
632
+ }
633
+ break;
634
+ default:
635
+ this[name] = this.input[name] = newValue;
636
+ if (this.textInput) {
637
+ this.textInput[name] = newValue;
638
+ }
639
+ this.handleInput();
640
+ break;
641
+ }
642
+ }
643
+ }
644
+ handleTextInput() {
645
+ if (this.textInput) {
646
+ this.input.value = Number(this.textInput.value);
647
+ this.handleInput();
648
+ }
649
+ }
650
+ calculateNormal(value) {
651
+ let min = Number(this.input.min);
652
+ let max = Number(this.input.max);
653
+ let val = Number(value);
654
+ return (val - min) / (max - min);
655
+ }
656
+
657
+ handleInput() {
658
+ let val = Number(this.input.value);
659
+ this.value = val;
660
+ let complete = this.calculateNormal(val);
661
+ this.style.setProperty("--slider-complete", complete);
662
+ if (this.textInput) {
663
+ this.textInput.value = val;
664
+ }
665
+ }
662
666
  }
663
- window.customElements.define('fig-slider', FigSlider);
667
+ window.customElements.define("fig-slider", FigSlider);
664
668
 
665
669
  class FigInputText extends HTMLElement {
666
- constructor() {
667
- super()
668
- }
669
- connectedCallback() {
670
- const append = this.querySelector('[slot=append]')
671
- const prepend = this.querySelector('[slot=prepend]')
672
- this.multiline = this.hasAttribute("multiline") || false
673
- this.value = this.getAttribute("value")
674
- this.type = this.getAttribute("type") || "text"
675
- this.placeholder = this.getAttribute("placeholder")
676
-
677
- let html = `<input
670
+ constructor() {
671
+ super();
672
+ }
673
+ connectedCallback() {
674
+ const append = this.querySelector("[slot=append]");
675
+ const prepend = this.querySelector("[slot=prepend]");
676
+ this.multiline = this.hasAttribute("multiline") || false;
677
+ this.value = this.getAttribute("value");
678
+ this.type = this.getAttribute("type") || "text";
679
+ this.placeholder = this.getAttribute("placeholder");
680
+
681
+ let html = `<input
678
682
  type="${this.type}"
679
683
  placeholder="${this.placeholder}"
680
- value="${this.value}" />`
681
- if (this.multiline) {
682
- html = `<textarea
683
- placeholder="${this.placeholder}">${this.value}</textarea>`
684
- }
685
- this.innerHTML = html
686
-
687
- this.input = this.querySelector('input,textarea')
688
- this.input.addEventListener('input', this.handleInput.bind(this))
689
-
690
- if (prepend) {
691
- prepend.addEventListener('click', this.focus.bind(this))
692
- this.prepend(prepend)
693
- }
694
- if (append) {
695
- append.addEventListener('click', this.focus.bind(this))
696
- this.append(append)
697
- }
698
- }
699
- focus() {
700
- this.input.focus()
701
- }
702
- handleInput() {
703
- this.value = this.input.value
704
- }
705
-
706
- static get observedAttributes() {
707
- return ['value', 'placeholder', 'label', 'disabled', 'type'];
708
- }
709
-
710
- attributeChangedCallback(name, oldValue, newValue) {
711
- if (this.input) {
712
- switch (name) {
713
- case "label":
714
- this.disabled = this.input.disabled = newValue
715
- break;
716
- default:
717
- this[name] = this.input[name] = newValue
718
- break;
719
- }
720
- }
721
- }
684
+ value="${this.value}" />`;
685
+ if (this.multiline) {
686
+ html = `<textarea
687
+ placeholder="${this.placeholder}">${this.value}</textarea>`;
688
+ }
689
+ this.innerHTML = html;
690
+
691
+ this.input = this.querySelector("input,textarea");
692
+ this.input.addEventListener("input", this.handleInput.bind(this));
693
+
694
+ if (prepend) {
695
+ prepend.addEventListener("click", this.focus.bind(this));
696
+ this.prepend(prepend);
697
+ }
698
+ if (append) {
699
+ append.addEventListener("click", this.focus.bind(this));
700
+ this.append(append);
701
+ }
702
+ }
703
+ focus() {
704
+ this.input.focus();
705
+ }
706
+ handleInput() {
707
+ this.value = this.input.value;
708
+ }
709
+
710
+ static get observedAttributes() {
711
+ return ["value", "placeholder", "label", "disabled", "type"];
712
+ }
713
+
714
+ attributeChangedCallback(name, oldValue, newValue) {
715
+ if (this.input) {
716
+ switch (name) {
717
+ case "label":
718
+ this.disabled = this.input.disabled = newValue;
719
+ break;
720
+ default:
721
+ this[name] = this.input[name] = newValue;
722
+ break;
723
+ }
724
+ }
725
+ }
722
726
  }
723
- window.customElements.define('fig-input-text', FigInputText);
724
-
727
+ window.customElements.define("fig-input-text", FigInputText);
725
728
 
726
729
  /* Form Field */
727
730
  class FigField extends HTMLElement {
728
- constructor() {
729
- super()
730
- }
731
- connectedCallback() {
732
- requestAnimationFrame(() => {
733
- this.label = this.querySelector('label')
734
- this.input = Array.from(this.childNodes).find(node => node.nodeName.toLowerCase().startsWith("fig-"))
735
- if (this.input && this.label) {
736
- this.label.addEventListener('click', this.focus.bind(this))
737
- let inputId = this.input.getAttribute("id") || uniqueId()
738
- this.input.setAttribute("id", inputId)
739
- this.label.setAttribute("for", inputId)
740
- }
741
- })
742
- }
743
- focus() {
744
- this.input.focus()
745
- }
731
+ constructor() {
732
+ super();
733
+ }
734
+ connectedCallback() {
735
+ requestAnimationFrame(() => {
736
+ this.label = this.querySelector("label");
737
+ this.input = Array.from(this.childNodes).find((node) =>
738
+ node.nodeName.toLowerCase().startsWith("fig-")
739
+ );
740
+ if (this.input && this.label) {
741
+ this.label.addEventListener("click", this.focus.bind(this));
742
+ let inputId = this.input.getAttribute("id") || uniqueId();
743
+ this.input.setAttribute("id", inputId);
744
+ this.label.setAttribute("for", inputId);
745
+ }
746
+ });
747
+ }
748
+ focus() {
749
+ this.input.focus();
750
+ }
746
751
  }
747
- window.customElements.define('fig-field', FigField);
748
-
752
+ window.customElements.define("fig-field", FigField);
749
753
 
750
754
  /* Color swatch */
751
755
  class FigInputColor extends HTMLElement {
752
- #rgba
753
- #swatch
754
- textInput
755
- #alphaInput
756
- constructor() {
757
- super()
758
- }
759
- connectedCallback() {
760
- this.#rgba = this.convertToRGBA(this.getAttribute("value"))
761
- const alpha = (this.#rgba.a * 100).toFixed(0)
762
- this.value = this.rgbAlphaToHex(
763
- {
764
- r: this.#rgba.r,
765
- g: this.#rgba.g,
766
- b: this.#rgba.b
767
- },
768
- alpha
769
- )
770
- let html = ``
771
- if (this.getAttribute("text")) {
772
- let label = `<fig-input-text placeholder="Text" value="${this.getAttribute("value")}"></fig-input-text>`
773
- if (this.getAttribute("alpha")) {
774
- label += `<fig-tooltip text="Opacity">
756
+ #rgba;
757
+ #swatch;
758
+ textInput;
759
+ #alphaInput;
760
+ constructor() {
761
+ super();
762
+ }
763
+ connectedCallback() {
764
+ this.#rgba = this.convertToRGBA(this.getAttribute("value"));
765
+ const alpha = (this.#rgba.a * 100).toFixed(0);
766
+ this.value = this.rgbAlphaToHex(
767
+ {
768
+ r: this.#rgba.r,
769
+ g: this.#rgba.g,
770
+ b: this.#rgba.b,
771
+ },
772
+ alpha
773
+ );
774
+ let html = ``;
775
+ if (this.getAttribute("text")) {
776
+ let label = `<fig-input-text placeholder="Text" value="${this.getAttribute(
777
+ "value"
778
+ )}"></fig-input-text>`;
779
+ if (this.getAttribute("alpha")) {
780
+ label += `<fig-tooltip text="Opacity">
775
781
  <fig-input-text
776
782
  placeholder="##"
777
783
  type="number"
@@ -780,307 +786,317 @@ class FigInputColor extends HTMLElement {
780
786
  value="${alpha}">
781
787
  <span slot="append">%</slot>
782
788
  </fig-input-text>
783
- </fig-tooltip>`
784
- }
785
- html = `<div class="input-combo">
789
+ </fig-tooltip>`;
790
+ }
791
+ html = `<div class="input-combo">
786
792
  <input type="color" value="${this.value}" />
787
793
  ${label}
788
- </div>`
794
+ </div>`;
795
+ } else {
796
+ html = `<input type="color" value="${this.value}" />`;
797
+ }
798
+ this.innerHTML = html;
799
+
800
+ this.style.setProperty("--alpha", this.#rgba.a);
801
+
802
+ this.#swatch = this.querySelector("[type=color]");
803
+ this.textInput = this.querySelector("[type=text]");
804
+ this.#alphaInput = this.querySelector("[type=number]");
805
+
806
+ this.#swatch.disabled = this.hasAttribute("disabled");
807
+ this.#swatch.addEventListener("input", this.handleInput.bind(this));
808
+
809
+ if (this.textInput) {
810
+ this.textInput.value = this.#swatch.value = this.rgbAlphaToHex(
811
+ this.#rgba,
812
+ 1
813
+ );
814
+ }
815
+
816
+ if (this.#alphaInput) {
817
+ this.#alphaInput.addEventListener(
818
+ "input",
819
+ this.handleAlphaInput.bind(this)
820
+ );
821
+ }
822
+ }
823
+ handleAlphaInput(event) {
824
+ //do not propagate to onInput handler for web component
825
+ event.stopPropagation();
826
+ this.#rgba = this.convertToRGBA(this.#swatch.value);
827
+ this.#rgba.a = Number(this.#alphaInput.value) / 100;
828
+ this.value = this.rgbAlphaToHex(
829
+ {
830
+ r: this.#rgba.r,
831
+ g: this.#rgba.g,
832
+ b: this.#rgba.b,
833
+ },
834
+ this.#rgba.a
835
+ );
836
+ this.style.setProperty("--alpha", this.#rgba.a);
837
+ const e = new CustomEvent("input", {
838
+ bubbles: true,
839
+ cancelable: true,
840
+ });
841
+ this.dispatchEvent(e);
842
+ }
843
+
844
+ focus() {
845
+ this.#swatch.focus();
846
+ }
847
+
848
+ handleInput(event) {
849
+ //do not propagate to onInput handler for web component
850
+ event.stopPropagation();
851
+
852
+ let alpha = this.#rgba.a;
853
+ this.#rgba = this.convertToRGBA(this.#swatch.value);
854
+ this.#rgba.a = alpha;
855
+ if (this.textInput) {
856
+ this.textInput.value = this.#swatch.value;
857
+ }
858
+ this.style.setProperty("--alpha", this.#rgba.a);
859
+ this.value = this.rgbAlphaToHex(
860
+ {
861
+ r: this.#rgba.r,
862
+ g: this.#rgba.g,
863
+ b: this.#rgba.b,
864
+ },
865
+ alpha
866
+ );
867
+ this.alpha = alpha;
868
+ if (this.#alphaInput) {
869
+ this.#alphaInput.value = this.#rgba.a.toFixed(0);
870
+ }
871
+ const e = new CustomEvent("input", {
872
+ bubbles: true,
873
+ cancelable: true,
874
+ });
875
+ this.dispatchEvent(e);
876
+ }
877
+
878
+ static get observedAttributes() {
879
+ return ["value", "style"];
880
+ }
881
+
882
+ attributeChangedCallback(name, oldValue, newValue) {
883
+ //this[name] = newValue;
884
+ }
885
+
886
+ rgbAlphaToHex({ r, g, b }, a = 1) {
887
+ // Ensure r, g, b are integers between 0 and 255
888
+ r = Math.max(0, Math.min(255, Math.round(r)));
889
+ g = Math.max(0, Math.min(255, Math.round(g)));
890
+ b = Math.max(0, Math.min(255, Math.round(b)));
891
+
892
+ // Ensure alpha is between 0 and 1
893
+ a = Math.max(0, Math.min(1, a));
894
+
895
+ // Convert to hex and pad with zeros if necessary
896
+ const hexR = r.toString(16).padStart(2, "0");
897
+ const hexG = g.toString(16).padStart(2, "0");
898
+ const hexB = b.toString(16).padStart(2, "0");
899
+
900
+ // If alpha is 1, return 6-digit hex
901
+ if (a === 1) {
902
+ return `#${hexR}${hexG}${hexB}`;
903
+ }
904
+
905
+ // Otherwise, include alpha in 8-digit hex
906
+ const alpha = Math.round(a * 255);
907
+ const hexA = alpha.toString(16).padStart(2, "0");
908
+ return `#${hexR}${hexG}${hexB}${hexA}`;
909
+ }
910
+
911
+ convertToRGBA(color) {
912
+ let r,
913
+ g,
914
+ b,
915
+ a = 1;
916
+
917
+ // Handle hex colors
918
+ if (color.startsWith("#")) {
919
+ let hex = color.slice(1);
920
+ if (hex.length === 8) {
921
+ a = parseInt(hex.slice(6), 16) / 255;
922
+ hex = hex.slice(0, 6);
923
+ }
924
+ r = parseInt(hex.slice(0, 2), 16);
925
+ g = parseInt(hex.slice(2, 4), 16);
926
+ b = parseInt(hex.slice(4, 6), 16);
927
+ }
928
+ // Handle rgba colors
929
+ else if (color.startsWith("rgba") || color.startsWith("rgb")) {
930
+ let matches = color.match(
931
+ /rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)/
932
+ );
933
+ if (matches) {
934
+ r = parseInt(matches[1]);
935
+ g = parseInt(matches[2]);
936
+ b = parseInt(matches[3]);
937
+ a = matches[4] ? parseFloat(matches[4]) : 1;
938
+ }
939
+ }
940
+ // Handle hsla colors
941
+ else if (color.startsWith("hsla") || color.startsWith("hsl")) {
942
+ let matches = color.match(
943
+ /hsla?\((\d+),\s*(\d+)%,\s*(\d+)%(?:,\s*(\d+(?:\.\d+)?))?\)/
944
+ );
945
+ if (matches) {
946
+ let h = parseInt(matches[1]) / 360;
947
+ let s = parseInt(matches[2]) / 100;
948
+ let l = parseInt(matches[3]) / 100;
949
+ a = matches[4] ? parseFloat(matches[4]) : 1;
950
+
951
+ if (s === 0) {
952
+ r = g = b = l; // achromatic
789
953
  } else {
790
- html = `<input type="color" value="${this.value}" />`
954
+ let hue2rgb = (p, q, t) => {
955
+ if (t < 0) t += 1;
956
+ if (t > 1) t -= 1;
957
+ if (t < 1 / 6) return p + (q - p) * 6 * t;
958
+ if (t < 1 / 2) return q;
959
+ if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
960
+ return p;
961
+ };
962
+
963
+ let q = l < 0.5 ? l * (1 + s) : l + s - l * s;
964
+ let p = 2 * l - q;
965
+ r = hue2rgb(p, q, h + 1 / 3);
966
+ g = hue2rgb(p, q, h);
967
+ b = hue2rgb(p, q, h - 1 / 3);
791
968
  }
792
- this.innerHTML = html
793
-
794
- this.style.setProperty('--alpha', this.#rgba.a)
795
-
796
-
797
- this.#swatch = this.querySelector('[type=color]')
798
- this.textInput = this.querySelector('[type=text]')
799
- this.#alphaInput = this.querySelector('[type=number]')
800
-
801
969
 
802
- this.#swatch.disabled = this.hasAttribute('disabled')
803
- this.#swatch.addEventListener('input', this.handleInput.bind(this))
804
-
805
- if (this.textInput) {
806
- this.textInput.value = this.#swatch.value = this.rgbAlphaToHex(this.#rgba, 1)
807
- }
808
-
809
- if (this.#alphaInput) {
810
- this.#alphaInput.addEventListener('input', this.handleAlphaInput.bind(this))
811
- }
812
-
813
- }
814
- handleAlphaInput(event) {
815
- //do not propagate to onInput handler for web component
816
- event.stopPropagation()
817
- this.#rgba = this.convertToRGBA(this.#swatch.value)
818
- this.#rgba.a = Number(this.#alphaInput.value) / 100
819
- this.value = this.rgbAlphaToHex(
820
- {
821
- r: this.#rgba.r,
822
- g: this.#rgba.g,
823
- b: this.#rgba.b
824
- },
825
- this.#rgba.a
826
- )
827
- this.style.setProperty('--alpha', this.#rgba.a)
828
- const e = new CustomEvent('input', {
829
- bubbles: true,
830
- cancelable: true
831
- });
832
- this.dispatchEvent(e)
833
- }
834
-
835
- focus() {
836
- this.#swatch.focus()
837
- }
838
-
839
- handleInput(event) {
840
- //do not propagate to onInput handler for web component
841
- event.stopPropagation()
842
-
843
- let alpha = this.#rgba.a
844
- this.#rgba = this.convertToRGBA(this.#swatch.value)
845
- this.#rgba.a = alpha
846
- if (this.textInput) {
847
- this.textInput.value = this.#swatch.value
848
- }
849
- this.style.setProperty('--alpha', this.#rgba.a)
850
- this.value = this.rgbAlphaToHex(
851
- {
852
- r: this.#rgba.r,
853
- g: this.#rgba.g,
854
- b: this.#rgba.b
855
- },
856
- alpha
857
- )
858
- this.alpha = alpha
859
- if (this.#alphaInput) {
860
- this.#alphaInput.value = this.#rgba.a.toFixed(0)
861
- }
862
- const e = new CustomEvent('input', {
863
- bubbles: true,
864
- cancelable: true
865
- });
866
- this.dispatchEvent(e)
970
+ r = Math.round(r * 255);
971
+ g = Math.round(g * 255);
972
+ b = Math.round(b * 255);
973
+ }
867
974
  }
868
-
869
- static get observedAttributes() {
870
- return ['value', 'style'];
975
+ // If it's not recognized, return null
976
+ else {
977
+ return null;
871
978
  }
872
979
 
873
- attributeChangedCallback(name, oldValue, newValue) {
874
- //this[name] = newValue;
875
- }
876
-
877
- rgbAlphaToHex({ r, g, b }, a = 1) {
878
- // Ensure r, g, b are integers between 0 and 255
879
- r = Math.max(0, Math.min(255, Math.round(r)));
880
- g = Math.max(0, Math.min(255, Math.round(g)));
881
- b = Math.max(0, Math.min(255, Math.round(b)));
882
-
883
- // Ensure alpha is between 0 and 1
884
- a = Math.max(0, Math.min(1, a));
885
-
886
- // Convert to hex and pad with zeros if necessary
887
- const hexR = r.toString(16).padStart(2, '0');
888
- const hexG = g.toString(16).padStart(2, '0');
889
- const hexB = b.toString(16).padStart(2, '0');
890
-
891
- // If alpha is 1, return 6-digit hex
892
- if (a === 1) {
893
- return `#${hexR}${hexG}${hexB}`;
894
- }
895
-
896
- // Otherwise, include alpha in 8-digit hex
897
- const alpha = Math.round(a * 255);
898
- const hexA = alpha.toString(16).padStart(2, '0');
899
- return `#${hexR}${hexG}${hexB}${hexA}`;
900
- }
901
-
902
- convertToRGBA(color) {
903
- let r, g, b, a = 1;
904
-
905
- // Handle hex colors
906
- if (color.startsWith('#')) {
907
- let hex = color.slice(1);
908
- if (hex.length === 8) {
909
- a = parseInt(hex.slice(6), 16) / 255;
910
- hex = hex.slice(0, 6);
911
- }
912
- r = parseInt(hex.slice(0, 2), 16);
913
- g = parseInt(hex.slice(2, 4), 16);
914
- b = parseInt(hex.slice(4, 6), 16);
915
- }
916
- // Handle rgba colors
917
- else if (color.startsWith('rgba') || color.startsWith('rgb')) {
918
- let matches = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)/);
919
- if (matches) {
920
- r = parseInt(matches[1]);
921
- g = parseInt(matches[2]);
922
- b = parseInt(matches[3]);
923
- a = matches[4] ? parseFloat(matches[4]) : 1;
924
- }
925
- }
926
- // Handle hsla colors
927
- else if (color.startsWith('hsla') || color.startsWith('hsl')) {
928
- let matches = color.match(/hsla?\((\d+),\s*(\d+)%,\s*(\d+)%(?:,\s*(\d+(?:\.\d+)?))?\)/);
929
- if (matches) {
930
- let h = parseInt(matches[1]) / 360;
931
- let s = parseInt(matches[2]) / 100;
932
- let l = parseInt(matches[3]) / 100;
933
- a = matches[4] ? parseFloat(matches[4]) : 1;
934
-
935
- if (s === 0) {
936
- r = g = b = l; // achromatic
937
- } else {
938
- let hue2rgb = (p, q, t) => {
939
- if (t < 0) t += 1;
940
- if (t > 1) t -= 1;
941
- if (t < 1 / 6) return p + (q - p) * 6 * t;
942
- if (t < 1 / 2) return q;
943
- if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
944
- return p;
945
- };
946
-
947
- let q = l < 0.5 ? l * (1 + s) : l + s - l * s;
948
- let p = 2 * l - q;
949
- r = hue2rgb(p, q, h + 1 / 3);
950
- g = hue2rgb(p, q, h);
951
- b = hue2rgb(p, q, h - 1 / 3);
952
- }
953
-
954
- r = Math.round(r * 255);
955
- g = Math.round(g * 255);
956
- b = Math.round(b * 255);
957
- }
958
- }
959
- // If it's not recognized, return null
960
- else {
961
- return null;
962
- }
963
-
964
- return { r, g, b, a };
965
- }
980
+ return { r, g, b, a };
981
+ }
966
982
  }
967
- window.customElements.define('fig-input-color', FigInputColor);
983
+ window.customElements.define("fig-input-color", FigInputColor);
968
984
 
969
985
  /* Checkbox */
970
986
  class FigCheckbox extends HTMLElement {
971
-
972
- constructor() {
973
- super()
974
- this.input = document.createElement("input")
975
- this.input.setAttribute("id", uniqueId())
976
- this.input.setAttribute("name", this.getAttribute("name") || "checkbox")
977
- this.input.setAttribute("type", "checkbox")
978
- this.labelElement = document.createElement("label")
979
- this.labelElement.setAttribute("for", this.input.id)
980
- }
981
- connectedCallback() {
982
- this.checked = this.input.checked = this.hasAttribute("checked") ? this.getAttribute('checked').toLowerCase() === "true" : false
983
- this.input.addEventListener("change", this.handleInput.bind(this))
984
-
985
- if (this.hasAttribute('disabled')) {
986
- this.input.disabled = true
987
- }
988
- if (this.hasAttribute('indeterminate')) {
989
- this.input.indeterminate = true
990
- this.input.setAttribute("indeterminate", "true")
991
- }
992
-
993
- this.append(this.input)
994
- this.append(this.labelElement)
995
-
996
- this.render()
997
- }
998
- static get observedAttributes() {
999
- return ["on", "disabled", "label", "checked"];
1000
- }
1001
-
1002
- render() {
1003
-
1004
- }
1005
-
1006
- focus() {
1007
- this.input.focus()
1008
- }
1009
-
1010
- disconnectedCallback() {
1011
- this.input.remove()
1012
- }
1013
-
1014
- attributeChangedCallback(name, oldValue, newValue) {
1015
- console.log(`Attribute ${name} change:`, oldValue, newValue);
1016
- switch (name) {
1017
- case "label":
1018
- this.labelElement.innerText = newValue
1019
- break;
1020
- case "checked":
1021
- this.checked = this.input.checked = this.hasAttribute("checked") ? true : false
1022
- break;
1023
- case "name":
1024
- case "value":
1025
- this.input.setAttribute(name, newValue)
1026
- break;
1027
- }
1028
- }
1029
-
1030
- handleInput(e) {
1031
- this.input.indeterminate = false
1032
- this.input.removeAttribute("indeterminate")
1033
- this.value = this.input.value
1034
- }
1035
-
987
+ constructor() {
988
+ super();
989
+ this.input = document.createElement("input");
990
+ this.input.setAttribute("id", uniqueId());
991
+ this.input.setAttribute("name", this.getAttribute("name") || "checkbox");
992
+ this.input.setAttribute("type", "checkbox");
993
+ this.labelElement = document.createElement("label");
994
+ this.labelElement.setAttribute("for", this.input.id);
995
+ }
996
+ connectedCallback() {
997
+ this.checked = this.input.checked = this.hasAttribute("checked")
998
+ ? this.getAttribute("checked").toLowerCase() === "true"
999
+ : false;
1000
+ this.input.addEventListener("change", this.handleInput.bind(this));
1001
+
1002
+ if (this.hasAttribute("disabled")) {
1003
+ this.input.disabled = true;
1004
+ }
1005
+ if (this.hasAttribute("indeterminate")) {
1006
+ this.input.indeterminate = true;
1007
+ this.input.setAttribute("indeterminate", "true");
1008
+ }
1009
+
1010
+ this.append(this.input);
1011
+ this.append(this.labelElement);
1012
+
1013
+ this.render();
1014
+ }
1015
+ static get observedAttributes() {
1016
+ return ["on", "disabled", "label", "checked"];
1017
+ }
1018
+
1019
+ render() {}
1020
+
1021
+ focus() {
1022
+ this.input.focus();
1023
+ }
1024
+
1025
+ disconnectedCallback() {
1026
+ this.input.remove();
1027
+ }
1028
+
1029
+ attributeChangedCallback(name, oldValue, newValue) {
1030
+ console.log(`Attribute ${name} change:`, oldValue, newValue);
1031
+ switch (name) {
1032
+ case "label":
1033
+ this.labelElement.innerText = newValue;
1034
+ break;
1035
+ case "checked":
1036
+ this.checked = this.input.checked = this.hasAttribute("checked")
1037
+ ? true
1038
+ : false;
1039
+ break;
1040
+ case "name":
1041
+ case "value":
1042
+ this.input.setAttribute(name, newValue);
1043
+ break;
1044
+ }
1045
+ }
1046
+
1047
+ handleInput(e) {
1048
+ this.input.indeterminate = false;
1049
+ this.input.removeAttribute("indeterminate");
1050
+ this.value = this.input.value;
1051
+ }
1036
1052
  }
1037
- window.customElements.define('fig-checkbox', FigCheckbox);
1053
+ window.customElements.define("fig-checkbox", FigCheckbox);
1038
1054
 
1039
1055
  /* Radio */
1040
1056
  class FigRadio extends FigCheckbox {
1041
- constructor() {
1042
- super()
1043
- this.input.setAttribute("type", "radio")
1044
- this.input.setAttribute("name", this.getAttribute("name") || "radio")
1045
- }
1057
+ constructor() {
1058
+ super();
1059
+ this.input.setAttribute("type", "radio");
1060
+ this.input.setAttribute("name", this.getAttribute("name") || "radio");
1061
+ }
1046
1062
  }
1047
- window.customElements.define('fig-radio', FigRadio);
1063
+ window.customElements.define("fig-radio", FigRadio);
1048
1064
 
1049
1065
  /* Switch */
1050
1066
  class FigSwitch extends FigCheckbox {
1051
- render() {
1052
- this.input.setAttribute("class", "switch")
1053
- this.on = this.input.checked = this.hasAttribute("on") ? this.getAttribute('on').toLowerCase() === "true" : false
1054
- }
1067
+ render() {
1068
+ this.input.setAttribute("class", "switch");
1069
+ this.on = this.input.checked = this.hasAttribute("on")
1070
+ ? this.getAttribute("on").toLowerCase() === "true"
1071
+ : false;
1072
+ }
1055
1073
  }
1056
- window.customElements.define('fig-switch', FigSwitch);
1057
-
1074
+ window.customElements.define("fig-switch", FigSwitch);
1058
1075
 
1059
1076
  /* Template */
1060
1077
  class MyCustomElement extends HTMLElement {
1061
- static observedAttributes = ["color", "size"];
1078
+ static observedAttributes = ["color", "size"];
1062
1079
 
1063
- constructor() {
1064
- // Always call super first in constructor
1065
- super();
1066
- }
1080
+ constructor() {
1081
+ // Always call super first in constructor
1082
+ super();
1083
+ }
1067
1084
 
1068
- connectedCallback() {
1069
- console.log("Custom element added to page.");
1070
- }
1085
+ connectedCallback() {
1086
+ console.log("Custom element added to page.");
1087
+ }
1071
1088
 
1072
- disconnectedCallback() {
1073
- console.log("Custom element removed from page.");
1074
- }
1089
+ disconnectedCallback() {
1090
+ console.log("Custom element removed from page.");
1091
+ }
1075
1092
 
1076
- adoptedCallback() {
1077
- console.log("Custom element moved to new page.");
1078
- }
1093
+ adoptedCallback() {
1094
+ console.log("Custom element moved to new page.");
1095
+ }
1079
1096
 
1080
- attributeChangedCallback(name, oldValue, newValue) {
1081
- console.log(`Attribute ${name} has changed.`);
1082
- }
1097
+ attributeChangedCallback(name, oldValue, newValue) {
1098
+ console.log(`Attribute ${name} has changed.`);
1099
+ }
1083
1100
  }
1084
1101
 
1085
1102
  customElements.define("my-custom-element", MyCustomElement);
1086
-