@rogieking/figui3 1.0.26 → 1.0.28

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 +937 -897
  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,208 @@ 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);
525
+ window.customElements.define("fig-segment", FigSegment);
500
526
 
501
527
  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
- }
528
+ constructor() {
529
+ super();
530
+ }
531
+ connectedCallback() {
532
+ this.name = this.getAttribute("name") || "segmented-control";
533
+ this.addEventListener("click", this.handleClick.bind(this));
534
+ }
535
+ handleClick(event) {
536
+ const target = event.target;
537
+ if (target.nodeName.toLowerCase() === "fig-segment") {
538
+ const segments = this.querySelectorAll("fig-segment");
539
+ for (const segment of segments) {
540
+ if (segment === target) {
541
+ this.selectedSegment = segment;
542
+ } else {
543
+ segment.removeAttribute("selected");
520
544
  }
545
+ }
521
546
  }
547
+ }
522
548
  }
523
- window.customElements.define('fig-segmented-control', FigSegmentedControl);
524
-
525
-
549
+ window.customElements.define("fig-segmented-control", FigSegmentedControl);
526
550
 
527
551
  /* Slider */
528
552
  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">
553
+ #typeDefaults = {
554
+ range: { min: 0, max: 100, step: 1 },
555
+ hue: { min: 0, max: 255, step: 1 },
556
+ delta: { min: -100, max: 100, step: 1 },
557
+ opacity: { min: 0, max: 1, step: 0.01, color: "#FF0000" },
558
+ };
559
+ constructor() {
560
+ super();
561
+ }
562
+ connectedCallback() {
563
+ this.value = this.getAttribute("value");
564
+ this.default = this.getAttribute("default") || null;
565
+ this.type = this.getAttribute("type") || "range";
566
+
567
+ const defaults = this.#typeDefaults[this.type];
568
+ this.min = this.getAttribute("min") || defaults.min;
569
+ this.max = this.getAttribute("max") || defaults.max;
570
+ this.step = this.getAttribute("step") || defaults.step;
571
+ this.color = this.getAttribute("color") || defaults?.color;
572
+ this.disabled = this.getAttribute("disabled") ? true : false;
573
+
574
+ if (this.color) {
575
+ this.style.setProperty("--color", this.color);
576
+ }
577
+
578
+ let html = "";
579
+ let slider = `<div class="fig-slider-input-container">
557
580
  <input
558
581
  type="range"
559
582
  ${this.disabled ? "disabled" : ""}
@@ -563,9 +586,9 @@ class FigSlider extends HTMLElement {
563
586
  class="${this.type}"
564
587
  value="${this.value}">
565
588
  ${this.innerHTML}
566
- </div>`
567
- if (this.getAttribute("text")) {
568
- html = `${slider}
589
+ </div>`;
590
+ if (this.getAttribute("text")) {
591
+ html = `${slider}
569
592
  <fig-input-text
570
593
  placeholder="##"
571
594
  type="number"
@@ -573,205 +596,212 @@ class FigSlider extends HTMLElement {
573
596
  max="${this.max}"
574
597
  step="${this.step}"
575
598
  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
- }
599
+ </fig-input-text>`;
600
+ } else {
601
+ html = slider;
602
+ }
603
+
604
+ this.innerHTML = html;
605
+
606
+ this.textInput = this.querySelector("input[type=number]");
607
+
608
+ this.input = this.querySelector("[type=range]");
609
+ this.input.addEventListener("input", this.handleInput.bind(this));
610
+ this.handleInput();
611
+
612
+ if (this.textInput) {
613
+ this.textInput.addEventListener("input", this.handleTextInput.bind(this));
614
+ }
615
+
616
+ if (this.default) {
617
+ this.style.setProperty("--default", this.calculateNormal(this.default));
618
+ }
619
+ //child nodes hack
620
+ requestAnimationFrame(() => {
621
+ this.datalist = this.querySelector("datalist");
622
+ if (this.datalist) {
623
+ this.datalist.setAttribute(
624
+ "id",
625
+ this.datalist.getAttribute("id") || uniqueId()
626
+ );
627
+ this.input.setAttribute("list", this.datalist.getAttribute("id"));
628
+ }
629
+ });
630
+ }
631
+ static get observedAttributes() {
632
+ return ["value", "step", "min", "max", "type", "disabled"];
633
+ }
634
+
635
+ focus() {
636
+ this.input.focus();
637
+ }
638
+
639
+ attributeChangedCallback(name, oldValue, newValue) {
640
+ if (this.input) {
641
+ console.log("attributeChangedCallback:", name, oldValue, newValue);
642
+ switch (name) {
643
+ case "color":
644
+ this.color = newValue;
645
+ this.style.setProperty("--color", this.color);
646
+ break;
647
+ case "type":
648
+ this.input.className = newValue;
649
+ break;
650
+ case "disabled":
651
+ this.disabled = this.input.disabled =
652
+ newValue === "true" ||
653
+ (newValue === undefined && newValue !== null);
654
+ if (this.textInput) {
655
+ this.textInput.disabled = this.disabled;
656
+ }
657
+ break;
658
+ default:
659
+ this[name] = this.input[name] = newValue;
660
+ if (this.textInput) {
661
+ this.textInput[name] = newValue;
662
+ }
663
+ this.handleInput();
664
+ break;
665
+ }
666
+ }
667
+ }
668
+ handleTextInput() {
669
+ if (this.textInput) {
670
+ this.input.value = Number(this.textInput.value);
671
+ this.handleInput();
672
+ }
673
+ }
674
+ calculateNormal(value) {
675
+ let min = Number(this.input.min);
676
+ let max = Number(this.input.max);
677
+ let val = Number(value);
678
+ return (val - min) / (max - min);
679
+ }
680
+
681
+ handleInput() {
682
+ let val = Number(this.input.value);
683
+ this.value = val;
684
+ let complete = this.calculateNormal(val);
685
+ this.style.setProperty("--slider-complete", complete);
686
+ if (this.textInput) {
687
+ this.textInput.value = val;
688
+ }
689
+ }
662
690
  }
