@rogieking/figui3 6.17.0 → 6.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -8
- package/dist/fig-editor.css +1 -1
- package/dist/fig-lab.css +1 -1
- package/dist/fig-lab.js +19 -13
- package/dist/fig.js +12 -12
- package/fig-lab.css +10 -4
- package/fig-lab.js +139 -28
- package/fig.js +29 -7
- package/package.json +1 -1
package/fig-lab.css
CHANGED
|
@@ -276,10 +276,6 @@ propskit-select {
|
|
|
276
276
|
background: transparent;
|
|
277
277
|
box-shadow: none;
|
|
278
278
|
|
|
279
|
-
&::after {
|
|
280
|
-
right: 0;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
279
|
--fig-select-trigger-width: auto;
|
|
284
280
|
}
|
|
285
281
|
}
|
|
@@ -644,6 +640,14 @@ propskit-slider {
|
|
|
644
640
|
|
|
645
641
|
&[type="stepper"] {
|
|
646
642
|
--slider-thumb-travel-offset: 0px;
|
|
643
|
+
datalist{
|
|
644
|
+
opacity: 0;
|
|
645
|
+
}
|
|
646
|
+
&:hover, &:focus-within {
|
|
647
|
+
datalist{
|
|
648
|
+
opacity: 1;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
647
651
|
}
|
|
648
652
|
|
|
649
653
|
&[type="stepper"] .fig-slider-input-container input[type="range"] {
|
|
@@ -1258,6 +1262,8 @@ fig-select {
|
|
|
1258
1262
|
|
|
1259
1263
|
&[disabled]:not([disabled="false"]) {
|
|
1260
1264
|
pointer-events: none;
|
|
1265
|
+
color: var(--figma-color-text-tertiary);
|
|
1266
|
+
|
|
1261
1267
|
&::after {
|
|
1262
1268
|
background-color: var(--figma-color-icon-disabled);
|
|
1263
1269
|
}
|
package/fig-lab.js
CHANGED
|
@@ -455,7 +455,7 @@ class PropskitSelect extends HTMLElement {
|
|
|
455
455
|
#boundHandleClick = this.#handleClick.bind(this);
|
|
456
456
|
|
|
457
457
|
static get observedAttributes() {
|
|
458
|
-
return ["label", "direction", "aria-label"];
|
|
458
|
+
return ["label", "direction", "aria-label", "options", "value"];
|
|
459
459
|
}
|
|
460
460
|
|
|
461
461
|
connectedCallback() {
|
|
@@ -473,10 +473,11 @@ class PropskitSelect extends HTMLElement {
|
|
|
473
473
|
|
|
474
474
|
for (const mutation of mutations) {
|
|
475
475
|
if (mutation.type !== "attributes") continue;
|
|
476
|
+
const name = mutation.attributeName;
|
|
476
477
|
if (
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
478
|
+
name === "label" ||
|
|
479
|
+
name === "direction" ||
|
|
480
|
+
name === "aria-label"
|
|
480
481
|
) {
|
|
481
482
|
syncField = true;
|
|
482
483
|
} else {
|
|
@@ -502,34 +503,18 @@ class PropskitSelect extends HTMLElement {
|
|
|
502
503
|
if (oldValue === newValue || !this.#field) return;
|
|
503
504
|
if (name === "label" || name === "direction" || name === "aria-label") {
|
|
504
505
|
this.#syncField();
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
if (name === "options" || name === "value") {
|
|
509
|
+
this.#syncSelectAttributes();
|
|
505
510
|
}
|
|
506
511
|
}
|
|
507
512
|
|
|
508
513
|
#initialize() {
|
|
509
|
-
const
|
|
510
|
-
(node) =>
|
|
511
|
-
node.nodeType !== Node.TEXT_NODE || Boolean(node.textContent?.trim()),
|
|
512
|
-
);
|
|
513
|
-
const customLabel = initialChildren.find(
|
|
514
|
-
(node) => node.nodeType === Node.ELEMENT_NODE && node.matches("label"),
|
|
515
|
-
);
|
|
514
|
+
const customLabel = this.querySelector(":scope > label");
|
|
516
515
|
const field = document.createElement("fig-field");
|
|
517
516
|
const label = customLabel || document.createElement("label");
|
|
518
517
|
const select = document.createElement("fig-select");
|
|
519
|
-
const existingPanel = initialChildren.find(
|
|
520
|
-
(node) =>
|
|
521
|
-
node.nodeType === Node.ELEMENT_NODE &&
|
|
522
|
-
node.tagName === "FIG-SELECT-OPTIONS",
|
|
523
|
-
);
|
|
524
|
-
const panel =
|
|
525
|
-
existingPanel || document.createElement("fig-select-options");
|
|
526
|
-
panel.setAttribute("slot", "panel");
|
|
527
|
-
|
|
528
|
-
for (const node of initialChildren) {
|
|
529
|
-
if (node === customLabel || node === existingPanel) continue;
|
|
530
|
-
panel.appendChild(node);
|
|
531
|
-
}
|
|
532
|
-
select.append(panel);
|
|
533
518
|
field.append(label, select);
|
|
534
519
|
this.#field = field;
|
|
535
520
|
this.#label = label;
|
|
@@ -586,7 +571,14 @@ class PropskitSelect extends HTMLElement {
|
|
|
586
571
|
|
|
587
572
|
#syncSelectAttributes() {
|
|
588
573
|
if (!this.#select) return;
|
|
589
|
-
const selectAttrs = this.#getForwardedSelectAttrNames()
|
|
574
|
+
const selectAttrs = this.#getForwardedSelectAttrNames().sort((a, b) => {
|
|
575
|
+
// Build options before applying value so fig-select can resolve selection.
|
|
576
|
+
if (a === "options") return -1;
|
|
577
|
+
if (b === "options") return 1;
|
|
578
|
+
if (a === "value") return 1;
|
|
579
|
+
if (b === "value") return -1;
|
|
580
|
+
return 0;
|
|
581
|
+
});
|
|
590
582
|
const nextManaged = new Set(selectAttrs);
|
|
591
583
|
|
|
592
584
|
for (const attrName of this.#managedSelectAttrs) {
|
|
@@ -4743,6 +4735,38 @@ function figLabUniqueId(prefix = "fig-select") {
|
|
|
4743
4735
|
return `${prefix}-${figLabSelectId}`;
|
|
4744
4736
|
}
|
|
4745
4737
|
|
|
4738
|
+
/** Parse options attr — same formats as fig-options / propskit-select. */
|
|
4739
|
+
function figLabParseOptionsAttribute(raw) {
|
|
4740
|
+
const text = raw || "";
|
|
4741
|
+
if (text.startsWith("[")) {
|
|
4742
|
+
try {
|
|
4743
|
+
const parsed = JSON.parse(text);
|
|
4744
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
4745
|
+
} catch {
|
|
4746
|
+
/* fall through */
|
|
4747
|
+
}
|
|
4748
|
+
}
|
|
4749
|
+
const delimiter = text.includes("\n") ? "\n" : ",";
|
|
4750
|
+
return text
|
|
4751
|
+
.split(delimiter)
|
|
4752
|
+
.map((s) => s.trim())
|
|
4753
|
+
.filter(Boolean);
|
|
4754
|
+
}
|
|
4755
|
+
|
|
4756
|
+
function figLabOptionEntryValue(opt) {
|
|
4757
|
+
if (opt && typeof opt === "object") {
|
|
4758
|
+
return String(opt.value ?? opt.label ?? "");
|
|
4759
|
+
}
|
|
4760
|
+
return String(opt ?? "");
|
|
4761
|
+
}
|
|
4762
|
+
|
|
4763
|
+
function figLabOptionEntryLabel(opt) {
|
|
4764
|
+
if (opt && typeof opt === "object") {
|
|
4765
|
+
return String(opt.label ?? opt.value ?? "");
|
|
4766
|
+
}
|
|
4767
|
+
return String(opt ?? "");
|
|
4768
|
+
}
|
|
4769
|
+
|
|
4746
4770
|
class FigSelectOption extends HTMLElement {
|
|
4747
4771
|
static get observedAttributes() {
|
|
4748
4772
|
return ["value", "disabled", "selected"];
|
|
@@ -4960,6 +4984,7 @@ class FigSelect extends HTMLElement {
|
|
|
4960
4984
|
#originalPositionPopup = null;
|
|
4961
4985
|
/** After open align, stop repositioning so overflow scroll isn't yanked back. */
|
|
4962
4986
|
#freezeMenuPosition = false;
|
|
4987
|
+
#syncingOptions = false;
|
|
4963
4988
|
#boundTriggerClick = this.#handleTriggerClick.bind(this);
|
|
4964
4989
|
#boundOptionClick = this.#handleOptionClick.bind(this);
|
|
4965
4990
|
#boundKeydown = this.#handleKeydown.bind(this);
|
|
@@ -4967,7 +4992,16 @@ class FigSelect extends HTMLElement {
|
|
|
4967
4992
|
#boundSlotChange = this.#handleSlotChange.bind(this);
|
|
4968
4993
|
|
|
4969
4994
|
static get observedAttributes() {
|
|
4970
|
-
return [
|
|
4995
|
+
return [
|
|
4996
|
+
"value",
|
|
4997
|
+
"disabled",
|
|
4998
|
+
"label",
|
|
4999
|
+
"options",
|
|
5000
|
+
"position",
|
|
5001
|
+
"offset",
|
|
5002
|
+
"closedby",
|
|
5003
|
+
"open",
|
|
5004
|
+
];
|
|
4971
5005
|
}
|
|
4972
5006
|
|
|
4973
5007
|
get value() {
|
|
@@ -4991,6 +5025,7 @@ class FigSelect extends HTMLElement {
|
|
|
4991
5025
|
connectedCallback() {
|
|
4992
5026
|
if (!this.#initialized) this.#initialize();
|
|
4993
5027
|
this.#ensurePanelSlotAttrs();
|
|
5028
|
+
this.#syncOptionsFromAttribute();
|
|
4994
5029
|
this.#syncDisabled();
|
|
4995
5030
|
this.#syncPopupAttrs();
|
|
4996
5031
|
this.#syncValue();
|
|
@@ -5007,6 +5042,11 @@ class FigSelect extends HTMLElement {
|
|
|
5007
5042
|
|
|
5008
5043
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
5009
5044
|
if (oldValue === newValue || !this.#initialized) return;
|
|
5045
|
+
if (name === "options") {
|
|
5046
|
+
this.#syncOptionsFromAttribute();
|
|
5047
|
+
this.#syncValue();
|
|
5048
|
+
return;
|
|
5049
|
+
}
|
|
5010
5050
|
if (name === "value" || name === "label") {
|
|
5011
5051
|
this.#syncValue();
|
|
5012
5052
|
return;
|
|
@@ -5057,6 +5097,65 @@ class FigSelect extends HTMLElement {
|
|
|
5057
5097
|
return this.querySelector(":scope > fig-select-options");
|
|
5058
5098
|
}
|
|
5059
5099
|
|
|
5100
|
+
#hasAuthoredOptions() {
|
|
5101
|
+
return Boolean(
|
|
5102
|
+
this.querySelector(
|
|
5103
|
+
":scope > fig-select-option:not([data-fig-generated]), :scope > fig-select-options > fig-select-option:not([data-fig-generated])",
|
|
5104
|
+
),
|
|
5105
|
+
);
|
|
5106
|
+
}
|
|
5107
|
+
|
|
5108
|
+
#ensureOptionsPanel() {
|
|
5109
|
+
let panel = this.#getPanel();
|
|
5110
|
+
if (panel) {
|
|
5111
|
+
if (!panel.hasAttribute("slot")) panel.setAttribute("slot", "panel");
|
|
5112
|
+
return panel;
|
|
5113
|
+
}
|
|
5114
|
+
panel = document.createElement("fig-select-options");
|
|
5115
|
+
panel.setAttribute("slot", "panel");
|
|
5116
|
+
panel.setAttribute("data-fig-generated", "");
|
|
5117
|
+
this.appendChild(panel);
|
|
5118
|
+
return panel;
|
|
5119
|
+
}
|
|
5120
|
+
|
|
5121
|
+
/**
|
|
5122
|
+
* When no authored fig-select-option exists, build panel/options from the
|
|
5123
|
+
* options attribute (comma / newline / JSON — same as fig-options).
|
|
5124
|
+
*/
|
|
5125
|
+
#syncOptionsFromAttribute() {
|
|
5126
|
+
if (this.#hasAuthoredOptions()) return;
|
|
5127
|
+
|
|
5128
|
+
const hasOptionsAttr = this.hasAttribute("options");
|
|
5129
|
+
const panel = hasOptionsAttr
|
|
5130
|
+
? this.#ensureOptionsPanel()
|
|
5131
|
+
: this.#getPanel();
|
|
5132
|
+
if (!panel) return;
|
|
5133
|
+
|
|
5134
|
+
this.#syncingOptions = true;
|
|
5135
|
+
try {
|
|
5136
|
+
for (const opt of panel.querySelectorAll(
|
|
5137
|
+
":scope > fig-select-option[data-fig-generated]",
|
|
5138
|
+
)) {
|
|
5139
|
+
opt.remove();
|
|
5140
|
+
}
|
|
5141
|
+
|
|
5142
|
+
if (!hasOptionsAttr) return;
|
|
5143
|
+
|
|
5144
|
+
const parsed = figLabParseOptionsAttribute(this.getAttribute("options"));
|
|
5145
|
+
const endBtn = panel.querySelector(":scope > .fig-overflow-end");
|
|
5146
|
+
for (const entry of parsed) {
|
|
5147
|
+
const el = document.createElement("fig-select-option");
|
|
5148
|
+
el.setAttribute("data-fig-generated", "");
|
|
5149
|
+
el.setAttribute("value", figLabOptionEntryValue(entry));
|
|
5150
|
+
el.textContent = figLabOptionEntryLabel(entry);
|
|
5151
|
+
if (endBtn) panel.insertBefore(el, endBtn);
|
|
5152
|
+
else panel.appendChild(el);
|
|
5153
|
+
}
|
|
5154
|
+
} finally {
|
|
5155
|
+
this.#syncingOptions = false;
|
|
5156
|
+
}
|
|
5157
|
+
}
|
|
5158
|
+
|
|
5060
5159
|
#initialize() {
|
|
5061
5160
|
this.#initialized = true;
|
|
5062
5161
|
const shadow = this.attachShadow({ mode: "open" });
|
|
@@ -5107,6 +5206,10 @@ class FigSelect extends HTMLElement {
|
|
|
5107
5206
|
outline: var(--figma-focus-outline);
|
|
5108
5207
|
outline-offset: var(--figma-focus-outline-offset);
|
|
5109
5208
|
}
|
|
5209
|
+
:host([disabled]:not([disabled="false"])) .fig-select-trigger,
|
|
5210
|
+
:host([disabled]:not([disabled="false"])) .fig-select-label {
|
|
5211
|
+
color: var(--figma-color-text-tertiary);
|
|
5212
|
+
}
|
|
5110
5213
|
.fig-select-label {
|
|
5111
5214
|
display: block;
|
|
5112
5215
|
width: 100%;
|
|
@@ -5305,7 +5408,7 @@ class FigSelect extends HTMLElement {
|
|
|
5305
5408
|
#setupObserver() {
|
|
5306
5409
|
if (this.#observer) return;
|
|
5307
5410
|
this.#observer = new MutationObserver((mutations) => {
|
|
5308
|
-
if (this.#syncingValue) return;
|
|
5411
|
+
if (this.#syncingValue || this.#syncingOptions) return;
|
|
5309
5412
|
let needsSync = false;
|
|
5310
5413
|
for (const mutation of mutations) {
|
|
5311
5414
|
if (mutation.type === "childList") {
|
|
@@ -5432,6 +5535,14 @@ class FigSelect extends HTMLElement {
|
|
|
5432
5535
|
|
|
5433
5536
|
if (!match) {
|
|
5434
5537
|
if (hasValueAttr) {
|
|
5538
|
+
// Options may not be built yet (options attr sync). Keep value until then.
|
|
5539
|
+
if (!options.length) {
|
|
5540
|
+
if (this.#labelEl) {
|
|
5541
|
+
this.#labelEl.textContent =
|
|
5542
|
+
previousValue || this.getAttribute("label") || "";
|
|
5543
|
+
}
|
|
5544
|
+
return;
|
|
5545
|
+
}
|
|
5435
5546
|
// Value orphaned (option removed / value attr changed) — clamp or clear.
|
|
5436
5547
|
match = this.#pickFallbackOption(options);
|
|
5437
5548
|
if (match) {
|
package/fig.js
CHANGED
|
@@ -7145,7 +7145,20 @@ class FigField extends HTMLElement {
|
|
|
7145
7145
|
!(node instanceof Element && node.classList.contains("fig-field-chevron")),
|
|
7146
7146
|
);
|
|
7147
7147
|
|
|
7148
|
-
|
|
7148
|
+
// Popup/menu hosts also expose `open`, but that is overlay state — not a
|
|
7149
|
+
// fig-field disclosure. Only treat true expand/collapse children as toggleable.
|
|
7150
|
+
const inputTag = this.input?.localName ?? "";
|
|
7151
|
+
const popupOpenHost =
|
|
7152
|
+
inputTag === "fig-select" ||
|
|
7153
|
+
inputTag === "fig-menu" ||
|
|
7154
|
+
inputTag === "fig-dropdown" ||
|
|
7155
|
+
inputTag === "fig-tooltip" ||
|
|
7156
|
+
(this.input instanceof HTMLDialogElement);
|
|
7157
|
+
this.#toggleable = !!(
|
|
7158
|
+
this.input &&
|
|
7159
|
+
"open" in this.input &&
|
|
7160
|
+
!popupOpenHost
|
|
7161
|
+
);
|
|
7149
7162
|
|
|
7150
7163
|
if (this.#toggleable && this.label) {
|
|
7151
7164
|
if (!this.#chevron || !this.#chevron.isConnected) {
|
|
@@ -7158,12 +7171,21 @@ class FigField extends HTMLElement {
|
|
|
7158
7171
|
|
|
7159
7172
|
this.#chevron.addEventListener("click", this.#boundToggle);
|
|
7160
7173
|
this.label.addEventListener("click", this.#boundToggle);
|
|
7161
|
-
} else
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
|
|
7165
|
-
|
|
7166
|
-
|
|
7174
|
+
} else {
|
|
7175
|
+
if (this.#chevron) {
|
|
7176
|
+
this.#chevron.removeEventListener("click", this.#boundToggle);
|
|
7177
|
+
this.#chevron.remove();
|
|
7178
|
+
this.#chevron = null;
|
|
7179
|
+
}
|
|
7180
|
+
if (this.input && this.label) {
|
|
7181
|
+
const nativeInputs = this.input.querySelectorAll(
|
|
7182
|
+
"input, select, textarea",
|
|
7183
|
+
);
|
|
7184
|
+
// A label with a `for` target already focuses and activates its control.
|
|
7185
|
+
// Only use the composite fallback when there is no single native target.
|
|
7186
|
+
if (nativeInputs.length !== 1) {
|
|
7187
|
+
this.label.addEventListener("click", this.#boundFocus);
|
|
7188
|
+
}
|
|
7167
7189
|
}
|
|
7168
7190
|
}
|
|
7169
7191
|
|
package/package.json
CHANGED