@rogieking/figui3 7.0.0 → 8.0.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 +2 -2
- package/components.css +3 -267
- package/dist/components.css +1 -1
- package/dist/fig-editor.css +1 -1
- package/dist/fig-editor.js +129 -38
- package/dist/fig-lab.js +15 -13
- package/dist/fig.css +1 -1
- package/dist/fig.js +20 -111
- package/fig-editor.css +265 -0
- package/fig-editor.js +1320 -0
- package/fig-lab.js +85 -178
- package/fig.js +36 -1172
- package/package.json +1 -1
package/fig-lab.js
CHANGED
|
@@ -1205,6 +1205,7 @@ class PropskitSelect extends HTMLElement {
|
|
|
1205
1205
|
#field = null;
|
|
1206
1206
|
#label = null;
|
|
1207
1207
|
#select = null;
|
|
1208
|
+
#usesFigSelect = false;
|
|
1208
1209
|
#hasCustomLabel = false;
|
|
1209
1210
|
#observer = null;
|
|
1210
1211
|
#managedSelectAttrs = new Set();
|
|
@@ -1221,6 +1222,49 @@ class PropskitSelect extends HTMLElement {
|
|
|
1221
1222
|
return ["label", "direction", "aria-label", "options", "value"];
|
|
1222
1223
|
}
|
|
1223
1224
|
|
|
1225
|
+
static #canUseFigSelect() {
|
|
1226
|
+
return ["fig-select", "fig-select-options", "fig-select-option"].every(
|
|
1227
|
+
(tag) => {
|
|
1228
|
+
const Constructor = customElements.get(tag);
|
|
1229
|
+
return (
|
|
1230
|
+
typeof Constructor === "function" &&
|
|
1231
|
+
Constructor.prototype instanceof HTMLElement
|
|
1232
|
+
);
|
|
1233
|
+
},
|
|
1234
|
+
);
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
static #parseOptionsAttribute(raw) {
|
|
1238
|
+
const text = raw || "";
|
|
1239
|
+
if (text.startsWith("[")) {
|
|
1240
|
+
try {
|
|
1241
|
+
const parsed = JSON.parse(text);
|
|
1242
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
1243
|
+
} catch {
|
|
1244
|
+
/* fall through */
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
const delimiter = text.includes("\n") ? "\n" : ",";
|
|
1248
|
+
return text
|
|
1249
|
+
.split(delimiter)
|
|
1250
|
+
.map((s) => s.trim())
|
|
1251
|
+
.filter(Boolean);
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
static #optionEntryValue(opt) {
|
|
1255
|
+
if (opt && typeof opt === "object") {
|
|
1256
|
+
return String(opt.value ?? opt.label ?? "");
|
|
1257
|
+
}
|
|
1258
|
+
return String(opt ?? "");
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
static #optionEntryLabel(opt) {
|
|
1262
|
+
if (opt && typeof opt === "object") {
|
|
1263
|
+
return String(opt.label ?? opt.value ?? "");
|
|
1264
|
+
}
|
|
1265
|
+
return String(opt ?? "");
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1224
1268
|
connectedCallback() {
|
|
1225
1269
|
if (!this.#field) this.#initialize();
|
|
1226
1270
|
this.#syncField();
|
|
@@ -1283,9 +1327,12 @@ class PropskitSelect extends HTMLElement {
|
|
|
1283
1327
|
const customLabel = this.querySelector(":scope > label");
|
|
1284
1328
|
const field = document.createElement("fig-field");
|
|
1285
1329
|
const label = customLabel || document.createElement("label");
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1330
|
+
this.#usesFigSelect = PropskitSelect.#canUseFigSelect();
|
|
1331
|
+
const select = document.createElement(
|
|
1332
|
+
this.#usesFigSelect ? "fig-select" : "fig-dropdown",
|
|
1333
|
+
);
|
|
1334
|
+
// Match menu width to the full-surface control when fig-select is available.
|
|
1335
|
+
if (this.#usesFigSelect) select.setAttribute("full", "");
|
|
1289
1336
|
field.append(label, select);
|
|
1290
1337
|
this.#field = field;
|
|
1291
1338
|
this.#label = label;
|
|
@@ -1322,7 +1369,7 @@ class PropskitSelect extends HTMLElement {
|
|
|
1322
1369
|
"Select",
|
|
1323
1370
|
);
|
|
1324
1371
|
// Always match menu width to the control surface.
|
|
1325
|
-
this.#select.setAttribute("full", "");
|
|
1372
|
+
if (this.#usesFigSelect) this.#select.setAttribute("full", "");
|
|
1326
1373
|
}
|
|
1327
1374
|
|
|
1328
1375
|
#getForwardedSelectAttrNames() {
|
|
@@ -1339,13 +1386,31 @@ class PropskitSelect extends HTMLElement {
|
|
|
1339
1386
|
"full",
|
|
1340
1387
|
"default",
|
|
1341
1388
|
]);
|
|
1389
|
+
// fig-dropdown consumes light-DOM <option>s, not an options attribute.
|
|
1390
|
+
if (!this.#usesFigSelect) reserved.add("options");
|
|
1342
1391
|
return this.getAttributeNames().filter(
|
|
1343
1392
|
(name) => !reserved.has(name) && !name.startsWith("data-"),
|
|
1344
1393
|
);
|
|
1345
1394
|
}
|
|
1346
1395
|
|
|
1396
|
+
#syncDropdownOptions() {
|
|
1397
|
+
if (!this.#select || this.#usesFigSelect) return;
|
|
1398
|
+
const parsed = PropskitSelect.#parseOptionsAttribute(
|
|
1399
|
+
this.getAttribute("options"),
|
|
1400
|
+
);
|
|
1401
|
+
this.#select.replaceChildren(
|
|
1402
|
+
...parsed.map((entry) => {
|
|
1403
|
+
const option = document.createElement("option");
|
|
1404
|
+
option.value = PropskitSelect.#optionEntryValue(entry);
|
|
1405
|
+
option.textContent = PropskitSelect.#optionEntryLabel(entry);
|
|
1406
|
+
return option;
|
|
1407
|
+
}),
|
|
1408
|
+
);
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1347
1411
|
#syncSelectAttributes() {
|
|
1348
1412
|
if (!this.#select) return;
|
|
1413
|
+
if (!this.#usesFigSelect) this.#syncDropdownOptions();
|
|
1349
1414
|
const selectAttrs = this.#getForwardedSelectAttrNames().sort((a, b) => {
|
|
1350
1415
|
// Build options before applying value so fig-select can resolve selection.
|
|
1351
1416
|
if (a === "options") return -1;
|
|
@@ -1370,13 +1435,15 @@ class PropskitSelect extends HTMLElement {
|
|
|
1370
1435
|
if (!this.#select) return;
|
|
1371
1436
|
this.#boundHandleInput ??= this.#forwardSelectEvent.bind(this, "input");
|
|
1372
1437
|
this.#boundHandleChange ??= this.#forwardSelectEvent.bind(this, "change");
|
|
1373
|
-
this.#boundHandleOptionHover ??= this.#forwardSelectEvent.bind(
|
|
1374
|
-
this,
|
|
1375
|
-
"optionhover",
|
|
1376
|
-
);
|
|
1377
1438
|
this.#select.addEventListener("input", this.#boundHandleInput);
|
|
1378
1439
|
this.#select.addEventListener("change", this.#boundHandleChange);
|
|
1379
|
-
|
|
1440
|
+
if (this.#usesFigSelect) {
|
|
1441
|
+
this.#boundHandleOptionHover ??= this.#forwardSelectEvent.bind(
|
|
1442
|
+
this,
|
|
1443
|
+
"optionhover",
|
|
1444
|
+
);
|
|
1445
|
+
this.#select.addEventListener("optionhover", this.#boundHandleOptionHover);
|
|
1446
|
+
}
|
|
1380
1447
|
}
|
|
1381
1448
|
|
|
1382
1449
|
#unbindSelectEvents() {
|
|
@@ -1415,7 +1482,7 @@ class PropskitSelect extends HTMLElement {
|
|
|
1415
1482
|
}
|
|
1416
1483
|
|
|
1417
1484
|
#isSelectMenuOpen() {
|
|
1418
|
-
if (!this.#select) return false;
|
|
1485
|
+
if (!this.#select || !this.#usesFigSelect) return false;
|
|
1419
1486
|
if (this.#select.open) return true;
|
|
1420
1487
|
const popup = this.#select.shadowRoot?.querySelector(
|
|
1421
1488
|
'dialog[is="fig-popup"]',
|
|
@@ -1426,11 +1493,12 @@ class PropskitSelect extends HTMLElement {
|
|
|
1426
1493
|
#handlePointerDown(event) {
|
|
1427
1494
|
if (!(event.target instanceof Element)) return;
|
|
1428
1495
|
if (event.target.closest("fig-menu")) return;
|
|
1429
|
-
// fig-select
|
|
1430
|
-
if (event.target.closest("fig-select")) {
|
|
1496
|
+
// Native dropdown / fig-select own their own trigger clicks.
|
|
1497
|
+
if (event.target.closest("fig-select, fig-dropdown")) {
|
|
1431
1498
|
this.#closeGesture = false;
|
|
1432
1499
|
return;
|
|
1433
1500
|
}
|
|
1501
|
+
if (!this.#usesFigSelect) return;
|
|
1434
1502
|
// Light-dismiss closes on pointerdown; remember so click doesn't reopen.
|
|
1435
1503
|
this.#closeGesture = this.#isSelectMenuOpen();
|
|
1436
1504
|
}
|
|
@@ -1439,7 +1507,10 @@ class PropskitSelect extends HTMLElement {
|
|
|
1439
1507
|
if (event.target instanceof Element && event.target.closest("fig-menu")) {
|
|
1440
1508
|
return;
|
|
1441
1509
|
}
|
|
1442
|
-
if (
|
|
1510
|
+
if (
|
|
1511
|
+
event.target instanceof Element &&
|
|
1512
|
+
event.target.closest("fig-select, fig-dropdown")
|
|
1513
|
+
) {
|
|
1443
1514
|
this.#closeGesture = false;
|
|
1444
1515
|
return;
|
|
1445
1516
|
}
|
|
@@ -1448,6 +1519,7 @@ class PropskitSelect extends HTMLElement {
|
|
|
1448
1519
|
return;
|
|
1449
1520
|
}
|
|
1450
1521
|
this.#select.focus();
|
|
1522
|
+
if (!this.#usesFigSelect) return;
|
|
1451
1523
|
|
|
1452
1524
|
if (this.#closeGesture || this.#isSelectMenuOpen()) {
|
|
1453
1525
|
this.#closeGesture = false;
|
|
@@ -6701,168 +6773,3 @@ class FigReorder extends HTMLElement {
|
|
|
6701
6773
|
}
|
|
6702
6774
|
|
|
6703
6775
|
figLabDefineElement("fig-reorder", FigReorder);
|
|
6704
|
-
|
|
6705
|
-
/*
|
|
6706
|
-
* fig-select currently lives in fig.js. Keep the lab consumers resilient to
|
|
6707
|
-
* reconnects and selected-option mutations until those core fixes can move
|
|
6708
|
-
* with the component.
|
|
6709
|
-
*/
|
|
6710
|
-
const figLabEnhancedSelects = new WeakSet();
|
|
6711
|
-
const figLabDisconnectedSelects = new WeakSet();
|
|
6712
|
-
const figLabReconnectFallbacks = new WeakSet();
|
|
6713
|
-
|
|
6714
|
-
function figLabSelectOptions(select) {
|
|
6715
|
-
return Array.from(select.querySelectorAll("fig-select-option"));
|
|
6716
|
-
}
|
|
6717
|
-
|
|
6718
|
-
function figLabSyncSelectedOption(select, requestedOption, emit = true) {
|
|
6719
|
-
const options = figLabSelectOptions(select);
|
|
6720
|
-
if (!options.length) return;
|
|
6721
|
-
let option =
|
|
6722
|
-
requestedOption && options.includes(requestedOption)
|
|
6723
|
-
? requestedOption
|
|
6724
|
-
: options.find((candidate) => figLabBooleanAttribute(candidate, "selected"));
|
|
6725
|
-
if (!option || figLabBooleanAttribute(option, "disabled")) {
|
|
6726
|
-
option =
|
|
6727
|
-
options.find((candidate) => !figLabBooleanAttribute(candidate, "disabled")) ||
|
|
6728
|
-
options[0];
|
|
6729
|
-
}
|
|
6730
|
-
if (!option) return;
|
|
6731
|
-
|
|
6732
|
-
const value = option.value ?? option.getAttribute("value") ?? "";
|
|
6733
|
-
const previousValue = select.getAttribute("value") ?? "";
|
|
6734
|
-
select.setAttribute("value", String(value));
|
|
6735
|
-
for (const candidate of options) {
|
|
6736
|
-
const selected = candidate === option;
|
|
6737
|
-
candidate.toggleAttribute("selected", selected);
|
|
6738
|
-
candidate.setAttribute("aria-selected", String(selected));
|
|
6739
|
-
}
|
|
6740
|
-
if (emit && previousValue !== String(value)) {
|
|
6741
|
-
select.dispatchEvent(
|
|
6742
|
-
new CustomEvent("input", {
|
|
6743
|
-
detail: String(value),
|
|
6744
|
-
bubbles: true,
|
|
6745
|
-
composed: true,
|
|
6746
|
-
}),
|
|
6747
|
-
);
|
|
6748
|
-
select.dispatchEvent(
|
|
6749
|
-
new CustomEvent("change", {
|
|
6750
|
-
detail: String(value),
|
|
6751
|
-
bubbles: true,
|
|
6752
|
-
composed: true,
|
|
6753
|
-
}),
|
|
6754
|
-
);
|
|
6755
|
-
}
|
|
6756
|
-
}
|
|
6757
|
-
|
|
6758
|
-
function figLabEnhanceSelect(select) {
|
|
6759
|
-
if (figLabEnhancedSelects.has(select)) return;
|
|
6760
|
-
const trigger = select.shadowRoot?.querySelector(".fig-select-trigger");
|
|
6761
|
-
if (!trigger) {
|
|
6762
|
-
requestAnimationFrame(() => {
|
|
6763
|
-
if (select.isConnected) figLabEnhanceSelect(select);
|
|
6764
|
-
});
|
|
6765
|
-
return;
|
|
6766
|
-
}
|
|
6767
|
-
figLabEnhancedSelects.add(select);
|
|
6768
|
-
|
|
6769
|
-
const observer = new MutationObserver((mutations) => {
|
|
6770
|
-
let selectedOption = null;
|
|
6771
|
-
let selectedWasRemoved = false;
|
|
6772
|
-
for (const mutation of mutations) {
|
|
6773
|
-
if (
|
|
6774
|
-
mutation.type !== "attributes" ||
|
|
6775
|
-
mutation.attributeName !== "selected" ||
|
|
6776
|
-
mutation.target.tagName !== "FIG-SELECT-OPTION"
|
|
6777
|
-
) {
|
|
6778
|
-
continue;
|
|
6779
|
-
}
|
|
6780
|
-
if (figLabBooleanAttribute(mutation.target, "selected")) {
|
|
6781
|
-
selectedOption = mutation.target;
|
|
6782
|
-
} else if (
|
|
6783
|
-
String(mutation.target.value ?? "") ===
|
|
6784
|
-
(select.getAttribute("value") ?? "")
|
|
6785
|
-
) {
|
|
6786
|
-
selectedWasRemoved = true;
|
|
6787
|
-
}
|
|
6788
|
-
}
|
|
6789
|
-
if (selectedOption) {
|
|
6790
|
-
const optionValue = String(selectedOption.value ?? "");
|
|
6791
|
-
if ((select.getAttribute("value") ?? "") !== optionValue) {
|
|
6792
|
-
figLabSyncSelectedOption(select, selectedOption);
|
|
6793
|
-
}
|
|
6794
|
-
} else if (selectedWasRemoved) {
|
|
6795
|
-
figLabSyncSelectedOption(select, null);
|
|
6796
|
-
}
|
|
6797
|
-
});
|
|
6798
|
-
observer.observe(select, {
|
|
6799
|
-
attributes: true,
|
|
6800
|
-
subtree: true,
|
|
6801
|
-
attributeFilter: ["selected"],
|
|
6802
|
-
});
|
|
6803
|
-
}
|
|
6804
|
-
|
|
6805
|
-
function figLabInstallSelectReconnectFallback(select) {
|
|
6806
|
-
if (figLabReconnectFallbacks.has(select)) return;
|
|
6807
|
-
const trigger = select.shadowRoot?.querySelector(".fig-select-trigger");
|
|
6808
|
-
if (!trigger) return;
|
|
6809
|
-
figLabReconnectFallbacks.add(select);
|
|
6810
|
-
|
|
6811
|
-
trigger.addEventListener("click", () => {
|
|
6812
|
-
if (figLabBooleanAttribute(select, "disabled")) return;
|
|
6813
|
-
select.open = !select.open;
|
|
6814
|
-
});
|
|
6815
|
-
select.addEventListener("click", (event) => {
|
|
6816
|
-
const option = event
|
|
6817
|
-
.composedPath()
|
|
6818
|
-
.find((node) => node?.tagName === "FIG-SELECT-OPTION");
|
|
6819
|
-
if (
|
|
6820
|
-
!option ||
|
|
6821
|
-
!select.contains(option) ||
|
|
6822
|
-
figLabBooleanAttribute(option, "disabled")
|
|
6823
|
-
) {
|
|
6824
|
-
return;
|
|
6825
|
-
}
|
|
6826
|
-
figLabSyncSelectedOption(select, option);
|
|
6827
|
-
select.open = false;
|
|
6828
|
-
});
|
|
6829
|
-
}
|
|
6830
|
-
|
|
6831
|
-
function figLabMarkDisconnectedSelectTree(root) {
|
|
6832
|
-
if (root instanceof Element && root.matches("fig-select")) {
|
|
6833
|
-
figLabDisconnectedSelects.add(root);
|
|
6834
|
-
}
|
|
6835
|
-
root
|
|
6836
|
-
.querySelectorAll?.("fig-select")
|
|
6837
|
-
.forEach((select) => figLabDisconnectedSelects.add(select));
|
|
6838
|
-
}
|
|
6839
|
-
|
|
6840
|
-
function figLabEnhanceSelectTree(root) {
|
|
6841
|
-
if (root instanceof Element && root.matches("fig-select")) {
|
|
6842
|
-
figLabEnhanceSelect(root);
|
|
6843
|
-
}
|
|
6844
|
-
root
|
|
6845
|
-
.querySelectorAll?.("fig-select")
|
|
6846
|
-
.forEach((select) => figLabEnhanceSelect(select));
|
|
6847
|
-
}
|
|
6848
|
-
|
|
6849
|
-
figLabEnhanceSelectTree(document);
|
|
6850
|
-
new MutationObserver((mutations) => {
|
|
6851
|
-
for (const mutation of mutations) {
|
|
6852
|
-
for (const node of mutation.removedNodes) {
|
|
6853
|
-
if (node instanceof Element) figLabMarkDisconnectedSelectTree(node);
|
|
6854
|
-
}
|
|
6855
|
-
for (const node of mutation.addedNodes) {
|
|
6856
|
-
if (!(node instanceof Element)) continue;
|
|
6857
|
-
figLabEnhanceSelectTree(node);
|
|
6858
|
-
const selects = node.matches("fig-select")
|
|
6859
|
-
? [node]
|
|
6860
|
-
: [...node.querySelectorAll("fig-select")];
|
|
6861
|
-
for (const select of selects) {
|
|
6862
|
-
if (figLabDisconnectedSelects.has(select)) {
|
|
6863
|
-
figLabInstallSelectReconnectFallback(select);
|
|
6864
|
-
}
|
|
6865
|
-
}
|
|
6866
|
-
}
|
|
6867
|
-
}
|
|
6868
|
-
}).observe(document.documentElement, { childList: true, subtree: true });
|