@rogieking/figui3 2.22.3 → 2.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/fig.js +38 -28
  2. package/index.html +20 -0
  3. package/package.json +1 -1
  4. package/dist/fig.js +0 -74
package/fig.js CHANGED
@@ -1055,7 +1055,6 @@ customElements.define("fig-dialog", FigDialog, { extends: "dialog" });
1055
1055
  * @attr {boolean|string} open - Open when present and not "false".
1056
1056
  */
1057
1057
  class FigPopup extends HTMLDialogElement {
1058
- #viewportPadding = 8;
1059
1058
  #anchorObserver = null;
1060
1059
  #contentObserver = null;
1061
1060
  #mutationObserver = null;
@@ -1078,7 +1077,9 @@ class FigPopup extends HTMLDialogElement {
1078
1077
  constructor() {
1079
1078
  super();
1080
1079
  this.#boundReposition = this.#queueReposition.bind(this);
1081
- this.#boundScroll = () => { if (this.open) this.#positionPopup(); };
1080
+ this.#boundScroll = (e) => {
1081
+ if (this.open && !this.contains(e.target)) this.#positionPopup();
1082
+ };
1082
1083
  this.#boundOutsidePointerDown = this.#handleOutsidePointerDown.bind(this);
1083
1084
  this.#boundPointerDown = this.#handlePointerDown.bind(this);
1084
1085
  this.#boundPointerMove = this.#handlePointerMove.bind(this);
@@ -1086,7 +1087,7 @@ class FigPopup extends HTMLDialogElement {
1086
1087
  }
1087
1088
 
1088
1089
  static get observedAttributes() {
1089
- return ["open", "anchor", "position", "offset", "variant", "theme", "drag", "handle", "autoresize"];
1090
+ return ["open", "anchor", "position", "offset", "variant", "theme", "drag", "handle", "autoresize", "viewport-margin"];
1090
1091
  }
1091
1092
 
1092
1093
  get open() {
@@ -1544,6 +1545,17 @@ class FigPopup extends HTMLDialogElement {
1544
1545
  };
1545
1546
  }
1546
1547
 
1548
+ #parseViewportMargins() {
1549
+ const raw = (this.getAttribute("viewport-margin") || "8").trim();
1550
+ const tokens = raw.split(/\s+/).map(Number).filter(n => !Number.isNaN(n));
1551
+ const d = 8;
1552
+ if (tokens.length === 0) return { top: d, right: d, bottom: d, left: d };
1553
+ if (tokens.length === 1) return { top: tokens[0], right: tokens[0], bottom: tokens[0], left: tokens[0] };
1554
+ if (tokens.length === 2) return { top: tokens[0], right: tokens[1], bottom: tokens[0], left: tokens[1] };
1555
+ if (tokens.length === 3) return { top: tokens[0], right: tokens[1], bottom: tokens[2], left: tokens[1] };
1556
+ return { top: tokens[0], right: tokens[1], bottom: tokens[2], left: tokens[3] };
1557
+ }
1558
+
1547
1559
  #getPlacementCandidates(vertical, horizontal, shorthand) {
1548
1560
  const opp = { top: "bottom", bottom: "top", left: "right", right: "left", center: "center" };
1549
1561
 
@@ -1688,31 +1700,29 @@ class FigPopup extends HTMLDialogElement {
1688
1700
  this.style.setProperty("--beak-offset", `${beakOffset}px`);
1689
1701
  }
1690
1702
 
1691
- #overflowScore(coords, popupRect) {
1703
+ #overflowScore(coords, popupRect, m) {
1692
1704
  const vw = window.innerWidth;
1693
1705
  const vh = window.innerHeight;
1694
1706
  const right = coords.left + popupRect.width;
1695
1707
  const bottom = coords.top + popupRect.height;
1696
- const pad = this.#viewportPadding;
1697
1708
 
1698
- const overflowLeft = Math.max(0, pad - coords.left);
1699
- const overflowTop = Math.max(0, pad - coords.top);
1700
- const overflowRight = Math.max(0, right - (vw - pad));
1701
- const overflowBottom = Math.max(0, bottom - (vh - pad));
1709
+ const overflowLeft = Math.max(0, m.left - coords.left);
1710
+ const overflowTop = Math.max(0, m.top - coords.top);
1711
+ const overflowRight = Math.max(0, right - (vw - m.right));
1712
+ const overflowBottom = Math.max(0, bottom - (vh - m.bottom));
1702
1713
 
1703
1714
  return overflowLeft + overflowTop + overflowRight + overflowBottom;
1704
1715
  }
1705
1716
 
1706
- #fits(coords, popupRect) {
1707
- return this.#overflowScore(coords, popupRect) === 0;
1717
+ #fits(coords, popupRect, m) {
1718
+ return this.#overflowScore(coords, popupRect, m) === 0;
1708
1719
  }
1709
1720
 
1710
- #clamp(coords, popupRect) {
1711
- const pad = this.#viewportPadding;
1712
- const minLeft = pad;
1713
- const minTop = pad;
1714
- const maxLeft = Math.max(pad, window.innerWidth - popupRect.width - pad);
1715
- const maxTop = Math.max(pad, window.innerHeight - popupRect.height - pad);
1721
+ #clamp(coords, popupRect, m) {
1722
+ const minLeft = m.left;
1723
+ const minTop = m.top;
1724
+ const maxLeft = Math.max(m.left, window.innerWidth - popupRect.width - m.right);
1725
+ const maxTop = Math.max(m.top, window.innerHeight - popupRect.height - m.bottom);
1716
1726
 
