@rogieking/figui3 1.9.7 → 2.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/base.css +13 -0
- package/components.css +346 -383
- package/example.html +1906 -1052
- package/fig.js +202 -43
- package/package.json +1 -1
package/fig.js
CHANGED
|
@@ -26,7 +26,7 @@ class FigButton extends HTMLElement {
|
|
|
26
26
|
#selected;
|
|
27
27
|
constructor() {
|
|
28
28
|
super();
|
|
29
|
-
this.attachShadow({ mode: "open" });
|
|
29
|
+
this.attachShadow({ mode: "open", delegatesFocus: true });
|
|
30
30
|
}
|
|
31
31
|
connectedCallback() {
|
|
32
32
|
this.type = this.getAttribute("type") || "button";
|
|
@@ -48,6 +48,11 @@ class FigButton extends HTMLElement {
|
|
|
48
48
|
background: transparent;
|
|
49
49
|
margin: calc(var(--spacer-2)*-1);
|
|
50
50
|
height: var(--spacer-4);
|
|
51
|
+
white-space: nowrap;
|
|
52
|
+
overflow: hidden;
|
|
53
|
+
text-overflow: ellipsis;
|
|
54
|
+
width: 100%;
|
|
55
|
+
min-width: 0;
|
|
51
56
|
}
|
|
52
57
|
</style>
|
|
53
58
|
<button type="${this.type}">
|
|
@@ -62,6 +67,16 @@ class FigButton extends HTMLElement {
|
|
|
62
67
|
requestAnimationFrame(() => {
|
|
63
68
|
this.button = this.shadowRoot.querySelector("button");
|
|
64
69
|
this.button.addEventListener("click", this.#handleClick.bind(this));
|
|
70
|
+
|
|
71
|
+
// Forward focus-visible state to host element
|
|
72
|
+
this.button.addEventListener("focus", () => {
|
|
73
|
+
if (this.button.matches(":focus-visible")) {
|
|
74
|
+
this.setAttribute("data-focus-visible", "");
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
this.button.addEventListener("blur", () => {
|
|
78
|
+
this.removeAttribute("data-focus-visible");
|
|
79
|
+
});
|
|
65
80
|
});
|
|
66
81
|
}
|
|
67
82
|
|
|
@@ -635,40 +650,52 @@ class FigDialog extends HTMLDialogElement {
|
|
|
635
650
|
|
|
636
651
|
#applyPosition() {
|
|
637
652
|
const position = this.getAttribute("position") || "";
|
|
638
|
-
const rect = this.getBoundingClientRect();
|
|
639
|
-
const viewportWidth = window.innerWidth;
|
|
640
|
-
const viewportHeight = window.innerHeight;
|
|
641
653
|
|
|
642
|
-
//
|
|
643
|
-
|
|
644
|
-
|
|
654
|
+
// Apply common styles
|
|
655
|
+
this.style.position = "fixed";
|
|
656
|
+
this.style.margin = "0";
|
|
657
|
+
|
|
658
|
+
// Reset position properties
|
|
659
|
+
this.style.top = "auto";
|
|
660
|
+
this.style.bottom = "auto";
|
|
661
|
+
this.style.left = "auto";
|
|
662
|
+
this.style.right = "auto";
|
|
663
|
+
this.style.transform = "none";
|
|
645
664
|
|
|
646
665
|
// Parse position attribute
|
|
647
666
|
const hasTop = position.includes("top");
|
|
648
667
|
const hasBottom = position.includes("bottom");
|
|
649
668
|
const hasLeft = position.includes("left");
|
|
650
669
|
const hasRight = position.includes("right");
|
|
670
|
+
const hasVCenter = position.includes("center") && !hasTop && !hasBottom;
|
|
671
|
+
const hasHCenter = position.includes("center") && !hasLeft && !hasRight;
|
|
651
672
|
|
|
652
673
|
// Vertical positioning
|
|
653
674
|
if (hasTop) {
|
|
654
|
-
top = this.#offset
|
|
675
|
+
this.style.top = `${this.#offset}px`;
|
|
655
676
|
} else if (hasBottom) {
|
|
656
|
-
|
|
677
|
+
this.style.bottom = `${this.#offset}px`;
|
|
678
|
+
} else if (hasVCenter) {
|
|
679
|
+
this.style.top = "50%";
|
|
657
680
|
}
|
|
658
681
|
|
|
659
682
|
// Horizontal positioning
|
|
660
683
|
if (hasLeft) {
|
|
661
|
-
left = this.#offset
|
|
684
|
+
this.style.left = `${this.#offset}px`;
|
|
662
685
|
} else if (hasRight) {
|
|
663
|
-
|
|
686
|
+
this.style.right = `${this.#offset}px`;
|
|
687
|
+
} else if (hasHCenter) {
|
|
688
|
+
this.style.left = "50%";
|
|
664
689
|
}
|
|
665
690
|
|
|
666
|
-
// Apply
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
691
|
+
// Apply transform for centering
|
|
692
|
+
if (hasVCenter && hasHCenter) {
|
|
693
|
+
this.style.transform = "translate(-50%, -50%)";
|
|
694
|
+
} else if (hasVCenter) {
|
|
695
|
+
this.style.transform = "translateY(-50%)";
|
|
696
|
+
} else if (hasHCenter) {
|
|
697
|
+
this.style.transform = "translateX(-50%)";
|
|
698
|
+
}
|
|
672
699
|
|
|
673
700
|
this.#positionInitialized = true;
|
|
674
701
|
}
|
|
@@ -740,6 +767,12 @@ class FigDialog extends HTMLDialogElement {
|
|
|
740
767
|
// Get current position from computed style
|
|
741
768
|
const rect = this.getBoundingClientRect();
|
|
742
769
|
|
|
770
|
+
// Ensure we are using top/left for dragging by converting current position
|
|
771
|
+
this.style.top = `${rect.top}px`;
|
|
772
|
+
this.style.left = `${rect.left}px`;
|
|
773
|
+
this.style.bottom = "auto";
|
|
774
|
+
this.style.right = "auto";
|
|
775
|
+
|
|
743
776
|
// Store offset from pointer to dialog top-left corner
|
|
744
777
|
this.#dragOffset.x = e.clientX - rect.left;
|
|
745
778
|
this.#dragOffset.y = e.clientY - rect.top;
|
|
@@ -2272,8 +2305,7 @@ class FigCheckbox extends HTMLElement {
|
|
|
2272
2305
|
this.input.setAttribute("id", figUniqueId());
|
|
2273
2306
|
this.input.setAttribute("name", this.name);
|
|
2274
2307
|
this.input.setAttribute("type", "checkbox");
|
|
2275
|
-
this.labelElement =
|
|
2276
|
-
this.labelElement.setAttribute("for", this.input.id);
|
|
2308
|
+
this.labelElement = null;
|
|
2277
2309
|
}
|
|
2278
2310
|
connectedCallback() {
|
|
2279
2311
|
this.checked = this.input.checked =
|
|
@@ -2289,7 +2321,12 @@ class FigCheckbox extends HTMLElement {
|
|
|
2289
2321
|
}
|
|
2290
2322
|
|
|
2291
2323
|
this.append(this.input);
|
|
2292
|
-
|
|
2324
|
+
|
|
2325
|
+
// Only create label if label attribute is present
|
|
2326
|
+
if (this.hasAttribute("label")) {
|
|
2327
|
+
this.#createLabel();
|
|
2328
|
+
this.labelElement.innerText = this.getAttribute("label");
|
|
2329
|
+
}
|
|
2293
2330
|
|
|
2294
2331
|
this.render();
|
|
2295
2332
|
}
|
|
@@ -2297,6 +2334,14 @@ class FigCheckbox extends HTMLElement {
|
|
|
2297
2334
|
return ["disabled", "label", "checked", "name", "value"];
|
|
2298
2335
|
}
|
|
2299
2336
|
|
|
2337
|
+
#createLabel() {
|
|
2338
|
+
if (!this.labelElement) {
|
|
2339
|
+
this.labelElement = document.createElement("label");
|
|
2340
|
+
this.labelElement.setAttribute("for", this.input.id);
|
|
2341
|
+
this.append(this.labelElement);
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2300
2345
|
render() {}
|
|
2301
2346
|
|
|
2302
2347
|
focus() {
|
|
@@ -2310,7 +2355,13 @@ class FigCheckbox extends HTMLElement {
|
|
|
2310
2355
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
2311
2356
|
switch (name) {
|
|
2312
2357
|
case "label":
|
|
2313
|
-
|
|
2358
|
+
if (newValue) {
|
|
2359
|
+
this.#createLabel();
|
|
2360
|
+
this.labelElement.innerText = newValue;
|
|
2361
|
+
} else if (this.labelElement) {
|
|
2362
|
+
this.labelElement.remove();
|
|
2363
|
+
this.labelElement = null;
|
|
2364
|
+
}
|
|
2314
2365
|
break;
|
|
2315
2366
|
case "checked":
|
|
2316
2367
|
this.checked = this.input.checked =
|
|
@@ -2368,29 +2419,139 @@ class FigSwitch extends FigCheckbox {
|
|
|
2368
2419
|
}
|
|
2369
2420
|
window.customElements.define("fig-switch", FigSwitch);
|
|
2370
2421
|
|
|
2371
|
-
/*
|
|
2372
|
-
|
|
2422
|
+
/* Toast */
|
|
2423
|
+
/**
|
|
2424
|
+
* A toast notification element for non-modal, time-based messages.
|
|
2425
|
+
* Always positioned at bottom center of the screen.
|
|
2426
|
+
* @attr {number} duration - Auto-dismiss duration in ms (0 = no auto-dismiss, default: 5000)
|
|
2427
|
+
* @attr {number} offset - Distance from bottom edge in pixels (default: 16)
|
|
2428
|
+
* @attr {string} theme - Visual theme: "dark" (default), "light", "danger", "brand"
|
|
2429
|
+
* @attr {boolean} open - Whether the toast is visible
|
|
2430
|
+
*/
|
|
2431
|
+
class FigToast extends HTMLDialogElement {
|
|
2432
|
+
#defaultOffset = 16; // 1rem in pixels
|
|
2433
|
+
#autoCloseTimer = null;
|
|
2434
|
+
|
|
2373
2435
|
constructor() {
|
|
2374
2436
|
super();
|
|
2375
2437
|
}
|
|
2376
|
-
}
|
|
2377
|
-
window.customElements.define("fig-bell", FigBell);
|
|
2378
2438
|
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
constructor() {
|
|
2382
|
-
super();
|
|
2439
|
+
get #offset() {
|
|
2440
|
+
return parseInt(this.getAttribute("offset") ?? this.#defaultOffset);
|
|
2383
2441
|
}
|
|
2384
|
-
}
|
|
2385
|
-
window.customElements.define("fig-badge", FigBadge);
|
|
2386
2442
|
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2443
|
+
connectedCallback() {
|
|
2444
|
+
// Set default theme if not specified
|
|
2445
|
+
if (!this.hasAttribute("theme")) {
|
|
2446
|
+
this.setAttribute("theme", "dark");
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
// Ensure toast is closed by default
|
|
2450
|
+
// Remove native open attribute if present and not explicitly "true"
|
|
2451
|
+
const shouldOpen =
|
|
2452
|
+
this.getAttribute("open") === "true" || this.getAttribute("open") === "";
|
|
2453
|
+
if (this.hasAttribute("open") && !shouldOpen) {
|
|
2454
|
+
this.removeAttribute("open");
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2457
|
+
// Close the dialog initially (override native behavior)
|
|
2458
|
+
if (!shouldOpen) {
|
|
2459
|
+
this.close();
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
requestAnimationFrame(() => {
|
|
2463
|
+
this.#addCloseListeners();
|
|
2464
|
+
this.#applyPosition();
|
|
2465
|
+
|
|
2466
|
+
// Auto-show if open attribute is explicitly true
|
|
2467
|
+
if (shouldOpen) {
|
|
2468
|
+
this.showToast();
|
|
2469
|
+
}
|
|
2470
|
+
});
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
disconnectedCallback() {
|
|
2474
|
+
this.#clearAutoClose();
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
#addCloseListeners() {
|
|
2478
|
+
this.querySelectorAll("[close-toast]").forEach((button) => {
|
|
2479
|
+
button.removeEventListener("click", this.#handleClose);
|
|
2480
|
+
button.addEventListener("click", this.#handleClose.bind(this));
|
|
2481
|
+
});
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
#handleClose() {
|
|
2485
|
+
this.hideToast();
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
#applyPosition() {
|
|
2489
|
+
// Always bottom center
|
|
2490
|
+
this.style.position = "fixed";
|
|
2491
|
+
this.style.margin = "0";
|
|
2492
|
+
this.style.top = "auto";
|
|
2493
|
+
this.style.bottom = `${this.#offset}px`;
|
|
2494
|
+
this.style.left = "50%";
|
|
2495
|
+
this.style.right = "auto";
|
|
2496
|
+
this.style.transform = "translateX(-50%)";
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2499
|
+
#startAutoClose() {
|
|
2500
|
+
this.#clearAutoClose();
|
|
2501
|
+
|
|
2502
|
+
const duration = parseInt(this.getAttribute("duration") ?? "5000");
|
|
2503
|
+
if (duration > 0) {
|
|
2504
|
+
this.#autoCloseTimer = setTimeout(() => {
|
|
2505
|
+
this.hideToast();
|
|
2506
|
+
}, duration);
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
#clearAutoClose() {
|
|
2511
|
+
if (this.#autoCloseTimer) {
|
|
2512
|
+
clearTimeout(this.#autoCloseTimer);
|
|
2513
|
+
this.#autoCloseTimer = null;
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
/**
|
|
2518
|
+
* Show the toast notification (non-modal)
|
|
2519
|
+
*/
|
|
2520
|
+
showToast() {
|
|
2521
|
+
this.show(); // Non-modal show
|
|
2522
|
+
this.#applyPosition();
|
|
2523
|
+
this.#startAutoClose();
|
|
2524
|
+
this.dispatchEvent(new CustomEvent("toast-show", { bubbles: true }));
|
|
2525
|
+
}
|
|
2526
|
+
|
|
2527
|
+
/**
|
|
2528
|
+
* Hide the toast notification
|
|
2529
|
+
*/
|
|
2530
|
+
hideToast() {
|
|
2531
|
+
this.#clearAutoClose();
|
|
2532
|
+
this.close();
|
|
2533
|
+
this.dispatchEvent(new CustomEvent("toast-hide", { bubbles: true }));
|
|
2534
|
+
}
|
|
2535
|
+
|
|
2536
|
+
static get observedAttributes() {
|
|
2537
|
+
return ["duration", "offset", "open", "theme"];
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2540
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
2541
|
+
if (name === "offset") {
|
|
2542
|
+
this.#applyPosition();
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2545
|
+
if (name === "open") {
|
|
2546
|
+
if (newValue !== null && newValue !== "false") {
|
|
2547
|
+
this.showToast();
|
|
2548
|
+
} else {
|
|
2549
|
+
this.hideToast();
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2391
2552
|
}
|
|
2392
2553
|
}
|
|
2393
|
-
|
|
2554
|
+
customElements.define("fig-toast", FigToast, { extends: "dialog" });
|
|
2394
2555
|
|
|
2395
2556
|
/* Combo Input */
|
|
2396
2557
|
/**
|
|
@@ -3065,15 +3226,14 @@ class FigInputAngle extends HTMLElement {
|
|
|
3065
3226
|
</div>
|
|
3066
3227
|
${
|
|
3067
3228
|
this.text
|
|
3068
|
-
? `<fig-input-
|
|
3069
|
-
type="number"
|
|
3229
|
+
? `<fig-input-number
|
|
3070
3230
|
name="angle"
|
|
3071
3231
|
step="0.1"
|
|
3072
3232
|
value="${this.angle}"
|
|
3073
3233
|
min="0"
|
|
3074
|
-
max="360"
|
|
3075
|
-
|
|
3076
|
-
</fig-input-
|
|
3234
|
+
max="360"
|
|
3235
|
+
units="°">
|
|
3236
|
+
</fig-input-number>`
|
|
3077
3237
|
: ""
|
|
3078
3238
|
}
|
|
3079
3239
|
`;
|
|
@@ -3082,7 +3242,7 @@ class FigInputAngle extends HTMLElement {
|
|
|
3082
3242
|
#setupListeners() {
|
|
3083
3243
|
this.handle = this.querySelector(".fig-input-angle-handle");
|
|
3084
3244
|
this.plane = this.querySelector(".fig-input-angle-plane");
|
|
3085
|
-
this.angleInput = this.querySelector("fig-input-
|
|
3245
|
+
this.angleInput = this.querySelector("fig-input-number[name='angle']");
|
|
3086
3246
|
this.plane.addEventListener("mousedown", this.#handleMouseDown.bind(this));
|
|
3087
3247
|
this.plane.addEventListener(
|
|
3088
3248
|
"touchstart",
|
|
@@ -3091,7 +3251,6 @@ class FigInputAngle extends HTMLElement {
|
|
|
3091
3251
|
window.addEventListener("keydown", this.#handleKeyDown.bind(this));
|
|
3092
3252
|
window.addEventListener("keyup", this.#handleKeyUp.bind(this));
|
|
3093
3253
|
if (this.text && this.angleInput) {
|
|
3094
|
-
this.angleInput = this.querySelector("fig-input-text");
|
|
3095
3254
|
this.angleInput.addEventListener(
|
|
3096
3255
|
"input",
|
|
3097
3256
|
this.#handleAngleInput.bind(this)
|