663
- window.customElements.define('fig-slider', FigSlider);
691
+ window.customElements.define("fig-slider", FigSlider);
664
692
 
665
693
  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
694
+ constructor() {
695
+ super();
696
+ }
697
+ connectedCallback() {
698
+ const append = this.querySelector("[slot=append]");
699
+ const prepend = this.querySelector("[slot=prepend]");
700
+ this.multiline = this.hasAttribute("multiline") || false;
701
+ this.value = this.getAttribute("value");
702
+ this.type = this.getAttribute("type") || "text";
703
+ this.placeholder = this.getAttribute("placeholder");
704
+
705
+ let html = `<input
678
706
  type="${this.type}"
679
707
  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
- }
708
+ value="${this.value}" />`;
709
+ if (this.multiline) {
710
+ html = `<textarea
711
+ placeholder="${this.placeholder}">${this.value}</textarea>`;
712
+ }
713
+ this.innerHTML = html;
714
+
715
+ this.input = this.querySelector("input,textarea");
716
+ this.input.addEventListener("input", this.handleInput.bind(this));
717
+
718
+ if (prepend) {
719
+ prepend.addEventListener("click", this.focus.bind(this));
720
+ this.prepend(prepend);
721
+ }
722
+ if (append) {
723
+ append.addEventListener("click", this.focus.bind(this));
724
+ this.append(append);
725
+ }
726
+ }
727
+ focus() {
728
+ this.input.focus();
729
+ }
730
+ handleInput() {
731
+ this.value = this.input.value;
732
+ }
733
+
734
+ static get observedAttributes() {
735
+ return ["value", "placeholder", "label", "disabled", "type"];
736
+ }
737
+
738
+ attributeChangedCallback(name, oldValue, newValue) {
739
+ if (this.input) {
740
+ switch (name) {
741
+ case "label":
742
+ this.disabled = this.input.disabled = newValue;
743
+ break;
744
+ default:
745
+ this[name] = this.input[name] = newValue;
746
+ break;
747
+ }
748
+ }
749
+ }
722
750
  }
