@reliability-design/web 1.4.8 → 1.4.9
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/CHANGELOG.md +61 -0
- package/dist/components/rel-alert/README.md +1 -1
- package/dist/components/rel-autocomplete/README.md +2 -1
- package/dist/components/rel-carousel/README.md +1 -0
- package/dist/components/rel-checkbox/README.md +2 -1
- package/dist/components/rel-date-picker/README.md +7 -1
- package/dist/components/rel-radio/README.md +1 -1
- package/dist/components/rel-range-picker/README.md +7 -1
- package/dist/components/rel-segmented-button/README.md +5 -4
- package/dist/components/rel-select/README.md +6 -3
- package/dist/components/rel-stepper/README.md +12 -3
- package/dist/components/rel-tab/README.md +1 -0
- package/dist/components/rel-textfield/README.md +2 -2
- package/dist/components/rel-time-picker/README.md +6 -0
- package/dist/index.d.mts +131 -18
- package/dist/index.d.ts +131 -18
- package/dist/index.js +1086 -239
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1071 -224
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -188,11 +188,23 @@ RelAccordion.styles = import_lit.css`
|
|
|
188
188
|
pointer-events: none;
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
/* No CSS border property here, deliberately. Figma's Plain stroke is
|
|
192
|
+
strokeAlign=INSIDE, which OVERLAYS the content: verified against the
|
|
193
|
+
live file, the Head frame is 539 wide inside a 539 component in every
|
|
194
|
+
one of the 24 variants, Plain and Shadow alike. A CSS border insets
|
|
195
|
+
its children instead, which pushed the header in by 1px on all four
|
|
196
|
+
sides — so the hover wash stopped 1px short of the card edge and a
|
|
197
|
+
ring of untinted --rel-bg-neutral survived around it. That ring is
|
|
198
|
+
what read as "a border appears on hover", and it appeared on EVERY
|
|
199
|
+
variant because the border lived on this shared base; Shadow merely
|
|
200
|
+
set border-color: transparent, which still reserves the 1px.
|
|
201
|
+
An outline with a negative offset reproduces strokeAlign=INSIDE
|
|
202
|
+
exactly: no layout inset, painted on top, follows border-radius, and
|
|
203
|
+
not clipped by this element's own overflow: hidden. */
|
|
191
204
|
.accordion {
|
|
192
205
|
background: var(--rel-bg-neutral);
|
|
193
206
|
box-sizing: border-box;
|
|
194
207
|
border-radius: var(--_acc-radius, var(--rel-border-radius-xs));
|
|
195
|
-
border: 1px solid var(--rel-border-subtle);
|
|
196
208
|
overflow: hidden;
|
|
197
209
|
box-shadow: var(--rel-shadows-sm);
|
|
198
210
|
}
|
|
@@ -201,8 +213,9 @@ RelAccordion.styles = import_lit.css`
|
|
|
201
213
|
--_acc-radius: var(--rel-border-radius-xs);
|
|
202
214
|
}
|
|
203
215
|
|
|
216
|
+
/* Live Figma: Style=Shadow carries NO stroke at all (strokes: []). */
|
|
204
217
|
:host([appearance="shadow"]) .accordion {
|
|
205
|
-
|
|
218
|
+
outline: none;
|
|
206
219
|
box-shadow: var(--rel-shadows-sm);
|
|
207
220
|
}
|
|
208
221
|
|
|
@@ -210,11 +223,32 @@ RelAccordion.styles = import_lit.css`
|
|
|
210
223
|
--_acc-radius: 0px;
|
|
211
224
|
}
|
|
212
225
|
|
|
226
|
+
/* Live Figma: Style=Plain is strokeWeight 1, strokeAlign INSIDE. */
|
|
213
227
|
:host([appearance="plain"]) .accordion {
|
|
214
|
-
|
|
228
|
+
outline: 1px solid var(--rel-border-subtle);
|
|
229
|
+
outline-offset: -1px;
|
|
215
230
|
box-shadow: none;
|
|
216
231
|
}
|
|
217
232
|
|
|
233
|
+
/* box-shadow is NOT painted in forced-colors mode (Windows High Contrast)
|
|
234
|
+
but outline is — the same UA behaviour styles/focus-ring.ts documents,
|
|
235
|
+
and the reason its outer ring is an outline rather than a third shadow
|
|
236
|
+
layer. It matters more here than there: appearance="shadow" has no
|
|
237
|
+
stroke in Figma, so once the shadow is dropped the card has NO boundary
|
|
238
|
+
at all and the header, body and page all read as one surface.
|
|
239
|
+
This used to work by accident — while the base rule declared a 1px solid
|
|
240
|
+
transparent border, the UA forced that transparent border to a visible
|
|
241
|
+
colour. Removing the border to fix the hover inset took the accident
|
|
242
|
+
away with it, so the edge is restored deliberately.
|
|
243
|
+
CanvasText rather than a token: in forced-colors the user's palette is
|
|
244
|
+
the whole point. Plain already has an outline and simply re-asserts it. */
|
|
245
|
+
@media (forced-colors: active) {
|
|
246
|
+
.accordion {
|
|
247
|
+
outline: 1px solid CanvasText;
|
|
248
|
+
outline-offset: -1px;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
218
252
|
/* ── Header ───────────────────────────────────────────────── */
|
|
219
253
|
|
|
220
254
|
.header {
|
|
@@ -635,8 +669,12 @@ RelAlert.styles = import_lit2.css`
|
|
|
635
669
|
gap: var(--rel-spacing-lg);
|
|
636
670
|
padding: var(--rel-spacing-md);
|
|
637
671
|
border-radius: var(--rel-border-radius-sm);
|
|
638
|
-
min-width: 360px;
|
|
639
672
|
box-sizing: border-box;
|
|
673
|
+
/* No min-width. Figma's Alert is a fixed 400 with minWidth UNSET on all
|
|
674
|
+
twelve variants (verified on the live file), so a 360px floor was
|
|
675
|
+
code-invented. It stopped the alert fitting any column narrower than
|
|
676
|
+
360px and is what forced the docs Variants grid to minmax(360px, 1fr).
|
|
677
|
+
A consumer who wants a floor can set one on the host. */
|
|
640
678
|
}
|
|
641
679
|
|
|
642
680
|
/* Figma "Frame 10": the leading icon and the content column, 12px apart.
|
|
@@ -649,10 +687,17 @@ RelAlert.styles = import_lit2.css`
|
|
|
649
687
|
min-width: 0;
|
|
650
688
|
}
|
|
651
689
|
|
|
690
|
+
/* Figma "Content": layoutGrow 1, layoutSizingHorizontal FILL — it takes the
|
|
691
|
+
width left over beside the leading icon. Without flex it hugged its text,
|
|
692
|
+
so a short message left the column narrow and a long one wrapped earlier
|
|
693
|
+
than it needed to. min-width: 0 is the companion every flex child needs
|
|
694
|
+
before it is allowed to shrink below its content. */
|
|
652
695
|
.rel-content {
|
|
653
696
|
display: flex;
|
|
654
697
|
flex-direction: column;
|
|
655
698
|
gap: var(--rel-spacing-3xs);
|
|
699
|
+
flex: 1;
|
|
700
|
+
min-width: 0;
|
|
656
701
|
}
|
|
657
702
|
|
|
658
703
|
.rel-message1 {
|
|
@@ -736,9 +781,16 @@ RelAlert.styles = import_lit2.css`
|
|
|
736
781
|
/* COLORS — semantic status tokens carry both light and dark modes.
|
|
737
782
|
Base classes are the SUBTLE style (Figma Style=Subtle): tone light fill,
|
|
738
783
|
no border, tone-colored title. */
|
|
739
|
-
.
|
|
740
|
-
|
|
741
|
-
|
|
784
|
+
/* No border here. Only Style=Outline has a stroke in Figma — Solid and
|
|
785
|
+
Subtle report strokes: NONE on the live file — and Outline's is
|
|
786
|
+
strokeAlign: INSIDE, which OVERLAYS the content rather than insetting it.
|
|
787
|
+
A CSS border does inset, so declaring a transparent one on the shared
|
|
788
|
+
base made every variant's content box 1px tighter than Figma and
|
|
789
|
+
reserved a pixel that only one appearance ever paints in.
|
|
790
|
+
The outline block below draws the real thing with outline-offset: -1px,
|
|
791
|
+
which reproduces inside-aligned stroke exactly: no layout inset, painted
|
|
792
|
+
on top, and it follows border-radius. Same fix as rel-accordion, which
|
|
793
|
+
had the identical bug. */
|
|
742
794
|
|
|
743
795
|
.alert-neutral {
|
|
744
796
|
background: var(--rel-bg-neutral);
|
|
@@ -835,26 +887,45 @@ RelAlert.styles = import_lit2.css`
|
|
|
835
887
|
/* Outline style (Figma Style=Outline): no fill, tone border, tone title */
|
|
836
888
|
:host([appearance="outline"]) .rel-alert {
|
|
837
889
|
background: transparent;
|
|
890
|
+
outline: 1px solid transparent;
|
|
891
|
+
outline-offset: -1px;
|
|
838
892
|
}
|
|
839
893
|
|
|
840
894
|
:host([appearance="outline"]) .alert-neutral {
|
|
841
|
-
|
|
895
|
+
outline-color: var(--rel-border-default);
|
|
842
896
|
}
|
|
843
897
|
|
|
844
898
|
:host([appearance="outline"]) .alert-info {
|
|
845
|
-
|
|
899
|
+
outline-color: var(--rel-border-info);
|
|
846
900
|
}
|
|
847
901
|
|
|
848
902
|
:host([appearance="outline"]) .alert-success {
|
|
849
|
-
|
|
903
|
+
outline-color: var(--rel-border-success);
|
|
850
904
|
}
|
|
851
905
|
|
|
852
906
|
:host([appearance="outline"]) .alert-warning {
|
|
853
|
-
|
|
907
|
+
outline-color: var(--rel-border-warning);
|
|
854
908
|
}
|
|
855
909
|
|
|
856
910
|
:host([appearance="outline"]) .alert-destructive {
|
|
857
|
-
|
|
911
|
+
outline-color: var(--rel-border-danger);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
/* Every appearance keeps a boundary in forced-colors mode (Windows High
|
|
915
|
+
Contrast), where the UA overrides fills and a Solid or Subtle alert would
|
|
916
|
+
otherwise be indistinguishable from the page. This used to work by
|
|
917
|
+
accident: while the shared base declared a 1px transparent border, the UA
|
|
918
|
+
forced it to a visible colour. Removing that border to fix the content
|
|
919
|
+
inset took the accident with it, so the edge is restored deliberately —
|
|
920
|
+
outline rather than border because outline is painted in that mode and
|
|
921
|
+
because it does not re-introduce the inset. CanvasText, not a token: in
|
|
922
|
+
forced-colors the user's palette is the point. Outline appearance simply
|
|
923
|
+
re-asserts what it already has. Same treatment as rel-accordion. */
|
|
924
|
+
@media (forced-colors: active) {
|
|
925
|
+
.rel-alert {
|
|
926
|
+
outline: 1px solid CanvasText;
|
|
927
|
+
outline-offset: -1px;
|
|
928
|
+
}
|
|
858
929
|
}
|
|
859
930
|
|
|
860
931
|
/* Close-icon colour on Subtle and Outline: the tone colour, matching the
|
|
@@ -1210,16 +1281,28 @@ var RelTextfield = class extends import_lit3.LitElement {
|
|
|
1210
1281
|
</div>`;
|
|
1211
1282
|
const belowTemplate = import_lit3.html`<div part="helper" class="${["textfield-support", isError ? "error" : "", belowText ? "" : "empty"].filter(Boolean).join(" ")}" id="${describedById}" aria-live="polite">${this.helperIcon && belowText ? import_lit3.html`<rel-icon part="helper-icon" name="${this.helperIcon}" aria-hidden="true"></rel-icon>` : ""}${belowText ?? ""}</div>`;
|
|
1212
1283
|
if (this.variant === "legend") {
|
|
1213
|
-
const
|
|
1284
|
+
const legendContainerClass = [
|
|
1285
|
+
"textfield-container",
|
|
1286
|
+
this.isDisabled ? "disabled" : "",
|
|
1287
|
+
this.multiline ? "multiline" : "",
|
|
1288
|
+
prefixClear ? "prefix-clear" : ""
|
|
1289
|
+
].filter(Boolean).join(" ");
|
|
1214
1290
|
return import_lit3.html`
|
|
1215
1291
|
<div class="${wrapperClasses}">
|
|
1216
|
-
<
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1292
|
+
<div part="control" class="${legendContainerClass}" @mousedown="${this.handleControlMousedown}">
|
|
1293
|
+
<!-- aria-hidden on the FIELDSET, and deliberately not countered on
|
|
1294
|
+
the legend: aria-hidden="false" cannot un-hide a descendant of
|
|
1295
|
+
a hidden element, so writing it would only mislead. Hiding is
|
|
1296
|
+
correct here — the fieldset now wraps nothing but the legend,
|
|
1297
|
+
so leaving it exposed would announce an empty group before the
|
|
1298
|
+
field. The name still works: accname step 2B includes hidden
|
|
1299
|
+
nodes when they are reached through aria-labelledby, which is
|
|
1300
|
+
how the input gets its name from this legend's id. -->
|
|
1301
|
+
<fieldset class="legend-outline" aria-hidden="true"><legend class="field-legend" id="${legendId}"><span class="legend-text">${labelHidden ? "" : this.label ?? ""}${this.required && !labelHidden ? import_lit3.html`<span class="required-mark" aria-hidden="true">*</span>` : ""}</span></legend></fieldset>
|
|
1302
|
+
${prefixTemplate}
|
|
1303
|
+
<div class="textfield-field">${inputTemplate}</div>
|
|
1304
|
+
${suffixTemplate}
|
|
1305
|
+
</div>
|
|
1223
1306
|
${belowTemplate}
|
|
1224
1307
|
</div>
|
|
1225
1308
|
`;
|
|
@@ -1332,7 +1415,23 @@ RelTextfield.styles = import_lit3.css`
|
|
|
1332
1415
|
|
|
1333
1416
|
:host([floatinglabel]) .textfield-label {
|
|
1334
1417
|
position: absolute;
|
|
1335
|
-
|
|
1418
|
+
/* ZERO, and the Figma numbers are why. Live spec, TextField Size=Medium
|
|
1419
|
+
Label Type=Floating: the "Inner Content" frame sits at dx=0 with
|
|
1420
|
+
padding-left 12, and the "Floating Label" frame sits at dx=12 with its
|
|
1421
|
+
own padding-left 4 — so the label BOX aligns with the content edge and
|
|
1422
|
+
its TEXT lands 4px in, that 4px being the notch padding rather than an
|
|
1423
|
+
indent.
|
|
1424
|
+
.textfield-field already begins at the control's border plus inline
|
|
1425
|
+
padding, i.e. at the content edge, so left: 0 puts the box exactly
|
|
1426
|
+
where Figma puts it. The old --_rel-tf-pad-x applied that inset a
|
|
1427
|
+
SECOND time and pushed the text to 29px from the control edge; briefly
|
|
1428
|
+
correcting it to -4px then over-shot the other way to 13px and left
|
|
1429
|
+
almost no border between the corner and the notch. Figma's own value is
|
|
1430
|
+
16px from the control edge; we land on 17px, the extra 1px being our
|
|
1431
|
+
border, which Figma's inside-aligned stroke absorbs into its padding.
|
|
1432
|
+
With a prefix affix present .textfield-field starts after it, so the
|
|
1433
|
+
label follows the icon. */
|
|
1434
|
+
left: 0;
|
|
1336
1435
|
top: 50%;
|
|
1337
1436
|
transform: translateY(-50%);
|
|
1338
1437
|
color: var(--rel-text-placeholder);
|
|
@@ -1363,19 +1462,59 @@ RelTextfield.styles = import_lit3.css`
|
|
|
1363
1462
|
but a ring and a notch cannot coexist on one box — see the legend
|
|
1364
1463
|
variant's own note below — and Recipe B requires the ring. The surface
|
|
1365
1464
|
colour is therefore a documented seam: --rel-textfield-surface. */
|
|
1465
|
+
/* TWO OVERLAPPING strips, not one exact one. The reference: the label is
|
|
1466
|
+
top: 0 against .textfield-field, whose top edge is the container's
|
|
1467
|
+
CONTENT-box top, so in the label's own coordinates 50% is one
|
|
1468
|
+
border-width below the border-box top. Measured from there the ring
|
|
1469
|
+
occupies [50% - notch, 50% - border] and the border [50% - border, 50%].
|
|
1470
|
+
A single strip cut to exactly that band leaked at some zoom levels and
|
|
1471
|
+
not others (v1.4.9 "slash" report): the border and box-shadow are
|
|
1472
|
+
pixel-snapped by Blink, while a strip inside a translateY(-50%) label
|
|
1473
|
+
rasterises at a fractional offset, so at 105%, 115%, 135% zoom, and at
|
|
1474
|
+
Windows 125% scaling with NO zoom, their edges disagreed by under a
|
|
1475
|
+
device pixel and a hairline of ring or border showed through the text.
|
|
1476
|
+
No offset tuning can fix that; it is right at one scale factor only.
|
|
1477
|
+
So each strip overshoots into paint of ITS OWN colour, and every edge
|
|
1478
|
+
lands either on same-colour paint or inside the other strip's opaque
|
|
1479
|
+
interior:
|
|
1480
|
+
::before surface colour, the whole band + 2px above + 2px below
|
|
1481
|
+
::after the control's FILL, the border + 3px below, painted on top
|
|
1482
|
+
The strips must OVERLAP, not abut. Abutting them at the ring/border
|
|
1483
|
+
junction leaked a constant quarter of the ring colour: two anti-aliased
|
|
1484
|
+
edges on one device row cover a*(1-a) of what is underneath, 0.25 at
|
|
1485
|
+
a = 0.5. Measured as exactly 61/244 before the overlap, 0 after.
|
|
1486
|
+
The overshoot is 2px, not 1: the strip snaps to the grid in the label's
|
|
1487
|
+
own (fractionally offset) space and the ring in the container's, so
|
|
1488
|
+
the two can disagree by up to a full device pixel, and a 1px overshoot
|
|
1489
|
+
still left a 34/255 residue on the ring's outermost row at 105% zoom.
|
|
1490
|
+
Every term derives from --_rel-tf-notch-h / --_rel-tf-border-w, so a
|
|
1491
|
+
ring change carries through. The ::after fill is the container's own
|
|
1492
|
+
background, not the surface seam, so the overshoot into the interior
|
|
1493
|
+
is invisible. Gate: qa/paint-geometry.mjs SLASH pass, zoom x DSF. */
|
|
1366
1494
|
:host([floatinglabel]) .textfield-wrapper.focused .textfield-label::before,
|
|
1367
1495
|
:host([floatinglabel]) .textfield-wrapper.has-value .textfield-label::before {
|
|
1368
1496
|
content: "";
|
|
1369
1497
|
position: absolute;
|
|
1370
1498
|
left: 0;
|
|
1371
1499
|
right: 0;
|
|
1372
|
-
top: 50
|
|
1373
|
-
height: var(--_rel-tf-notch-h, 1px);
|
|
1374
|
-
transform: translateY(-50%);
|
|
1500
|
+
top: calc(50% - var(--_rel-tf-notch-h, 1px) - 2px);
|
|
1501
|
+
height: calc(var(--_rel-tf-notch-h, 1px) + 4px);
|
|
1375
1502
|
background: var(--rel-textfield-surface, var(--rel-bg-neutral));
|
|
1376
1503
|
z-index: -1;
|
|
1377
1504
|
}
|
|
1378
1505
|
|
|
1506
|
+
:host([floatinglabel]) .textfield-wrapper.focused .textfield-label::after,
|
|
1507
|
+
:host([floatinglabel]) .textfield-wrapper.has-value .textfield-label::after {
|
|
1508
|
+
content: "";
|
|
1509
|
+
position: absolute;
|
|
1510
|
+
left: 0;
|
|
1511
|
+
right: 0;
|
|
1512
|
+
top: calc(50% - var(--_rel-tf-border-w, 1px));
|
|
1513
|
+
height: calc(var(--_rel-tf-border-w, 1px) + 3px);
|
|
1514
|
+
background: var(--rel-bg-neutral);
|
|
1515
|
+
z-index: -1;
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1379
1518
|
/* Figma keeps the docked label on --rel-text-label and ramps it per size:
|
|
1380
1519
|
body/md/regular (L), body/sm/regular (M), body/xs/regular (S). */
|
|
1381
1520
|
:host([floatinglabel]) .textfield-wrapper.focused .textfield-label,
|
|
@@ -1449,6 +1588,9 @@ RelTextfield.styles = import_lit3.css`
|
|
|
1449
1588
|
:host([floatinglabel]) .textfield-wrapper.disabled .textfield-label::before {
|
|
1450
1589
|
background: var(--rel-textfield-surface, var(--rel-bg-neutral-elevated-2));
|
|
1451
1590
|
}
|
|
1591
|
+
:host([floatinglabel]) .textfield-wrapper.disabled .textfield-label::after {
|
|
1592
|
+
background: var(--rel-bg-neutral-elevated-2);
|
|
1593
|
+
}
|
|
1452
1594
|
|
|
1453
1595
|
/* Container */
|
|
1454
1596
|
.textfield-container {
|
|
@@ -1861,11 +2003,17 @@ RelTextfield.styles = import_lit3.css`
|
|
|
1861
2003
|
color: var(--rel-text-placeholder);
|
|
1862
2004
|
}
|
|
1863
2005
|
|
|
1864
|
-
|
|
2006
|
+
/* Hiding the placeholder only makes sense where a floating label is
|
|
2007
|
+
standing in its place. The legend variant has no floating label — its
|
|
2008
|
+
label lives in the notch at every state, and the legend branch renders
|
|
2009
|
+
no .textfield-label at all — so applying this there left an empty field
|
|
2010
|
+
at rest: no placeholder AND no label. Excluding legend restores the
|
|
2011
|
+
placeholder for the one variant that has nothing to replace it. */
|
|
2012
|
+
:host([floatinglabel]:not([variant="legend"])) .textfield-input::placeholder {
|
|
1865
2013
|
opacity: 0;
|
|
1866
2014
|
}
|
|
1867
2015
|
|
|
1868
|
-
:host([floatinglabel]) .textfield-wrapper.focused .textfield-input::placeholder {
|
|
2016
|
+
:host([floatinglabel]:not([variant="legend"])) .textfield-wrapper.focused .textfield-input::placeholder {
|
|
1869
2017
|
opacity: 1;
|
|
1870
2018
|
}
|
|
1871
2019
|
|
|
@@ -1933,26 +2081,129 @@ RelTextfield.styles = import_lit3.css`
|
|
|
1933
2081
|
/* ── Legend (notched-outline) variant ──
|
|
1934
2082
|
Native <fieldset>/<legend>: the legend notches the top border and stays
|
|
1935
2083
|
fixed in place across all states (no float/animation). */
|
|
1936
|
-
|
|
1937
|
-
|
|
2084
|
+
/* The container keeps a TRANSPARENT border rather than dropping it. The
|
|
2085
|
+
border is 1px of layout that the default variant also has, and the
|
|
2086
|
+
inline padding is measured from inside it — remove it and a legend
|
|
2087
|
+
field's text sits 1px left of its default-variant neighbour in the same
|
|
2088
|
+
form. The visible stroke is painted by .legend-outline on top, exactly
|
|
2089
|
+
over this transparent one, so the two never double up (including in
|
|
2090
|
+
forced-colors, where they coincide rather than stack). */
|
|
2091
|
+
:host([variant="legend"]) .textfield-container {
|
|
2092
|
+
border-color: transparent;
|
|
2093
|
+
background: transparent;
|
|
2094
|
+
/* The legend protrudes above the top edge; overflow: hidden from the
|
|
2095
|
+
base rule would slice its top half off. */
|
|
2096
|
+
overflow: visible;
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
/* The decorative outline layer. inset: 0 makes its border box identical to
|
|
2100
|
+
the container's, so the painted outline IS --_rel-tf-h — 32 / 40 / 48,
|
|
2101
|
+
with no legend-half subtracted, which is the whole bug this replaced.
|
|
2102
|
+
pointer-events: none keeps clicks going to the control beneath, and the
|
|
2103
|
+
content is lifted above it rather than this being pushed to z-index: -1,
|
|
2104
|
+
because .textfield-container is position: relative with z-index: auto
|
|
2105
|
+
and therefore does NOT create a stacking context — a negative z-index
|
|
2106
|
+
here could slip behind an ancestor's background. */
|
|
2107
|
+
:host([variant="legend"]) .legend-outline {
|
|
2108
|
+
position: absolute;
|
|
2109
|
+
/* NOT inset: 0. An absolutely positioned box's insets resolve against
|
|
2110
|
+
its containing block's PADDING box, and the container keeps a 1px
|
|
2111
|
+
transparent border (see above), so inset: 0 put this layer's border
|
|
2112
|
+
box 1px inside the container's on every side — a painted outline 2px
|
|
2113
|
+
short of the ladder at every size, measured 30/38/46 against 32/40/48.
|
|
2114
|
+
Pulling out by the border width lands the two border boxes exactly on
|
|
2115
|
+
each other, which is what makes the painted height the ladder value. */
|
|
2116
|
+
inset: calc(-1 * var(--_rel-tf-border-w, 1px));
|
|
2117
|
+
/* The legend has a small height (see .field-legend), and Blink then
|
|
2118
|
+
paints the fieldset's top border (legend-height - border) / 2 lower
|
|
2119
|
+
than the layer's top edge. Pull the top edge up by that outset so the
|
|
2120
|
+
painted line stays exactly on the container's top edge and the
|
|
2121
|
+
painted height stays on the 32 / 40 / 48 ladder. Only the top moves;
|
|
2122
|
+
the bottom stays put. */
|
|
2123
|
+
--_rel-tf-legend-h: 3px;
|
|
2124
|
+
--_rel-tf-legend-outset: calc(
|
|
2125
|
+
(var(--_rel-tf-legend-h) - var(--_rel-tf-border-w, 1px)) / 2
|
|
2126
|
+
);
|
|
2127
|
+
top: calc(-1 * var(--_rel-tf-border-w, 1px) - var(--_rel-tf-legend-outset));
|
|
1938
2128
|
margin: 0;
|
|
1939
2129
|
min-inline-size: 0;
|
|
1940
|
-
|
|
2130
|
+
/* Aligns the legend TEXT with the input text. The legend carries its own
|
|
2131
|
+
--rel-spacing-2xs of side padding to cut the notch a little wider than
|
|
2132
|
+
the glyphs, so the layer insets by that much less than the control's
|
|
2133
|
+
own inline padding. Derived from --_rel-tf-pad-x rather than restated,
|
|
2134
|
+
which is what makes the small size line up: the old rule hard-coded
|
|
2135
|
+
--rel-spacing-sm and could not see the size ramp at all, because
|
|
2136
|
+
--_rel-tf-pad-x is declared on .textfield-container and the legend
|
|
2137
|
+
variant never rendered one. */
|
|
2138
|
+
padding-inline: calc(
|
|
2139
|
+
var(--_rel-tf-pad-x, var(--rel-spacing-sm)) - var(--rel-spacing-2xs)
|
|
2140
|
+
);
|
|
2141
|
+
padding-block: 0;
|
|
1941
2142
|
border: 1px solid var(--rel-border-default);
|
|
1942
2143
|
border-radius: var(--rel-border-radius-xs);
|
|
1943
2144
|
background: var(--rel-bg-neutral);
|
|
2145
|
+
pointer-events: none;
|
|
1944
2146
|
transition: border-color 0.2s;
|
|
1945
2147
|
}
|
|
1946
2148
|
|
|
2149
|
+
/* Lift the real content above the decorative layer. */
|
|
2150
|
+
:host([variant="legend"]) .textfield-field,
|
|
2151
|
+
:host([variant="legend"]) .textfield-affix {
|
|
2152
|
+
position: relative;
|
|
2153
|
+
z-index: 1;
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
/* The legend is taken OUT OF FLOW — height: 0 with visible overflow.
|
|
2157
|
+
Blink draws a fieldset's top border through the legend's VERTICAL
|
|
2158
|
+
MIDDLE, so an in-flow legend pushes the painted line down by half its
|
|
2159
|
+
own line-height: measured 23 / 30 / 37 against the 32 / 40 / 48 ladder,
|
|
2160
|
+
which is exactly half of the 18 / 20 / 22 legend line-heights. Collapsing
|
|
2161
|
+
the legend's height puts its middle on the layer's top edge, so the
|
|
2162
|
+
border is drawn there and the painted outline is the full ladder value.
|
|
2163
|
+
The notch still cuts, because the gap spans the legend's WIDTH and that
|
|
2164
|
+
is unaffected. The text is then lifted back onto the line by
|
|
2165
|
+
.legend-text, which needs its own element: translateY(-50%) on a
|
|
2166
|
+
zero-height box resolves to zero, so the transform has to live on a
|
|
2167
|
+
child that still has height. */
|
|
2168
|
+
/* NOT height: 0 any more, and the reason is Blink's cutout rule. The gap
|
|
2169
|
+
it cuts in the top border for the legend is max(legend height, border
|
|
2170
|
+
width) tall, measured from the layer's top edge. A zero-height legend
|
|
2171
|
+
therefore gets a cutout EXACTLY the border's height — and at any
|
|
2172
|
+
fractional scale factor (Windows 125%: DSF 1.25) the pixel-snapped
|
|
2173
|
+
border lands partly outside that rect, so it painted straight through
|
|
2174
|
+
the legend text at half strength (measured 122/244 on every legend cell
|
|
2175
|
+
at DSF 1.25, clean at DSF 1). A 3px legend makes the cutout 3px tall
|
|
2176
|
+
with a pixel of margin either side of the border. The price is Blink's
|
|
2177
|
+
other rule — the border is drawn through the legend's vertical middle,
|
|
2178
|
+
(3 - 1) / 2 = 1px down — which .legend-outline cancels by starting 1px
|
|
2179
|
+
higher, so the painted outline is still the ladder value. The text is
|
|
2180
|
+
lifted onto that line by .legend-text below. */
|
|
1947
2181
|
:host([variant="legend"]) .field-legend {
|
|
1948
|
-
|
|
2182
|
+
display: block;
|
|
2183
|
+
height: var(--_rel-tf-legend-h, 3px);
|
|
2184
|
+
overflow: visible;
|
|
2185
|
+
margin: 0;
|
|
1949
2186
|
padding: 0 var(--rel-spacing-2xs);
|
|
1950
2187
|
color: var(--rel-text-label);
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
2188
|
+
/* REGULAR, not medium. Figma's floated label is 400 at every size — the
|
|
2189
|
+
same finding already recorded at rel-select.ts. This rule used to be
|
|
2190
|
+
the only one in the ramp at 500, so the DEFAULT size rendered heavier
|
|
2191
|
+
than both small and large, which set their own regular weights below. */
|
|
2192
|
+
font-family: var(--rel-body-sm-regular-font-family);
|
|
2193
|
+
font-weight: var(--rel-body-sm-regular-font-weight);
|
|
2194
|
+
font-size: var(--rel-body-sm-regular-font-size);
|
|
2195
|
+
line-height: var(--rel-body-sm-regular-line-height);
|
|
2196
|
+
letter-spacing: var(--rel-body-sm-regular-letter-spacing);
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
/* Carries the legend's text back onto the border line the zero-height
|
|
2200
|
+
legend now sits on. inline-block so it has a height of its own for the
|
|
2201
|
+
percentage to resolve against. */
|
|
2202
|
+
:host([variant="legend"]) .legend-text {
|
|
2203
|
+
display: inline-block;
|
|
2204
|
+
/* Centre the text on the painted line, which sits at the legend's
|
|
2205
|
+
vertical middle (half the legend's height below its top). */
|
|
2206
|
+
transform: translateY(calc(-50% + var(--_rel-tf-legend-h, 3px) / 2));
|
|
1956
2207
|
}
|
|
1957
2208
|
|
|
1958
2209
|
:host([variant="legend"]) .textfield-input {
|
|
@@ -1960,88 +2211,78 @@ RelTextfield.styles = import_lit3.css`
|
|
|
1960
2211
|
box-sizing: border-box;
|
|
1961
2212
|
}
|
|
1962
2213
|
|
|
1963
|
-
/* Legend on the same 32/40/48 scale. The fieldset's border box excludes
|
|
1964
|
-
the legend's protruding top half, so sizing the fieldset itself keeps
|
|
1965
|
-
the visible outline at exactly --_rel-tf-h; the row fills the remaining
|
|
1966
|
-
content area and centres the input. Multiline keeps auto height. */
|
|
1967
|
-
:host([variant="legend"]) .field-fieldset:not(.multiline) {
|
|
1968
|
-
box-sizing: border-box;
|
|
1969
|
-
height: var(--_rel-tf-h, 40px);
|
|
1970
|
-
/* Flex column so the row fills the content box below the legend (a
|
|
1971
|
-
percentage height on the row doesn't resolve inside a fieldset). The
|
|
1972
|
-
<legend> is never a flex item, so the notch is unaffected. */
|
|
1973
|
-
display: flex;
|
|
1974
|
-
flex-direction: column;
|
|
1975
|
-
}
|
|
1976
|
-
:host([variant="legend"]) .field-fieldset:not(.multiline) .field-row {
|
|
1977
|
-
flex: 1;
|
|
1978
|
-
min-height: 0;
|
|
1979
|
-
}
|
|
1980
|
-
:host([variant="legend"]) .field-fieldset:not(.multiline) .textfield-input {
|
|
1981
|
-
padding-top: 0;
|
|
1982
|
-
padding-bottom: 0;
|
|
1983
|
-
/* Fill the row so the whole outline is clickable (see the default
|
|
1984
|
-
variant's note above). */
|
|
1985
|
-
align-self: stretch;
|
|
1986
|
-
}
|
|
1987
|
-
|
|
1988
2214
|
/* labelStyle="none" + legend: nothing to seat in the notch, so drop the
|
|
1989
2215
|
legend entirely and let the top border run continuous. */
|
|
1990
2216
|
:host([label-style="none"][variant="legend"]) .field-legend {
|
|
1991
2217
|
display: none;
|
|
1992
2218
|
}
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
:host([variant="legend"]) .field-row {
|
|
1998
|
-
display: flex;
|
|
1999
|
-
align-items: center;
|
|
2000
|
-
}
|
|
2001
|
-
:host([variant="legend"]) .textfield-affix.prefix.fill {
|
|
2002
|
-
border-radius: 0;
|
|
2219
|
+
/* No legend rendered means no outset either, so the layer goes back to
|
|
2220
|
+
sitting exactly on the container's border box. */
|
|
2221
|
+
:host([label-style="none"][variant="legend"]) .legend-outline {
|
|
2222
|
+
top: calc(-1 * var(--_rel-tf-border-w, 1px));
|
|
2003
2223
|
}
|
|
2004
2224
|
|
|
2005
|
-
/*
|
|
2006
|
-
|
|
2007
|
-
|
|
2225
|
+
/* A fill prefix used to be squared off because the fieldset's content box
|
|
2226
|
+
started below the legend's midline, so it could not reach the top
|
|
2227
|
+
border. The content now rides the ordinary container row, whose top IS
|
|
2228
|
+
the border, so the fill prefix can keep the control's own radius. */
|
|
2229
|
+
|
|
2230
|
+
/* Every state below paints on the OUTLINE LAYER, not the container: the
|
|
2231
|
+
container's own border is transparent by design. The container must also
|
|
2232
|
+
be told to drop the shared focus ring — see the note on focus. */
|
|
2233
|
+
:host([variant="legend"]) .textfield-wrapper:not(.focused):not(.error):not(.disabled)
|
|
2234
|
+
.textfield-container:hover .legend-outline {
|
|
2008
2235
|
border-color: var(--rel-text-placeholder);
|
|
2009
2236
|
}
|
|
2010
2237
|
|
|
2011
|
-
/* Focus — border-color only. A box-shadow
|
|
2012
|
-
rectangular border
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2238
|
+
/* Focus — border-color only, no ring. A box-shadow or outline ring hugs
|
|
2239
|
+
the full rectangular border box and would draw straight across the
|
|
2240
|
+
notch, reading as a line struck through the legend. That is the same
|
|
2241
|
+
collision the floating label has on the other variants, except here
|
|
2242
|
+
there is no mask strip to hide it, so the ring is dropped outright and
|
|
2243
|
+
the legend recolours instead. */
|
|
2244
|
+
:host([variant="legend"]) .textfield-wrapper.focused .textfield-container,
|
|
2245
|
+
:host([variant="legend"]) .textfield-container:focus-within {
|
|
2017
2246
|
box-shadow: none;
|
|
2247
|
+
border-color: transparent;
|
|
2248
|
+
}
|
|
2249
|
+
:host([variant="legend"]) .textfield-wrapper.focused .legend-outline,
|
|
2250
|
+
:host([variant="legend"]) .textfield-container:focus-within .legend-outline {
|
|
2251
|
+
border-color: var(--rel-border-strong);
|
|
2018
2252
|
}
|
|
2019
2253
|
:host([variant="legend"]) .textfield-wrapper.focused .field-legend,
|
|
2020
|
-
:host([variant="legend"]) .
|
|
2254
|
+
:host([variant="legend"]) .textfield-container:focus-within .field-legend {
|
|
2021
2255
|
color: var(--rel-border-strong);
|
|
2022
2256
|
}
|
|
2023
2257
|
|
|
2024
2258
|
/* Error */
|
|
2025
|
-
:host([variant="legend"]) .textfield-wrapper.error .
|
|
2026
|
-
border-color: var(--rel-border-danger);
|
|
2259
|
+
:host([variant="legend"]) .textfield-wrapper.error .textfield-container {
|
|
2027
2260
|
box-shadow: none;
|
|
2261
|
+
border-color: transparent;
|
|
2262
|
+
}
|
|
2263
|
+
:host([variant="legend"]) .textfield-wrapper.error .legend-outline {
|
|
2264
|
+
border-color: var(--rel-border-danger);
|
|
2028
2265
|
}
|
|
2029
2266
|
:host([variant="legend"]) .textfield-wrapper.error .field-legend {
|
|
2030
2267
|
color: var(--rel-text-danger);
|
|
2031
2268
|
}
|
|
2032
2269
|
|
|
2033
|
-
/* Disabled
|
|
2034
|
-
|
|
2270
|
+
/* Disabled — the fill moves to the layer with the stroke, since the
|
|
2271
|
+
container is transparent and would otherwise paint the dulled surface
|
|
2272
|
+
underneath the layer's own background. */
|
|
2273
|
+
:host([variant="legend"]) .textfield-wrapper.disabled .textfield-container {
|
|
2274
|
+
background: transparent;
|
|
2275
|
+
}
|
|
2276
|
+
:host([variant="legend"]) .textfield-wrapper.disabled .legend-outline {
|
|
2035
2277
|
background: var(--rel-bg-neutral-elevated-2);
|
|
2036
2278
|
border-color: var(--rel-border-accent);
|
|
2037
|
-
cursor: not-allowed;
|
|
2038
2279
|
}
|
|
2039
2280
|
:host([variant="legend"]) .textfield-wrapper.disabled .field-legend {
|
|
2040
2281
|
color: var(--rel-text-disabled);
|
|
2041
2282
|
}
|
|
2042
2283
|
|
|
2043
2284
|
/* Readonly */
|
|
2044
|
-
:host([variant="legend"]) .textfield-wrapper.readonly .
|
|
2285
|
+
:host([variant="legend"]) .textfield-wrapper.readonly .textfield-container {
|
|
2045
2286
|
cursor: default;
|
|
2046
2287
|
}
|
|
2047
2288
|
|
|
@@ -2260,20 +2501,28 @@ var RelCheckbox = class extends import_lit4.LitElement {
|
|
|
2260
2501
|
.checked=${this.status === "checked"}
|
|
2261
2502
|
.indeterminate=${this.status === "indeterminate"}
|
|
2262
2503
|
aria-checked="${this.status === "indeterminate" ? "mixed" : import_lit4.nothing}"
|
|
2504
|
+
aria-label="${this.fieldAriaLabel ?? import_lit4.nothing}"
|
|
2263
2505
|
@click=${this.toggle}
|
|
2264
2506
|
@blur=${() => this._onTouched()}
|
|
2265
2507
|
?disabled=${this.state === "disabled"}
|
|
2266
2508
|
/>
|
|
2267
2509
|
|
|
2510
|
+
<!-- Both marks are Figma's own paths, read from the live Published DS
|
|
2511
|
+
file (set 8418:1987) on 2026-09-25 and left in their authored
|
|
2512
|
+
16-unit box. Figma knocks these out of the filled square; we paint
|
|
2513
|
+
them in the inverse tone over it instead, which is the same
|
|
2514
|
+
picture and keeps the existing per-state fill rules working.
|
|
2515
|
+
Do not re-scale the coordinates — the svg is sized to the drawn
|
|
2516
|
+
square so one unit here is one Figma unit. -->
|
|
2268
2517
|
<span class="box">
|
|
2269
|
-
<svg class="check-icon" viewBox="0 0
|
|
2518
|
+
<svg class="check-icon" viewBox="0 0 16 16" fill="var(--rel-text-inverse)">
|
|
2270
2519
|
<path
|
|
2271
|
-
d="M6.
|
|
2520
|
+
d="M6.22222 12.4444L1.77778 8L3.03111 6.74667L6.22222 9.92889L12.9689 3.18222L14.2222 4.44444L6.22222 12.4444Z"
|
|
2272
2521
|
/>
|
|
2273
2522
|
</svg>
|
|
2274
2523
|
|
|
2275
|
-
<svg class="indeterminate-icon" viewBox="0 0 16
|
|
2276
|
-
<rect width="
|
|
2524
|
+
<svg class="indeterminate-icon" viewBox="0 0 16 16" fill="var(--rel-text-inverse)">
|
|
2525
|
+
<rect x="3.55556" y="7.11111" width="8.88889" height="1.77778" />
|
|
2277
2526
|
</svg>
|
|
2278
2527
|
</span>
|
|
2279
2528
|
|
|
@@ -2374,11 +2623,25 @@ RelCheckbox.styles = import_lit4.css`
|
|
|
2374
2623
|
The inset is expressed as a ratio rather than four fractional pixel
|
|
2375
2624
|
values, so it stays exact at any device pixel ratio and follows the
|
|
2376
2625
|
frame if the ramp ever changes.
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2626
|
+
|
|
2627
|
+
The control was UNFLATTENED in the 2026-09-23 Published DS revision, so
|
|
2628
|
+
the stroke and the marks are no longer guesses. Verified against the live
|
|
2629
|
+
file (set 8418:1987, node paths read 2026-09-25): the artwork box is 16
|
|
2630
|
+
units wide and EVERY value in it is 16/9 = 1.7778 units —
|
|
2631
|
+
|
|
2632
|
+
corner radius 1.7778 (outer rect: C0.8 0 0 0.8 0 1.77778)
|
|
2633
|
+
border thickness 1.7778 (outer 0…16 hollowed to 1.7778…14.2222)
|
|
2634
|
+
indeterminate dash height 1.7778 (y 7.11111 → 8.88889)
|
|
2635
|
+
indeterminate dash width 8.8889 (x 3.55556 → 12.4444, centred on 8)
|
|
2636
|
+
|
|
2637
|
+
Because the artwork box IS the drawn square (both are 0.8 × the frame),
|
|
2638
|
+
one sixteenth of the square is one unit, so a ninth of the square is the
|
|
2639
|
+
single number that governs radius, border and dash alike. That is why
|
|
2640
|
+
they are derived below rather than tabulated per size: four hand-written
|
|
2641
|
+
radii are what let large and extra-large silently share medium's 4px.
|
|
2642
|
+
Figma leaves the INNER corner square, which border-radius reproduces
|
|
2643
|
+
for free — an outer radius equal to the border width leaves no inner
|
|
2644
|
+
curve. */
|
|
2382
2645
|
.box {
|
|
2383
2646
|
width: var(--_rel-cb-size);
|
|
2384
2647
|
height: var(--_rel-cb-size);
|
|
@@ -2389,6 +2652,12 @@ RelCheckbox.styles = import_lit4.css`
|
|
|
2389
2652
|
flex-shrink: 0;
|
|
2390
2653
|
}
|
|
2391
2654
|
|
|
2655
|
+
/* ONE box-shadow declaration, and every state contributes by setting
|
|
2656
|
+
--_rel-cb-border-color instead of restating the shadow. Restating it per
|
|
2657
|
+
state is the trap rel-stepper's connector hit and styles/focus-ring.ts
|
|
2658
|
+
documents at length: box-shadow is a single property, so two rules that
|
|
2659
|
+
both declare it do not compose — specificity picks a winner and the loser
|
|
2660
|
+
vanishes silently. */
|
|
2392
2661
|
.box::before {
|
|
2393
2662
|
content: "";
|
|
2394
2663
|
position: absolute;
|
|
@@ -2396,11 +2665,30 @@ RelCheckbox.styles = import_lit4.css`
|
|
|
2396
2665
|
inset: 10%;
|
|
2397
2666
|
box-sizing: border-box;
|
|
2398
2667
|
border-radius: var(--_rel-cb-radius);
|
|
2399
|
-
|
|
2668
|
+
/* Resting wall colour; the state rules below override just this. */
|
|
2669
|
+
--_rel-cb-border-color: var(--rel-text-default);
|
|
2670
|
+
box-shadow: inset 0 0 0 var(--_rel-cb-border) var(--_rel-cb-border-color);
|
|
2400
2671
|
background: transparent;
|
|
2401
2672
|
transition: all 0.2s;
|
|
2402
2673
|
}
|
|
2403
2674
|
|
|
2675
|
+
/* box-shadow is NOT painted in forced-colors mode (Windows High Contrast) —
|
|
2676
|
+
the same UA behaviour that styles/focus-ring.ts keeps its outer ring on an
|
|
2677
|
+
outline for. For a focus ring that costs a refinement; here it would erase
|
|
2678
|
+
the ENTIRE affordance, because the wall is all there is to see on an
|
|
2679
|
+
unchecked box. So forced-colors gets a real border back.
|
|
2680
|
+
|
|
2681
|
+
Safe to add rather than swap: the 10% inset pins all four edges, so the
|
|
2682
|
+
drawn square's outer edge is 80% of the frame whatever the wall is made
|
|
2683
|
+
of, and the shadow it sits beside is not painted here anyway. Bound to
|
|
2684
|
+
CanvasText because the forced palette, not the token, is what the user
|
|
2685
|
+
asked for. */
|
|
2686
|
+
@media (forced-colors: active) {
|
|
2687
|
+
.box::before {
|
|
2688
|
+
border: 1px solid CanvasText;
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2404
2692
|
/* Figma puts the focus ring on the WHOLE component at 1.5px OUTSIDE, not
|
|
2405
2693
|
around the control alone. Matches rel-radio, which already does this. */
|
|
2406
2694
|
.wrapper:has(input:focus-visible) {
|
|
@@ -2412,9 +2700,16 @@ RelCheckbox.styles = import_lit4.css`
|
|
|
2412
2700
|
}
|
|
2413
2701
|
|
|
2414
2702
|
svg {
|
|
2415
|
-
/*
|
|
2416
|
-
|
|
2417
|
-
|
|
2703
|
+
/* Both marks are authored in Figma's 16-unit artwork box, and that box is
|
|
2704
|
+
the drawn square — 80% of the frame, the same as ::before's inset. So
|
|
2705
|
+
the svg covers the square exactly and every coordinate inside it is a
|
|
2706
|
+
Figma unit; position and size fall out of the path instead of being
|
|
2707
|
+
re-derived here. This was 55%/55% against an 8:1 viewBox, which (with
|
|
2708
|
+
preserveAspectRatio defaulting to meet) rendered the dash at 55%/8 =
|
|
2709
|
+
6.875% of the frame — 1.6× thinner than Figma's 11.11%, and fractional
|
|
2710
|
+
at every size, which is what made it look blurred. */
|
|
2711
|
+
width: 80%;
|
|
2712
|
+
height: 80%;
|
|
2418
2713
|
position: relative;
|
|
2419
2714
|
display: none;
|
|
2420
2715
|
}
|
|
@@ -2430,7 +2725,7 @@ RelCheckbox.styles = import_lit4.css`
|
|
|
2430
2725
|
tokens for this same glyph. */
|
|
2431
2726
|
.checked .box::before {
|
|
2432
2727
|
background: var(--rel-text-default);
|
|
2433
|
-
border-color: var(--rel-text-default);
|
|
2728
|
+
--_rel-cb-border-color: var(--rel-text-default);
|
|
2434
2729
|
}
|
|
2435
2730
|
|
|
2436
2731
|
.checked .check-icon {
|
|
@@ -2452,13 +2747,13 @@ RelCheckbox.styles = import_lit4.css`
|
|
|
2452
2747
|
glyph with --rel-text-danger, a text token, never a border-* one; the two
|
|
2453
2748
|
are the same value in light mode but diverge in dark (red-300 vs -400). */
|
|
2454
2749
|
.error .box::before {
|
|
2455
|
-
border-color: var(--rel-text-danger);
|
|
2750
|
+
--_rel-cb-border-color: var(--rel-text-danger);
|
|
2456
2751
|
}
|
|
2457
2752
|
|
|
2458
2753
|
/* Error checked — error fill, transparent checkmark */
|
|
2459
2754
|
.error.checked .box::before {
|
|
2460
2755
|
background: var(--rel-action-danger-fill);
|
|
2461
|
-
border-color: var(--rel-action-danger-fill);
|
|
2756
|
+
--_rel-cb-border-color: var(--rel-action-danger-fill);
|
|
2462
2757
|
}
|
|
2463
2758
|
|
|
2464
2759
|
/* Figma State=Error tints the SUPPORTING text danger; the label stays
|
|
@@ -2481,7 +2776,7 @@ RelCheckbox.styles = import_lit4.css`
|
|
|
2481
2776
|
(gray-500 vs gray-700) and identical to --rel-text-caption, so a hovered
|
|
2482
2777
|
label was indistinguishable from supporting text. */
|
|
2483
2778
|
.default .row:hover .box::before {
|
|
2484
|
-
border-color: var(--rel-text-label);
|
|
2779
|
+
--_rel-cb-border-color: var(--rel-text-label);
|
|
2485
2780
|
}
|
|
2486
2781
|
|
|
2487
2782
|
.default .row:hover .label {
|
|
@@ -2490,7 +2785,7 @@ RelCheckbox.styles = import_lit4.css`
|
|
|
2490
2785
|
|
|
2491
2786
|
.default.checked .row:hover .box::before {
|
|
2492
2787
|
background: var(--rel-text-label);
|
|
2493
|
-
border-color: var(--rel-text-label);
|
|
2788
|
+
--_rel-cb-border-color: var(--rel-text-label);
|
|
2494
2789
|
}
|
|
2495
2790
|
|
|
2496
2791
|
.default.indeterminate .row:hover .indeterminate-icon {
|
|
@@ -2546,19 +2841,80 @@ RelCheckbox.styles = import_lit4.css`
|
|
|
2546
2841
|
role and compact indent token. The drawn square is 80% of the frame
|
|
2547
2842
|
(see .box::before above), so these numbers are footprints, not squares:
|
|
2548
2843
|
small 16 body-sm / body-xs indent xl (24)
|
|
2549
|
-
medium 18 body-md / body-
|
|
2550
|
-
large 20 body-lg / body-
|
|
2551
|
-
extra-large 24 body-xl / body-
|
|
2552
|
-
|
|
2844
|
+
medium 18 body-md / body-sm indent xl (24)
|
|
2845
|
+
large 20 body-lg / body-md indent 2xl (28)
|
|
2846
|
+
extra-large 24 body-xl / body-lg indent 3xl (32)
|
|
2847
|
+
Verified against the live Published DS file 2026-09-25: .Check Control is
|
|
2848
|
+
16/18/20/24 across all 15 variants per size. This ramp is correct — do
|
|
2849
|
+
not "fix" it because the three lower steps look alike; Figma's own steps
|
|
2850
|
+
are 2px apart and the difference it leans on is the type role.
|
|
2851
|
+
Supporting text (2026-09-26 Trello ticket, "visual adjustment"): ONE step
|
|
2852
|
+
below the label on the body scale at every size — 12/10, 14/12, 16/14,
|
|
2853
|
+
20/16. It used to be two steps below at medium and above. The live file
|
|
2854
|
+
(lastModified 2026-09-23) carries the new pair at Small, Medium and
|
|
2855
|
+
Extra-Large; its Large variants still say body-sm (12) for supporting,
|
|
2856
|
+
which would give Medium and Large the same supporting size and breaks
|
|
2857
|
+
the ticket's own rule, so Large follows the ticket (body-md) and the
|
|
2858
|
+
Figma omission is flagged to the designer rather than copied.
|
|
2859
|
+
|
|
2860
|
+
Only the FRAME and the type roles vary per size. Radius and border are
|
|
2861
|
+
derived from the frame (a ninth of the drawn square, see .box::before),
|
|
2862
|
+
so they cannot fall out of step the way four literals did: large and
|
|
2863
|
+
extra-large previously declared no radius at all and inherited medium's
|
|
2864
|
+
4px, leaving a 24px box and an 18px box with the same corner. */
|
|
2865
|
+
/* DO NOT ROUND THESE. The expression lands on fractional pixels at two of
|
|
2866
|
+
the four sizes and that is deliberate:
|
|
2867
|
+
|
|
2868
|
+
small 16px frame → 12.8px square → 1.4222px (2.84 device px @2x)
|
|
2869
|
+
medium 18px frame → 14.4px square → 1.6px (3.2)
|
|
2870
|
+
large 20px frame → 16px square → 1.7778px (3.56)
|
|
2871
|
+
extra-large 24px frame → 19.2px square → 2.1333px (4.27)
|
|
2872
|
+
|
|
2873
|
+
The fractions are downstream of the FRAME ramp, which is Figma's and
|
|
2874
|
+
already shipped, so rounding the derived value would break the fixed
|
|
2875
|
+
relationship between the radius and the square it sits on — and that
|
|
2876
|
+
relationship is the only thing making the four sizes read as one control
|
|
2877
|
+
rather than four drawings.
|
|
2878
|
+
|
|
2879
|
+
The blur this replaced was not caused by fractionality. It was a 1.6x
|
|
2880
|
+
ERROR: the dash rendered 1.10px where Figma wanted 1.78px, because an 8:1
|
|
2881
|
+
viewBox was being letterboxed into a square box. Fixing the ratio fixes
|
|
2882
|
+
the symptom; rounding on top would be a second, unrelated invention —
|
|
2883
|
+
and inventing a tidier-looking number than the derived one is exactly
|
|
2884
|
+
what produced the rel-radio Small regression that 1.4.9 reverts.
|
|
2885
|
+
|
|
2886
|
+
── WHY THE WALL IS AN INSET SHADOW AND NOT A BORDER ──
|
|
2887
|
+
Because border-width is the one length here that the engine refuses to
|
|
2888
|
+
keep. Blink snaps it to whole CSS pixels at used-value time: declared as
|
|
2889
|
+
a border, the derived value computed to 1 / 1 / 1 / 2px for the four
|
|
2890
|
+
sizes, and read the same at device pixel ratios 1, 2 AND 3, while the
|
|
2891
|
+
radius on the very same element read 1.42222px throughout. One property
|
|
2892
|
+
keeping its fraction is the control group for the other losing it, so
|
|
2893
|
+
that is snapping and not a getComputedStyle artefact (measured
|
|
2894
|
+
2026-09-25).
|
|
2895
|
+
|
|
2896
|
+
Snapped, the faithful value painted WORSE than the flat 2px guess it
|
|
2897
|
+
replaced — sum of absolute painted error 1.933px against 1.333px:
|
|
2898
|
+
|
|
2899
|
+
size Figma as border error flat 2px error
|
|
2900
|
+
s 1.4222 1px 0.422 2px 0.578
|
|
2901
|
+
m 1.6 1px 0.600 2px 0.400
|
|
2902
|
+
l 1.7778 1px 0.778 2px 0.222
|
|
2903
|
+
xl 2.1333 2px 0.133 2px 0.133
|
|
2904
|
+
|
|
2905
|
+
box-shadow spread is NOT snapped — it antialiases sub-pixel — so an inset
|
|
2906
|
+
shadow paints the derived value exactly at every size and the error goes
|
|
2907
|
+
to ~0, with no CSS round() and so no browser-floor question. */
|
|
2553
2908
|
:host {
|
|
2554
2909
|
--_rel-cb-size: 18px;
|
|
2555
|
-
|
|
2910
|
+
/* drawn square = frame × 0.8; every Figma value in it = square ÷ 9 */
|
|
2911
|
+
--_rel-cb-radius: calc(var(--_rel-cb-size) * 0.8 / 9);
|
|
2912
|
+
--_rel-cb-border: calc(var(--_rel-cb-size) * 0.8 / 9);
|
|
2556
2913
|
--_rel-cb-indent: var(--rel-spacing-xl);
|
|
2557
2914
|
}
|
|
2558
2915
|
|
|
2559
2916
|
:host([size="small"]) {
|
|
2560
2917
|
--_rel-cb-size: 16px;
|
|
2561
|
-
--_rel-cb-radius: 3px;
|
|
2562
2918
|
--_rel-cb-indent: var(--rel-spacing-xl);
|
|
2563
2919
|
}
|
|
2564
2920
|
|
|
@@ -2582,11 +2938,11 @@ RelCheckbox.styles = import_lit4.css`
|
|
|
2582
2938
|
}
|
|
2583
2939
|
|
|
2584
2940
|
.text {
|
|
2585
|
-
font-family: var(--rel-body-
|
|
2586
|
-
font-weight: var(--rel-body-
|
|
2587
|
-
font-size: var(--rel-body-
|
|
2588
|
-
line-height: var(--rel-body-
|
|
2589
|
-
letter-spacing: var(--rel-body-
|
|
2941
|
+
font-family: var(--rel-body-sm-regular-font-family);
|
|
2942
|
+
font-weight: var(--rel-body-sm-regular-font-weight);
|
|
2943
|
+
font-size: var(--rel-body-sm-regular-font-size);
|
|
2944
|
+
line-height: var(--rel-body-sm-regular-line-height);
|
|
2945
|
+
letter-spacing: var(--rel-body-sm-regular-letter-spacing);
|
|
2590
2946
|
}
|
|
2591
2947
|
|
|
2592
2948
|
:host([size="small"]) .label {
|
|
@@ -2597,6 +2953,14 @@ RelCheckbox.styles = import_lit4.css`
|
|
|
2597
2953
|
letter-spacing: var(--rel-body-sm-regular-letter-spacing);
|
|
2598
2954
|
}
|
|
2599
2955
|
|
|
2956
|
+
:host([size="small"]) .text {
|
|
2957
|
+
font-family: var(--rel-body-xs-regular-font-family);
|
|
2958
|
+
font-weight: var(--rel-body-xs-regular-font-weight);
|
|
2959
|
+
font-size: var(--rel-body-xs-regular-font-size);
|
|
2960
|
+
line-height: var(--rel-body-xs-regular-line-height);
|
|
2961
|
+
letter-spacing: var(--rel-body-xs-regular-letter-spacing);
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2600
2964
|
:host([size="large"]) .label {
|
|
2601
2965
|
font-family: var(--rel-body-lg-regular-font-family);
|
|
2602
2966
|
font-weight: var(--rel-body-lg-regular-font-weight);
|
|
@@ -2606,11 +2970,11 @@ RelCheckbox.styles = import_lit4.css`
|
|
|
2606
2970
|
}
|
|
2607
2971
|
|
|
2608
2972
|
:host([size="large"]) .text {
|
|
2609
|
-
font-family: var(--rel-body-
|
|
2610
|
-
font-weight: var(--rel-body-
|
|
2611
|
-
font-size: var(--rel-body-
|
|
2612
|
-
line-height: var(--rel-body-
|
|
2613
|
-
letter-spacing: var(--rel-body-
|
|
2973
|
+
font-family: var(--rel-body-md-regular-font-family);
|
|
2974
|
+
font-weight: var(--rel-body-md-regular-font-weight);
|
|
2975
|
+
font-size: var(--rel-body-md-regular-font-size);
|
|
2976
|
+
line-height: var(--rel-body-md-regular-line-height);
|
|
2977
|
+
letter-spacing: var(--rel-body-md-regular-letter-spacing);
|
|
2614
2978
|
}
|
|
2615
2979
|
|
|
2616
2980
|
:host([size="extra-large"]) .label {
|
|
@@ -2622,11 +2986,11 @@ RelCheckbox.styles = import_lit4.css`
|
|
|
2622
2986
|
}
|
|
2623
2987
|
|
|
2624
2988
|
:host([size="extra-large"]) .text {
|
|
2625
|
-
font-family: var(--rel-body-
|
|
2626
|
-
font-weight: var(--rel-body-
|
|
2627
|
-
font-size: var(--rel-body-
|
|
2628
|
-
line-height: var(--rel-body-
|
|
2629
|
-
letter-spacing: var(--rel-body-
|
|
2989
|
+
font-family: var(--rel-body-lg-regular-font-family);
|
|
2990
|
+
font-weight: var(--rel-body-lg-regular-font-weight);
|
|
2991
|
+
font-size: var(--rel-body-lg-regular-font-size);
|
|
2992
|
+
line-height: var(--rel-body-lg-regular-line-height);
|
|
2993
|
+
letter-spacing: var(--rel-body-lg-regular-letter-spacing);
|
|
2630
2994
|
}
|
|
2631
2995
|
`;
|
|
2632
2996
|
__decorateClass([
|
|
@@ -2656,6 +3020,9 @@ __decorateClass([
|
|
|
2656
3020
|
__decorateClass([
|
|
2657
3021
|
(0, import_decorators4.property)({ type: String, reflect: true })
|
|
2658
3022
|
], RelCheckbox.prototype, "alignment", 2);
|
|
3023
|
+
__decorateClass([
|
|
3024
|
+
(0, import_decorators4.property)({ type: String, attribute: "field-aria-label" })
|
|
3025
|
+
], RelCheckbox.prototype, "fieldAriaLabel", 2);
|
|
2659
3026
|
RelCheckbox = __decorateClass([
|
|
2660
3027
|
(0, import_decorators4.customElement)("rel-checkbox")
|
|
2661
3028
|
], RelCheckbox);
|
|
@@ -3741,6 +4108,14 @@ var RelAutocomplete = class extends import_lit8.LitElement {
|
|
|
3741
4108
|
get optionCheckboxSize() {
|
|
3742
4109
|
return this.size;
|
|
3743
4110
|
}
|
|
4111
|
+
/** Native tooltip carrying the row's full text. Both lines truncate to one
|
|
4112
|
+
* line with an ellipsis, so a long supporting line is otherwise unrecoverable
|
|
4113
|
+
* by sight; hovering the row reveals it. Only rows that carry a second line
|
|
4114
|
+
* get one, so plain lists do not sprout tooltips. */
|
|
4115
|
+
rowTitle(item) {
|
|
4116
|
+
return item.supportingText ? `${item.label}
|
|
4117
|
+
${item.supportingText}` : import_lit8.nothing;
|
|
4118
|
+
}
|
|
3744
4119
|
get selectAllStatus() {
|
|
3745
4120
|
const filtered = this.filteredItems.filter((i) => !i.disabled);
|
|
3746
4121
|
if (filtered.length === 0) return "unchecked";
|
|
@@ -4018,6 +4393,7 @@ var RelAutocomplete = class extends import_lit8.LitElement {
|
|
|
4018
4393
|
role="option"
|
|
4019
4394
|
aria-selected="${this.selectedValues.includes(item.value)}"
|
|
4020
4395
|
aria-disabled="${item.disabled ? "true" : import_lit8.nothing}"
|
|
4396
|
+
title="${this.rowTitle(item)}"
|
|
4021
4397
|
@mousedown="${(e) => e.preventDefault()}"
|
|
4022
4398
|
@click="${() => this.toggleMultiOption(item)}"
|
|
4023
4399
|
@pointerenter="${() => {
|
|
@@ -4041,6 +4417,7 @@ var RelAutocomplete = class extends import_lit8.LitElement {
|
|
|
4041
4417
|
role="option"
|
|
4042
4418
|
aria-selected="${item.value === this.value}"
|
|
4043
4419
|
aria-disabled="${item.disabled ? "true" : import_lit8.nothing}"
|
|
4420
|
+
title="${this.rowTitle(item)}"
|
|
4044
4421
|
@mousedown="${(e) => e.preventDefault()}"
|
|
4045
4422
|
@click="${() => this.selectItem(item)}"
|
|
4046
4423
|
@pointerenter="${() => {
|
|
@@ -4083,7 +4460,11 @@ RelAutocomplete.styles = import_lit8.css`
|
|
|
4083
4460
|
surface with a 4px radius and shadow/md, and no border. ── */
|
|
4084
4461
|
.ac-panel {
|
|
4085
4462
|
position: fixed;
|
|
4086
|
-
|
|
4463
|
+
/* Only consulted on the no-Popover-API fallback path: once the panel is
|
|
4464
|
+
promoted to the top layer, z-index is inert and stacking is by
|
|
4465
|
+
promotion order. Shared with the pickers so the whole overlay family
|
|
4466
|
+
is overridable from one place. */
|
|
4467
|
+
z-index: var(--rel-overlay-fallback-z, 1001);
|
|
4087
4468
|
display: none;
|
|
4088
4469
|
/* Reset native [popover] UA styles (the panel is promoted to the
|
|
4089
4470
|
top layer so transformed ancestors — e.g. a centered rel-modal
|
|
@@ -4169,13 +4550,32 @@ RelAutocomplete.styles = import_lit8.css`
|
|
|
4169
4550
|
}
|
|
4170
4551
|
|
|
4171
4552
|
/* Hover and the keyboard highlight share Figma's Hover wash; Active —
|
|
4172
|
-
the committed selection — reuses it and bumps the label to medium.
|
|
4173
|
-
|
|
4553
|
+
the committed selection — reuses it and bumps the label to medium.
|
|
4554
|
+
|
|
4555
|
+
NOTE there is deliberately no :hover in this rule. The pointerenter
|
|
4556
|
+
handler already moves highlightedIndex onto the hovered row, so hover
|
|
4557
|
+
is expressed through .highlighted and a :hover selector here would be
|
|
4558
|
+
redundant — except in one case, where it was a real defect: arrowing
|
|
4559
|
+
the list moves .highlighted but CANNOT clear a CSS :hover, so the row
|
|
4560
|
+
the mouse happens to rest on stayed lit alongside the keyboard cursor
|
|
4561
|
+
and the committed selection. Three rows painted the same wash for
|
|
4562
|
+
three different meanings, and the user could not tell which one Enter
|
|
4563
|
+
would commit. Measured before the fix: rows 0/2/4 all rgb(243,243,243)
|
|
4564
|
+
with row 2 carrying :hover and .highlighted=false. */
|
|
4174
4565
|
.ac-option.highlighted,
|
|
4175
4566
|
.ac-option[aria-selected="true"] {
|
|
4176
4567
|
background: var(--rel-autocomplete-option-hover, var(--rel-action-overlay-hover));
|
|
4177
4568
|
}
|
|
4178
4569
|
|
|
4570
|
+
/* The Select-all row is the one row with no pointerenter handler (it is
|
|
4571
|
+
not in the options list, so it has no index to highlight), so it keeps
|
|
4572
|
+
a plain :hover. rel-select instead carries a selectAllHighlighted
|
|
4573
|
+
state for this; autocomplete has no such state and does not need one
|
|
4574
|
+
for a row that cannot be reached by the arrow keys. */
|
|
4575
|
+
.ac-select-all:hover {
|
|
4576
|
+
background: var(--rel-autocomplete-option-hover, var(--rel-action-overlay-hover));
|
|
4577
|
+
}
|
|
4578
|
+
|
|
4179
4579
|
.ac-option:active {
|
|
4180
4580
|
background: var(--rel-autocomplete-option-pressed, var(--rel-action-overlay-pressed));
|
|
4181
4581
|
}
|
|
@@ -4273,6 +4673,28 @@ RelAutocomplete.styles = import_lit8.css`
|
|
|
4273
4673
|
letter-spacing: var(--rel-body-sm-regular-letter-spacing);
|
|
4274
4674
|
}
|
|
4275
4675
|
|
|
4676
|
+
/* Multi-select rows delegate to a nested rel-checkbox, which wraps its
|
|
4677
|
+
text by default, so the SAME item rendered as one truncated line in
|
|
4678
|
+
single mode and as three wrapped lines — or a sideways-overflowing
|
|
4679
|
+
identifier — under multiple (measured 2026-09-26: row 58px vs 120px,
|
|
4680
|
+
panel scrollWidth 326 in a 320 panel). The checkbox's label and helper
|
|
4681
|
+
are exposed as parts, so the row can reach them from here and apply the
|
|
4682
|
+
same one-line ellipsis; the host itself needs min-width: 0 or the flex
|
|
4683
|
+
row lets it grow past the panel instead of shrinking its text. */
|
|
4684
|
+
.ac-option > rel-checkbox {
|
|
4685
|
+
flex: 1;
|
|
4686
|
+
min-width: 0;
|
|
4687
|
+
}
|
|
4688
|
+
|
|
4689
|
+
.ac-option > rel-checkbox::part(label),
|
|
4690
|
+
.ac-option > rel-checkbox::part(helper) {
|
|
4691
|
+
display: block;
|
|
4692
|
+
min-width: 0;
|
|
4693
|
+
overflow: hidden;
|
|
4694
|
+
text-overflow: ellipsis;
|
|
4695
|
+
white-space: nowrap;
|
|
4696
|
+
}
|
|
4697
|
+
|
|
4276
4698
|
:host([size="large"]) .ac-option-supporting {
|
|
4277
4699
|
font-family: var(--rel-body-md-regular-font-family);
|
|
4278
4700
|
font-weight: var(--rel-body-md-regular-font-weight);
|
|
@@ -6637,9 +7059,23 @@ RelCarousel.styles = import_lit14.css`
|
|
|
6637
7059
|
height: 100%;
|
|
6638
7060
|
}
|
|
6639
7061
|
|
|
7062
|
+
/* The height override stretches a content-height slide to fill the fixed
|
|
7063
|
+
viewport, so a caption that used to hug its text was left anchored to
|
|
7064
|
+
the top of a much taller box — measured 84px of content top-aligned in
|
|
7065
|
+
180px, i.e. ~96px of dead space beneath it. Figma centres it: every
|
|
7066
|
+
vertical variant has the Content slot on justify=CENTER align=CENTER.
|
|
7067
|
+
Centring needs a formatting context the slide does not otherwise have,
|
|
7068
|
+
hence the flex display. It is behind a hook because forcing flex onto a
|
|
7069
|
+
consumer's slotted element can reflow that element's own children —
|
|
7070
|
+
set --rel-carousel-slide-display: block to opt out. Scoped to vertical
|
|
7071
|
+
only: horizontal slides are content-height, so there is nothing to
|
|
7072
|
+
centre and no reason to touch their box model. */
|
|
6640
7073
|
:host([orientation="vertical"]) ::slotted(*) {
|
|
6641
7074
|
width: 100% !important;
|
|
6642
7075
|
height: var(--rel-carousel-slide-height, 100%) !important;
|
|
7076
|
+
display: var(--rel-carousel-slide-display, flex);
|
|
7077
|
+
flex-direction: column;
|
|
7078
|
+
justify-content: center;
|
|
6643
7079
|
}
|
|
6644
7080
|
|
|
6645
7081
|
.track.dragging {
|
|
@@ -6759,6 +7195,50 @@ RelCarousel.styles = import_lit14.css`
|
|
|
6759
7195
|
transform: none;
|
|
6760
7196
|
}
|
|
6761
7197
|
|
|
7198
|
+
/* Vertical orientation: the arrows ride the axis the carousel actually
|
|
7199
|
+
moves on. Without this they kept the horizontal placement — measured
|
|
7200
|
+
prev at x=32 y=98 and next at x=264 y=98, i.e. mid-left and mid-right,
|
|
7201
|
+
identical to the horizontal case — so they flanked the caption from
|
|
7202
|
+
both sides while the motion was up and down. Figma nests both arrow
|
|
7203
|
+
buttons in a layout=col Stack for the vertical variants. */
|
|
7204
|
+
:host([orientation="vertical"]) .nav-button {
|
|
7205
|
+
top: auto;
|
|
7206
|
+
left: 50%;
|
|
7207
|
+
right: auto;
|
|
7208
|
+
transform: translateX(-50%);
|
|
7209
|
+
}
|
|
7210
|
+
|
|
7211
|
+
:host([orientation="vertical"]) .nav-button.prev {
|
|
7212
|
+
top: var(--rel-spacing-xs);
|
|
7213
|
+
left: 50%;
|
|
7214
|
+
}
|
|
7215
|
+
|
|
7216
|
+
:host([orientation="vertical"]) .nav-button.next {
|
|
7217
|
+
bottom: var(--rel-spacing-xs);
|
|
7218
|
+
right: auto;
|
|
7219
|
+
}
|
|
7220
|
+
|
|
7221
|
+
/* chevron-left becomes up, chevron-right becomes down. Applied to the
|
|
7222
|
+
bottom-arrows row too: those buttons still page the carousel along the
|
|
7223
|
+
vertical axis, so a left/right glyph would misdescribe them wherever
|
|
7224
|
+
they sit. */
|
|
7225
|
+
:host([orientation="vertical"]) .nav-button svg {
|
|
7226
|
+
transform: rotate(90deg);
|
|
7227
|
+
}
|
|
7228
|
+
|
|
7229
|
+
/* Type=Bottom Arrows keeps its arrows inline in the indicators row even
|
|
7230
|
+
when the carousel is vertical. This rule has to restate the reset
|
|
7231
|
+
because the vertical block above outranks the plain .dots .nav-button
|
|
7232
|
+
selector (0,3,0 against 0,2,0). */
|
|
7233
|
+
:host([orientation="vertical"]) .dots .nav-button {
|
|
7234
|
+
position: static;
|
|
7235
|
+
transform: none;
|
|
7236
|
+
top: auto;
|
|
7237
|
+
bottom: auto;
|
|
7238
|
+
left: auto;
|
|
7239
|
+
right: auto;
|
|
7240
|
+
}
|
|
7241
|
+
|
|
6762
7242
|
.nav-button svg {
|
|
6763
7243
|
width: 16px;
|
|
6764
7244
|
height: 16px;
|
|
@@ -6993,6 +7473,7 @@ RelCardMedia = __decorateClass([
|
|
|
6993
7473
|
var import_lit16 = require("lit");
|
|
6994
7474
|
var import_decorators15 = require("lit/decorators.js");
|
|
6995
7475
|
var import_live = require("lit/directives/live.js");
|
|
7476
|
+
var import_rel_icon9 = require("@reliability-design/icons/rel-icon");
|
|
6996
7477
|
|
|
6997
7478
|
// src/components/rel-date-picker/date-format.ts
|
|
6998
7479
|
function formatDatePattern(date, pattern, locale = "en-US") {
|
|
@@ -7353,6 +7834,34 @@ var RelDatePicker = class extends import_lit16.LitElement {
|
|
|
7353
7834
|
this.onReposition = () => {
|
|
7354
7835
|
if (this.open) this.updatePopoverPosition();
|
|
7355
7836
|
};
|
|
7837
|
+
/* Outside scroll CLOSES the panel; it used to reposition.
|
|
7838
|
+
*
|
|
7839
|
+
* Repositioning is what produced the reported "panel floats over the site
|
|
7840
|
+
* header" bug: the panel is in the top layer, so it paints above all normal
|
|
7841
|
+
* content by definition, and tracking a trigger that scrolls under a sticky
|
|
7842
|
+
* nav leaves a calendar anchored to something nobody can see. No z-index can
|
|
7843
|
+
* fix that — the top layer is not in the page's stacking order at all.
|
|
7844
|
+
*
|
|
7845
|
+
* rel-select reached the same conclusion for its own listbox and its comment
|
|
7846
|
+
* records the other half of the reason: re-deriving fixed coordinates on
|
|
7847
|
+
* every scroll event is fragile and was making the panel jump to the top of
|
|
7848
|
+
* the viewport. This is that pattern, so the family now behaves alike.
|
|
7849
|
+
*
|
|
7850
|
+
* Scrolling the panel's OWN content must not close it. The test is
|
|
7851
|
+
* CONTAINMENT, not equality with one known scroller: the panel is itself
|
|
7852
|
+
* `overflow-y: auto` today, but the same guard has to hold for any scroller
|
|
7853
|
+
* nested inside it (rel-time-picker's per-column lists are exactly that
|
|
7854
|
+
* case), and an equality test would silently close the panel the first time
|
|
7855
|
+
* one is added.
|
|
7856
|
+
*
|
|
7857
|
+
* resize still repositions — a resize keeps the trigger on screen, so there
|
|
7858
|
+
* is nothing to run away from. */
|
|
7859
|
+
this.onPageScroll = (e) => {
|
|
7860
|
+
if (!this.open) return;
|
|
7861
|
+
const panel = this.popoverElement;
|
|
7862
|
+
if (panel && e.composedPath().includes(panel)) return;
|
|
7863
|
+
this.closeCalendarPopover(false);
|
|
7864
|
+
};
|
|
7356
7865
|
this.updatePopoverPosition = () => {
|
|
7357
7866
|
const panel = this.popoverElement;
|
|
7358
7867
|
const anchor = this.triggerButton;
|
|
@@ -7478,7 +7987,7 @@ var RelDatePicker = class extends import_lit16.LitElement {
|
|
|
7478
7987
|
document.removeEventListener("click", this.handleClickOutside);
|
|
7479
7988
|
document.removeEventListener("keydown", this.handleEscapeKey);
|
|
7480
7989
|
window.removeEventListener("resize", this.onReposition);
|
|
7481
|
-
window.removeEventListener("scroll", this.
|
|
7990
|
+
window.removeEventListener("scroll", this.onPageScroll, true);
|
|
7482
7991
|
this.hideTopLayer();
|
|
7483
7992
|
}
|
|
7484
7993
|
updated(changedProperties) {
|
|
@@ -7492,7 +8001,7 @@ var RelDatePicker = class extends import_lit16.LitElement {
|
|
|
7492
8001
|
if (changedProperties.has("open")) {
|
|
7493
8002
|
if (!this.open) {
|
|
7494
8003
|
window.removeEventListener("resize", this.onReposition);
|
|
7495
|
-
window.removeEventListener("scroll", this.
|
|
8004
|
+
window.removeEventListener("scroll", this.onPageScroll, true);
|
|
7496
8005
|
this.hideTopLayer();
|
|
7497
8006
|
this.focusPanelOnOpen = true;
|
|
7498
8007
|
this.panelView = "days";
|
|
@@ -7500,7 +8009,7 @@ var RelDatePicker = class extends import_lit16.LitElement {
|
|
|
7500
8009
|
}
|
|
7501
8010
|
this.showTopLayer();
|
|
7502
8011
|
window.addEventListener("resize", this.onReposition);
|
|
7503
|
-
window.addEventListener("scroll", this.
|
|
8012
|
+
window.addEventListener("scroll", this.onPageScroll, true);
|
|
7504
8013
|
const focusPanel = this.focusPanelOnOpen;
|
|
7505
8014
|
this.focusPanelOnOpen = true;
|
|
7506
8015
|
if (focusPanel) {
|
|
@@ -8012,6 +8521,28 @@ var RelDatePicker = class extends import_lit16.LitElement {
|
|
|
8012
8521
|
* Index of the single grid cell carrying tabindex="0". The old expression
|
|
8013
8522
|
* (`isSelected || (index === 0 && !value)`) left the grid with ZERO tab
|
|
8014
8523
|
* stops once the user arrowed around without a selection.
|
|
8524
|
+
*
|
|
8525
|
+
* KNOWN GAP (deferred from 1.4.9, needs a ticket covering BOTH calendars):
|
|
8526
|
+
* `focusedDate` is read here as the first candidate but is never assigned
|
|
8527
|
+
* anywhere in this file, so it is permanently null and the tab stop tracks
|
|
8528
|
+
* the SELECTION rather than focus. Tab out of the grid and back and you
|
|
8529
|
+
* return to the selected day, not the day you left.
|
|
8530
|
+
*
|
|
8531
|
+
* Wiring it from each cell's `@focus` was tried and is NOT sufficient, which
|
|
8532
|
+
* is why this is still open. Measured 2026-09-25 by driving real keys: from
|
|
8533
|
+
* Sun 27 Sep, one ArrowDown lands focus on Sun 25 Oct instead of Sun 4 Oct,
|
|
8534
|
+
* because handleDayKeydown moves by grid INDEX and then lets the view flip
|
|
8535
|
+
* month, so index+7 addresses a different date in the new grid. lit reuses
|
|
8536
|
+
* the same button elements across that re-render, so the focused element
|
|
8537
|
+
* never changes and NO focus event fires — the handler cannot observe the
|
|
8538
|
+
* move it is supposed to record.
|
|
8539
|
+
*
|
|
8540
|
+
* The real fix is to navigate by DATE and set the rove explicitly before
|
|
8541
|
+
* focusing, the way rel-range-picker.moveGridFocusTo already does (verified
|
|
8542
|
+
* correct in the same run: Sep 27 → Oct 4 → Oct 11, tab stop and focus
|
|
8543
|
+
* agreeing at every step). That is a rewrite of this component's arrow-key
|
|
8544
|
+
* model, not a contained fix, and it must land on both calendars together or
|
|
8545
|
+
* they diverge again.
|
|
8015
8546
|
*/
|
|
8016
8547
|
getRovingIndex(days) {
|
|
8017
8548
|
const candidates = [
|
|
@@ -8226,11 +8757,11 @@ var RelDatePicker = class extends import_lit16.LitElement {
|
|
|
8226
8757
|
?disabled=${this.disabled}
|
|
8227
8758
|
@click=${this.handleIconClick}
|
|
8228
8759
|
>
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8760
|
+
<!-- Figma pins this suffix to node 5137:1877, which resolves to
|
|
8761
|
+
COMPONENT_SET 5137:1876 "Calendar Month" — verified against
|
|
8762
|
+
the live Published DS file 2026-09-25. This was a hand-drawn
|
|
8763
|
+
inline path that matched no icon in the set. -->
|
|
8764
|
+
<rel-icon name="calendar-month"></rel-icon>
|
|
8234
8765
|
</button>
|
|
8235
8766
|
</span>
|
|
8236
8767
|
</div>
|
|
@@ -8589,18 +9120,28 @@ RelDatePicker.styles = import_lit16.css`
|
|
|
8589
9120
|
}
|
|
8590
9121
|
|
|
8591
9122
|
/* Figma's suffix icon is 18px at medium; rel-textfield ramps 16 / 18 / 20
|
|
8592
|
-
across the size scale, so the trigger follows the same ramp.
|
|
8593
|
-
|
|
9123
|
+
across the size scale, so the trigger follows the same ramp.
|
|
9124
|
+
Both selectors are needed and neither is redundant: the CLEAR (×) button
|
|
9125
|
+
is still a raw inline <svg>, while the calendar suffix is now a
|
|
9126
|
+
<rel-icon> whose own svg lives in a shadow root that .icon-button svg
|
|
9127
|
+
cannot reach. Sizing the rel-icon ELEMENT works because a declaration
|
|
9128
|
+
from the outer tree beats the :host([size]) rules inside it — which is
|
|
9129
|
+
also why the element carries no size attribute: one source for the
|
|
9130
|
+
ramp, not two that have to agree. */
|
|
9131
|
+
.icon-button svg,
|
|
9132
|
+
.icon-button rel-icon {
|
|
8594
9133
|
width: 18px;
|
|
8595
9134
|
height: 18px;
|
|
8596
9135
|
}
|
|
8597
9136
|
|
|
8598
|
-
.date-picker-wrapper.small .icon-button svg
|
|
9137
|
+
.date-picker-wrapper.small .icon-button svg,
|
|
9138
|
+
.date-picker-wrapper.small .icon-button rel-icon {
|
|
8599
9139
|
width: 16px;
|
|
8600
9140
|
height: 16px;
|
|
8601
9141
|
}
|
|
8602
9142
|
|
|
8603
|
-
.date-picker-wrapper.large .icon-button svg
|
|
9143
|
+
.date-picker-wrapper.large .icon-button svg,
|
|
9144
|
+
.date-picker-wrapper.large .icon-button rel-icon {
|
|
8604
9145
|
width: 20px;
|
|
8605
9146
|
height: 20px;
|
|
8606
9147
|
}
|
|
@@ -8624,7 +9165,14 @@ RelDatePicker.styles = import_lit16.css`
|
|
|
8624
9165
|
position: absolute;
|
|
8625
9166
|
top: calc(100% + 8px);
|
|
8626
9167
|
left: 0;
|
|
8627
|
-
|
|
9168
|
+
/* Fallback stacking ONLY: while the panel is in the top layer this is
|
|
9169
|
+
inert, because top-layer elements are not in the page stacking order at
|
|
9170
|
+
all and order of promotion decides. It applies solely where the Popover
|
|
9171
|
+
API is unavailable. One overridable hook across every overlay, so a
|
|
9172
|
+
consumer with its own stacking scheme sets it once; the default matches
|
|
9173
|
+
rel-select / rel-autocomplete / rel-popover / rel-menubar. This was
|
|
9174
|
+
2147483647, which cannot be layered against anything. */
|
|
9175
|
+
z-index: var(--rel-overlay-fallback-z, 1001);
|
|
8628
9176
|
background: var(--rel-bg-neutral);
|
|
8629
9177
|
border-radius: var(--rel-border-radius-sm);
|
|
8630
9178
|
/* Figma ".Date Picker Calendar" is a shadowed card with no border.
|
|
@@ -9121,7 +9669,7 @@ RelDatePicker = __decorateClass([
|
|
|
9121
9669
|
var import_lit17 = require("lit");
|
|
9122
9670
|
var import_decorators16 = require("lit/decorators.js");
|
|
9123
9671
|
var import_live2 = require("lit/directives/live.js");
|
|
9124
|
-
var
|
|
9672
|
+
var import_rel_icon10 = require("@reliability-design/icons/rel-icon");
|
|
9125
9673
|
|
|
9126
9674
|
// src/utils/shortcuts.ts
|
|
9127
9675
|
function isApplePlatform() {
|
|
@@ -9288,6 +9836,21 @@ var RelRangePicker = class extends import_lit17.LitElement {
|
|
|
9288
9836
|
this.onReposition = () => {
|
|
9289
9837
|
if (this.open && this.mode === "popover") this.updatePopoverPosition();
|
|
9290
9838
|
};
|
|
9839
|
+
/* Outside scroll CLOSES the panel; it used to reposition. Same change as
|
|
9840
|
+
* rel-date-picker and rel-time-picker — see the long note in
|
|
9841
|
+
* rel-date-picker.onPageScroll for why tracking the trigger is what put the
|
|
9842
|
+
* panel over the sticky site header, and why z-index cannot fix it.
|
|
9843
|
+
*
|
|
9844
|
+
* ONLY in popover mode. `popup` mode centres the shell in the viewport behind
|
|
9845
|
+
* a backdrop and never anchors to the trigger, so there is no trigger to
|
|
9846
|
+
* scroll away from and closing on scroll would just be a dialog that vanishes
|
|
9847
|
+
* when the page moves. Guarded exactly as onReposition above is. */
|
|
9848
|
+
this.onPageScroll = (e) => {
|
|
9849
|
+
if (!this.open || this.mode !== "popover") return;
|
|
9850
|
+
const panel = this.popoverEl;
|
|
9851
|
+
if (panel && e.composedPath().includes(panel)) return;
|
|
9852
|
+
this.cancelPopover();
|
|
9853
|
+
};
|
|
9291
9854
|
this.updatePopoverPosition = () => {
|
|
9292
9855
|
const panel = this.popoverEl;
|
|
9293
9856
|
if (!panel || !this.open || this.mode !== "popover") return;
|
|
@@ -9500,7 +10063,7 @@ var RelRangePicker = class extends import_lit17.LitElement {
|
|
|
9500
10063
|
document.removeEventListener("click", this.handleClickOutside);
|
|
9501
10064
|
document.removeEventListener("keydown", this.handleGlobalKeydown);
|
|
9502
10065
|
window.removeEventListener("resize", this.onReposition);
|
|
9503
|
-
window.removeEventListener("scroll", this.
|
|
10066
|
+
window.removeEventListener("scroll", this.onPageScroll, true);
|
|
9504
10067
|
this.hideTopLayer();
|
|
9505
10068
|
}
|
|
9506
10069
|
updated(changed) {
|
|
@@ -9514,10 +10077,10 @@ var RelRangePicker = class extends import_lit17.LitElement {
|
|
|
9514
10077
|
if (this.open) {
|
|
9515
10078
|
this.showTopLayer();
|
|
9516
10079
|
window.addEventListener("resize", this.onReposition);
|
|
9517
|
-
window.addEventListener("scroll", this.
|
|
10080
|
+
window.addEventListener("scroll", this.onPageScroll, true);
|
|
9518
10081
|
} else {
|
|
9519
10082
|
window.removeEventListener("resize", this.onReposition);
|
|
9520
|
-
window.removeEventListener("scroll", this.
|
|
10083
|
+
window.removeEventListener("scroll", this.onPageScroll, true);
|
|
9521
10084
|
this.hideTopLayer();
|
|
9522
10085
|
}
|
|
9523
10086
|
}
|
|
@@ -10131,6 +10694,21 @@ var RelRangePicker = class extends import_lit17.LitElement {
|
|
|
10131
10694
|
* The cell that carries tabindex="0". Prefers the last roved date, then the
|
|
10132
10695
|
* draft start, then today, then the first selectable day — always exactly
|
|
10133
10696
|
* one Tab stop, even after arrowing away with nothing selected.
|
|
10697
|
+
*
|
|
10698
|
+
* `focusedDate` is written by moveGridFocusTo, which sets it from the DATE
|
|
10699
|
+
* before moving focus rather than reading it back off a focus event. That
|
|
10700
|
+
* ordering is what makes the arrow keys correct across a month boundary —
|
|
10701
|
+
* verified 2026-09-25 by driving real keys: Sep 27 → Oct 4 → Oct 11 with the
|
|
10702
|
+
* tab stop and focus agreeing at every step, including the view change.
|
|
10703
|
+
* rel-date-picker moves by grid INDEX instead and lands on the wrong date
|
|
10704
|
+
* there; see the note on its getRovingIndex. Do not "simplify" this to a
|
|
10705
|
+
* focus listener — lit reuses the same cell elements across the re-render, so
|
|
10706
|
+
* the focused element does not change and no focus event fires.
|
|
10707
|
+
*
|
|
10708
|
+
* Still open for BOTH calendars: focus arriving from a click or from Tab does
|
|
10709
|
+
* not update the rove, only the arrow keys do. Deferred from 1.4.9 as a
|
|
10710
|
+
* shared ticket, since fixing one calendar alone reintroduces the divergence
|
|
10711
|
+
* the shared period-page module exists to prevent.
|
|
10134
10712
|
*/
|
|
10135
10713
|
getRovingIso(days) {
|
|
10136
10714
|
const inView = days.filter((d) => d.isCurrentMonth);
|
|
@@ -10186,7 +10764,14 @@ var RelRangePicker = class extends import_lit17.LitElement {
|
|
|
10186
10764
|
aria-label=${triggerLabel || this.placeholder}
|
|
10187
10765
|
@click=${() => this.toggleOpen()}
|
|
10188
10766
|
>
|
|
10189
|
-
|
|
10767
|
+
<!-- Deliberately the SAME icon as rel-date-picker, at the user's
|
|
10768
|
+
request, so the two triggers read as one family. Figma does
|
|
10769
|
+
NOT specify a trigger icon here: the range-picker spec lines
|
|
10770
|
+
only carry node 5137:2842, which resolves to "Plus" and
|
|
10771
|
+
appears in 440 places across the component specs — it is the
|
|
10772
|
+
un-overridden INSTANCE_SWAP default, not a choice. So this is
|
|
10773
|
+
ours to pick, and it should move with the date picker. -->
|
|
10774
|
+
<rel-icon name="calendar-month"></rel-icon>
|
|
10190
10775
|
</rel-button>
|
|
10191
10776
|
` : import_lit17.html`
|
|
10192
10777
|
<div
|
|
@@ -10241,9 +10826,7 @@ var RelRangePicker = class extends import_lit17.LitElement {
|
|
|
10241
10826
|
?disabled=${this.disabled}
|
|
10242
10827
|
@click=${this.handleIconClick}
|
|
10243
10828
|
>
|
|
10244
|
-
<
|
|
10245
|
-
<path d="M4 2h1V1h2v1h2V1h2v1h1a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2zm8 3H4v7h8V5z"/>
|
|
10246
|
-
</svg>
|
|
10829
|
+
<rel-icon name="calendar-month"></rel-icon>
|
|
10247
10830
|
</button>
|
|
10248
10831
|
</span>
|
|
10249
10832
|
</div>
|
|
@@ -10614,9 +11197,18 @@ RelRangePicker.styles = import_lit17.css`
|
|
|
10614
11197
|
color: var(--rel-text-default);
|
|
10615
11198
|
}
|
|
10616
11199
|
|
|
10617
|
-
.
|
|
11200
|
+
/* Both selectors, deliberately. The calendar trigger is a <rel-icon>,
|
|
11201
|
+
whose own svg lives in a shadow root that a plain .icon-button svg
|
|
11202
|
+
selector cannot reach — so sizing only the raw svg would leave it at its
|
|
11203
|
+
component default. The clear (x) button is still a hand-drawn inline
|
|
11204
|
+
svg, so the raw selector has to stay as well. Sizing the rel-icon
|
|
11205
|
+
ELEMENT works because a declaration from the outer tree beats the
|
|
11206
|
+
:host([size]) rules inside it. Same fix as rel-date-picker. */
|
|
11207
|
+
.icon-button svg,
|
|
11208
|
+
.icon-button rel-icon {
|
|
10618
11209
|
width: 16px;
|
|
10619
11210
|
height: 16px;
|
|
11211
|
+
--rel-icon-size: 16px;
|
|
10620
11212
|
}
|
|
10621
11213
|
|
|
10622
11214
|
/* ── Popover shell ── */
|
|
@@ -10644,7 +11236,14 @@ RelRangePicker.styles = import_lit17.css`
|
|
|
10644
11236
|
position: absolute;
|
|
10645
11237
|
top: calc(100% + 8px);
|
|
10646
11238
|
left: 0;
|
|
10647
|
-
|
|
11239
|
+
/* Fallback stacking ONLY: while the panel is in the top layer this is
|
|
11240
|
+
inert, because top-layer elements are not in the page stacking order at
|
|
11241
|
+
all and order of promotion decides. It applies solely where the Popover
|
|
11242
|
+
API is unavailable. One overridable hook across every overlay, so a
|
|
11243
|
+
consumer with its own stacking scheme sets it once; the default matches
|
|
11244
|
+
rel-select / rel-autocomplete / rel-popover / rel-menubar. This was
|
|
11245
|
+
2147483647, which cannot be layered against anything. */
|
|
11246
|
+
z-index: var(--rel-overlay-fallback-z, 1001);
|
|
10648
11247
|
border-radius: var(--rel-border-radius-sm, 8px);
|
|
10649
11248
|
background: var(--rel-bg-neutral);
|
|
10650
11249
|
/* Figma effectStyle=shadow/xl — the token now exists. */
|
|
@@ -10678,7 +11277,8 @@ RelRangePicker.styles = import_lit17.css`
|
|
|
10678
11277
|
width: auto;
|
|
10679
11278
|
height: auto;
|
|
10680
11279
|
border: none;
|
|
10681
|
-
|
|
11280
|
+
/* One below the shell, so the backdrop never covers its own panel. */
|
|
11281
|
+
z-index: calc(var(--rel-overlay-fallback-z, 1001) - 1);
|
|
10682
11282
|
background: rgba(0,0,0,0.4);
|
|
10683
11283
|
}
|
|
10684
11284
|
|
|
@@ -11433,7 +12033,7 @@ RelDivider = __decorateClass([
|
|
|
11433
12033
|
// src/components/rel-drawer/rel-drawer.ts
|
|
11434
12034
|
var import_lit21 = require("lit");
|
|
11435
12035
|
var import_decorators20 = require("lit/decorators.js");
|
|
11436
|
-
var
|
|
12036
|
+
var import_rel_icon11 = require("@reliability-design/icons/rel-icon");
|
|
11437
12037
|
|
|
11438
12038
|
// src/components/rel-drawer/rel-drawer-footer.ts
|
|
11439
12039
|
var import_lit20 = require("lit");
|
|
@@ -12366,12 +12966,12 @@ RelLayout = __decorateClass([
|
|
|
12366
12966
|
// src/components/rel-menu/rel-menu.ts
|
|
12367
12967
|
var import_lit25 = require("lit");
|
|
12368
12968
|
var import_decorators24 = require("lit/decorators.js");
|
|
12369
|
-
var
|
|
12969
|
+
var import_rel_icon13 = require("@reliability-design/icons/rel-icon");
|
|
12370
12970
|
|
|
12371
12971
|
// src/components/rel-radio/rel-radio.ts
|
|
12372
12972
|
var import_lit24 = require("lit");
|
|
12373
12973
|
var import_decorators23 = require("lit/decorators.js");
|
|
12374
|
-
var
|
|
12974
|
+
var import_rel_icon12 = require("@reliability-design/icons/rel-icon");
|
|
12375
12975
|
var SIZE_ALIASES5 = {
|
|
12376
12976
|
sm: "small",
|
|
12377
12977
|
md: "medium",
|
|
@@ -12398,18 +12998,27 @@ var RelRadio = class extends import_lit24.LitElement {
|
|
|
12398
12998
|
};
|
|
12399
12999
|
this._onTouched = () => {
|
|
12400
13000
|
};
|
|
13001
|
+
/** Whether the author wrote `density` themselves, as opposed to inheriting
|
|
13002
|
+
* the default. Read once at connect: Lit reflects during update, which is
|
|
13003
|
+
* after this runs, so the attribute is present here only if authored. */
|
|
13004
|
+
this.densityExplicit = false;
|
|
12401
13005
|
}
|
|
12402
13006
|
willUpdate(changed) {
|
|
12403
13007
|
if (changed.has("size")) {
|
|
12404
13008
|
const canonical = SIZE_ALIASES5[this.size];
|
|
12405
13009
|
if (canonical) this.size = canonical;
|
|
12406
13010
|
}
|
|
12407
|
-
|
|
13011
|
+
const densityChosen = this.densityExplicit || changed.get("density") !== void 0;
|
|
13012
|
+
if ((changed.has("alignment") || changed.has("density")) && this.alignment === "trailing" && this.density === "compact" && densityChosen) {
|
|
12408
13013
|
console.warn(
|
|
12409
13014
|
'[rel-radio] density="compact" has no trailing layout; rendering comfortable density.'
|
|
12410
13015
|
);
|
|
12411
13016
|
}
|
|
12412
13017
|
}
|
|
13018
|
+
connectedCallback() {
|
|
13019
|
+
super.connectedCallback();
|
|
13020
|
+
this.densityExplicit = this.hasAttribute("density");
|
|
13021
|
+
}
|
|
12413
13022
|
/** Trailing has no compact layout in the spec — trailing wins. */
|
|
12414
13023
|
get effectiveDensity() {
|
|
12415
13024
|
return this.alignment === "trailing" ? "comfortable" : this.density;
|
|
@@ -12677,13 +13286,22 @@ RelRadio.styles = import_lit24.css`
|
|
|
12677
13286
|
letter-spacing: var(--rel-body-lg-regular-letter-spacing);
|
|
12678
13287
|
}
|
|
12679
13288
|
|
|
13289
|
+
/* Supporting text sits ONE step below the label on the body scale at every
|
|
13290
|
+
size — 14/12 small, 16/14 medium, 20/16 large (2026-09-26 Trello ticket,
|
|
13291
|
+
"visual adjustment", confirmed by the designer as applying to every
|
|
13292
|
+
label + supporting pair, not just the checkbox). It used to be two steps
|
|
13293
|
+
below. The Figma ticket covered the Checkbox set only; extending the
|
|
13294
|
+
rule to Radio was proposed from the code side and approved by the
|
|
13295
|
+
designer on 2026-09-26, so the Radio Button set (lastModified
|
|
13296
|
+
2026-09-23) still shows the old two-step pairs (body-xs / body-sm /
|
|
13297
|
+
body-md) and will be updated to match the code, not the other way. */
|
|
12680
13298
|
.radio-supporting {
|
|
12681
13299
|
color: var(--rel-text-caption);
|
|
12682
|
-
font-family: var(--rel-body-
|
|
12683
|
-
font-weight: var(--rel-body-
|
|
12684
|
-
font-size: var(--rel-body-
|
|
12685
|
-
line-height: var(--rel-body-
|
|
12686
|
-
letter-spacing: var(--rel-body-
|
|
13300
|
+
font-family: var(--rel-body-md-regular-font-family);
|
|
13301
|
+
font-weight: var(--rel-body-md-regular-font-weight);
|
|
13302
|
+
font-size: var(--rel-body-md-regular-font-size);
|
|
13303
|
+
line-height: var(--rel-body-md-regular-line-height);
|
|
13304
|
+
letter-spacing: var(--rel-body-md-regular-letter-spacing);
|
|
12687
13305
|
}
|
|
12688
13306
|
|
|
12689
13307
|
/* Compact density — supporting text sits under the whole row, indented
|
|
@@ -12743,11 +13361,11 @@ RelRadio.styles = import_lit24.css`
|
|
|
12743
13361
|
}
|
|
12744
13362
|
|
|
12745
13363
|
:host([size="large"]) .radio-supporting {
|
|
12746
|
-
font-family: var(--rel-body-
|
|
12747
|
-
font-weight: var(--rel-body-
|
|
12748
|
-
font-size: var(--rel-body-
|
|
12749
|
-
line-height: var(--rel-body-
|
|
12750
|
-
letter-spacing: var(--rel-body-
|
|
13364
|
+
font-family: var(--rel-body-lg-regular-font-family);
|
|
13365
|
+
font-weight: var(--rel-body-lg-regular-font-weight);
|
|
13366
|
+
font-size: var(--rel-body-lg-regular-font-size);
|
|
13367
|
+
line-height: var(--rel-body-lg-regular-line-height);
|
|
13368
|
+
letter-spacing: var(--rel-body-lg-regular-letter-spacing);
|
|
12751
13369
|
}
|
|
12752
13370
|
|
|
12753
13371
|
:host([size="large"]) .radio-supporting.indent {
|
|
@@ -12764,39 +13382,29 @@ RelRadio.styles = import_lit24.css`
|
|
|
12764
13382
|
letter-spacing: var(--rel-body-sm-semibold-letter-spacing);
|
|
12765
13383
|
}
|
|
12766
13384
|
|
|
12767
|
-
/*
|
|
12768
|
-
|
|
12769
|
-
the RING is inset 2px from the BOX on each side (box - 4), and the DOT
|
|
12770
|
-
is half the ring. M is 20/16/8, L is 24/20/10, so S is 16/12/6 — all
|
|
12771
|
-
integers, so the 2px ring stays crisp at 1x and 2x. Deriving from the
|
|
12772
|
-
stated ring/box = 0.8 instead would have given 12.8 and a 6.4 dot,
|
|
12773
|
-
which is where a 2px border on a fractional circle goes soft. */
|
|
12774
|
-
:host([size="small"]) .radio-custom {
|
|
12775
|
-
width: 16px;
|
|
12776
|
-
height: 16px;
|
|
12777
|
-
}
|
|
13385
|
+
/* SMALL DELIBERATELY DECLARES NO CONTROL — it shares medium's 20px box,
|
|
13386
|
+
16px ring and 8px dot, and only the TYPE below steps down.
|
|
12778
13387
|
|
|
12779
|
-
|
|
12780
|
-
|
|
12781
|
-
|
|
12782
|
-
|
|
13388
|
+
v1.4.8 ticket 11b gave Small its own 16/12/6 control, derived from the
|
|
13389
|
+
premise "the ramp is 16 / 20 / 24". That premise was never read off
|
|
13390
|
+
Figma, and it is wrong. Verified against the LIVE Published DS file on
|
|
13391
|
+
2026-09-25 (set "Radio Button" 8408:395, 45 variants): .Radio Control
|
|
13392
|
+
measures 20×20 in all 15 Small variants, 20×20 in all 15 Medium, and
|
|
13393
|
+
24×24 in all 15 Large. Figma discriminates Small from Medium by type
|
|
13394
|
+
alone. memory/project_web_components.md recorded exactly this on
|
|
13395
|
+
2026-08-21 — "S and M share the 20px control, only typography steps
|
|
13396
|
+
down" — before 1.4.8 overrode it.
|
|
12783
13397
|
|
|
12784
|
-
|
|
12785
|
-
|
|
12786
|
-
|
|
12787
|
-
|
|
13398
|
+
So the correct Small is the ABSENCE of a rule. Adding one back means
|
|
13399
|
+
re-deriving it from Figma, not from the invariant the other two sizes
|
|
13400
|
+
happen to satisfy: that invariant (ring = box − 4, dot = ring ÷ 2) is a
|
|
13401
|
+
coincidence of rounding, since Figma's ring is proportional at 0.8 and
|
|
13402
|
+
our Large ring is a deliberate 19.2 → 20. */
|
|
12788
13403
|
|
|
12789
|
-
/* The compact indent is box + the 8px row gap,
|
|
12790
|
-
|
|
13404
|
+
/* The compact indent is box + the 8px row gap, so Small takes medium's
|
|
13405
|
+
2xl (28) along with medium's 20px box. */
|
|
12791
13406
|
:host([size="small"]) .radio-supporting.indent {
|
|
12792
|
-
padding-left: var(--rel-spacing-
|
|
12793
|
-
}
|
|
12794
|
-
|
|
12795
|
-
/* The option avatar matches the control box at every size (20 at M, 24 at
|
|
12796
|
-
L), so it follows Small down to 16. */
|
|
12797
|
-
:host([size="small"]) .radio-avatar {
|
|
12798
|
-
width: 16px;
|
|
12799
|
-
height: 16px;
|
|
13407
|
+
padding-left: var(--rel-spacing-2xl);
|
|
12800
13408
|
}
|
|
12801
13409
|
|
|
12802
13410
|
:host([size="small"]) .radio-label {
|
|
@@ -12808,11 +13416,11 @@ RelRadio.styles = import_lit24.css`
|
|
|
12808
13416
|
}
|
|
12809
13417
|
|
|
12810
13418
|
:host([size="small"]) .radio-supporting {
|
|
12811
|
-
font-family: var(--rel-body-
|
|
12812
|
-
font-weight: var(--rel-body-
|
|
12813
|
-
font-size: var(--rel-body-
|
|
12814
|
-
line-height: var(--rel-body-
|
|
12815
|
-
letter-spacing: var(--rel-body-
|
|
13419
|
+
font-family: var(--rel-body-sm-regular-font-family);
|
|
13420
|
+
font-weight: var(--rel-body-sm-regular-font-weight);
|
|
13421
|
+
font-size: var(--rel-body-sm-regular-font-size);
|
|
13422
|
+
line-height: var(--rel-body-sm-regular-line-height);
|
|
13423
|
+
letter-spacing: var(--rel-body-sm-regular-letter-spacing);
|
|
12816
13424
|
}
|
|
12817
13425
|
|
|
12818
13426
|
/* Error state (Figma State=Error) — ring, dot, and supporting text go
|
|
@@ -13427,12 +14035,23 @@ RelMenu._css = `
|
|
|
13427
14035
|
flex: 1 1 auto;
|
|
13428
14036
|
}
|
|
13429
14037
|
|
|
13430
|
-
/* Submenu affordance \u2014 same rel-icon chevron the menubar uses.
|
|
14038
|
+
/* Submenu affordance \u2014 same rel-icon chevron the menubar uses.
|
|
14039
|
+
|
|
14040
|
+
No opacity here: verified against the live Figma file, "Chevron Right"
|
|
14041
|
+
is opacity 1 on both the instance and its vector, on every State and
|
|
14042
|
+
Size, bound to the same colour variable as the row label beside it.
|
|
14043
|
+
The 0.6 that used to sit here dulled it to a mid grey.
|
|
14044
|
+
|
|
14045
|
+
The colour is declared rather than inherited on purpose. rel-menu
|
|
14046
|
+
renders to the LIGHT DOM (createRenderRoot returns this), so these
|
|
14047
|
+
selectors are unscoped globals and an inherited colour would travel
|
|
14048
|
+
through the consumer's page cascade \u2014 an app setting a colour on any
|
|
14049
|
+
ancestor would have been repainting this chevron. */
|
|
13431
14050
|
.submenu-chevron {
|
|
13432
14051
|
margin-left: auto;
|
|
13433
14052
|
display: inline-flex;
|
|
13434
14053
|
align-items: center;
|
|
13435
|
-
|
|
14054
|
+
color: var(--rel-text-default);
|
|
13436
14055
|
pointer-events: none;
|
|
13437
14056
|
}
|
|
13438
14057
|
|
|
@@ -13518,7 +14137,7 @@ RelMenu = __decorateClass([
|
|
|
13518
14137
|
// src/components/rel-menubar/rel-menubar.ts
|
|
13519
14138
|
var import_lit26 = require("lit");
|
|
13520
14139
|
var import_decorators25 = require("lit/decorators.js");
|
|
13521
|
-
var
|
|
14140
|
+
var import_rel_icon14 = require("@reliability-design/icons/rel-icon");
|
|
13522
14141
|
var RelMenubar = class extends import_lit26.LitElement {
|
|
13523
14142
|
constructor() {
|
|
13524
14143
|
super(...arguments);
|
|
@@ -14885,7 +15504,7 @@ RelOtp = __decorateClass([
|
|
|
14885
15504
|
// src/components/rel-pagination/rel-pagination.ts
|
|
14886
15505
|
var import_lit29 = require("lit");
|
|
14887
15506
|
var import_decorators28 = require("lit/decorators.js");
|
|
14888
|
-
var
|
|
15507
|
+
var import_rel_icon15 = require("@reliability-design/icons/rel-icon");
|
|
14889
15508
|
var RelPagination = class extends import_lit29.LitElement {
|
|
14890
15509
|
constructor() {
|
|
14891
15510
|
super(...arguments);
|
|
@@ -16751,7 +17370,7 @@ RelResizableHandle = __decorateClass([
|
|
|
16751
17370
|
// src/components/rel-segmented-button/rel-segmented-button.ts
|
|
16752
17371
|
var import_lit36 = require("lit");
|
|
16753
17372
|
var import_decorators35 = require("lit/decorators.js");
|
|
16754
|
-
var
|
|
17373
|
+
var import_rel_icon16 = require("@reliability-design/icons/rel-icon");
|
|
16755
17374
|
var RelSegmentedButton = class extends import_lit36.LitElement {
|
|
16756
17375
|
constructor() {
|
|
16757
17376
|
super(...arguments);
|
|
@@ -16783,12 +17402,12 @@ var RelSegmentedButton = class extends import_lit36.LitElement {
|
|
|
16783
17402
|
setActive(option) {
|
|
16784
17403
|
if (!option.disabled) {
|
|
16785
17404
|
this.active = option.value;
|
|
17405
|
+
const detail = { value: option.value };
|
|
16786
17406
|
this.dispatchEvent(
|
|
16787
|
-
new CustomEvent("
|
|
16788
|
-
|
|
16789
|
-
|
|
16790
|
-
|
|
16791
|
-
})
|
|
17407
|
+
new CustomEvent("rel-change", { detail, bubbles: true, composed: true })
|
|
17408
|
+
);
|
|
17409
|
+
this.dispatchEvent(
|
|
17410
|
+
new CustomEvent("changed", { detail, bubbles: true, composed: true })
|
|
16792
17411
|
);
|
|
16793
17412
|
}
|
|
16794
17413
|
}
|
|
@@ -17041,7 +17660,7 @@ RelSegmentedButton = __decorateClass([
|
|
|
17041
17660
|
var import_lit37 = require("lit");
|
|
17042
17661
|
var import_decorators36 = require("lit/decorators.js");
|
|
17043
17662
|
var import_repeat2 = require("lit/directives/repeat.js");
|
|
17044
|
-
var
|
|
17663
|
+
var import_rel_icon17 = require("@reliability-design/icons/rel-icon");
|
|
17045
17664
|
function normalizeOption(o) {
|
|
17046
17665
|
if (typeof o === "string") return { value: o, label: o, supportingText: "", icon: "" };
|
|
17047
17666
|
return {
|
|
@@ -17239,6 +17858,14 @@ var RelSelect = class extends import_lit37.LitElement {
|
|
|
17239
17858
|
get optionCheckboxSize() {
|
|
17240
17859
|
return this.size;
|
|
17241
17860
|
}
|
|
17861
|
+
/** Native tooltip carrying the row's full text. Both lines truncate to one
|
|
17862
|
+
* line with an ellipsis, so a long supporting line is otherwise unrecoverable
|
|
17863
|
+
* by sight; hovering the row reveals it. Only rows that carry a second line
|
|
17864
|
+
* get one, so plain lists do not sprout tooltips. */
|
|
17865
|
+
rowTitle(option) {
|
|
17866
|
+
return option.supportingText ? `${option.label}
|
|
17867
|
+
${option.supportingText}` : import_lit37.nothing;
|
|
17868
|
+
}
|
|
17242
17869
|
get selectAllStatus() {
|
|
17243
17870
|
const filtered = this.filteredOptions;
|
|
17244
17871
|
if (filtered.length === 0) return "unchecked";
|
|
@@ -17257,13 +17884,27 @@ var RelSelect = class extends import_lit37.LitElement {
|
|
|
17257
17884
|
window.removeEventListener("resize", this.updatePosition);
|
|
17258
17885
|
window.removeEventListener("scroll", this.onPageScroll, true);
|
|
17259
17886
|
}
|
|
17887
|
+
/**
|
|
17888
|
+
* BOTH events fire, deliberately — do not delete either.
|
|
17889
|
+
*
|
|
17890
|
+
* This repo is split down the middle on the convention, not inconsistent in
|
|
17891
|
+
* one place: rel-select and rel-segmented-button emit `changed`, while
|
|
17892
|
+
* rel-switch, rel-checkbox and rel-radio emit `rel-change`. Renaming
|
|
17893
|
+
* `changed` would break every existing rel-select consumer, so 1.4.9 adds
|
|
17894
|
+
* the prefixed name alongside it rather than moving anyone. New code should
|
|
17895
|
+
* listen for `rel-change`; `changed` stays for as long as 1.x does.
|
|
17896
|
+
*
|
|
17897
|
+
* One `detail` object is shared by both dispatches so the two events cannot
|
|
17898
|
+
* drift apart — a listener on one sees exactly what a listener on the other
|
|
17899
|
+
* sees.
|
|
17900
|
+
*/
|
|
17260
17901
|
emitChange() {
|
|
17902
|
+
const detail = { value: this.multiple ? this.selectedValues : this.selected };
|
|
17261
17903
|
this.dispatchEvent(
|
|
17262
|
-
new CustomEvent("changed", {
|
|
17263
|
-
|
|
17264
|
-
|
|
17265
|
-
|
|
17266
|
-
})
|
|
17904
|
+
new CustomEvent("changed", { detail, bubbles: true, composed: true })
|
|
17905
|
+
);
|
|
17906
|
+
this.dispatchEvent(
|
|
17907
|
+
new CustomEvent("rel-change", { detail, bubbles: true, composed: true })
|
|
17267
17908
|
);
|
|
17268
17909
|
}
|
|
17269
17910
|
openListbox() {
|
|
@@ -17707,6 +18348,7 @@ var RelSelect = class extends import_lit37.LitElement {
|
|
|
17707
18348
|
role="option"
|
|
17708
18349
|
aria-selected="${this.selectedValues.includes(option.value)}"
|
|
17709
18350
|
aria-label="${option.label}"
|
|
18351
|
+
title="${this.rowTitle(option)}"
|
|
17710
18352
|
@pointerenter="${() => {
|
|
17711
18353
|
this.highlightedIndex = index;
|
|
17712
18354
|
this.selectAllHighlighted = false;
|
|
@@ -17738,6 +18380,7 @@ var RelSelect = class extends import_lit37.LitElement {
|
|
|
17738
18380
|
part="option" class="select-option ${index === this.highlightedIndex ? "highlighted" : ""}"
|
|
17739
18381
|
role="option"
|
|
17740
18382
|
aria-selected="${option.value === this.selected}"
|
|
18383
|
+
title="${this.rowTitle(option)}"
|
|
17741
18384
|
@mousedown="${(e) => e.preventDefault()}"
|
|
17742
18385
|
@click="${() => this.selectSingleOption(option)}"
|
|
17743
18386
|
@pointerenter="${() => this.highlightedIndex = index}"
|
|
@@ -18058,12 +18701,40 @@ RelSelect.styles = import_lit37.css`
|
|
|
18058
18701
|
--_rel-sel-icon: 16px;
|
|
18059
18702
|
}
|
|
18060
18703
|
|
|
18704
|
+
/* Border geometry, in one place, so the mask strip below is DERIVED from
|
|
18705
|
+
the paint it has to cover rather than restating it. Declared on the
|
|
18706
|
+
wrapper and not on .select-trigger because .floating-label is the
|
|
18707
|
+
trigger's SIBLING, not its child, and would not inherit from it. */
|
|
18708
|
+
.select-wrapper {
|
|
18709
|
+
--_rel-sel-border-w: 1px;
|
|
18710
|
+
--_rel-sel-ring-w: 0px;
|
|
18711
|
+
--_rel-sel-notch-h: calc(var(--_rel-sel-border-w) + var(--_rel-sel-ring-w));
|
|
18712
|
+
}
|
|
18713
|
+
|
|
18714
|
+
.select-wrapper.focused {
|
|
18715
|
+
--_rel-sel-ring-w: 2px;
|
|
18716
|
+
}
|
|
18717
|
+
|
|
18061
18718
|
.floating-label {
|
|
18062
18719
|
position: absolute;
|
|
18720
|
+
/* Matches Figma directly and needs no arithmetic: the live spec puts the
|
|
18721
|
+
"Floating Label" frame at dx=12 from the Input frame's edge, and this
|
|
18722
|
+
element's containing block (.select-container) IS the trigger's outer
|
|
18723
|
+
edge, so --_rel-sel-pad lands it exactly there. rel-textfield needs a
|
|
18724
|
+
different value (0) for the same result, because ITS containing block
|
|
18725
|
+
already starts inside the border and padding. Do not copy one into the
|
|
18726
|
+
other — I briefly "corrected" this one to subtract the notch padding
|
|
18727
|
+
and moved a label that was already right. */
|
|
18063
18728
|
left: var(--_rel-sel-pad, var(--rel-spacing-sm));
|
|
18064
18729
|
top: 50%;
|
|
18065
18730
|
transform: translateY(-50%);
|
|
18066
|
-
background
|
|
18731
|
+
/* NO background here. It used to paint --rel-select-bg across the
|
|
18732
|
+
label's whole ~20px line box to hide a 1px border, which erased about
|
|
18733
|
+
10px of the trigger's interior and 10px of whatever sat above it.
|
|
18734
|
+
Figma does not do that: the "Floating Label" frame is a strip whose
|
|
18735
|
+
HEIGHT EQUALS THE BORDER IT MASKS — size=45x1 at rest and 45x2 when
|
|
18736
|
+
focused (rel-select.md). rel-textfield already moved off the chip for
|
|
18737
|
+
exactly this reason; the strip is now on ::before below. */
|
|
18067
18738
|
/* Figma pads the floating-label chip by --rel-spacing-2xs; this was a
|
|
18068
18739
|
raw 4px that happened to match but was not on the scale. */
|
|
18069
18740
|
padding: 0 var(--rel-spacing-2xs);
|
|
@@ -18109,6 +18780,8 @@ RelSelect.styles = import_lit37.css`
|
|
|
18109
18780
|
.select-wrapper.has-value .floating-label,
|
|
18110
18781
|
.select-wrapper.open .floating-label {
|
|
18111
18782
|
top: 0;
|
|
18783
|
+
/* Same inset as the resting rule above, so the label does not shift
|
|
18784
|
+
horizontally as it floats. */
|
|
18112
18785
|
left: var(--_rel-sel-pad, var(--rel-spacing-sm));
|
|
18113
18786
|
/* Figma's floated label is REGULAR 400, not medium 500. */
|
|
18114
18787
|
font-family: var(--rel-body-sm-regular-font-family);
|
|
@@ -18137,10 +18810,66 @@ RelSelect.styles = import_lit37.css`
|
|
|
18137
18810
|
letter-spacing: var(--rel-body-xs-regular-letter-spacing);
|
|
18138
18811
|
}
|
|
18139
18812
|
|
|
18140
|
-
/*
|
|
18141
|
-
|
|
18142
|
-
|
|
18143
|
-
|
|
18813
|
+
/* The mask: TWO OVERLAPPING strips, same idea as rel-textfield, different
|
|
18814
|
+
reference — and the reference is the whole reason this cannot be copied
|
|
18815
|
+
across. In rel-textfield the label is positioned inside the control, so
|
|
18816
|
+
its 50% is one border-width BELOW the border-box top. Here
|
|
18817
|
+
.floating-label is a SIBLING of .select-trigger inside
|
|
18818
|
+
.select-container, so its 50% IS the trigger's border-box top. Measured
|
|
18819
|
+
from there the focus outline occupies [50% - ringW, 50%] (outlines draw
|
|
18820
|
+
outside the border box) and the border [50%, 50% + borderW].
|
|
18821
|
+
A single strip cut to exactly that band leaked at some zoom levels and
|
|
18822
|
+
not others (v1.4.9 "slash" report): border and outline are
|
|
18823
|
+
pixel-snapped by Blink, while a strip inside a translateY(-50%) label
|
|
18824
|
+
rasterises at a fractional offset, so their edges disagreed by under a
|
|
18825
|
+
device pixel and a hairline showed through the text. So each strip
|
|
18826
|
+
overshoots into paint of ITS OWN colour, and the two overlap so no two
|
|
18827
|
+
anti-aliased edges share a row (abutting edges leak a*(1-a) of the band
|
|
18828
|
+
— measured as exactly a quarter of the ring colour on rel-textfield):
|
|
18829
|
+
::before surface colour, ring + border + 2px above + 2px below
|
|
18830
|
+
::after the trigger's FILL, border + 3px below, painted on top
|
|
18831
|
+
2px of overshoot, not 1: the strip and the band snap to the device grid
|
|
18832
|
+
in different coordinate spaces and can disagree by a full pixel. */
|
|
18833
|
+
.select-wrapper.focused .floating-label::before,
|
|
18834
|
+
.select-wrapper.has-value .floating-label::before,
|
|
18835
|
+
.select-wrapper.open .floating-label::before {
|
|
18836
|
+
content: "";
|
|
18837
|
+
position: absolute;
|
|
18838
|
+
left: 0;
|
|
18839
|
+
right: 0;
|
|
18840
|
+
top: calc(50% - var(--_rel-sel-ring-w, 0px) - 2px);
|
|
18841
|
+
height: calc(var(--_rel-sel-notch-h, 1px) + 4px);
|
|
18842
|
+
background: var(--rel-select-surface, var(--rel-select-bg, var(--rel-bg-neutral)));
|
|
18843
|
+
z-index: -1;
|
|
18844
|
+
}
|
|
18845
|
+
|
|
18846
|
+
.select-wrapper.focused .floating-label::after,
|
|
18847
|
+
.select-wrapper.has-value .floating-label::after,
|
|
18848
|
+
.select-wrapper.open .floating-label::after {
|
|
18849
|
+
content: "";
|
|
18850
|
+
position: absolute;
|
|
18851
|
+
left: 0;
|
|
18852
|
+
right: 0;
|
|
18853
|
+
top: 50%;
|
|
18854
|
+
height: calc(var(--_rel-sel-border-w, 1px) + 3px);
|
|
18855
|
+
background: var(--rel-select-bg, var(--rel-bg-neutral));
|
|
18856
|
+
z-index: -1;
|
|
18857
|
+
}
|
|
18858
|
+
|
|
18859
|
+
/* Figma swaps the strip's fill on the disabled variant
|
|
18860
|
+
(--rel-bg-neutral-elevated-2), matching the disabled trigger fill at
|
|
18861
|
+
.select-wrapper.disabled .select-trigger. rel-select had no equivalent of
|
|
18862
|
+
rel-textfield's disabled swap at all, so the old chip painted
|
|
18863
|
+
--rel-bg-neutral over a trigger filled --rel-bg-neutral-elevated-2: a
|
|
18864
|
+
visibly lighter block sitting on the field. */
|
|
18865
|
+
.select-wrapper.disabled .floating-label::before {
|
|
18866
|
+
background: var(
|
|
18867
|
+
--rel-select-surface-disabled,
|
|
18868
|
+
var(--rel-select-bg-disabled, var(--rel-bg-neutral-elevated-2))
|
|
18869
|
+
);
|
|
18870
|
+
}
|
|
18871
|
+
.select-wrapper.disabled .floating-label::after {
|
|
18872
|
+
background: var(--rel-select-bg-disabled, var(--rel-bg-neutral-elevated-2));
|
|
18144
18873
|
}
|
|
18145
18874
|
|
|
18146
18875
|
/* The real placeholder hides while the float-label rests in its spot,
|
|
@@ -18196,7 +18925,12 @@ RelSelect.styles = import_lit37.css`
|
|
|
18196
18925
|
/* ── Dropdown listbox ── */
|
|
18197
18926
|
.select-listbox {
|
|
18198
18927
|
position: fixed;
|
|
18199
|
-
|
|
18928
|
+
/* Only consulted on the no-Popover-API fallback path: once the listbox
|
|
18929
|
+
is promoted to the top layer, z-index is inert and stacking is by
|
|
18930
|
+
promotion order. Shared with the pickers so the whole overlay family
|
|
18931
|
+
is overridable from one place instead of disagreeing (they were on
|
|
18932
|
+
2147483647, which beat consumer app chrome unconditionally). */
|
|
18933
|
+
z-index: var(--rel-overlay-fallback-z, 1001);
|
|
18200
18934
|
display: none;
|
|
18201
18935
|
/* Reset native [popover] UA styles (the listbox is promoted to the
|
|
18202
18936
|
top layer so transformed ancestors — e.g. a centered rel-modal
|
|
@@ -18421,6 +19155,26 @@ RelSelect.styles = import_lit37.css`
|
|
|
18421
19155
|
white-space: nowrap;
|
|
18422
19156
|
}
|
|
18423
19157
|
|
|
19158
|
+
/* Multi-select rows delegate to a nested rel-checkbox, which wraps its
|
|
19159
|
+
text by default, so the same option truncated to one line in single
|
|
19160
|
+
mode and wrapped to three (or overflowed the panel sideways on an
|
|
19161
|
+
unbroken identifier) under multiple. The checkbox's label and helper
|
|
19162
|
+
are parts, so the row reaches them from here and applies the same
|
|
19163
|
+
one-line ellipsis. Same fix as rel-autocomplete, same date. */
|
|
19164
|
+
.checkbox-option > rel-checkbox {
|
|
19165
|
+
flex: 1;
|
|
19166
|
+
min-width: 0;
|
|
19167
|
+
}
|
|
19168
|
+
|
|
19169
|
+
.checkbox-option > rel-checkbox::part(label),
|
|
19170
|
+
.checkbox-option > rel-checkbox::part(helper) {
|
|
19171
|
+
display: block;
|
|
19172
|
+
min-width: 0;
|
|
19173
|
+
overflow: hidden;
|
|
19174
|
+
text-overflow: ellipsis;
|
|
19175
|
+
white-space: nowrap;
|
|
19176
|
+
}
|
|
19177
|
+
|
|
18424
19178
|
/* One line, truncated like the label. supportingText used to render only in
|
|
18425
19179
|
multi-select rows (through rel-checkbox); single-select rows drop it on
|
|
18426
19180
|
the floor, which made the same option object render differently
|
|
@@ -18858,7 +19612,7 @@ RelSkeleton = __decorateClass([
|
|
|
18858
19612
|
// src/components/rel-sheet/rel-sheet.ts
|
|
18859
19613
|
var import_lit39 = require("lit");
|
|
18860
19614
|
var import_decorators38 = require("lit/decorators.js");
|
|
18861
|
-
var
|
|
19615
|
+
var import_rel_icon18 = require("@reliability-design/icons/rel-icon");
|
|
18862
19616
|
var SIZE_ALIASES7 = {
|
|
18863
19617
|
sm: "small",
|
|
18864
19618
|
md: "medium",
|
|
@@ -20272,7 +21026,7 @@ RelSlider = __decorateClass([
|
|
|
20272
21026
|
// src/components/rel-snackbar/rel-snackbar.ts
|
|
20273
21027
|
var import_lit41 = require("lit");
|
|
20274
21028
|
var import_decorators40 = require("lit/decorators.js");
|
|
20275
|
-
var
|
|
21029
|
+
var import_rel_icon19 = require("@reliability-design/icons/rel-icon");
|
|
20276
21030
|
var DESTRUCTIVE_ALIASES2 = /* @__PURE__ */ new Set(["error", "danger", "critical"]);
|
|
20277
21031
|
var RelSnackbar = class extends import_lit41.LitElement {
|
|
20278
21032
|
constructor() {
|
|
@@ -20663,6 +21417,7 @@ var RelStepper = class extends import_lit42.LitElement {
|
|
|
20663
21417
|
this.orientation = "horizontal";
|
|
20664
21418
|
this.size = "large";
|
|
20665
21419
|
this.type = "default";
|
|
21420
|
+
this.connector = "solid";
|
|
20666
21421
|
}
|
|
20667
21422
|
willUpdate(changed) {
|
|
20668
21423
|
if (changed.has("size") && this.size === "small") {
|
|
@@ -20673,6 +21428,14 @@ var RelStepper = class extends import_lit42.LitElement {
|
|
|
20673
21428
|
"Figma has no Size axis on the Stepper: the connector geometry is fixed, so size no longer changes anything. Retune the connector with the CSS hook instead."
|
|
20674
21429
|
);
|
|
20675
21430
|
}
|
|
21431
|
+
if (changed.has("textAlign") && this.textAlign !== "below" && !this.labelPlacement) {
|
|
21432
|
+
warnDeprecated(
|
|
21433
|
+
"rel-stepper",
|
|
21434
|
+
"textAlign",
|
|
21435
|
+
"label-placement",
|
|
21436
|
+
'Use label-placement="inverted" for textalign="below" and label-placement="centered" for textalign="right".'
|
|
21437
|
+
);
|
|
21438
|
+
}
|
|
20676
21439
|
}
|
|
20677
21440
|
isDone(step) {
|
|
20678
21441
|
return step.status === "done";
|
|
@@ -21055,9 +21818,15 @@ RelStepper.styles = import_lit42.css`
|
|
|
21055
21818
|
/********************************************
|
|
21056
21819
|
* Connector Lines
|
|
21057
21820
|
********************************************/
|
|
21821
|
+
/* The line colour is held in a custom property rather than written straight
|
|
21822
|
+
to background, because the dashed variant below needs the SAME colour
|
|
21823
|
+
inside a gradient. With the colour in background the state rules and the
|
|
21824
|
+
dash pattern would each have to win the same declaration, and whichever
|
|
21825
|
+
came last would erase the other. */
|
|
21058
21826
|
.connector {
|
|
21059
21827
|
position: absolute;
|
|
21060
|
-
|
|
21828
|
+
--_rel-stepper-line-color: var(--rel-bg-neutral-elevated-2);
|
|
21829
|
+
background: var(--_rel-stepper-line-color);
|
|
21061
21830
|
z-index: 1;
|
|
21062
21831
|
}
|
|
21063
21832
|
|
|
@@ -21069,7 +21838,6 @@ RelStepper.styles = import_lit42.css`
|
|
|
21069
21838
|
transform: translateY(-50%);
|
|
21070
21839
|
height: 1px;
|
|
21071
21840
|
position: absolute;
|
|
21072
|
-
background: var(--rel-bg-neutral-elevated-2);
|
|
21073
21841
|
}
|
|
21074
21842
|
|
|
21075
21843
|
.rel-stepper.horizontal .rel-step:last-child .connector {
|
|
@@ -21104,6 +21872,37 @@ RelStepper.styles = import_lit42.css`
|
|
|
21104
21872
|
display: none;
|
|
21105
21873
|
}
|
|
21106
21874
|
|
|
21875
|
+
/* ── connector="dot" (Figma ".Line Types" Type=Dot, set 6437:428) ──
|
|
21876
|
+
Verified against the LIVE Published DS file 2026-09-25: both Dot variants
|
|
21877
|
+
carry RECTANGLE "Line" 1×20 with strokeWeight=1 and strokeDashes=[2,2],
|
|
21878
|
+
i.e. a 1px line, 2px on, 2px off. (The Default variants carry no dashes;
|
|
21879
|
+
the COMPONENT_SET node's own [10,5] is the set frame's border, not a
|
|
21880
|
+
variant value.)
|
|
21881
|
+
|
|
21882
|
+
A repeating gradient rather than a dashed border-style, because the dashed
|
|
21883
|
+
border style is engine-defined — no browser lets you state a 2/2 period,
|
|
21884
|
+
and each one picks its own dash length from the border width. The
|
|
21885
|
+
gradient is exact and stays exact at any device pixel ratio.
|
|
21886
|
+
|
|
21887
|
+
The line thickness still comes from the height/width declared above, so
|
|
21888
|
+
this only replaces the paint. Colour comes through the custom property so
|
|
21889
|
+
done / progress steps dash in their own tone. */
|
|
21890
|
+
:host([connector="dot"]) .rel-stepper.horizontal .connector {
|
|
21891
|
+
background: repeating-linear-gradient(
|
|
21892
|
+
to right,
|
|
21893
|
+
var(--_rel-stepper-line-color) 0 2px,
|
|
21894
|
+
transparent 2px 4px
|
|
21895
|
+
);
|
|
21896
|
+
}
|
|
21897
|
+
|
|
21898
|
+
:host([connector="dot"]) .rel-stepper.vertical .connector {
|
|
21899
|
+
background: repeating-linear-gradient(
|
|
21900
|
+
to bottom,
|
|
21901
|
+
var(--_rel-stepper-line-color) 0 2px,
|
|
21902
|
+
transparent 2px 4px
|
|
21903
|
+
);
|
|
21904
|
+
}
|
|
21905
|
+
|
|
21107
21906
|
/********************************************
|
|
21108
21907
|
* Step States
|
|
21109
21908
|
********************************************/
|
|
@@ -21129,11 +21928,11 @@ RelStepper.styles = import_lit42.css`
|
|
|
21129
21928
|
}
|
|
21130
21929
|
|
|
21131
21930
|
.rel-step.done .connector {
|
|
21132
|
-
|
|
21931
|
+
--_rel-stepper-line-color: var(--rel-border-strong);
|
|
21133
21932
|
}
|
|
21134
21933
|
|
|
21135
21934
|
.rel-step.progress .connector {
|
|
21136
|
-
|
|
21935
|
+
--_rel-stepper-line-color: var(--rel-border-strong);
|
|
21137
21936
|
}
|
|
21138
21937
|
|
|
21139
21938
|
/* Figma "Content": body/md/medium, text default; Inactive at 40% */
|
|
@@ -21219,6 +22018,9 @@ __decorateClass([
|
|
|
21219
22018
|
__decorateClass([
|
|
21220
22019
|
(0, import_decorators41.property)({ type: String, reflect: true })
|
|
21221
22020
|
], RelStepper.prototype, "type", 2);
|
|
22021
|
+
__decorateClass([
|
|
22022
|
+
(0, import_decorators41.property)({ type: String, reflect: true })
|
|
22023
|
+
], RelStepper.prototype, "connector", 2);
|
|
21222
22024
|
__decorateClass([
|
|
21223
22025
|
(0, import_decorators41.property)({ type: String, reflect: true, attribute: "label-placement" })
|
|
21224
22026
|
], RelStepper.prototype, "labelPlacement", 2);
|
|
@@ -21701,7 +22503,7 @@ RelSwitch = __decorateClass([
|
|
|
21701
22503
|
// src/components/rel-tab/rel-tab.ts
|
|
21702
22504
|
var import_lit44 = require("lit");
|
|
21703
22505
|
var import_decorators43 = require("lit/decorators.js");
|
|
21704
|
-
var
|
|
22506
|
+
var import_rel_icon20 = require("@reliability-design/icons/rel-icon");
|
|
21705
22507
|
var RelTabItem = class extends import_lit44.LitElement {
|
|
21706
22508
|
constructor() {
|
|
21707
22509
|
super(...arguments);
|
|
@@ -21773,6 +22575,13 @@ var RelTab = class extends import_lit44.LitElement {
|
|
|
21773
22575
|
composed: true
|
|
21774
22576
|
})
|
|
21775
22577
|
);
|
|
22578
|
+
this.dispatchEvent(
|
|
22579
|
+
new CustomEvent("rel-change", {
|
|
22580
|
+
detail: index,
|
|
22581
|
+
bubbles: true,
|
|
22582
|
+
composed: true
|
|
22583
|
+
})
|
|
22584
|
+
);
|
|
21776
22585
|
}
|
|
21777
22586
|
onKeyDown(event, index) {
|
|
21778
22587
|
if (event.key === "Enter" || event.key === " ") {
|
|
@@ -22049,7 +22858,7 @@ RelTab = __decorateClass([
|
|
|
22049
22858
|
var import_lit45 = require("lit");
|
|
22050
22859
|
var import_decorators44 = require("lit/decorators.js");
|
|
22051
22860
|
var import_live3 = require("lit/directives/live.js");
|
|
22052
|
-
var
|
|
22861
|
+
var import_rel_icon21 = require("@reliability-design/icons/rel-icon");
|
|
22053
22862
|
|
|
22054
22863
|
// src/components/rel-time-picker/time-parse.ts
|
|
22055
22864
|
function parseTimeInput(text) {
|
|
@@ -22154,6 +22963,23 @@ var RelTimePicker = class extends import_lit45.LitElement {
|
|
|
22154
22963
|
this.onReposition = () => {
|
|
22155
22964
|
if (this.open) this.updatePopoverPosition();
|
|
22156
22965
|
};
|
|
22966
|
+
/* Outside scroll CLOSES the panel; it used to reposition. Same change as
|
|
22967
|
+
* rel-date-picker and rel-range-picker, and the pattern rel-select has used
|
|
22968
|
+
* all along — see the long note in rel-date-picker.onPageScroll for why
|
|
22969
|
+
* repositioning is what produced the "panel floats over the sticky header"
|
|
22970
|
+
* report, and why no z-index can fix it.
|
|
22971
|
+
*
|
|
22972
|
+
* This component is the reason the exemption tests CONTAINMENT rather than
|
|
22973
|
+
* equality with the panel: the hour / minute / period columns are
|
|
22974
|
+
* `overflow-y: auto` in their own right (`.column-list`), so a scroll that
|
|
22975
|
+
* must NOT close the panel very often has one of them as its target, not the
|
|
22976
|
+
* panel. Discards the draft, like every other outside dismissal. */
|
|
22977
|
+
this.onPageScroll = (e) => {
|
|
22978
|
+
if (!this.open) return;
|
|
22979
|
+
const panel = this.popoverElement;
|
|
22980
|
+
if (panel && e.composedPath().includes(panel)) return;
|
|
22981
|
+
this.cancelPopover(false);
|
|
22982
|
+
};
|
|
22157
22983
|
this.updatePopoverPosition = () => {
|
|
22158
22984
|
const panel = this.popoverElement;
|
|
22159
22985
|
const anchor = this.triggerButton;
|
|
@@ -22213,7 +23039,7 @@ var RelTimePicker = class extends import_lit45.LitElement {
|
|
|
22213
23039
|
document.removeEventListener("click", this.handleClickOutside);
|
|
22214
23040
|
document.removeEventListener("keydown", this.handleEscapeKey);
|
|
22215
23041
|
window.removeEventListener("resize", this.onReposition);
|
|
22216
|
-
window.removeEventListener("scroll", this.
|
|
23042
|
+
window.removeEventListener("scroll", this.onPageScroll, true);
|
|
22217
23043
|
this.hideTopLayer();
|
|
22218
23044
|
}
|
|
22219
23045
|
// `open` is a public reflected property (consumers may set it directly,
|
|
@@ -22223,7 +23049,7 @@ var RelTimePicker = class extends import_lit45.LitElement {
|
|
|
22223
23049
|
if (changedProperties.has("open")) {
|
|
22224
23050
|
if (!this.open) {
|
|
22225
23051
|
window.removeEventListener("resize", this.onReposition);
|
|
22226
|
-
window.removeEventListener("scroll", this.
|
|
23052
|
+
window.removeEventListener("scroll", this.onPageScroll, true);
|
|
22227
23053
|
this.hideTopLayer();
|
|
22228
23054
|
this.focusPanelOnOpen = true;
|
|
22229
23055
|
return;
|
|
@@ -22231,7 +23057,7 @@ var RelTimePicker = class extends import_lit45.LitElement {
|
|
|
22231
23057
|
this.resetDraftFromValue();
|
|
22232
23058
|
this.showTopLayer();
|
|
22233
23059
|
window.addEventListener("resize", this.onReposition);
|
|
22234
|
-
window.addEventListener("scroll", this.
|
|
23060
|
+
window.addEventListener("scroll", this.onPageScroll, true);
|
|
22235
23061
|
const focusPanel = this.focusPanelOnOpen;
|
|
22236
23062
|
this.focusPanelOnOpen = true;
|
|
22237
23063
|
setTimeout(() => {
|
|
@@ -23208,7 +24034,14 @@ RelTimePicker.styles = import_lit45.css`
|
|
|
23208
24034
|
position: absolute;
|
|
23209
24035
|
top: calc(100% + 8px);
|
|
23210
24036
|
left: 0;
|
|
23211
|
-
|
|
24037
|
+
/* Fallback stacking ONLY: while the panel is in the top layer this is
|
|
24038
|
+
inert, because top-layer elements are not in the page stacking order at
|
|
24039
|
+
all and order of promotion decides. It applies solely where the Popover
|
|
24040
|
+
API is unavailable. One overridable hook across every overlay, so a
|
|
24041
|
+
consumer with its own stacking scheme sets it once; the default matches
|
|
24042
|
+
rel-select / rel-autocomplete / rel-popover / rel-menubar. This was
|
|
24043
|
+
2147483647, which cannot be layered against anything. */
|
|
24044
|
+
z-index: var(--rel-overlay-fallback-z, 1001);
|
|
23212
24045
|
background: var(--rel-bg-surface);
|
|
23213
24046
|
border: 1px solid var(--rel-border-subtle);
|
|
23214
24047
|
border-radius: var(--rel-border-radius-sm);
|
|
@@ -23557,7 +24390,7 @@ RelTimePicker = __decorateClass([
|
|
|
23557
24390
|
// src/components/rel-toast/rel-toast.ts
|
|
23558
24391
|
var import_lit46 = require("lit");
|
|
23559
24392
|
var import_decorators45 = require("lit/decorators.js");
|
|
23560
|
-
var
|
|
24393
|
+
var import_rel_icon22 = require("@reliability-design/icons/rel-icon");
|
|
23561
24394
|
var SIZE_ALIASES10 = {
|
|
23562
24395
|
sm: "small",
|
|
23563
24396
|
md: "medium",
|
|
@@ -24350,7 +25183,7 @@ RelTooltip = __decorateClass([
|
|
|
24350
25183
|
// src/components/rel-upload/rel-upload.ts
|
|
24351
25184
|
var import_lit48 = require("lit");
|
|
24352
25185
|
var import_decorators47 = require("lit/decorators.js");
|
|
24353
|
-
var
|
|
25186
|
+
var import_rel_icon23 = require("@reliability-design/icons/rel-icon");
|
|
24354
25187
|
var uid = 0;
|
|
24355
25188
|
var RelUpload = class extends import_lit48.LitElement {
|
|
24356
25189
|
constructor() {
|
|
@@ -24763,7 +25596,11 @@ var RelUpload = class extends import_lit48.LitElement {
|
|
|
24763
25596
|
}
|
|
24764
25597
|
renderRow(f) {
|
|
24765
25598
|
return import_lit48.html`
|
|
24766
|
-
|
|
25599
|
+
<!-- role="listitem" is required, not decorative: the container at
|
|
25600
|
+
.rows carries role="list", and a list whose children are plain divs
|
|
25601
|
+
fails axe's aria-required-children. It was failing on the uploading,
|
|
25602
|
+
failure, disabled and delayed stories alike. -->
|
|
25603
|
+
<div class="row ${f.status}" role="listitem">
|
|
24767
25604
|
<rel-icon name="document" size="md"></rel-icon>
|
|
24768
25605
|
|
|
24769
25606
|
<div class="row-content">
|
|
@@ -24868,8 +25705,19 @@ RelUpload.styles = import_lit48.css`
|
|
|
24868
25705
|
font-family: var(--rel-fonts-font-family);
|
|
24869
25706
|
}
|
|
24870
25707
|
|
|
25708
|
+
/* not-allowed must live on the HOST, which keeps pointer-events: auto.
|
|
25709
|
+
It used to sit on .dropzone, where it was unreachable: .dropzone is
|
|
25710
|
+
inside .body, .body is pointer-events: none when disabled, and an
|
|
25711
|
+
element that is not a hit target never has its cursor consulted —
|
|
25712
|
+
the cursor resolves from the nearest ancestor that IS one. Measured on
|
|
25713
|
+
a disabled upload before the fix: .dropzone computed not-allowed
|
|
25714
|
+
while the user saw the default arrow. getComputedStyle agreed with the
|
|
25715
|
+
CSS and disagreed with the screen, so anyone re-verifying this must
|
|
25716
|
+
HOVER it rather than read the computed value.
|
|
25717
|
+
rel-button hit the same trap and documents the same resolution. */
|
|
24871
25718
|
:host([disabled]) {
|
|
24872
25719
|
opacity: 0.6;
|
|
25720
|
+
cursor: not-allowed;
|
|
24873
25721
|
}
|
|
24874
25722
|
|
|
24875
25723
|
:host([disabled]) .body {
|
|
@@ -24882,7 +25730,6 @@ RelUpload.styles = import_lit48.css`
|
|
|
24882
25730
|
instead of a faded live one. */
|
|
24883
25731
|
:host([disabled]) .dropzone {
|
|
24884
25732
|
background: var(--rel-bg-neutral-elevated-2);
|
|
24885
|
-
cursor: not-allowed;
|
|
24886
25733
|
}
|
|
24887
25734
|
|
|
24888
25735
|
.body {
|
|
@@ -25160,7 +26007,7 @@ RelUpload = __decorateClass([
|
|
|
25160
26007
|
// src/components/rel-upload/rel-upload-actions.ts
|
|
25161
26008
|
var import_lit49 = require("lit");
|
|
25162
26009
|
var import_decorators48 = require("lit/decorators.js");
|
|
25163
|
-
var
|
|
26010
|
+
var import_rel_icon24 = require("@reliability-design/icons/rel-icon");
|
|
25164
26011
|
var EMPTY_STATE = {
|
|
25165
26012
|
phase: "empty",
|
|
25166
26013
|
total: 0,
|