1717
1727
  return {
1718
1728
  left: Math.min(maxLeft, Math.max(minLeft, coords.left)),
@@ -1727,14 +1737,15 @@ class FigPopup extends HTMLDialogElement {
1727
1737
  const offset = this.#parseOffset();
1728
1738
  const { vertical, horizontal, shorthand } = this.#parsePosition();
1729
1739
  const anchor = this.#resolveAnchor();
1740
+ const m = this.#parseViewportMargins();
1730
1741
 
1731
1742
  if (!anchor) {
1732
1743
  this.#updatePopoverBeak(null, popupRect, 0, 0, "top");
1733
1744
  const centered = {
1734
- left: (window.innerWidth - popupRect.width) / 2,
1735
- top: (window.innerHeight - popupRect.height) / 2,
1745
+ left: (m.left + (window.innerWidth - m.right - m.left - popupRect.width) / 2),
1746
+ top: (m.top + (window.innerHeight - m.bottom - m.top - popupRect.height) / 2),
1736
1747
  };
1737
- const clamped = this.#clamp(centered, popupRect);
1748
+ const clamped = this.#clamp(centered, popupRect, m);
1738
1749
  this.style.left = `${clamped.left}px`;
1739
1750
  this.style.top = `${clamped.top}px`;
1740
1751
  return;
@@ -1751,31 +1762,30 @@ class FigPopup extends HTMLDialogElement {
1751
1762
  const placementSide = this.#getPlacementSide(v, h, s);
1752
1763
 
1753
1764
  if (s) {
1754
- // Shorthand: clamp cross-axis to viewport, check primary axis fit
1755
- const clamped = this.#clamp(coords, popupRect);
1765
+ const clamped = this.#clamp(coords, popupRect, m);
1756
1766
  const primaryFits = (s === "left" || s === "right")
1757
- ? (coords.left >= this.#viewportPadding && coords.left + popupRect.width <= window.innerWidth - this.#viewportPadding)
1758
- : (coords.top >= this.#viewportPadding && coords.top + popupRect.height <= window.innerHeight - this.#viewportPadding);
1767
+ ? (coords.left >= m.left && coords.left + popupRect.width <= window.innerWidth - m.right)
1768
+ : (coords.top >= m.top && coords.top + popupRect.height <= window.innerHeight - m.bottom);
1759
1769
  if (primaryFits) {
1760
1770
  this.style.left = `${clamped.left}px`;
1761
1771
  this.style.top = `${clamped.top}px`;
1762
1772
  this.#updatePopoverBeak(anchorRect, popupRect, clamped.left, clamped.top, placementSide);
1763
1773
  return;
1764
1774
  }
1765
- const score = this.#overflowScore(coords, popupRect);
1775
+ const score = this.#overflowScore(coords, popupRect, m);
1766
1776
  if (score < bestScore) {
1767
1777
  bestScore = score;
1768
1778
  best = clamped;
1769
1779
  bestSide = placementSide;
1770
1780
  }
1771
1781
  } else {
1772
- if (this.#fits(coords, popupRect)) {
1782
+ if (this.#fits(coords, popupRect, m)) {
1773
1783
  this.style.left = `${coords.left}px`;
1774
1784
  this.style.top = `${coords.top}px`;
1775
1785
  this.#updatePopoverBeak(anchorRect, popupRect, coords.left, coords.top, placementSide);
1776
1786
  return;
1777
1787
  }
1778
- const score = this.#overflowScore(coords, popupRect);
1788
+ const score = this.#overflowScore(coords, popupRect, m);
1779
1789
  if (score < bestScore) {
1780
1790
  bestScore = score;
1781
1791
  best = coords;
@@ -1784,7 +1794,7 @@ class FigPopup extends HTMLDialogElement {
1784
1794
  }
1785
1795
  }
1786
1796
 
1787
- const clamped = this.#clamp(best || { left: 0, top: 0 }, popupRect);
1797
+ const clamped = this.#clamp(best || { left: 0, top: 0 }, popupRect, m);
1788
1798
  this.style.left = `${clamped.left}px`;
1789
1799
  this.style.top = `${clamped.top}px`;
1790
1800
  this.#updatePopoverBeak(anchorRect, popupRect, clamped.left, clamped.top, bestSide);
package/index.html CHANGED
@@ -3272,6 +3272,26 @@
3272
3272
  ...
3273
3273
  &lt;/dialog&gt;</code></pre>
3274
3274
 
3275
+ <h3>Viewport Margin</h3>
3276
+ <p class="description">Use <code>viewport-margin</code> to define safe areas the popup should avoid (e.g. a bottom toolbar). Uses CSS margin shorthand: <code>top right bottom left</code>.</p>
3277
+ <fig-button id="popup-open-viewport-margin"
3278
+ onclick="document.getElementById('popup-viewport-margin').open = true">Open (64px bottom margin)</fig-button>
3279
+ <dialog id="popup-viewport-margin"
3280
+ is="fig-popup"
3281
+ anchor="#popup-open-viewport-margin"
3282
+ position="bottom center"
3283
+ offset="8"
3284
+ viewport-margin="8 8 64 8">
3285
+ <vstack style="min-width: 14rem;">
3286
+ <strong style="padding: 0 var(--spacer-1);">Viewport Margin</strong>
3287
+ <p style="padding: 0 var(--spacer-1); margin: 0; font-size: var(--font-size-small); color: var(--figma-color-text-secondary);">This popup won't overlap the bottom 64px of the viewport.</p>
3288
+ <fig-input-text placeholder="Try scrolling down"></fig-input-text>
3289
+ </vstack>
3290
+ </dialog>
3291
+ <pre><code>&lt;dialog is="fig-popup" viewport-margin="8 8 64 8" position="bottom center" ...&gt;
3292
+ ...
3293
+ &lt;/dialog&gt;</code></pre>
3294
+
3275
3295
  </section>
3276
3296
  <hr>
3277
3297
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "2.22.3",
3
+ "version": "2.23.0",
4
4
  "description": "A lightweight web components library for building Figma plugin and widget UIs with native look and feel",
5
5
  "author": "Rogie King",
6
6
  "license": "MIT",
package/dist/fig.js DELETED
@@ -1,74 +0,0 @@
1
- function U(){return Date.now().toString(36)+Math.random().toString(36).substring(2)}if(window.customElements&&!window.customElements.get("fig-button")){class j extends HTMLElement{type;#j;constructor(){super();this.attachShadow({mode:"open"})}connectedCallback(){this.type=this.getAttribute("type")||"button",this.shadowRoot.innerHTML=`
2
- <style>
3
- button, button:hover, button:active {
4
- padding: 0 var(--spacer-2);
5
- appearance: none;
6
- display: flex;
7
- border: 0;
8
- flex: 1;
9
- text-align: center;
10
- align-items: stretch;
11
- justify-content: center;
12
- font: inherit;
13
- color: inherit;
14
- outline: 0;
15
- place-items: center;
16
- background: transparent;
17
- margin: calc(var(--spacer-2)*-1);
18
- height: var(--spacer-4);
19
- }
20
- </style>
21
- <button type="${this.type}">
22
- <slot></slot>
23
- </button>
24
- `,this.#j=this.hasAttribute("selected")&&this.getAttribute("selected")!=="false",requestAnimationFrame(()=>{this.button=this.shadowRoot.querySelector("button"),this.button.addEventListener("click",this.#J.bind(this))})}get type(){return this.type}set type(K){this.setAttribute("type",K)}get selected(){return this.#j}set selected(K){this.setAttribute("selected",K)}#J(){if(this.type==="toggle")this.toggleAttribute("selected",!this.hasAttribute("selected"));if(this.type==="submit")this.closest("form").dispatchEvent(new Event("submit"))}static get observedAttributes(){return["disabled","selected"]}attributeChangedCallback(K,J,Q){if(this.button)switch(this.button[K]=Q,K){case"disabled":this.disabled=this.button.disabled=Q==="true"||Q===void 0&&Q!==null;break;case"type":this.type=Q,this.button.type=this.type,this.button.setAttribute("type",this.type);break;case"selected":this.#j=Q==="true";break}}}window.customElements.define("fig-button",j)}if(window.customElements&&!window.customElements.get("fig-dropdown")){class j extends HTMLElement{constructor(){super();this.select=document.createElement("select"),this.optionsSlot=document.createElement("slot"),this.attachShadow({mode:"open"})}#j(){this.select.addEventListener("input",this.#J.bind(this)),this.select.addEventListener("change",this.#K.bind(this))}connectedCallback(){this.type=this.getAttribute("type")||"select",this.appendChild(this.select),this.shadowRoot.appendChild(this.optionsSlot),this.optionsSlot.addEventListener("slotchange",this.slotChange.bind(this)),this.#j()}slotChange(){while(this.select.firstChild)this.select.firstChild.remove();if(this.type==="dropdown"){const K=document.createElement("option");K.setAttribute("hidden","true"),K.setAttribute("selected","true"),this.select.appendChild(K)}if(this.optionsSlot.assignedNodes().forEach((K)=>{if(K.nodeName==="OPTION"||K.nodeName==="OPTGROUP")this.select.appendChild(K.cloneNode(!0))}),this.#Q(this.value),this.type==="dropdown")this.select.selectedIndex=-1}#J(K){this.value=K.target.value,this.setAttribute("value",this.value)}#K(){if(this.type==="dropdown")this.select.selectedIndex=-1}focus(){this.select.focus()}blur(){this.select.blur()}get value(){return this.select?.value}set value(K){this.setAttribute("value",K)}static get observedAttributes(){return["value","type"]}#Q(K){if(this.select)this.select.querySelectorAll("option").forEach((J,Q)=>{if(J.value===this.getAttribute("value"))this.select.selectedIndex=Q})}attributeChangedCallback(K,J,Q){if(K==="value")this.#Q(Q);if(K==="type")this.type=Q}}customElements.define("fig-dropdown",j)}class P extends HTMLElement{constructor(){super();this.action=this.getAttribute("action")||"hover",this.delay=parseInt(this.getAttribute("delay"))||500,this.isOpen=!1}connectedCallback(){this.setup(),this.setupEventListeners()}disconnectedCallback(){this.destroy()}setup(){this.style.display="contents"}render(){this.destroy(),this.popup=document.createElement("span"),this.popup.setAttribute("class","fig-tooltip"),this.popup.style.position="fixed",this.popup.style.visibility="hidden",this.popup.style.display="inline-flex",this.popup.style.pointerEvents="none",this.popup.innerText=this.getAttribute("text"),document.body.append(this.popup)}destroy(){if(this.popup)this.popup.remove();document.body.addEventListener("click",this.hidePopupOutsideClick)}setupEventListeners(){if(this.action==="hover")this.addEventListener("pointerenter",this.showDelayedPopup.bind(this)),this.addEventListener("pointerleave",this.hidePopup.bind(this));else if(this.action==="click")this.addEventListener("click",this.showDelayedPopup.bind(this)),document.body.addEventListener("click",this.hidePopupOutsideClick.bind(this))}getOffset(){const j={left:8,top:4,right:8,bottom:4},K=this.getAttribute("offset");if(!K)return j;const[J,Q,X,W]=K.split(",").map(Number);return{left:isNaN(J)?j.left:J,top:isNaN(Q)?j.top:Q,right:isNaN(X)?j.right:X,bottom:isNaN(W)?j.bottom:W}}showDelayedPopup(){this.render(),clearTimeout(this.timeout),this.timeout=setTimeout(this.showPopup.bind(this),this.delay)}showPopup(){const j=this.firstElementChild.getBoundingClientRect(),K=this.popup.getBoundingClientRect(),J=this.getOffset();let Q=j.top-K.height-J.top,X=j.left+(j.width-K.width)/2;if(this.popup.setAttribute("position","top"),Q<0)this.popup.setAttribute("position","bottom"),Q=j.bottom+J.bottom;if(X<J.left)X=J.left;else if(X+K.width>window.innerWidth-J.right)X=window.innerWidth-K.width-J.right;this.popup.style.top=`${Q}px`,this.popup.style.left=`${X}px`,this.popup.style.opacity="1",this.popup.style.visibility="visible",this.popup.style.display="block",this.popup.style.pointerEvents="all",this.popup.style.zIndex=parseInt(new Date().getTime()/1000),this.isOpen=!0}hidePopup(){clearTimeout(this.timeout),this.popup.style.opacity="0",this.popup.style.display="block",this.popup.style.pointerEvents="none",this.destroy(),this.isOpen=!1}hidePopupOutsideClick(j){if(this.isOpen&&!this.popup.contains(j.target))this.hidePopup()}}customElements.define("fig-tooltip",P);class y extends P{static observedAttributes=["action","size"];constructor(){super();this.action=this.getAttribute("action")||"click",this.delay=parseInt(this.getAttribute("delay"))||0}render(){this.popup=this.popup||this.querySelector("[popover]"),this.popup.setAttribute("class","fig-popover"),this.popup.style.position="fixed",this.popup.style.display="block",this.popup.style.pointerEvents="none",document.body.append(this.popup)}destroy(){}}customElements.define("fig-popover",y);class L extends HTMLElement{constructor(){super();this.attachShadow({mode:"open"}),this.dialog=document.createElement("dialog"),this.contentSlot=document.createElement("slot")}connectedCallback(){this.modal=this.hasAttribute("modal")&&this.getAttribute("modal")!=="false",this.appendChild(this.dialog),this.shadowRoot.appendChild(this.contentSlot),this.contentSlot.addEventListener("slotchange",this.slotChange.bind(this)),requestAnimationFrame(()=>{this.#j()})}#j(){this.dialog.querySelectorAll("fig-button[close-dialog]").forEach((j)=>{j.removeEventListener("click",this.close),j.addEventListener("click",this.close.bind(this))})}disconnectedCallback(){this.contentSlot.removeEventListener("slotchange",this.slotChange)}slotChange(){while(this.dialog.firstChild)this.dialog.firstChild.remove();this.contentSlot.assignedNodes().forEach((j)=>{if(j!==this.dialog)this.dialog.appendChild(j.cloneNode(!0))}),this.#j()}static get observedAttributes(){return["open","modal"]}attributeChangedCallback(j,K,J){switch(j){case"open":if(this.open=J==="true"&&J!=="false",this?.show)this[this.open?"show":"close"]();break;case"modal":this.modal=J==="true"&&J!=="false";break}}show(){if(this.modal)this.dialog.showModal();else this.dialog.show()}close(){this.dialog.close()}}customElements.define("fig-dialog",L);class k extends HTMLElement{#j;#J;#K;#Q;#W;#X;constructor(){super()}connectedCallback(){if(this.#j=this.querySelector("[popover]"),this.#J=this,this.#Q=Number(this.getAttribute("delay"))||0,this.#X=this.getAttribute("trigger-action")||"click",this.#K=`tooltip-${U()}`,this.#j){if(this.#j.setAttribute("id",this.#K),this.#j.setAttribute("role","tooltip"),this.#j.setAttribute("popover","manual"),this.#j.style["position-anchor"]=`--${this.#K}`,this.#J.setAttribute("popovertarget",this.#K),this.#J.setAttribute("popovertargetaction","toggle"),this.#J.style["anchor-name"]=`--${this.#K}`,this.#X==="hover")this.#J.addEventListener("mouseover",this.handleOpen.bind(this)),this.#J.addEventListener("mouseout",this.handleClose.bind(this));else this.#J.addEventListener("click",this.handleToggle.bind(this));document.body.append(this.#j)}}handleClose(){clearTimeout(this.#W),this.#j.hidePopover()}handleToggle(){if(this.#j.matches(":popover-open"))this.handleClose();else this.handleOpen()}handleOpen(){clearTimeout(this.#W),this.#W=setTimeout(()=>{this.#j.showPopover()},this.#Q)}}window.customElements.define("fig-popover-2",k);class A extends HTMLElement{constructor(){super()}connectedCallback(){this.setAttribute("label",this.innerText),this.addEventListener("click",this.handleClick.bind(this))}disconnectedCallback(){this.removeEventListener("click",this.handleClick)}handleClick(){this.setAttribute("selected","true")}}window.customElements.define("fig-tab",A);class O extends HTMLElement{constructor(){super()}connectedCallback(){this.name=this.getAttribute("name")||"tabs",this.addEventListener("click",this.handleClick.bind(this))}disconnectedCallback(){this.removeEventListener("click",this.handleClick)}handleClick(j){const K=j.target;if(K.nodeName.toLowerCase()==="fig-tab"){const J=this.querySelectorAll("fig-tab");for(let Q of J)if(Q===K)this.selectedTab=Q;else Q.removeAttribute("selected")}}}window.customElements.define("fig-tabs",O);class B extends HTMLElement{#j;#J;constructor(){super()}connectedCallback(){this.addEventListener("click",this.handleClick.bind(this))}disconnectedCallback(){this.removeEventListener("click",this.handleClick)}handleClick(){this.setAttribute("selected","true")}get value(){return this.#j}set value(j){this.#j=j,this.setAttribute("value",j)}get selected(){return this.#J}set selected(j){this.#J=j,this.setAttribute("selected",j)}static get observedAttributes(){return["selected","value"]}attributeChangedCallback(j,K,J){switch(j){case"value":this.#j=J;break;case"selected":this.#J=J;break}}}window.customElements.define("fig-segment",B);class R extends HTMLElement{constructor(){super()}connectedCallback(){this.name=this.getAttribute("name")||"segmented-control",this.addEventListener("click",this.handleClick.bind(this))}handleClick(j){const K=j.target;if(K.nodeName.toLowerCase()==="fig-segment"){const J=this.querySelectorAll("fig-segment");for(let Q of J)if(Q===K)this.selectedSegment=Q;else Q.removeAttribute("selected")}}}window.customElements.define("fig-segmented-control",R);class N extends HTMLElement{#j={range:{min:0,max:100,step:1},hue:{min:0,max:255,step:1},delta:{min:-100,max:100,step:1},stepper:{min:0,max:100,step:25},opacity:{min:0,max:100,step:0.1,color:"#FF0000"}};constructor(){super()}#J(){this.value=Number(this.getAttribute("value")||0),this.default=this.getAttribute("default")||null,this.type=this.getAttribute("type")||"range",this.text=this.getAttribute("text")||!1,this.units=this.getAttribute("units")||"",this.transform=Number(this.getAttribute("transform")||1),this.disabled=this.getAttribute("disabled")?!0:!1;const j=this.#j[this.type];if(this.min=Number(this.getAttribute("min")||j.min),this.max=Number(this.getAttribute("max")||j.max),this.step=Number(this.getAttribute("step")||j.step),this.color=this.getAttribute("color")||j?.color,this.color)this.style.setProperty("--color",this.color);let K="",J=`<div class="fig-slider-input-container">
25
- <input
26
- type="range"
27
- ${this.disabled?"disabled":""}
28
- min="${this.min}"
29
- max="${this.max}"
30
- step="${this.step}"
31
- class="${this.type}"
32
- value="${this.value}">
33
- ${this.initialInnerHTML}
34
- </div>`;if(this.text)K=`${J}
35
- <fig-input-text
36
- placeholder="##"
37
- type="number"
38
- min="${this.min}"
39
- max="${this.max}"
40
- transform="${this.transform}"
41
- step="${this.step}"
42
- value="${this.value}">
43
- ${this.units?`<span slot="append">${this.units}</span>`:""}
44
- </fig-input-text>`;else K=J;this.innerHTML=K,requestAnimationFrame(()=>{if(this.input=this.querySelector("[type=range]"),this.input.removeEventListener("input",this.handleInput),this.input.addEventListener("input",this.handleInput.bind(this)),this.default)this.style.setProperty("--default",this.#K(this.default));if(this.datalist=this.querySelector("datalist"),this.figInputText=this.querySelector("fig-input-text"),this.datalist)this.datalist.setAttribute("id",this.datalist.getAttribute("id")||U()),this.input.setAttribute("list",this.datalist.getAttribute("id"));if(this.figInputText)this.figInputText.removeEventListener("input",this.handleTextInput),this.figInputText.addEventListener("input",this.handleTextInput.bind(this));this.handleInput()})}connectedCallback(){this.initialInnerHTML=this.innerHTML,this.#J()}static get observedAttributes(){return["value","step","min","max","type","disabled","color","units","transform"]}focus(){this.input.focus()}attributeChangedCallback(j,K,J){if(this.input)switch(j){case"color":this.color=J,this.style.setProperty("--color",this.color);break;case"disabled":if(this.disabled=this.input.disabled=J==="true"||J===void 0&&J!==null,this.figInputText)this.figInputText.disabled=this.disabled,this.figInputText.setAttribute("disabled",this.disabled);break;case"value":if(this.value=J,this.figInputText)this.figInputText.setAttribute("value",J);break;case"transform":if(this.transform=Number(J)||1,this.figInputText)this.figInputText.setAttribute("transform",this.transform);break;case"min":case"max":case"step":case"type":case"text":case"units":this[j]=J,this.#J();break;default:this[j]=this.input[j]=J,this.handleInput();break}}handleTextInput(){if(this.figInputText)this.value=this.input.value=this.figInputText.value,this.#Q()}#K(j){let K=Number(this.input.min),J=Number(this.input.max);return(j-K)/(J-K)}#Q(){let j=this.#K(this.value),K=this.#K(this.default);this.style.setProperty("--slider-complete",j),this.style.setProperty("--default",K),this.style.setProperty("--unchanged",j===K?1:0)}handleInput(){let j=this.input.value;if(this.value=j,this.#Q(),this.figInputText)this.figInputText.setAttribute("value",j)}}window.customElements.define("fig-slider",N);class q extends HTMLElement{constructor(){super()}connectedCallback(){if(this.multiline=this.hasAttribute("multiline")||!1,this.value=this.getAttribute("value")||"",this.type=this.getAttribute("type")||"text",this.type==="number"){if(this.getAttribute("step"))this.step=Number(this.getAttribute("step"));if(this.getAttribute("min"))this.min=Number(this.getAttribute("min"));if(this.getAttribute("max"))this.max=Number(this.getAttribute("max"));if(this.transform=Number(this.getAttribute("transform")||1),this.getAttribute("value"))this.value=Number(this.value)}this.placeholder=this.getAttribute("placeholder")||"";let j=`<input
45
- type="${this.type}"
46
- placeholder="${this.placeholder}"
47
- value="${this.type==="number"?this.#j(this.value):this.value}" />`;if(this.multiline)j=`<textarea
48
- placeholder="${this.placeholder}">${this.value}</textarea>`;requestAnimationFrame(()=>{const K=this.querySelector("[slot=append]"),J=this.querySelector("[slot=prepend]");if(this.innerHTML=j,J)J.addEventListener("click",this.focus.bind(this)),this.prepend(J);if(K)K.addEventListener("click",this.focus.bind(this)),this.append(K);if(this.input=this.querySelector("input,textarea"),this.type==="number"){if(this.getAttribute("min"))this.input.setAttribute("min",this.#j(this.min));if(this.getAttribute("max"))this.input.setAttribute("max",this.#j(this.max));if(this.getAttribute("step"))this.input.setAttribute("step",this.#j(this.step))}this.input.addEventListener("input",this.#J.bind(this))})}focus(){this.input.focus()}#j(j){return j*(this.transform||1)}#J(j){let K=j.target.value;if(this.type==="number")this.value=K/(this.transform||1);else this.value=K}static get observedAttributes(){return["value","placeholder","label","disabled","type","step","min","max","transform"]}attributeChangedCallback(j,K,J){if(this.input)switch(j){case"disabled":this.disabled=this.input.disabled=J;break;case"transform":if(this.type==="number")this.transform=Number(J)||1,this.min=this.#j(this.min),this.max=this.#j(this.max),this.step=this.#j(this.step),this.value=this.#j(this.value);break;default:let Q=J;if(this.type==="number")Q=this.#j(Q);if(this[j]=this.input[j]=Q,this.input)this.input.setAttribute(j,Q);break}}}window.customElements.define("fig-input-text",q);class T extends HTMLElement{constructor(){super()}connectedCallback(){this.src=this.getAttribute("src"),this.name=this.getAttribute("name"),this.initials=this.getInitials(this.name),this.setAttribute("initials",this.initials),this.setSrc(this.src),requestAnimationFrame(()=>{this.img=this.querySelector("img")})}setSrc(j){if(this.src=j,j)this.innerHTML=`<img src="${this.src}" ${this.name?`alt="${this.name}"`:""} />`}getInitials(j){return j?j.split(" ").map((K)=>K[0]).join(""):""}static get observedAttributes(){return["src","href","name"]}attributeChangedCallback(j,K,J){if(this[j]=J,j==="name")this.img?.setAttribute("alt",J),this.name=J,this.initials=this.getInitials(this.name),this.setAttribute("initials",this.initials);else if(j==="src")this.src=J,this.setSrc(this.src)}}window.customElements.define("fig-avatar",T);class S extends HTMLElement{constructor(){super()}connectedCallback(){requestAnimationFrame(()=>{if(this.label=this.querySelector("label"),this.input=Array.from(this.childNodes).find((j)=>j.nodeName.toLowerCase().startsWith("fig-")),this.input&&this.label){this.label.addEventListener("click",this.focus.bind(this));let j=this.input.getAttribute("id")||U();this.input.setAttribute("id",j),this.label.setAttribute("for",j)}})}focus(){this.input.focus()}}window.customElements.define("fig-field",S);class C extends HTMLElement{#j;#J;textInput;#K;constructor(){super()}connectedCallback(){this.#j=this.convertToRGBA(this.getAttribute("value"));const j=(this.#j.a*100).toFixed(0);this.value=this.rgbAlphaToHex({r:this.#j.r,g:this.#j.g,b:this.#j.b},j);let K="";if(this.getAttribute("text")){let J=`<fig-input-text placeholder="Text" value="${this.getAttribute("value")}"></fig-input-text>`;if(this.getAttribute("alpha")==="true")J+=`<fig-tooltip text="Opacity">
49
- <fig-input-text
50
- placeholder="##"
51
- type="number"
52
- min="0"
53
- max="100"
54
- value="${j}">
55
- <span slot="append">%</slot>
56
- </fig-input-text>
57
- </fig-tooltip>`;K=`<div class="input-combo">
58
- <fig-chit type="color" disabled="false" value="${this.value}"></fig-chit>
59
- ${J}
60
- </div>`}else K=`<fig-chit type="color" disabled="false" value="${this.value}"></fig-chit>`;this.innerHTML=K,this.style.setProperty("--alpha",this.#j.a),requestAnimationFrame(()=>{if(this.#J=this.querySelector("input[type=color]"),this.textInput=this.querySelector("input[type=text]"),this.#K=this.querySelector("input[type=number]"),this.#J.disabled=this.hasAttribute("disabled"),this.#J.addEventListener("input",this.handleInput.bind(this)),this.textInput)this.textInput.value=this.#J.value=this.rgbAlphaToHex(this.#j,1);if(this.#K)this.#K.addEventListener("input",this.handleAlphaInput.bind(this))})}handleAlphaInput(j){j.stopPropagation(),this.#j=this.convertToRGBA(this.#J.value),this.#j.a=Number(this.#K.value)/100,this.value=this.rgbAlphaToHex({r:this.#j.r,g:this.#j.g,b:this.#j.b},this.#j.a),this.style.setProperty("--alpha",this.#j.a);const K=new CustomEvent("input",{bubbles:!0,cancelable:!0});this.dispatchEvent(K)}focus(){this.#J.focus()}handleInput(j){j.stopPropagation();let K=this.#j.a;if(this.#j=this.convertToRGBA(this.#J.value),this.#j.a=K,this.textInput)this.textInput.value=this.#J.value;if(this.style.setProperty("--alpha",this.#j.a),this.value=this.rgbAlphaToHex({r:this.#j.r,g:this.#j.g,b:this.#j.b},K),this.alpha=K,this.#K)this.#K.value=this.#j.a.toFixed(0);const J=new CustomEvent("input",{bubbles:!0,cancelable:!0});this.dispatchEvent(J)}static get observedAttributes(){return["value","style"]}attributeChangedCallback(j,K,J){}rgbAlphaToHex({r:j,g:K,b:J},Q=1){j=Math.max(0,Math.min(255,Math.round(j))),K=Math.max(0,Math.min(255,Math.round(K))),J=Math.max(0,Math.min(255,Math.round(J))),Q=Math.max(0,Math.min(1,Q));const X=j.toString(16).padStart(2,"0"),W=K.toString(16).padStart(2,"0"),_=J.toString(16).padStart(2,"0");if(Q===1)return`#${X}${W}${_}`;const Z=Math.round(Q*255).toString(16).padStart(2,"0");return`#${X}${W}${_}${Z}`}convertToRGBA(j){let K,J,Q,X=1;if(j.startsWith("#")){let W=j.slice(1);if(W.length===8)X=parseInt(W.slice(6),16)/255,W=W.slice(0,6);K=parseInt(W.slice(0,2),16),J=parseInt(W.slice(2,4),16),Q=parseInt(W.slice(4,6),16)}else if(j.startsWith("rgba")||j.startsWith("rgb")){let W=j.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)/);if(W)K=parseInt(W[1]),J=parseInt(W[2]),Q=parseInt(W[3]),X=W[4]?parseFloat(W[4]):1}else if(j.startsWith("hsla")||j.startsWith("hsl")){let W=j.match(/hsla?\((\d+),\s*(\d+)%,\s*(\d+)%(?:,\s*(\d+(?:\.\d+)?))?\)/);if(W){let _=parseInt(W[1])/360,$=parseInt(W[2])/100,Z=parseInt(W[3])/100;if(X=W[4]?parseFloat(W[4]):1,$===0)K=J=Q=Z;else{let H=(z,D,Y)=>{if(Y<0)Y+=1;if(Y>1)Y-=1;if(Y<0.16666666666666666)return z+(D-z)*6*Y;if(Y<0.5)return D;if(Y<0.6666666666666666)return z+(D-z)*(0.6666666666666666-Y)*6;return z},E=Z<0.5?Z*(1+$):Z+$-Z*$,M=2*Z-E;K=H(M,E,_+0.3333333333333333),J=H(M,E,_),Q=H(M,E,_-0.3333333333333333)}K=Math.round(K*255),J=Math.round(J*255),Q=Math.round(Q*255)}}else return null;return{r:K,g:J,b:Q,a:X}}}window.customElements.define("fig-input-color",C);class G extends HTMLElement{constructor(){super();this.input=document.createElement("input"),this.name=this.getAttribute("name")||"checkbox",this.value=this.getAttribute("value")||"",this.input.setAttribute("id",U()),this.input.setAttribute("name",this.name),this.input.setAttribute("type","checkbox"),this.labelElement=document.createElement("label"),this.labelElement.setAttribute("for",this.input.id)}connectedCallback(){if(this.checked=this.input.checked=this.hasAttribute("checked")&&this.getAttribute("checked")!=="false",this.input.addEventListener("change",this.handleInput.bind(this)),this.hasAttribute("disabled"))this.input.disabled=!0;if(this.hasAttribute("indeterminate"))this.input.indeterminate=!0,this.input.setAttribute("indeterminate","true");this.append(this.input),this.append(this.labelElement),this.render()}static get observedAttributes(){return["disabled","label","checked","name","value"]}render(){}focus(){this.input.focus()}disconnectedCallback(){this.input.remove()}attributeChangedCallback(j,K,J){switch(j){case"label":this.labelElement.innerText=J;break;case"checked":this.checked=this.input.checked=this.hasAttribute("checked")&&this.getAttribute("checked")!=="false";break;default:this.input[j]=J,this.input.setAttribute(j,J);break}}handleInput(j){this.input.indeterminate=!1,this.input.removeAttribute("indeterminate"),this.value=this.input.value}}window.customElements.define("fig-checkbox",G);class x extends G{constructor(){super();this.input.setAttribute("type","radio"),this.input.setAttribute("name",this.getAttribute("name")||"radio")}}window.customElements.define("fig-radio",x);class I extends G{render(){this.input.setAttribute("class","switch")}}window.customElements.define("fig-switch",I);class v extends HTMLElement{constructor(){super()}}window.customElements.define("fig-bell",v);class F extends HTMLElement{constructor(){super()}}window.customElements.define("fig-badge",F);class f extends HTMLElement{constructor(){super()}}window.customElements.define("fig-accordion",f);class b extends HTMLElement{constructor(){super()}getOptionsFromAttribute(){return(this.getAttribute("options")||"").split(",")}connectedCallback(){this.options=this.getOptionsFromAttribute(),this.placeholder=this.getAttribute("placeholder")||"",this.value=this.getAttribute("value")||"",this.innerHTML=`<div class="input-combo">
61
- <fig-input-text placeholder="${this.placeholder}">
62
- </fig-input-text>
63
- <fig-button type="select" variant="input" icon>
64
- <svg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
65
- <path d='M5.87868 7.12132L8 9.24264L10.1213 7.12132' stroke='currentColor' stroke-opacity="0.9" stroke-linecap='round'/>
66
- </svg>
67
- <fig-dropdown type="dropdown">
68
- ${this.options.map((j)=>`<option>${j}</option>`).join("")}
69
- </fig-dropdown>
70
- </fig-button>
71
- </div>`,requestAnimationFrame(()=>{this.input=this.querySelector("fig-input-text"),this.select=this.querySelector("fig-dropdown"),this.select.addEventListener("input",this.handleSelectInput.bind(this))})}handleSelectInput(j){this.value=j.target.parentNode.value,this.setAttribute("value",this.value)}handleInput(j){this.value=this.input.value}static get observedAttributes(){return["options","placeholder","value"]}attributeChangedCallback(j,K,J){if(j==="options"){if(this.options=J.split(","),this.dropdown)this.dropdown.innerHTML=this.options.map((Q)=>`<option>${Q}</option>`).join("")}if(j==="placeholder")this.placeholder=J;if(j==="value")this.value=J,this.input.setAttribute("value",J)}}window.customElements.define("fig-combo-input",b);class h extends HTMLElement{constructor(){super()}connectedCallback(){this.type=this.getAttribute("type")||"color",this.src=this.getAttribute("src")||"",this.value=this.getAttribute("value")||"",this.size=this.getAttribute("size")||"small",this.disabled=this.getAttribute("disabled")==="true"?!0:!1,this.innerHTML=`<input type="color" value="${this.value}" />`,this.#j(this.src),requestAnimationFrame(()=>{this.input=this.querySelector("input")})}#j(j){if(j)this.src=j,this.style.setProperty("--src",`url(${j})`);else this.style.removeProperty("--src")}static get observedAttributes(){return["src","value","disabled"]}attributeChangedCallback(j,K,J){switch(j){case"src":this.#j(J);break;case"disabled":this.disabled=J.toLowerCase()==="true";break;default:if(this.input)this.input[j]=J;this.#j(this.src);break}}}window.customElements.define("fig-chit",h);class d extends HTMLElement{constructor(){super()}#j(){return`<fig-chit type="image" size="large" ${this.src?`src="${this.src}"`:""} disabled="true"></fig-chit>${this.upload?`<fig-button variant="primary" type="upload">
72
- ${this.label}
73
- <input type="file" accept="image/*" />
74
- </fig-button>`:""}`}connectedCallback(){this.src=this.getAttribute("src")||"",this.upload=this.getAttribute("upload")==="true",this.label=this.getAttribute("label")||"Upload",this.size=this.getAttribute("size")||"small",this.innerHTML=this.#j(),this.#J()}#J(){requestAnimationFrame(()=>{if(this.chit=this.querySelector("fig-chit"),this.upload)this.uploadButton=this.querySelector("fig-button"),this.fileInput=this.uploadButton?.querySelector("input"),this.fileInput.addEventListener("change",this.handleFileInput.bind(this))})}handleFileInput(j){this.src=URL.createObjectURL(j.target.files[0]),this.setAttribute("src",this.src),this.chit.setAttribute("src",this.src)}static get observedAttributes(){return["src","upload"]}attributeChangedCallback(j,K,J){if(j==="src"){if(this.src=J,this.chit)this.chit.setAttribute("src",this.src)}if(j==="upload")this.upload=J.toLowerCase()==="true",this.innerHTML=this.#j(),this.#J();if(j==="size")this.size=J}}window.customElements.define("fig-image",d);