@rogieking/figui3 7.1.0 → 8.0.1
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 +0 -264
- package/dist/components.css +1 -1
- package/dist/fig-editor.css +1 -1
- package/dist/fig-editor.js +129 -38
- package/dist/fig-lab.css +1 -1
- 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.css +21 -2
- package/fig-lab.js +99 -176
- package/fig.js +36 -1172
- package/package.json +1 -1
package/fig-lab.css
CHANGED
|
@@ -656,7 +656,7 @@ propskit-select {
|
|
|
656
656
|
width: calc(50% - var(--spacer-2-5));
|
|
657
657
|
}
|
|
658
658
|
|
|
659
|
-
fig-select {
|
|
659
|
+
:is(fig-select, fig-dropdown) {
|
|
660
660
|
padding-right: var(--spacer-1);
|
|
661
661
|
|
|
662
662
|
&::after {
|
|
@@ -667,6 +667,10 @@ propskit-select {
|
|
|
667
667
|
fig-select::part(trigger) {
|
|
668
668
|
padding-left: var(--spacer-2-5);
|
|
669
669
|
}
|
|
670
|
+
|
|
671
|
+
fig-dropdown > select {
|
|
672
|
+
padding-left: var(--spacer-2-5);
|
|
673
|
+
}
|
|
670
674
|
}
|
|
671
675
|
}
|
|
672
676
|
|
|
@@ -721,7 +725,7 @@ propskit-select {
|
|
|
721
725
|
}
|
|
722
726
|
}
|
|
723
727
|
|
|
724
|
-
fig-select {
|
|
728
|
+
:is(fig-select, fig-dropdown) {
|
|
725
729
|
position: relative;
|
|
726
730
|
display: flex;
|
|
727
731
|
flex: 1 1 auto;
|
|
@@ -732,13 +736,28 @@ propskit-select {
|
|
|
732
736
|
border-radius: var(--radius-medium);
|
|
733
737
|
background: transparent;
|
|
734
738
|
box-shadow: none;
|
|
739
|
+
}
|
|
735
740
|
|
|
741
|
+
fig-select {
|
|
736
742
|
--fig-select-trigger-width: 100%;
|
|
737
743
|
|
|
738
744
|
&::part(label) {
|
|
739
745
|
text-align: right;
|
|
740
746
|
}
|
|
741
747
|
}
|
|
748
|
+
|
|
749
|
+
/* fig-dropdown fallback: match fig-select trigger layout in the field. */
|
|
750
|
+
fig-dropdown > select {
|
|
751
|
+
width: 100%;
|
|
752
|
+
height: 100%;
|
|
753
|
+
min-width: 0;
|
|
754
|
+
padding: 0 var(--spacer-4) 0 var(--spacer-2);
|
|
755
|
+
text-align: right;
|
|
756
|
+
text-align-last: right;
|
|
757
|
+
color: inherit;
|
|
758
|
+
background: transparent;
|
|
759
|
+
box-shadow: none;
|
|
760
|
+
}
|
|
742
761
|
}
|
|
743
762
|
}
|
|
744
763
|
|
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,8 +1327,11 @@ 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
|
-
|
|
1330
|
+
this.#usesFigSelect = PropskitSelect.#canUseFigSelect();
|
|
1331
|
+
const select = document.createElement(
|
|
1332
|
+
this.#usesFigSelect ? "fig-select" : "fig-dropdown",
|
|
1333
|
+
);
|
|
1334
|
+
// Match menu/control width to the full-surface field.
|
|
1288
1335
|
select.setAttribute("full", "");
|
|
1289
1336
|
field.append(label, select);
|
|
1290
1337
|
this.#field = field;
|
|
@@ -1339,13 +1386,47 @@ 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
|
+
// Keep fig-dropdown's internal <select>; only replace slotted options.
|
|
1402
|
+
for (const child of [...this.#select.children]) {
|
|
1403
|
+
if (child.localName === "option" || child.localName === "optgroup") {
|
|
1404
|
+
child.remove();
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
for (const entry of parsed) {
|
|
1408
|
+
const option = document.createElement("option");
|
|
1409
|
+
option.value = PropskitSelect.#optionEntryValue(entry);
|
|
1410
|
+
option.textContent = PropskitSelect.#optionEntryLabel(entry);
|
|
1411
|
+
this.#select.appendChild(option);
|
|
1412
|
+
}
|
|
1413
|
+
// Re-apply value after options land; fig-dropdown clones via async slotchange.
|
|
1414
|
+
const value = this.getAttribute("value");
|
|
1415
|
+
if (value != null) {
|
|
1416
|
+
const applyValue = () => {
|
|
1417
|
+
this.#select?.setAttribute("value", value);
|
|
1418
|
+
if (this.#select && "value" in this.#select) {
|
|
1419
|
+
this.#select.value = value;
|
|
1420
|
+
}
|
|
1421
|
+
};
|
|
1422
|
+
applyValue();
|
|
1423
|
+
queueMicrotask(applyValue);
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1347
1427
|
#syncSelectAttributes() {
|
|
1348
1428
|
if (!this.#select) return;
|
|
1429
|
+
if (!this.#usesFigSelect) this.#syncDropdownOptions();
|
|
1349
1430
|
const selectAttrs = this.#getForwardedSelectAttrNames().sort((a, b) => {
|
|
1350
1431
|
// Build options before applying value so fig-select can resolve selection.
|
|
1351
1432
|
if (a === "options") return -1;
|
|
@@ -1370,13 +1451,15 @@ class PropskitSelect extends HTMLElement {
|
|
|
1370
1451
|
if (!this.#select) return;
|
|
1371
1452
|
this.#boundHandleInput ??= this.#forwardSelectEvent.bind(this, "input");
|
|
1372
1453
|
this.#boundHandleChange ??= this.#forwardSelectEvent.bind(this, "change");
|
|
1373
|
-
this.#boundHandleOptionHover ??= this.#forwardSelectEvent.bind(
|
|
1374
|
-
this,
|
|
1375
|
-
"optionhover",
|
|
1376
|
-
);
|
|
1377
1454
|
this.#select.addEventListener("input", this.#boundHandleInput);
|
|
1378
1455
|
this.#select.addEventListener("change", this.#boundHandleChange);
|
|
1379
|
-
|
|
1456
|
+
if (this.#usesFigSelect) {
|
|
1457
|
+
this.#boundHandleOptionHover ??= this.#forwardSelectEvent.bind(
|
|
1458
|
+
this,
|
|
1459
|
+
"optionhover",
|
|
1460
|
+
);
|
|
1461
|
+
this.#select.addEventListener("optionhover", this.#boundHandleOptionHover);
|
|
1462
|
+
}
|
|
1380
1463
|
}
|
|
1381
1464
|
|
|
1382
1465
|
#unbindSelectEvents() {
|
|
@@ -1415,7 +1498,7 @@ class PropskitSelect extends HTMLElement {
|
|
|
1415
1498
|
}
|
|
1416
1499
|
|
|
1417
1500
|
#isSelectMenuOpen() {
|
|
1418
|
-
if (!this.#select) return false;
|
|
1501
|
+
if (!this.#select || !this.#usesFigSelect) return false;
|
|
1419
1502
|
if (this.#select.open) return true;
|
|
1420
1503
|
const popup = this.#select.shadowRoot?.querySelector(
|
|
1421
1504
|
'dialog[is="fig-popup"]',
|
|
@@ -1426,11 +1509,12 @@ class PropskitSelect extends HTMLElement {
|
|
|
1426
1509
|
#handlePointerDown(event) {
|
|
1427
1510
|
if (!(event.target instanceof Element)) return;
|
|
1428
1511
|
if (event.target.closest("fig-menu")) return;
|
|
1429
|
-
// fig-select
|
|
1430
|
-
if (event.target.closest("fig-select")) {
|
|
1512
|
+
// Native dropdown / fig-select own their own trigger clicks.
|
|
1513
|
+
if (event.target.closest("fig-select, fig-dropdown")) {
|
|
1431
1514
|
this.#closeGesture = false;
|
|
1432
1515
|
return;
|
|
1433
1516
|
}
|
|
1517
|
+
if (!this.#usesFigSelect) return;
|
|
1434
1518
|
// Light-dismiss closes on pointerdown; remember so click doesn't reopen.
|
|
1435
1519
|
this.#closeGesture = this.#isSelectMenuOpen();
|
|
1436
1520
|
}
|
|
@@ -1439,7 +1523,10 @@ class PropskitSelect extends HTMLElement {
|
|
|
1439
1523
|
if (event.target instanceof Element && event.target.closest("fig-menu")) {
|
|
1440
1524
|
return;
|
|
1441
1525
|
}
|
|
1442
|
-
if (
|
|
1526
|
+
if (
|
|
1527
|
+
event.target instanceof Element &&
|
|
1528
|
+
event.target.closest("fig-select, fig-dropdown")
|
|
1529
|
+
) {
|
|
1443
1530
|
this.#closeGesture = false;
|
|
1444
1531
|
return;
|
|
1445
1532
|
}
|
|
@@ -1448,6 +1535,7 @@ class PropskitSelect extends HTMLElement {
|
|
|
1448
1535
|
return;
|
|
1449
1536
|
}
|
|
1450
1537
|
this.#select.focus();
|
|
1538
|
+
if (!this.#usesFigSelect) return;
|
|
1451
1539
|
|
|
1452
1540
|
if (this.#closeGesture || this.#isSelectMenuOpen()) {
|
|
1453
1541
|
this.#closeGesture = false;
|
|
@@ -6701,168 +6789,3 @@ class FigReorder extends HTMLElement {
|
|
|
6701
6789
|
}
|
|
6702
6790
|
|
|
6703
6791
|
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 });
|