@rogieking/figui3 6.18.4 → 6.19.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/components.css +1 -0
- package/dist/components.css +1 -1
- package/dist/fig-editor.css +1 -1
- package/dist/fig-lab.css +1 -1
- package/dist/fig-lab.js +12 -12
- package/dist/fig.css +1 -1
- package/dist/fig.js +1 -1
- package/fig-lab.css +133 -0
- package/fig-lab.js +484 -6
- package/fig.js +1 -1
- package/package.json +1 -1
package/fig-lab.js
CHANGED
|
@@ -243,6 +243,7 @@ class PropskitColor extends HTMLElement {
|
|
|
243
243
|
#boundHandleInput = null;
|
|
244
244
|
#boundHandleChange = null;
|
|
245
245
|
#boundHandleClick = this.#handleClick.bind(this);
|
|
246
|
+
#initialValue = null;
|
|
246
247
|
|
|
247
248
|
static get observedAttributes() {
|
|
248
249
|
return ["label", "direction", "aria-label"];
|
|
@@ -253,6 +254,10 @@ class PropskitColor extends HTMLElement {
|
|
|
253
254
|
this.#syncField();
|
|
254
255
|
this.#syncInputAttributes();
|
|
255
256
|
this.#bindInputEvents();
|
|
257
|
+
if (this.#initialValue === null) {
|
|
258
|
+
this.#initialValue =
|
|
259
|
+
this.getAttribute("default") ?? this.getAttribute("value") ?? "";
|
|
260
|
+
}
|
|
256
261
|
this.removeEventListener("click", this.#boundHandleClick);
|
|
257
262
|
this.addEventListener("click", this.#boundHandleClick);
|
|
258
263
|
|
|
@@ -359,6 +364,7 @@ class PropskitColor extends HTMLElement {
|
|
|
359
364
|
"size",
|
|
360
365
|
"aria-label",
|
|
361
366
|
"text",
|
|
367
|
+
"default",
|
|
362
368
|
]);
|
|
363
369
|
return this.getAttributeNames().filter(
|
|
364
370
|
(name) => !reserved.has(name) && !name.startsWith("data-"),
|
|
@@ -374,7 +380,16 @@ class PropskitColor extends HTMLElement {
|
|
|
374
380
|
if (!nextManaged.has(attrName)) this.#input.removeAttribute(attrName);
|
|
375
381
|
}
|
|
376
382
|
for (const attrName of inputAttrs) {
|
|
377
|
-
|
|
383
|
+
const next = this.getAttribute(attrName) ?? "";
|
|
384
|
+
// Soft sync only. Never force-clear value here — that fights live edits
|
|
385
|
+
// (fig-input-color often keeps a stale value attribute while editing).
|
|
386
|
+
if (
|
|
387
|
+
attrName === "value" &&
|
|
388
|
+
this.#input.getAttribute("value") === next
|
|
389
|
+
) {
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
this.#input.setAttribute(attrName, next);
|
|
378
393
|
}
|
|
379
394
|
|
|
380
395
|
this.#input.setAttribute("text", "true");
|
|
@@ -399,10 +414,31 @@ class PropskitColor extends HTMLElement {
|
|
|
399
414
|
}
|
|
400
415
|
}
|
|
401
416
|
|
|
417
|
+
#valueFromColorEvent(event) {
|
|
418
|
+
const detail =
|
|
419
|
+
event instanceof CustomEvent && event.detail !== undefined
|
|
420
|
+
? event.detail
|
|
421
|
+
: undefined;
|
|
422
|
+
if (typeof detail === "string" && detail) return detail;
|
|
423
|
+
if (detail && typeof detail === "object") {
|
|
424
|
+
if (typeof detail.value === "string" && detail.value) return detail.value;
|
|
425
|
+
if (typeof detail.hex === "string" && detail.hex) return detail.hex;
|
|
426
|
+
if (typeof detail.color === "string" && detail.color) return detail.color;
|
|
427
|
+
}
|
|
428
|
+
// Prefer live JS value — fig-input-color often leaves the attribute stale.
|
|
429
|
+
if (typeof this.#input?.value === "string" && this.#input.value) {
|
|
430
|
+
return this.#input.value;
|
|
431
|
+
}
|
|
432
|
+
return this.#input?.getAttribute("value") ?? "";
|
|
433
|
+
}
|
|
434
|
+
|
|
402
435
|
#forwardInputEvent(type, event) {
|
|
403
436
|
event.stopImmediatePropagation();
|
|
404
|
-
const value = this.#
|
|
437
|
+
const value = this.#valueFromColorEvent(event);
|
|
405
438
|
this.setAttribute("value", value);
|
|
439
|
+
if (this.#input && this.#input.getAttribute("value") !== value) {
|
|
440
|
+
this.#input.setAttribute("value", value);
|
|
441
|
+
}
|
|
406
442
|
const detail =
|
|
407
443
|
event instanceof CustomEvent && event.detail !== undefined
|
|
408
444
|
? event.detail
|
|
@@ -425,15 +461,55 @@ class PropskitColor extends HTMLElement {
|
|
|
425
461
|
}
|
|
426
462
|
|
|
427
463
|
get value() {
|
|
428
|
-
return this
|
|
464
|
+
return this.getAttribute("value") ?? this.#input?.value ?? this.#input?.getAttribute("value") ?? "";
|
|
429
465
|
}
|
|
430
466
|
|
|
431
467
|
set value(nextValue) {
|
|
432
468
|
if (nextValue === null || nextValue === undefined || nextValue === "") {
|
|
433
469
|
this.removeAttribute("value");
|
|
434
|
-
|
|
435
|
-
|
|
470
|
+
this.#input?.removeAttribute("value");
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
const next = String(nextValue);
|
|
474
|
+
this.setAttribute("value", next);
|
|
475
|
+
if (this.#input && this.#input.getAttribute("value") !== next) {
|
|
476
|
+
this.#input.setAttribute("value", next);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/** Force fig-input-color UI refresh even when the hex string is unchanged. */
|
|
481
|
+
#forceInputValue(next) {
|
|
482
|
+
if (!this.#input) return;
|
|
483
|
+
if (this.#input.getAttribute("value") === next) {
|
|
484
|
+
this.#input.removeAttribute("value");
|
|
436
485
|
}
|
|
486
|
+
this.#input.setAttribute("value", next);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
#defaultValue() {
|
|
490
|
+
return this.getAttribute("default") ?? this.#initialValue ?? "";
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
resetToDefault() {
|
|
494
|
+
const next = String(this.#defaultValue());
|
|
495
|
+
this.setAttribute("value", next);
|
|
496
|
+
this.#forceInputValue(next);
|
|
497
|
+
this.dispatchEvent(
|
|
498
|
+
new CustomEvent("input", {
|
|
499
|
+
detail: next,
|
|
500
|
+
bubbles: true,
|
|
501
|
+
cancelable: true,
|
|
502
|
+
composed: true,
|
|
503
|
+
}),
|
|
504
|
+
);
|
|
505
|
+
this.dispatchEvent(
|
|
506
|
+
new CustomEvent("change", {
|
|
507
|
+
detail: next,
|
|
508
|
+
bubbles: true,
|
|
509
|
+
cancelable: true,
|
|
510
|
+
composed: true,
|
|
511
|
+
}),
|
|
512
|
+
);
|
|
437
513
|
}
|
|
438
514
|
|
|
439
515
|
focus(options) {
|
|
@@ -521,6 +597,8 @@ class PropskitSelect extends HTMLElement {
|
|
|
521
597
|
const field = document.createElement("fig-field");
|
|
522
598
|
const label = customLabel || document.createElement("label");
|
|
523
599
|
const select = document.createElement("fig-select");
|
|
600
|
+
// Match menu width to the full-surface control.
|
|
601
|
+
select.setAttribute("full", "");
|
|
524
602
|
field.append(label, select);
|
|
525
603
|
this.#field = field;
|
|
526
604
|
this.#label = label;
|
|
@@ -556,6 +634,8 @@ class PropskitSelect extends HTMLElement {
|
|
|
556
634
|
this.#label.textContent?.trim() ||
|
|
557
635
|
"Select",
|
|
558
636
|
);
|
|
637
|
+
// Always match menu width to the control surface.
|
|
638
|
+
this.#select.setAttribute("full", "");
|
|
559
639
|
}
|
|
560
640
|
|
|
561
641
|
#getForwardedSelectAttrNames() {
|
|
@@ -569,6 +649,7 @@ class PropskitSelect extends HTMLElement {
|
|
|
569
649
|
"id",
|
|
570
650
|
"size",
|
|
571
651
|
"aria-label",
|
|
652
|
+
"full",
|
|
572
653
|
]);
|
|
573
654
|
return this.getAttributeNames().filter(
|
|
574
655
|
(name) => !reserved.has(name) && !name.startsWith("data-"),
|
|
@@ -1101,6 +1182,397 @@ class PropskitNumber extends HTMLElement {
|
|
|
1101
1182
|
}
|
|
1102
1183
|
customElements.define("propskit-number", PropskitNumber);
|
|
1103
1184
|
|
|
1185
|
+
/* Collapsible property group — always collapsible (no collapsible attr). */
|
|
1186
|
+
class PropskitGroup extends HTMLElement {
|
|
1187
|
+
static observedAttributes = ["name", "open", "show-reset"];
|
|
1188
|
+
|
|
1189
|
+
static #CONTROL_SELECTOR = [
|
|
1190
|
+
"propskit-color",
|
|
1191
|
+
"propskit-number",
|
|
1192
|
+
"propskit-select",
|
|
1193
|
+
"propskit-slider",
|
|
1194
|
+
"propskit-switch",
|
|
1195
|
+
"propskit-text",
|
|
1196
|
+
].join(",");
|
|
1197
|
+
|
|
1198
|
+
#header = null;
|
|
1199
|
+
#chevron = null;
|
|
1200
|
+
#resetTooltip = null;
|
|
1201
|
+
#defaults = new WeakMap();
|
|
1202
|
+
#childObserver = null;
|
|
1203
|
+
#dirtyFrame = 0;
|
|
1204
|
+
#boundOnControlEvent = () => this.#queueDirtySync();
|
|
1205
|
+
|
|
1206
|
+
connectedCallback() {
|
|
1207
|
+
this.#render();
|
|
1208
|
+
this.#bindDirtyListeners();
|
|
1209
|
+
requestAnimationFrame(() => {
|
|
1210
|
+
this.#ensureDefaults();
|
|
1211
|
+
this.#syncDirtyState();
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
disconnectedCallback() {
|
|
1216
|
+
this.#unbindDirtyListeners();
|
|
1217
|
+
if (this.#dirtyFrame) {
|
|
1218
|
+
cancelAnimationFrame(this.#dirtyFrame);
|
|
1219
|
+
this.#dirtyFrame = 0;
|
|
1220
|
+
}
|
|
1221
|
+
if (this.#header) {
|
|
1222
|
+
this.#header.removeEventListener("click", this.#handleToggle);
|
|
1223
|
+
this.#header.removeEventListener("keydown", this.#handleHeaderKeyDown);
|
|
1224
|
+
}
|
|
1225
|
+
const resetBtn = this.#resetTooltip?.querySelector("fig-button");
|
|
1226
|
+
resetBtn?.removeEventListener("click", this.#handleReset);
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
1230
|
+
if (oldValue === newValue) return;
|
|
1231
|
+
if (name === "open") {
|
|
1232
|
+
this.#header?.setAttribute("aria-expanded", String(this.open));
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
if (name === "show-reset") {
|
|
1236
|
+
this.#syncResetButton();
|
|
1237
|
+
this.#syncDirtyState();
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
this.#render();
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
get open() {
|
|
1244
|
+
const attr = this.getAttribute("open");
|
|
1245
|
+
return attr !== null && attr !== "false";
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
set open(value) {
|
|
1249
|
+
const was = this.open;
|
|
1250
|
+
if (value) {
|
|
1251
|
+
this.setAttribute("open", "true");
|
|
1252
|
+
} else {
|
|
1253
|
+
this.setAttribute("open", "false");
|
|
1254
|
+
}
|
|
1255
|
+
this.#header?.setAttribute("aria-expanded", String(!!value));
|
|
1256
|
+
if (was !== !!value) {
|
|
1257
|
+
this.dispatchEvent(
|
|
1258
|
+
new CustomEvent("openchange", {
|
|
1259
|
+
detail: { open: !!value },
|
|
1260
|
+
bubbles: true,
|
|
1261
|
+
}),
|
|
1262
|
+
);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
/** When true (default), show the reset control while the group is dirty. */
|
|
1267
|
+
get showReset() {
|
|
1268
|
+
const attr = this.getAttribute("show-reset");
|
|
1269
|
+
if (attr === null) return true;
|
|
1270
|
+
return attr !== "false";
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
set showReset(value) {
|
|
1274
|
+
if (value) this.setAttribute("show-reset", "true");
|
|
1275
|
+
else this.setAttribute("show-reset", "false");
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
get dirty() {
|
|
1279
|
+
return this.hasAttribute("data-dirty") && this.getAttribute("data-dirty") !== "false";
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
/** Restore all propskit controls in this group to their captured defaults. */
|
|
1283
|
+
resetProperties() {
|
|
1284
|
+
this.#resetControls();
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
#bindDirtyListeners() {
|
|
1288
|
+
this.removeEventListener("input", this.#boundOnControlEvent, true);
|
|
1289
|
+
this.removeEventListener("change", this.#boundOnControlEvent, true);
|
|
1290
|
+
this.addEventListener("input", this.#boundOnControlEvent, true);
|
|
1291
|
+
this.addEventListener("change", this.#boundOnControlEvent, true);
|
|
1292
|
+
|
|
1293
|
+
if (!this.#childObserver) {
|
|
1294
|
+
this.#childObserver = new MutationObserver(() => {
|
|
1295
|
+
this.#ensureDefaults();
|
|
1296
|
+
this.#queueDirtySync();
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
this.#childObserver.disconnect();
|
|
1300
|
+
this.#childObserver.observe(this, { childList: true, subtree: true });
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
#unbindDirtyListeners() {
|
|
1304
|
+
this.removeEventListener("input", this.#boundOnControlEvent, true);
|
|
1305
|
+
this.removeEventListener("change", this.#boundOnControlEvent, true);
|
|
1306
|
+
this.#childObserver?.disconnect();
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
#queueDirtySync() {
|
|
1310
|
+
if (this.#dirtyFrame) return;
|
|
1311
|
+
this.#dirtyFrame = requestAnimationFrame(() => {
|
|
1312
|
+
this.#dirtyFrame = 0;
|
|
1313
|
+
this.#ensureDefaults();
|
|
1314
|
+
this.#syncDirtyState();
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
#isResetTarget(target) {
|
|
1319
|
+
return (
|
|
1320
|
+
target instanceof Element &&
|
|
1321
|
+
Boolean(
|
|
1322
|
+
target.closest(
|
|
1323
|
+
".propskit-group-reset, .propskit-group-reset-tooltip",
|
|
1324
|
+
),
|
|
1325
|
+
)
|
|
1326
|
+
);
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
#handleToggle = (e) => {
|
|
1330
|
+
if (this.#isResetTarget(e.target)) return;
|
|
1331
|
+
e.stopPropagation();
|
|
1332
|
+
this.open = !this.open;
|
|
1333
|
+
};
|
|
1334
|
+
|
|
1335
|
+
#handleHeaderKeyDown = (e) => {
|
|
1336
|
+
if (this.#isResetTarget(e.target)) return;
|
|
1337
|
+
if (e.key !== "Enter" && e.key !== " ") return;
|
|
1338
|
+
e.preventDefault();
|
|
1339
|
+
e.stopPropagation();
|
|
1340
|
+
this.open = !this.open;
|
|
1341
|
+
};
|
|
1342
|
+
|
|
1343
|
+
#handleReset = (e) => {
|
|
1344
|
+
e.preventDefault();
|
|
1345
|
+
e.stopPropagation();
|
|
1346
|
+
this.#resetControls();
|
|
1347
|
+
};
|
|
1348
|
+
|
|
1349
|
+
#controls() {
|
|
1350
|
+
return [
|
|
1351
|
+
...this.querySelectorAll(PropskitGroup.#CONTROL_SELECTOR),
|
|
1352
|
+
].filter((el) => el.closest("propskit-group") === this);
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
#snapshotControl(el) {
|
|
1356
|
+
if (el.localName === "propskit-switch") {
|
|
1357
|
+
return { kind: "checked", value: Boolean(el.checked) };
|
|
1358
|
+
}
|
|
1359
|
+
if ("value" in el) {
|
|
1360
|
+
return { kind: "value", value: el.value };
|
|
1361
|
+
}
|
|
1362
|
+
return { kind: "value", value: el.getAttribute("value") ?? "" };
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
#ensureDefaults() {
|
|
1366
|
+
for (const el of this.#controls()) {
|
|
1367
|
+
if (this.#defaults.has(el)) continue;
|
|
1368
|
+
if (el.hasAttribute("default")) {
|
|
1369
|
+
if (el.localName === "propskit-switch") {
|
|
1370
|
+
this.#defaults.set(el, {
|
|
1371
|
+
kind: "checked",
|
|
1372
|
+
value: figLabBooleanAttribute(el, "default"),
|
|
1373
|
+
});
|
|
1374
|
+
continue;
|
|
1375
|
+
}
|
|
1376
|
+
this.#defaults.set(el, {
|
|
1377
|
+
kind: "value",
|
|
1378
|
+
value: el.getAttribute("default") ?? "",
|
|
1379
|
+
});
|
|
1380
|
+
continue;
|
|
1381
|
+
}
|
|
1382
|
+
this.#defaults.set(el, this.#snapshotControl(el));
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
#controlIsDirty(el) {
|
|
1387
|
+
const snap = this.#defaults.get(el);
|
|
1388
|
+
if (!snap) return false;
|
|
1389
|
+
const cur = this.#snapshotControl(el);
|
|
1390
|
+
if (snap.kind === "checked") {
|
|
1391
|
+
return Boolean(cur.value) !== Boolean(snap.value);
|
|
1392
|
+
}
|
|
1393
|
+
return String(cur.value ?? "") !== String(snap.value ?? "");
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
#computeDirty() {
|
|
1397
|
+
for (const el of this.#controls()) {
|
|
1398
|
+
if (this.#controlIsDirty(el)) return true;
|
|
1399
|
+
}
|
|
1400
|
+
return false;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
#syncDirtyState() {
|
|
1404
|
+
const dirty = this.#computeDirty();
|
|
1405
|
+
if (dirty) this.setAttribute("data-dirty", "");
|
|
1406
|
+
else this.removeAttribute("data-dirty");
|
|
1407
|
+
this.#syncResetButton();
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
#restoreControl(el) {
|
|
1411
|
+
const snap = this.#defaults.get(el);
|
|
1412
|
+
if (!snap) return;
|
|
1413
|
+
|
|
1414
|
+
if (snap.kind === "checked") {
|
|
1415
|
+
el.checked = snap.value;
|
|
1416
|
+
const detail = {
|
|
1417
|
+
checked: Boolean(snap.value),
|
|
1418
|
+
value: el.getAttribute("value") ?? "",
|
|
1419
|
+
};
|
|
1420
|
+
el.dispatchEvent(
|
|
1421
|
+
new CustomEvent("input", {
|
|
1422
|
+
detail,
|
|
1423
|
+
bubbles: true,
|
|
1424
|
+
cancelable: true,
|
|
1425
|
+
composed: true,
|
|
1426
|
+
}),
|
|
1427
|
+
);
|
|
1428
|
+
el.dispatchEvent(
|
|
1429
|
+
new CustomEvent("change", {
|
|
1430
|
+
detail,
|
|
1431
|
+
bubbles: true,
|
|
1432
|
+
cancelable: true,
|
|
1433
|
+
composed: true,
|
|
1434
|
+
}),
|
|
1435
|
+
);
|
|
1436
|
+
return;
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
if (typeof el.resetToDefault === "function") {
|
|
1440
|
+
const prevDefault = el.getAttribute("default");
|
|
1441
|
+
el.setAttribute("default", String(snap.value ?? ""));
|
|
1442
|
+
el.resetToDefault();
|
|
1443
|
+
if (prevDefault === null) el.removeAttribute("default");
|
|
1444
|
+
else el.setAttribute("default", prevDefault);
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
el.value = snap.value;
|
|
1449
|
+
const detail =
|
|
1450
|
+
el.getAttribute?.("value") ??
|
|
1451
|
+
("value" in el ? el.value : snap.value);
|
|
1452
|
+
el.dispatchEvent(
|
|
1453
|
+
new CustomEvent("input", {
|
|
1454
|
+
detail,
|
|
1455
|
+
bubbles: true,
|
|
1456
|
+
cancelable: true,
|
|
1457
|
+
composed: true,
|
|
1458
|
+
}),
|
|
1459
|
+
);
|
|
1460
|
+
el.dispatchEvent(
|
|
1461
|
+
new CustomEvent("change", {
|
|
1462
|
+
detail,
|
|
1463
|
+
bubbles: true,
|
|
1464
|
+
cancelable: true,
|
|
1465
|
+
composed: true,
|
|
1466
|
+
}),
|
|
1467
|
+
);
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
#resetControls() {
|
|
1471
|
+
this.#ensureDefaults();
|
|
1472
|
+
for (const el of this.#controls()) this.#restoreControl(el);
|
|
1473
|
+
this.#syncDirtyState();
|
|
1474
|
+
this.dispatchEvent(
|
|
1475
|
+
new CustomEvent("reset", {
|
|
1476
|
+
bubbles: true,
|
|
1477
|
+
composed: true,
|
|
1478
|
+
}),
|
|
1479
|
+
);
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
#syncResetButton() {
|
|
1483
|
+
if (!this.showReset) {
|
|
1484
|
+
this.#resetTooltip?.setAttribute("hidden", "");
|
|
1485
|
+
return;
|
|
1486
|
+
}
|
|
1487
|
+
this.#ensureResetButton();
|
|
1488
|
+
this.#resetTooltip?.removeAttribute("hidden");
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
#ensureResetButton() {
|
|
1492
|
+
if (!this.#header || !this.showReset) return;
|
|
1493
|
+
let tip = this.#header.querySelector(":scope > .propskit-group-reset-tooltip");
|
|
1494
|
+
if (!tip) {
|
|
1495
|
+
tip = document.createElement("fig-tooltip");
|
|
1496
|
+
tip.className = "propskit-group-reset-tooltip";
|
|
1497
|
+
tip.setAttribute("text", "Reset properties");
|
|
1498
|
+
const btn = document.createElement("fig-button");
|
|
1499
|
+
btn.className = "propskit-group-reset";
|
|
1500
|
+
btn.setAttribute("variant", "ghost");
|
|
1501
|
+
btn.setAttribute("icon", "");
|
|
1502
|
+
btn.setAttribute("aria-label", "Reset properties");
|
|
1503
|
+
const icon = document.createElement("fig-icon");
|
|
1504
|
+
icon.setAttribute("name", "reset");
|
|
1505
|
+
btn.appendChild(icon);
|
|
1506
|
+
tip.appendChild(btn);
|
|
1507
|
+
this.#header.appendChild(tip);
|
|
1508
|
+
btn.addEventListener("click", this.#handleReset);
|
|
1509
|
+
} else {
|
|
1510
|
+
const btn = tip.querySelector("fig-button");
|
|
1511
|
+
btn?.removeEventListener("click", this.#handleReset);
|
|
1512
|
+
btn?.addEventListener("click", this.#handleReset);
|
|
1513
|
+
}
|
|
1514
|
+
this.#resetTooltip = tip;
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
#render() {
|
|
1518
|
+
const nameAttr = this.getAttribute("name");
|
|
1519
|
+
const label = nameAttr || "Group";
|
|
1520
|
+
const userHeader = this.querySelector(":scope > fig-header");
|
|
1521
|
+
|
|
1522
|
+
if (userHeader) {
|
|
1523
|
+
this.#header = userHeader;
|
|
1524
|
+
} else if (!this.#header || !this.#header.dataset.generated) {
|
|
1525
|
+
this.#header = document.createElement("fig-header");
|
|
1526
|
+
this.#header.setAttribute("borderless", "");
|
|
1527
|
+
this.#header.dataset.generated = "true";
|
|
1528
|
+
this.prepend(this.#header);
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
let h3 = this.#header.querySelector("h3");
|
|
1532
|
+
if (!h3) {
|
|
1533
|
+
h3 = document.createElement("h3");
|
|
1534
|
+
this.#header.prepend(h3);
|
|
1535
|
+
}
|
|
1536
|
+
if (!h3.id) h3.id = figLabUniqueId("propskit-group");
|
|
1537
|
+
if (this.#header.dataset.generated) {
|
|
1538
|
+
// Preserve chevron while updating the title text node.
|
|
1539
|
+
const chevron = h3.querySelector(".propskit-group-chevron");
|
|
1540
|
+
h3.textContent = label;
|
|
1541
|
+
if (chevron) h3.prepend(chevron);
|
|
1542
|
+
}
|
|
1543
|
+
if (!this.hasAttribute("role")) this.setAttribute("role", "group");
|
|
1544
|
+
if (
|
|
1545
|
+
!this.hasAttribute("aria-label") &&
|
|
1546
|
+
!this.hasAttribute("aria-labelledby")
|
|
1547
|
+
) {
|
|
1548
|
+
this.setAttribute("aria-labelledby", h3.id);
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
if (!h3.querySelector(".propskit-group-chevron")) {
|
|
1552
|
+
const chevron = document.createElement("fig-icon");
|
|
1553
|
+
chevron.setAttribute("name", "chevron");
|
|
1554
|
+
chevron.setAttribute("size", "small");
|
|
1555
|
+
chevron.className = "propskit-group-chevron";
|
|
1556
|
+
h3.prepend(chevron);
|
|
1557
|
+
}
|
|
1558
|
+
this.#chevron = h3.querySelector(".propskit-group-chevron");
|
|
1559
|
+
this.#syncResetButton();
|
|
1560
|
+
this.#header.removeEventListener("click", this.#handleToggle);
|
|
1561
|
+
this.#header.addEventListener("click", this.#handleToggle);
|
|
1562
|
+
this.#header.setAttribute("role", "button");
|
|
1563
|
+
this.#header.setAttribute("tabindex", "0");
|
|
1564
|
+
this.#header.setAttribute("aria-expanded", String(this.open));
|
|
1565
|
+
this.#header.removeEventListener("keydown", this.#handleHeaderKeyDown);
|
|
1566
|
+
this.#header.addEventListener("keydown", this.#handleHeaderKeyDown);
|
|
1567
|
+
|
|
1568
|
+
if (!this.hasAttribute("open")) {
|
|
1569
|
+
this.setAttribute("open", "false");
|
|
1570
|
+
this.#header.setAttribute("aria-expanded", "false");
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
customElements.define("propskit-group", PropskitGroup);
|
|
1575
|
+
|
|
1104
1576
|
/* Field + Slider wrapper */
|
|
1105
1577
|
class PropskitSlider extends HTMLElement {
|
|
1106
1578
|
#field = null;
|
|
@@ -5496,7 +5968,12 @@ class FigSelect extends HTMLElement {
|
|
|
5496
5968
|
|
|
5497
5969
|
const selectedOffsetX = optionTextRect.left - popupRect.left;
|
|
5498
5970
|
const selectedOffsetY = optionTextRect.top - popupRect.top;
|
|
5499
|
-
|
|
5971
|
+
const full = figLabBooleanAttribute(this, "full");
|
|
5972
|
+
// [full]: pin menu to host width/edges. Otherwise overlay selected
|
|
5973
|
+
// option text on the trigger label (blend-mode style).
|
|
5974
|
+
let left = full
|
|
5975
|
+
? this.getBoundingClientRect().left
|
|
5976
|
+
: labelRect.left - selectedOffsetX;
|
|
5500
5977
|
let top = labelRect.top - selectedOffsetY;
|
|
5501
5978
|
|
|
5502
5979
|
// Keep the whole menu in-view when aligning over the selected option
|
|
@@ -5877,6 +6354,7 @@ class FigSelect extends HTMLElement {
|
|
|
5877
6354
|
const full = figLabBooleanAttribute(this, "full");
|
|
5878
6355
|
|
|
5879
6356
|
// Use !important — fig-select::part(listbox) width rules beat element.style.
|
|
6357
|
+
// [full]: lock to host. Otherwise content-sized with host as min and 20rem max.
|
|
5880
6358
|
if (full) {
|
|
5881
6359
|
this.#popup.style.setProperty("width", `${anchorWidth}px`, "important");
|
|
5882
6360
|
this.#popup.style.setProperty("min-width", `${anchorWidth}px`, "important");
|
package/fig.js
CHANGED
|
@@ -15141,7 +15141,7 @@ customElements.define("fig-preview", FigPreview);
|
|
|
15141
15141
|
const FIG_ICON_TOKENS = {
|
|
15142
15142
|
chevron: "--icon-16-chevron",
|
|
15143
15143
|
checkmark: "--icon-16-checkmark",
|
|
15144
|
-
reset: "--icon-16-reset",
|
|
15144
|
+
reset: { medium: "--icon-24-reset", small: "--icon-16-reset" },
|
|
15145
15145
|
"arrow-left": {
|
|
15146
15146
|
medium: "--icon-24-arrow-left",
|
|
15147
15147
|
small: "--icon-16-arrow-left",
|
package/package.json
CHANGED