723
- window.customElements.define('fig-input-text', FigInputText);
724
-
751
+ window.customElements.define("fig-input-text", FigInputText);
725
752
 
726
753
  /* Form Field */
727
754
  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
- }
755
+ constructor() {
756
+ super();
757
+ }
758
+ connectedCallback() {
759
+ requestAnimationFrame(() => {
760
+ this.label = this.querySelector("label");
761
+ this.input = Array.from(this.childNodes).find((node) =>
762
+ node.nodeName.toLowerCase().startsWith("fig-")
763
+ );
764
+ if (this.input && this.label) {
765
+ this.label.addEventListener("click", this.focus.bind(this));
766
+ let inputId = this.input.getAttribute("id") || uniqueId();
767
+ this.input.setAttribute("id", inputId);
768
+ this.label.setAttribute("for", inputId);
769
+ }
770
+ });
771
+ }
772
+ focus() {
773
+ this.input.focus();
774
+ }
746
775
  }
747
- window.customElements.define('fig-field', FigField);
748
-
776
+ window.customElements.define("fig-field", FigField);
749
777
 
750
778
  /* Color swatch */
751
779
  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">
780
+ #rgba;
781
+ #swatch;
782
+ textInput;
783
+ #alphaInput;
784
+ constructor() {
785
+ super();
786
+ }
787
+ connectedCallback() {
788
+ this.#rgba = this.convertToRGBA(this.getAttribute("value"));
789
+ const alpha = (this.#rgba.a * 100).toFixed(0);
790
+ this.value = this.rgbAlphaToHex(
791
+ {
792
+ r: this.#rgba.r,
793
+ g: this.#rgba.g,
794
+ b: this.#rgba.b,
795
+ },
796
+ alpha
797
+ );
798
+ let html = ``;
799
+ if (this.getAttribute("text")) {
800
+ let label = `<fig-input-text placeholder="Text" value="${this.getAttribute(
801
+ "value"
802
+ )}"></fig-input-text>`;
803
+ if (this.getAttribute("alpha")) {
804
+ label += `<fig-tooltip text="Opacity">
775
805
  <fig-input-text
776
806
  placeholder="##"
777
807
  type="number"
@@ -780,307 +810,317 @@ class FigInputColor extends HTMLElement {
780
810
  value="${alpha}">
781
811
  <span slot="append">%</slot>
782
812
  </fig-input-text>
783
- </fig-tooltip>`
784
- }
785
- html = `<div class="input-combo">
813
+ </fig-tooltip>`;
814
+ }
815
+ html = `<div class="input-combo">
786
816
  <input type="color" value="${this.value}" />
787
817
  ${label}
788
- </div>`
818
+ </div>`;
819
+ } else {
820
+ html = `<input type="color" value="${this.value}" />`;
821
+ }
822
+ this.innerHTML = html;
823
+
824
+ this.style.setProperty("--alpha", this.#rgba.a);
825
+
826
+ this.#swatch = this.querySelector("[type=color]");
827
+ this.textInput = this.querySelector("[type=text]");
828
+ this.#alphaInput = this.querySelector("[type=number]");
829
+
830
+ this.#swatch.disabled = this.hasAttribute("disabled");
831
+ this.#swatch.addEventListener("input", this.handleInput.bind(this));
832
+
833
+ if (this.textInput) {
834
+ this.textInput.value = this.#swatch.value = this.rgbAlphaToHex(
835
+ this.#rgba,
836
+ 1
837
+ );
838
+ }
839
+
840
+ if (this.#alphaInput) {
841
+ this.#alphaInput.addEventListener(
842
+ "input",
843
+ this.handleAlphaInput.bind(this)
844
+ );
845
+ }
846
+ }
847
+ handleAlphaInput(event) {
848
+ //do not propagate to onInput handler for web component
849
+ event.stopPropagation();
850
+ this.#rgba = this.convertToRGBA(this.#swatch.value);
851
+ this.#rgba.a = Number(this.#alphaInput.value) / 100;
852
+ this.value = this.rgbAlphaToHex(
853
+ {
854
+ r: this.#rgba.r,
855
+ g: this.#rgba.g,
856
+ b: this.#rgba.b,
857
+ },
858
+ this.#rgba.a
859
+ );
860
+ this.style.setProperty("--alpha", this.#rgba.a);
861
+ const e = new CustomEvent("input", {
862
+ bubbles: true,
863
+ cancelable: true,
864
+ });
865
+ this.dispatchEvent(e);
866
+ }
867
+
868
+ focus() {
869
+ this.#swatch.focus();
870
+ }
871
+
872
+ handleInput(event) {
873
+ //do not propagate to onInput handler for web component
874
+ event.stopPropagation();
875
+
876
+ let alpha = this.#rgba.a;
877
+ this.#rgba = this.convertToRGBA(this.#swatch.value);
878
+ this.#rgba.a = alpha;
879
+ if (this.textInput) {
880
+ this.textInput.value = this.#swatch.value;
881
+ }
882
+ this.style.setProperty("--alpha", this.#rgba.a);
883
+ this.value = this.rgbAlphaToHex(
884
+ {
885
+ r: this.#rgba.r,
886
+ g: this.#rgba.g,
887
+ b: this.#rgba.b,
888
+ },
889
+ alpha
890
+ );
891
+ this.alpha = alpha;
892
+ if (this.#alphaInput) {
893
+ this.#alphaInput.value = this.#rgba.a.toFixed(0);
894
+ }
895
+ const e = new CustomEvent("input", {
896
+ bubbles: true,
897
+ cancelable: true,
898
+ });
899
+ this.dispatchEvent(e);
900
+ }
901
+
902
+ static get observedAttributes() {
903
+ return ["value", "style"];
904
+ }
905
+
906
+ attributeChangedCallback(name, oldValue, newValue) {
907
+ //this[name] = newValue;
908
+ }
909
+
910
+ rgbAlphaToHex({ r, g, b }, a = 1) {
911
+ // Ensure r, g, b are integers between 0 and 255
912
+ r = Math.max(0, Math.min(255, Math.round(r)));
913
+ g = Math.max(0, Math.min(255, Math.round(g)));
914
+ b = Math.max(0, Math.min(255, Math.round(b)));
915
+
916
+ // Ensure alpha is between 0 and 1
917
+ a = Math.max(0, Math.min(1, a));
918
+
919
+ // Convert to hex and pad with zeros if necessary
920
+ const hexR = r.toString(16).padStart(2, "0");
921
+ const hexG = g.toString(16).padStart(2, "0");
922
+ const hexB = b.toString(16).padStart(2, "0");
923
+
924
+ // If alpha is 1, return 6-digit hex
925
+ if (a === 1) {
926
+ return `#${hexR}${hexG}${hexB}`;
927
+ }
928
+
929
+ // Otherwise, include alpha in 8-digit hex
930
+ const alpha = Math.round(a * 255);
931
+ const hexA = alpha.toString(16).padStart(2, "0");
932
+ return `#${hexR}${hexG}${hexB}${hexA}`;
933
+ }
934
+
935
+ convertToRGBA(color) {
936
+ let r,
937
+ g,
938
+ b,
939
+ a = 1;
940
+
941
+ // Handle hex colors
942
+ if (color.startsWith("#")) {
943
+ let hex = color.slice(1);
944
+ if (hex.length === 8) {
945
+ a = parseInt(hex.slice(6), 16) / 255;
946
+ hex = hex.slice(0, 6);
947
+ }
948
+ r = parseInt(hex.slice(0, 2), 16);
949
+ g = parseInt(hex.slice(2, 4), 16);
950
+ b = parseInt(hex.slice(4, 6), 16);
951
+ }
952
+ // Handle rgba colors
953
+ else if (color.startsWith("rgba") || color.startsWith("rgb")) {
954
+ let matches = color.match(
955
+ /rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)/
956
+ );
957
+ if (matches) {
958
+ r = parseInt(matches[1]);
959
+ g = parseInt(matches[2]);
960
+ b = parseInt(matches[3]);
961
+ a = matches[4] ? parseFloat(matches[4]) : 1;
962
+ }
963
+ }
964
+ // Handle hsla colors
965
+ else if (color.startsWith("hsla") || color.startsWith("hsl")) {
966
+ let matches = color.match(
967
+ /hsla?\((\d+),\s*(\d+)%,\s*(\d+)%(?:,\s*(\d+(?:\.\d+)?))?\)/
968
+ );
969
+ if (matches) {
970
+ let h = parseInt(matches[1]) / 360;
971
+ let s = parseInt(matches[2]) / 100;
972
+ let l = parseInt(matches[3]) / 100;
973
+ a = matches[4] ? parseFloat(matches[4]) : 1;
974
+
975
+ if (s === 0) {
976
+ r = g = b = l; // achromatic
789
977
  } else {
790
- html = `<input type="color" value="${this.value}" />`
791
- }
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
-
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)
978
+ let hue2rgb = (p, q, t) => {
979
+ if (t < 0) t += 1;
980
+ if (t > 1) t -= 1;
981
+ if (t < 1 / 6) return p + (q - p) * 6 * t;
982
+ if (t < 1 / 2) return q;
983
+ if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
984
+ return p;
985
+ };
986
+
987
+ let q = l < 0.5 ? l * (1 + s) : l + s - l * s;
988
+ let p = 2 * l - q;
989
+ r = hue2rgb(p, q, h + 1 / 3);
990
+ g = hue2rgb(p, q, h);
991
+ b = hue2rgb(p, q, h - 1 / 3);
861
992
  }
