@rogieking/figui3 4.1.4 → 4.2.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.
Files changed (3) hide show
  1. package/components.css +184 -205
  2. package/fig.js +53 -1
  3. package/package.json +1 -1
package/components.css CHANGED
@@ -1036,7 +1036,6 @@ fig-tab,
1036
1036
  }
1037
1037
  }
1038
1038
 
1039
- &:has(:checked),
1040
1039
  &[selected]:not([selected="false"]) {
1041
1040
  background-color: var(--figma-color-bg-secondary);
1042
1041
  font-weight: var(--body-medium-strong-fontWeight);
@@ -1046,22 +1045,13 @@ fig-tab,
1046
1045
  &:hover {
1047
1046
  background-color: var(--figma-color-bg-secondary);
1048
1047
  }
1049
-
1050
- & [type="radio"] {
1051
- position: absolute;
1052
- inset: 0;
1053
- border-radius: 0;
1054
- opacity: 0;
1055
- z-index: 1;
1056
- width: 100%;
1057
- height: 100%;
1058
- }
1059
1048
  }
1060
1049
 
1061
1050
  fig-tabs,
1062
1051
  .tabs {
1063
1052
  display: flex;
1064
- gap: var(--spacer-2);
1053
+ gap: var(--spacer-1);
1054
+ padding: var(--spacer-1) var(--spacer-2);
1065
1055
  }
1066
1056
 
1067
1057
  fig-tab-content,
@@ -1990,52 +1980,52 @@ input[type="checkbox"].switch {
1990
1980
  transition: var(--input-transition);
1991
1981
  box-shadow: var(--handle-shadow);
1992
1982
  }
1993
- }
1994
-
1995
- input[type="checkbox"].switch[indeterminate="true"]:after {
1996
- width: 0.625rem;
1997
- height: 0.125rem;
1998
- transform: none;
1999
- }
2000
1983
 
2001
- input[type="checkbox"].switch[indeterminate="true"],
2002
- input[type="checkbox"].switch:checked {
2003
- background-color: var(--figma-color-bg-brand);
2004
- }
1984
+ &[indeterminate="true"]:after {
1985
+ width: 0.625rem;
1986
+ height: 0.125rem;
1987
+ transform: none;
1988
+ }
2005
1989
 
2006
- input[type="checkbox"].switch:checked:after {
2007
- transform: translate(
2008
- calc(
2009
- (var(--width) - var(--knob-width)) * 0.5 +
2010
- (var(--height) - var(--knob-height)) * -0.5
2011
- )
2012
- );
2013
- }
1990
+ &[indeterminate="true"],
1991
+ &:checked {
1992
+ background-color: var(--figma-color-bg-brand);
1993
+ }
2014
1994
 