862
- const e = new CustomEvent('input', {
863
- bubbles: true,
864
- cancelable: true
865
- });
866
- this.dispatchEvent(e)
867
- }
868
993
 
869
- static get observedAttributes() {
870
- return ['value', 'style'];
994
+ r = Math.round(r * 255);
995
+ g = Math.round(g * 255);
996
+ b = Math.round(b * 255);
997
+ }
871
998
  }
872
-
873
- attributeChangedCallback(name, oldValue, newValue) {
874
- //this[name] = newValue;
999
+ // If it's not recognized, return null
1000
+ else {
1001
+ return null;
875
1002
  }
876
1003
 
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
- }
1004
+ return { r, g, b, a };
1005
+ }
966
1006
  }
967
- window.customElements.define('fig-input-color', FigInputColor);
1007
+ window.customElements.define("fig-input-color", FigInputColor);
968
1008
 
969
1009
  /* Checkbox */
970
1010
  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
-
1011
+ constructor() {
1012
+ super();
1013
+ this.input = document.createElement("input");
1014
+ this.input.setAttribute("id", uniqueId());
1015
+ this.input.setAttribute("name", this.getAttribute("name") || "checkbox");
1016
+ this.input.setAttribute("type", "checkbox");
1017
+ this.labelElement = document.createElement("label");
1018
+ this.labelElement.setAttribute("for", this.input.id);
1019
+ }
1020
+ connectedCallback() {
1021
+ this.checked = this.input.checked = this.hasAttribute("checked")
1022
+ ? this.getAttribute("checked").toLowerCase() === "true"
1023
+ : false;
1024
+ this.input.addEventListener("change", this.handleInput.bind(this));
1025
+
1026
+ if (this.hasAttribute("disabled")) {
1027
+ this.input.disabled = true;
1028
+ }
1029
+ if (this.hasAttribute("indeterminate")) {
1030
+ this.input.indeterminate = true;
1031
+ this.input.setAttribute("indeterminate", "true");
1032
+ }
1033
+
1034
+ this.append(this.input);
1035
+ this.append(this.labelElement);
1036
+
1037
+ this.render();
1038
+ }
1039
+ static get observedAttributes() {
1040
+ return ["on", "disabled", "label", "checked"];
1041
+ }
1042
+
1043
+ render() {}
1044
+
1045
+ focus() {
1046
+ this.input.focus();
1047
+ }
1048
+
1049
+ disconnectedCallback() {
1050
+ this.input.remove();
1051
+ }
1052
+
1053
+ attributeChangedCallback(name, oldValue, newValue) {
1054
+ console.log(`Attribute ${name} change:`, oldValue, newValue);
1055
+ switch (name) {
1056
+ case "label":
1057
+ this.labelElement.innerText = newValue;
1058
+ break;
1059
+ case "checked":
1060
+ this.checked = this.input.checked = this.hasAttribute("checked")
1061
+ ? true
1062
+ : false;
1063
+ break;
1064
+ case "name":
1065
+ case "value":
1066
+ this.input.setAttribute(name, newValue);
1067
+ break;
1068
+ }
1069
+ }
1070
+
1071
+ handleInput(e) {
1072
+ this.input.indeterminate = false;
1073
+ this.input.removeAttribute("indeterminate");
1074
+ this.value = this.input.value;
1075
+ }
1036
1076
  }