2015
- input[type="checkbox"].switch:disabled {
2016
- background-color: transparent;
2017
- cursor: not-allowed;
2018
- &:after {
2019
- background-color: var(--figma-color-icon-disabled);
2020
- box-shadow: none;
1995
+ &:checked:after {
1996
+ transform: translate(
1997
+ calc(
1998
+ (var(--width) - var(--knob-width)) * 0.5 +
1999
+ (var(--height) - var(--knob-height)) * -0.5
2000
+ )
2001
+ );
2021
2002
  }
2022
2003
 
2023
- &:checked,
2024
- &[indeterminate="true"] {
2025
- background-color: var(--figma-color-bg-disabled);
2026
- box-shadow: none;
2004
+ &:disabled {
2005
+ background-color: transparent;
2006
+ cursor: not-allowed;
2027
2007
  &:after {
2028
- background-color: var(--figma-color-bg);
2008
+ background-color: var(--figma-color-icon-disabled);
2009
+ box-shadow: none;
2010
+ }
2011
+
2012
+ &:checked,
2013
+ &[indeterminate="true"] {
2014
+ background-color: var(--figma-color-bg-disabled);
2015
+ box-shadow: none;
2016
+ &:after {
2017
+ background-color: var(--figma-color-bg);
2018
+ }
2029
2019
  }
2030
2020
  }
2031
- }
2032
2021
 
2033
- input[type="checkbox"].switch:focus {
2034
- outline: 0;
2035
- }
2022
+ &:focus {
2023
+ outline: 0;
2024
+ }
2036
2025
 
2037
- input[type="checkbox"].switch:checked:focus {
2038
- outline: 0;
2026
+ &:checked:focus {
2027
+ outline: 0;
2028
+ }
2039
2029
  }
2040
2030
 
2041
2031
  /* Checkbox */
@@ -2143,6 +2133,22 @@ input[type="radio"] {
2143
2133
  }
2144
2134
  }
2145
2135
 
2136
+ &:disabled {
2137
+ background-color: transparent;
2138
+ cursor: not-allowed;
2139
+ &:after {
2140
+ background-color: var(--figma-color-icon-disabled);
2141
+ box-shadow: none;
2142
+ }
2143
+ &:checked {
2144
+ background-color: var(--figma-color-bg-disabled);
2145
+ box-shadow: none;
2146
+ &:after {
2147
+ background-color: var(--figma-color-icon-disabled);
2148
+ }
2149
+ }
2150
+ }
2151
+
2146
2152
  &::after {
2147
2153
  content: "";
2148
2154
  width: 0.375rem;
@@ -2242,7 +2248,6 @@ details {
2242
2248
  }
2243
2249
 
2244
2250
  /* Sliders */
2245
- .fig-slider,
2246
2251
  fig-slider {
2247
2252
  --slider-field-height: 1.5rem;
2248
2253
  --slider-height: 1rem;
@@ -2274,17 +2279,28 @@ fig-slider {
2274
2279
  --slider-transition: none;
2275
2280
  --handle-transition: var(--slider-transition);
2276
2281
 
2277
- display: inline-flex;
2282
+ display: flex;
2278
2283
  align-items: center;
2279
2284
  position: relative;
2285
+ min-width: 0;
2280
2286
  height: var(--slider-field-height);
2281
2287
  transition: var(--slider-transition);
2282
2288
 
2289
+ & .slider {
2290
+ flex: 1 1 0;
2291
+ min-width: 0;
2292
+ }
2293
+
2294
+ & fig-input-number {
2295
+ flex: 0 0 3rem;
2296
+ }
2297
+
2283
2298
  .fig-slider-input-container {
2284
2299
  height: var(--slider-height);
2285
2300
  position: relative;
2286
2301
  display: block;
2287
- width: 100%;
2302
+ flex: 1 1 0;
2303
+ min-width: 0;
2288
2304
  transition: var(--slider-transition);
2289
2305
  background: var(--figma-color-bg-secondary);
2290
2306
  border-radius: var(--slider-border-radius);
@@ -2356,31 +2372,36 @@ fig-slider {
2356
2372
  }
2357
2373
  }
2358
2374
 
2359
- /* Chromium */
2360
2375
  input[type="range"] {
2361
2376
  height: var(--slider-height);
2362
2377
  appearance: none;
2363
2378
  -webkit-appearance: none;
2379
+ -moz-appearance: none;
2364
2380
  border-radius: var(--slider-border-radius);
2365
2381
  display: block;
2366
2382
  width: 100%;
2383
+ min-width: 0;
2367
2384
  background-color: transparent;
2368
2385
  transition: var(--slider-transition);
2369
2386
  position: relative;
2370
2387
 
2371
- &:active::-webkit-slider-thumb {
2372
- cursor: grabbing;
2373
- cursor: -webkit-grabbing;
2374
- }
2375
-
2376
2388
  &:focus {
2377
2389
  outline: none;
2378
2390
  }
2379
2391
 
2392
+ &:disabled {
2393
+ cursor: not-allowed;
2394
+ }
2395
+
2396
+ /* Chromium thumb */
2397
+ &:active::-webkit-slider-thumb {
2398
+ cursor: grabbing;
2399
+ }
2400
+
2380
2401
  &::-webkit-slider-thumb {
2381
2402
  appearance: none;
2382
- background: transparent;
2383
2403
  -webkit-appearance: none;
2404
+ background: transparent;
2384
2405
  color-scheme: inherit;
2385
2406
  transition: var(--handle-transition);
2386
2407
  border-radius: var(--slider-thumb-radius);
@@ -2391,7 +2412,6 @@ fig-slider {
2391
2412
  );
2392
2413
  outline: var(--slider-thumb-outline);
2393
2414
  outline-offset: var(--slider-thumb-outline-offset);
2394
-
2395
2415
  aspect-ratio: 1;
2396
2416
  border: none;
2397
2417
  position: relative;
@@ -2401,22 +2421,8 @@ fig-slider {
2401
2421
  opacity: var(--slider-thumb-opacity);
2402
2422
  }
2403
2423
 
2404
- &:disabled {
2405
- cursor: not-allowed;
2406
-
2407
- &::-webkit-slider-runnable-track {
2408
- color-scheme: inherit;
2409
- background: linear-gradient(
2410
- to right,
2411
- var(--figma-color-bg-secondary) 0%,
2412
- var(--figma-color-bg-secondary) var(--slider-percent),
2413
- var(--figma-color-bg) var(--slider-percent)
2414
- );
2415
- }
2416
-
2417
- &::-webkit-slider-thumb {
2418
- box-shadow: inset 0 0 0 1rem var(--figma-color-bg-disabled);
2419
- }
2424
+ &:disabled::-webkit-slider-thumb {
2425
+ box-shadow: inset 0 0 0 1rem var(--figma-color-bg-disabled);
2420
2426
  }
2421
2427
 
2422
2428
  &::-webkit-slider-runnable-track {
@@ -2427,6 +2433,15 @@ fig-slider {
2427
2433
  border-radius: var(--slider-border-radius);
2428
2434
  }
2429
2435
 
2436
+ &:disabled::-webkit-slider-runnable-track {
2437
+ background: linear-gradient(
2438
+ to right,
2439
+ var(--figma-color-bg-secondary) 0%,
2440
+ var(--figma-color-bg-secondary) var(--slider-percent),
2441
+ var(--figma-color-bg) var(--slider-percent)
2442
+ );
2443
+ }
2444
+
2430
2445
  &.hue::-webkit-slider-runnable-track {
2431
2446
  background: var(--bg-hue);
2432
2447
  }
@@ -2436,34 +2451,18 @@ fig-slider {
2436
2451
  linear-gradient(to right, transparent, var(--color)),
2437
2452
  var(--checkerboard);
2438
2453
  }
2439
- }
2440
-
2441
- /* Firefox */
2442
- input[type="range"] {
2443
- height: var(--slider-height);
2444
- appearance: none;
2445
- -moz-appearance: none;
2446
- border-radius: var(--slider-border-radius);
2447
- display: block;
2448
- width: 100%;
2449
- background-color: transparent;
2450
- transition: var(--slider-transition);
2451
- position: relative;
2452
-
2453
- &:focus {
2454
- outline: none;
2455
- }
2456
2454
 
2455
+ /* Firefox thumb */
2457
2456
  &:active::-moz-range-thumb {
2458
2457
  cursor: grabbing;
2459
- cursor: -webkit-grabbing;
2460
2458
  }
2461
2459
 
2462
2460
  &::-moz-range-thumb {
2463
- color-scheme: inherit;
2464
2461
  appearance: none;
2465
2462
  -moz-appearance: none;
2466
2463
  background: transparent;
2464
+ color-scheme: inherit;
2465
+ transition: var(--handle-transition);
2467
2466
  border-radius: var(--slider-thumb-radius);
2468
2467
  height: var(--slider-thumb-height);
2469
2468
  width: var(--slider-thumb-width);
@@ -2475,26 +2474,11 @@ fig-slider {
2475
2474
  z-index: 1;
2476
2475
  cursor: default;
2477
2476
  box-shadow: var(--slider-handle-shadow);
2478
- transition: var(--handle-transition);
2479
2477
  opacity: var(--slider-thumb-opacity);
2480
2478
  }
2481
2479
 
2482
- &:disabled {
2483
- cursor: not-allowed;
2484
-
2485
- &::-moz-range-track {
2486
- color-scheme: inherit;
2487
- background: linear-gradient(
2488
- to right,
2489
- var(--figma-color-bg-secondary) 0%,
2490
- var(--figma-color-bg-secondary) var(--slider-percent),
2491
- var(--figma-color-bg) var(--slider-percent)
2492
- );
2493
- }
2494
-
2495
- &::-moz-range-thumb {
2496
- box-shadow: inset 0 0 0 1rem var(--figma-color-bg-disabled);
2497
- }
2480
+ &:disabled::-moz-range-thumb {
2481
+ box-shadow: inset 0 0 0 1rem var(--figma-color-bg-disabled);
2498
2482
  }
2499
2483
 
2500
2484
  &::-moz-range-track {
@@ -2504,6 +2488,15 @@ fig-slider {
2504
2488
  transition: var(--slider-transition);
2505
2489
  }
2506
2490
 
2491
+ &:disabled::-moz-range-track {
2492
+ background: linear-gradient(
2493
+ to right,
2494
+ var(--figma-color-bg-secondary) 0%,
2495
+ var(--figma-color-bg-secondary) var(--slider-percent),
2496
+ var(--figma-color-bg) var(--slider-percent)
2497
+ );
2498
+ }
2499
+
2507
2500
  &.hue::-moz-range-track {
2508
2501
  background: var(--bg-hue);
2509
2502
  }
@@ -2584,7 +2577,7 @@ fig-slider {
2584
2577
  }
2585
2578
  fig-input-text,
2586
2579
  fig-input-number {
2587
- flex-basis: 5rem;
2580
+ flex-basis: 3rem;
2588
2581
  border-top-left-radius: 0;
2589
2582
  border-bottom-left-radius: 0;
2590
2583
  border-left: 1px solid var(--figma-color-bg);
@@ -2617,7 +2610,8 @@ dialog,
2617
2610
 
2618
2611
  box-shadow: var(--figma-elevation-500-modal-window);
2619
2612
 
2620
- footer {
2613
+ footer,
2614
+ fig-footer {
2621
2615
  display: flex;
2622
2616
  justify-content: flex-end;
2623
2617
  padding: var(--spacer-2);
@@ -2693,6 +2687,19 @@ dialog,
2693
2687
  dialog[is="fig-dialog"] {
2694
2688
  --z-index: 999999;
2695
2689
  z-index: var(--z-index);
2690
+
2691
+ &[resizable]:not([resizable="false"]) {
2692
+ resize: both;
2693
+ overflow: auto;
2694
+ min-width: 12rem;
2695
+ min-height: 6rem;
2696
+ }
2697
+ & > fig-header[dialog-header]:not([borderless]):not([borderless="false"]) {
2698
+ margin-bottom: var(--spacer-2-5);
2699
+ }
2700
+ & > fig-footer:not([borderless]):not([borderless="false"]) {
2701
+ margin-top: var(--spacer-2-5);
2702
+ }
2696
2703
  }
2697
2704
 
2698
2705
  dialog[is="fig-popup"] {
@@ -2948,7 +2955,6 @@ fig-input-fill,
2948
2955
  fig-checkbox,
2949
2956
  fig-radio,
2950
2957
  fig-tab,
2951
- fig-tabs,
2952
2958
  fig-segmented-control,
2953
2959
  fig-input-palette {
2954
2960
  display: inline-flex;
@@ -3013,7 +3019,7 @@ fig-header {
3013
3019
 
3014
3020
  fig-group {
3015
3021
  display: block;
3016
- padding-bottom: var(--spacer-2-5);
3022
+ margin-bottom: var(--spacer-2-5);
3017
3023
 
3018
3024
  /* Sibling groups */
3019
3025
  & + fig-group {
@@ -3021,9 +3027,6 @@ fig-group {
3021
3027
  &:not([name]) {
3022
3028
  padding-top: var(--spacer-2-5);
3023
3029
  }
3024
- &:last-of-type {
3025
- padding-bottom: 0;
3026
- }
3027
3030
  }
3028
3031
 
3029
3032
  & > fig-header {
@@ -3048,7 +3051,7 @@ fig-group {
3048
3051
 
3049
3052
  &[collapsible]:not([open]):not([open="true"]),
3050
3053
  &[collapsible][open="false"] {
3051
- padding-bottom: 0;
3054
+ margin-bottom: 0;
3052
3055
  fig-header {
3053
3056
  color: var(--figma-color-text-secondary);
3054
3057
  &:hover {
@@ -3442,39 +3445,27 @@ fig-input-palette {
3442
3445
  }
3443
3446
  }
3444
3447
 
3445
- fig-slider {
3446
- display: flex;
3447
-
3448
- & .slider {
3449
- flex-grow: 1;
3450
- }
3451
-
3452
- & fig-input-number {
3453
- flex-basis: 5rem;
3454
- }
3455
-
3456
- fig-field[direction="horizontal"] & {
3457
- flex: 1;
3458
- min-width: 0;
3459
- }
3460
- }
3461
-
3462
- fig-field,
3463
- .fig-field {
3464
- --field-label-width: 4rem;
3465
- display: flex;
3466
- padding: var(--spacer-1) var(--spacer-3);
3448
+ fig-field {
3449
+ --fig-field-gap: var(--spacer-2);
3450
+ --fig-field-left-padding: var(--spacer-3);
3451
+ --fig-field-right-padding: var(--spacer-3);
3452
+ --fig-field-top-padding: var(--spacer-1);
3453
+ --fig-field-bottom-padding: var(--spacer-1);
3454
+ --fig-field-label-ratio: 1fr;
3455
+ --fig-field-input-ratio: 2fr;
3456
+ display: grid;
3457
+ grid-template-columns: var(--fig-field-left-padding) 1fr var(
3458
+ --fig-field-right-padding
3459
+ );
3460
+ grid-template-rows: auto auto;
3461
+ grid-template-areas:
3462
+ "chevron label pad"
3463
+ ". input pad";
3467
3464
  margin: 0;
3468
- flex-direction: column;
3465
+ padding: var(--fig-field-top-padding) 0 var(--fig-field-bottom-padding) 0;
3469
3466
  gap: 0;
3470
3467
  align-items: start;
3471
3468
 
3472
- &[direction="horizontal"] {
3473
- flex-direction: row;
3474
- align-items: center;
3475
- gap: var(--spacer-2);
3476
- }
3477
-
3478
3469
  & > [full] {
3479
3470
  flex: 1 1 auto;
3480
3471
  }
@@ -3485,15 +3476,17 @@ fig-field,
3485
3476
  }
3486
3477
 
3487
3478
  & > label {
3488
- flex: 0;
3479
+ grid-area: label;
3480
+ display: block;
3489
3481
  padding: var(--spacer-1) 0;
3490
- min-height: calc(1rem + var(--spacer-1) * 2);
3491
- display: flex;
3492
- align-items: center;
3493
- width: 100%;
3482
+ line-height: 1rem;
3494
3483
  user-select: none;
3495
3484
  }
3496
3485
 
3486
+ & > *:not(.fig-field-chevron):not(label) {
3487
+ grid-area: input;
3488
+ }
3489
+
3497
3490
  & > .fig-field-chevron {
3498
3491
  --icon: var(--icon-chevron);
3499
3492
  --size: 1rem;
@@ -3502,64 +3495,37 @@ fig-field,
3502
3495
  transition: transform var(--transition-duration)
3503
3496
  var(--transition-timing-function);
3504
3497
  flex-shrink: 0;
3505
- margin: var(--spacer-1) 0;
3506
- margin-left: calc(var(--spacer-3) * -1);
3507
- margin-right: calc(var(--spacer-2) * -1);
3498
+ grid-area: chevron;
3499
+ margin-top: var(--spacer-1);
3508
3500
  }
3509
3501
 
3510
3502
  &:has(> [open]:not([open="false"])) > .fig-field-chevron {
3511
3503
  transform: rotate(0deg);
3512
3504
  }
3513
3505
 
3514
- &[direction="horizontal"] > label {
3515
- display: block;
3516
- width: auto;
3517
- min-width: var(--field-label-width);
3518
- max-width: var(--field-label-width);
3519
- overflow: hidden;
3520
- text-overflow: ellipsis;
3521
- white-space: nowrap;
3522
- padding: 0;
3523
- line-height: 1.5rem;
3524
- }
3525
-
3526
3506
  &[direction="horizontal"] {
3527
- gap: var(--spacer-2);
3507
+ display: grid;
3508
+ grid-template-columns:
3509
+ var(--fig-field-left-padding) minmax(0, var(--fig-field-label-ratio))
3510
+ minmax(0, var(--fig-field-input-ratio)) var(--fig-field-right-padding);
3511
+ grid-template-areas: "chevron label input pad";
3512
+ gap: 0;
3528
3513
  align-items: start;
3529
- flex-direction: row;
3530
- &[columns="thirds"] {
3531
- display: grid;
3532
- grid-template-columns: repeat(3, 1fr);
3533
- gap: var(--spacer-2);
3534
-
3535
- & > label {
3536
- grid-column: 1;
3537
- }
3538
3514
 
3539
- & > label ~ * {
3540
- grid-column: 2 / span 2;
3541
- }
3542
-
3543
- &:not(:has(> label)) > * {
3544
- grid-column: 1 / -1;
3545
- }
3546
- }
3547
3515
  &[columns="half"] {
3548
- display: grid;
3549
- grid-template-columns: repeat(2, 1fr);
3550
- gap: var(--spacer-2);
3551
-
3552
- & > label {
3553
- grid-column: 1;
3554
- }
3555
-
3556
- & > label ~ * {
3557
- grid-column: 2 / span 2;
3558
- }
3559
-
3560
- &:not(:has(> label)) > * {
3561
- grid-column: 1 / -1;
3562
- }
3516
+ --fig-field-label-ratio: 2fr;
3517
+ }
3518
+ & > label {
3519
+ overflow: hidden;
3520
+ text-overflow: ellipsis;
3521
+ white-space: nowrap;
3522
+ padding-right: var(--fig-field-gap);
3523
+ min-width: 0;
3524
+ }
3525
+ /* If the field has no label, set the input ratio to 1fr */
3526
+ &:not(:has(> label)) {
3527
+ --fig-field-input-ratio: 1fr;
3528
+ --fig-field-label-ratio: 0fr;
3563
3529
  }
3564
3530
  }
3565
3531
  }
@@ -3617,14 +3583,18 @@ fig-segmented-control {
3617
3583
 
3618
3584
  & fig-segment {
3619
3585
  flex: 1 1 0;
3620
- display: flex;
3621
- align-items: center;
3622
- justify-content: center;
3586
+ min-width: 0;
3587
+ display: block;
3588
+ text-align: center;
3589
+ line-height: calc(1.5rem - 2px);
3623
3590
  position: relative;
3624
3591
  z-index: 1;
3625
3592
  appearance: none;
3626
3593
  color: var(--figma-color-text-secondary);
3627
3594
  padding: 0 var(--spacer-2);
3595
+ overflow: hidden;
3596
+ text-overflow: ellipsis;
3597
+ white-space: nowrap;
3628
3598
  transition: none;
3629
3599
 
3630
3600
  &[selected]:not([selected="false"]),
@@ -4547,13 +4517,16 @@ fig-chooser {
4547
4517
 
4548
4518
  display: flex;
4549
4519
  flex-direction: column;
4550
- gap: var(--fig-chooser-gap);
4520
+ gap: 0;
4551
4521
  overflow: visible auto;
4552
4522
  scrollbar-width: none;
4553
4523
  scroll-snap-type: y mandatory;
4554
4524
 
4555
4525
  > * {
4556
4526
  scroll-snap-align: start;
4527
+ &:not(.fig-chooser-nav-start):not(.fig-chooser-nav-end) {
4528
+ margin-block-end: var(--fig-chooser-gap);
4529
+ }
4557
4530
  }
4558
4531
 
4559
4532
  &[padding="false"] {
@@ -4585,6 +4558,12 @@ fig-chooser {
4585
4558
  fig-image[full] {
4586
4559
  min-width: 3rem;
4587
4560
  }
4561
+ > * {
4562
+ &:not(.fig-chooser-nav-start):not(.fig-chooser-nav-end) {
4563
+ margin-inline-end: var(--fig-chooser-gap);
4564
+ margin-block-end: 0;
4565
+ }
4566
+ }
4588
4567
  }
4589
4568
 
4590
4569
  &[layout="grid"] {
package/fig.js CHANGED
@@ -1110,6 +1110,8 @@ customElements.define("fig-truncate", FigTruncate);
1110
1110
  * @attr {boolean} drag - Whether the dialog is draggable
1111
1111
  * @attr {string} handle - CSS selector for the drag handle element (e.g., "fig-header"). If not specified, the entire dialog is draggable when drag is enabled.
1112
1112
  * @attr {string} position - Position of the dialog (e.g., "bottom right", "top left", "center center")
1113
+ * @attr {string} title - Title text for the auto-generated header. If no fig-header[dialog-header] exists, one is prepended with this title and a close button.
1114
+ * @attr {boolean} resizable - Whether the dialog can be manually resized by the user (default: false)
1113
1115
  */
1114
1116
  class FigDialog extends HTMLDialogElement {
1115
1117
  #isDragging = false;
@@ -1140,6 +1142,8 @@ class FigDialog extends HTMLDialogElement {
1140
1142
  this.drag =
1141
1143
  this.hasAttribute("drag") && this.getAttribute("drag") !== "false";
1142
1144
 
1145
+ this.#ensureHeader();
1146
+
1143
1147
  requestAnimationFrame(() => {
1144
1148
  this.#addCloseListeners();
1145
1149
  this.#setupDragListeners();
@@ -1154,6 +1158,29 @@ class FigDialog extends HTMLDialogElement {
1154
1158
  });
1155
1159
  }
1156
1160
 
1161
+ #ensureHeader() {
1162
+ if (this.querySelector("fig-header[dialog-header]")) return;
1163
+ const header = document.createElement("fig-header");
1164
+ header.setAttribute("dialog-header", "");
1165
+ header.setAttribute("data-auto", "");
1166
+ const h3 = document.createElement("h3");
1167
+ h3.textContent = this.getAttribute("title") || "Dialog";
1168
+ const tooltip = document.createElement("fig-tooltip");
1169
+ tooltip.setAttribute("text", "Close");
1170
+ const btn = document.createElement("fig-button");
1171
+ btn.setAttribute("variant", "ghost");
1172
+ btn.setAttribute("icon", "");
1173
+ btn.setAttribute("close-dialog", "");
1174
+ const icon = document.createElement("span");
1175
+ icon.className = "fig-mask-icon";
1176
+ icon.style.setProperty("--icon", "var(--icon-close)");
1177
+ btn.appendChild(icon);
1178
+ tooltip.appendChild(btn);
1179
+ header.appendChild(h3);
1180
+ header.appendChild(tooltip);
1181
+ this.prepend(header);
1182
+ }
1183
+
1157
1184
  #addCloseListeners() {
1158
1185
  this.querySelectorAll("fig-button[close-dialog]").forEach((button) => {
1159
1186
  button.removeEventListener("click", this.#boundClose);
@@ -1370,7 +1397,7 @@ class FigDialog extends HTMLDialogElement {
1370
1397
  }
1371
1398
 
1372
1399
  static get observedAttributes() {
1373
- return ["modal", "drag", "position", "handle"];
1400
+ return ["modal", "drag", "position", "handle", "title", "resizable"];
1374
1401
  }
1375
1402
 
1376
1403
  attributeChangedCallback(name, oldValue, newValue) {
@@ -1391,6 +1418,13 @@ class FigDialog extends HTMLDialogElement {
1391
1418
  if (name === "position" && this.#positionInitialized) {
1392
1419
  this.#applyPosition();
1393
1420
  }
1421
+
1422
+ if (name === "title") {
1423
+ const autoHeader = this.querySelector("fig-header[data-auto] h3");
1424
+ if (autoHeader) {
1425
+ autoHeader.textContent = newValue || "Dialog";
1426
+ }
1427
+ }
1394
1428
  }
1395
1429
  }
1396
1430
  figDefineCustomizedBuiltIn("fig-dialog", FigDialog, { extends: "dialog" });
@@ -10652,6 +10686,24 @@ class FigGroup extends HTMLElement {
10652
10686
  }
10653
10687
  customElements.define("fig-group", FigGroup);
10654
10688
 
10689
+ /**
10690
+ * A presentational header element used inside fig-dialog, fig-group, and other containers.
10691
+ * Styling is handled entirely in CSS; this registration makes it a known custom element.
10692
+ *
10693
+ * @attr {boolean} borderless - Removes the bottom border
10694
+ * @attr {boolean} dialog-header - Marks this as a dialog header (auto-generated by fig-dialog)
10695
+ */
10696
+ class FigHeader extends HTMLElement {}
10697
+ customElements.define("fig-header", FigHeader);
10698
+
10699
+ /**
10700
+ * fig-footer
10701
+ * @element fig-footer
10702
+ * @attr {boolean} borderless - Removes the top border
10703
+ */
10704
+ class FigFooter extends HTMLElement {}
10705
+ customElements.define("fig-footer", FigFooter);
10706
+
10655
10707
  // FigFillPicker
10656
10708
  /**
10657
10709
  * A comprehensive fill picker component supporting solid colors, gradients, images, video, and webcam.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "4.1.4",
3
+ "version": "4.2.0",
4
4
  "description": "A lightweight web components library for building Figma plugin and widget UIs with native look and feel",
5
5
  "author": "Rogie King",
6
6
  "license": "MIT",