1037
- window.customElements.define('fig-checkbox', FigCheckbox);
1077
+ window.customElements.define("fig-checkbox", FigCheckbox);
1038
1078
 
1039
1079
  /* Radio */
1040
1080
  class FigRadio extends FigCheckbox {
1041
- constructor() {
1042
- super()
1043
- this.input.setAttribute("type", "radio")
1044
- this.input.setAttribute("name", this.getAttribute("name") || "radio")
1045
- }
1081
+ constructor() {
1082
+ super();
1083
+ this.input.setAttribute("type", "radio");
1084
+ this.input.setAttribute("name", this.getAttribute("name") || "radio");
1085
+ }
1046
1086
  }
1047
- window.customElements.define('fig-radio', FigRadio);
1087
+ window.customElements.define("fig-radio", FigRadio);
1048
1088
 
1049
1089
  /* Switch */
1050
1090
  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
- }
1091
+ render() {
1092
+ this.input.setAttribute("class", "switch");
1093
+ this.on = this.input.checked = this.hasAttribute("on")
1094
+ ? this.getAttribute("on").toLowerCase() === "true"
1095
+ : false;
1096
+ }
1055
1097
  }
1056
- window.customElements.define('fig-switch', FigSwitch);
1057
-
1098
+ window.customElements.define("fig-switch", FigSwitch);
1058
1099
 
1059
1100
  /* Template */
1060
1101
  class MyCustomElement extends HTMLElement {
1061
- static observedAttributes = ["color", "size"];
1102
+ static observedAttributes = ["color", "size"];
1062
1103
 
1063
- constructor() {
1064
- // Always call super first in constructor
1065
- super();
1066
- }
1104
+ constructor() {
1105
+ // Always call super first in constructor
1106
+ super();
1107
+ }
1067
1108
 
1068
- connectedCallback() {
1069
- console.log("Custom element added to page.");
1070
- }
1109
+ connectedCallback() {
1110
+ console.log("Custom element added to page.");
1111
+ }
1071
1112
 
1072
- disconnectedCallback() {
1073
- console.log("Custom element removed from page.");
1074
- }
1113
+ disconnectedCallback() {
1114
+ console.log("Custom element removed from page.");
1115
+ }
1075
1116
 
1076
- adoptedCallback() {
1077
- console.log("Custom element moved to new page.");
1078
- }
1117
+ adoptedCallback() {
1118
+ console.log("Custom element moved to new page.");
1119
+ }
1079
1120
 
1080
- attributeChangedCallback(name, oldValue, newValue) {
1081
- console.log(`Attribute ${name} has changed.`);
1082
- }
1121
+ attributeChangedCallback(name, oldValue, newValue) {
1122
+ console.log(`Attribute ${name} has changed.`);
1123
+ }
1083
1124
  }
1084
1125
 
1085
1126
  customElements.define("my-custom-element", MyCustomElement);
1086
-