@reliability-design/web 1.4.2 → 1.4.3
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 +27 -0
- package/dist/components/rel-autocomplete/README.md +3 -1
- package/dist/components/rel-autocomplete/rel-autocomplete.ts +24 -2
- package/dist/components/rel-button/rel-button.ts +2 -2
- package/dist/components/rel-radio/README.md +16 -1
- package/dist/components/rel-radio/rel-radio.ts +52 -1
- package/dist/components/rel-select/README.md +6 -0
- package/dist/components/rel-select/rel-select.ts +58 -7
- package/dist/components/rel-textfield/README.md +16 -0
- package/dist/components/rel-textfield/rel-textfield.ts +104 -6
- package/dist/components/rel-time-picker/README.md +199 -0
- package/dist/components/rel-time-picker/rel-time-picker.ts +994 -0
- package/dist/index.d.mts +187 -38
- package/dist/index.d.ts +187 -38
- package/dist/index.js +1195 -89
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1195 -90
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -681,6 +681,7 @@ var RelTextfield = class extends LitElement3 {
|
|
|
681
681
|
this.internalState = "default";
|
|
682
682
|
this._wasError = false;
|
|
683
683
|
this.uniqueId = `textfield-${Math.random().toString(36).slice(2)}`;
|
|
684
|
+
this._warnedNoName = false;
|
|
684
685
|
}
|
|
685
686
|
get isDisabled() {
|
|
686
687
|
return this.disabled || this.state === "disabled";
|
|
@@ -696,6 +697,15 @@ var RelTextfield = class extends LitElement3 {
|
|
|
696
697
|
get helper() {
|
|
697
698
|
return this.helperText ?? this.supportingText;
|
|
698
699
|
}
|
|
700
|
+
/** labelStyle supersedes the deprecated floatingLabel boolean. */
|
|
701
|
+
get resolvedLabelStyle() {
|
|
702
|
+
if (this.labelStyle) return this.labelStyle;
|
|
703
|
+
return this.floatingLabel ? "floating" : "default";
|
|
704
|
+
}
|
|
705
|
+
/** True when no visible label renders and the input needs an ARIA name. */
|
|
706
|
+
get labelHidden() {
|
|
707
|
+
return this.resolvedLabelStyle === "none";
|
|
708
|
+
}
|
|
699
709
|
connectedCallback() {
|
|
700
710
|
super.connectedCallback();
|
|
701
711
|
const leftIcon = this.getAttribute("lefticon") || this.getAttribute("leftIcon");
|
|
@@ -703,6 +713,20 @@ var RelTextfield = class extends LitElement3 {
|
|
|
703
713
|
const rightIcon = this.getAttribute("righticon") || this.getAttribute("rightIcon");
|
|
704
714
|
if (rightIcon) this.rightIcon = rightIcon;
|
|
705
715
|
}
|
|
716
|
+
willUpdate(changed) {
|
|
717
|
+
if (changed.has("labelStyle") && this.labelStyle) {
|
|
718
|
+
this.floatingLabel = this.labelStyle === "floating";
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
updated() {
|
|
722
|
+
if (this._warnedNoName || !this.labelHidden) return;
|
|
723
|
+
if (this.fieldAriaLabel || this.fieldAriaLabelledby || this.getAttribute("aria-label")) return;
|
|
724
|
+
this._warnedNoName = true;
|
|
725
|
+
console.warn(
|
|
726
|
+
'<rel-textfield label-style="none"> has no accessible name. Set `field-aria-label` or `field-aria-labelledby` \u2014 a placeholder is not a valid naming mechanism.',
|
|
727
|
+
this
|
|
728
|
+
);
|
|
729
|
+
}
|
|
706
730
|
/** Focuses the inner input/textarea (the host itself is not focusable). */
|
|
707
731
|
focus(options) {
|
|
708
732
|
this.renderRoot.querySelector("input, textarea")?.focus(options);
|
|
@@ -770,15 +794,20 @@ var RelTextfield = class extends LitElement3 {
|
|
|
770
794
|
!this.multiline && leadingIcon ? "has-left-icon" : "",
|
|
771
795
|
!this.multiline && this.rightIcon && this.variant !== "legend" ? "has-right-icon" : ""
|
|
772
796
|
].filter(Boolean).join(" ");
|
|
797
|
+
const labelStyle = this.resolvedLabelStyle;
|
|
798
|
+
const labelHidden = labelStyle === "none";
|
|
773
799
|
const labelTemplate = (extraClass = "") => {
|
|
774
|
-
if (!this.label) return "";
|
|
800
|
+
if (!this.label || labelHidden) return "";
|
|
775
801
|
return html3`<label
|
|
776
802
|
part="label"
|
|
777
803
|
class="textfield-label${extraClass}"
|
|
778
804
|
for="${inputId}"
|
|
779
805
|
>${this.label}${this.required ? html3`<span class="required-mark" aria-hidden="true">*</span>` : ""}</label>`;
|
|
780
806
|
};
|
|
781
|
-
const
|
|
807
|
+
const legendLabelledby = this.variant === "legend" && this.label && !labelHidden ? legendId : void 0;
|
|
808
|
+
const ariaLabelledby = this.fieldAriaLabelledby ?? legendLabelledby;
|
|
809
|
+
const hasVisibleLabel = !labelHidden && !!this.label;
|
|
810
|
+
const ariaLabel = !hasVisibleLabel && !ariaLabelledby ? this.fieldAriaLabel : void 0;
|
|
782
811
|
const inputTemplate = this.multiline ? html3`<textarea part="input"
|
|
783
812
|
id="${inputId}"
|
|
784
813
|
class="${inputClasses}"
|
|
@@ -792,6 +821,7 @@ var RelTextfield = class extends LitElement3 {
|
|
|
792
821
|
?required="${this.required}"
|
|
793
822
|
aria-invalid="${isError}"
|
|
794
823
|
aria-required="${this.required}"
|
|
824
|
+
aria-label="${ariaLabel ?? nothing}"
|
|
795
825
|
aria-labelledby="${ariaLabelledby ?? nothing}"
|
|
796
826
|
aria-describedby="${describedById ?? nothing}"
|
|
797
827
|
@focus="${this.handleFocus}"
|
|
@@ -810,6 +840,7 @@ var RelTextfield = class extends LitElement3 {
|
|
|
810
840
|
?required="${this.required}"
|
|
811
841
|
aria-invalid="${isError}"
|
|
812
842
|
aria-required="${this.required}"
|
|
843
|
+
aria-label="${ariaLabel ?? nothing}"
|
|
813
844
|
aria-labelledby="${ariaLabelledby ?? nothing}"
|
|
814
845
|
aria-describedby="${describedById ?? nothing}"
|
|
815
846
|
@focus="${this.handleFocus}"
|
|
@@ -831,7 +862,7 @@ var RelTextfield = class extends LitElement3 {
|
|
|
831
862
|
const fieldsetClass = ["field-fieldset", this.multiline ? "multiline" : ""].filter(Boolean).join(" ");
|
|
832
863
|
return html3`
|
|
833
864
|
<div class="${wrapperClasses}">
|
|
834
|
-
<fieldset part="control" class="${fieldsetClass}"><legend class="field-legend" id="${legendId}">${this.label ?? ""}${this.required ? html3`<span class="required-mark" aria-hidden="true">*</span>` : ""}</legend>
|
|
865
|
+
<fieldset part="control" class="${fieldsetClass}"><legend class="field-legend" id="${legendId}">${labelHidden ? "" : this.label ?? ""}${this.required && !labelHidden ? html3`<span class="required-mark" aria-hidden="true">*</span>` : ""}</legend>
|
|
835
866
|
${!this.multiline ? leadingIconTemplate : ""}
|
|
836
867
|
${inputTemplate}
|
|
837
868
|
</fieldset>
|
|
@@ -841,10 +872,10 @@ var RelTextfield = class extends LitElement3 {
|
|
|
841
872
|
}
|
|
842
873
|
return html3`
|
|
843
874
|
<div class="${wrapperClasses}">
|
|
844
|
-
${
|
|
875
|
+
${labelHidden ? html3`<div class="label-reserve" aria-hidden="true"></div>` : labelStyle !== "floating" ? labelTemplate() : ""}
|
|
845
876
|
|
|
846
877
|
<div part="control" class="${["textfield-container", this.isDisabled ? "disabled" : "", this.multiline ? "multiline" : ""].filter(Boolean).join(" ")}">
|
|
847
|
-
${
|
|
878
|
+
${labelStyle === "floating" ? labelTemplate(leadingIcon && !this.multiline ? " has-left-icon" : "") : ""}
|
|
848
879
|
${!this.multiline ? leadingIconTemplate : ""}
|
|
849
880
|
|
|
850
881
|
${inputTemplate}
|
|
@@ -903,6 +934,22 @@ RelTextfield.styles = css3`
|
|
|
903
934
|
margin-left: 2px;
|
|
904
935
|
}
|
|
905
936
|
|
|
937
|
+
/* ── labelStyle="none" ──
|
|
938
|
+
No label renders. An empty spacer holds the label row's full height so the
|
|
939
|
+
input box still bottom-aligns with labelled siblings in a form row.
|
|
940
|
+
The reserve covers label line-height + the wrapper gap, and the negative
|
|
941
|
+
margin cancels the flex gap that would otherwise sit below it — so
|
|
942
|
+
--rel-field-label-reserve: 0 collapses the row to genuinely zero
|
|
943
|
+
(toolbars, table cells), not just to the leftover gap. */
|
|
944
|
+
.label-reserve {
|
|
945
|
+
flex: none;
|
|
946
|
+
height: var(
|
|
947
|
+
--rel-field-label-reserve,
|
|
948
|
+
calc(var(--rel-body-sm-medium-line-height) + var(--rel-spacing-2xs))
|
|
949
|
+
);
|
|
950
|
+
margin-bottom: calc(-1 * var(--rel-spacing-2xs));
|
|
951
|
+
}
|
|
952
|
+
|
|
906
953
|
/* Floating label variant */
|
|
907
954
|
:host([floatinglabel]) .textfield-wrapper {
|
|
908
955
|
gap: 0;
|
|
@@ -1279,6 +1326,12 @@ RelTextfield.styles = css3`
|
|
|
1279
1326
|
box-sizing: border-box;
|
|
1280
1327
|
}
|
|
1281
1328
|
|
|
1329
|
+
/* labelStyle="none" + legend: nothing to seat in the notch, so drop the
|
|
1330
|
+
legend entirely and let the top border run continuous. */
|
|
1331
|
+
:host([label-style="none"][variant="legend"]) .field-legend {
|
|
1332
|
+
display: none;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1282
1335
|
/* Prefix icon (legend): vertically centered; input inset via has-left-icon */
|
|
1283
1336
|
:host([variant="legend"]) .icon.left {
|
|
1284
1337
|
top: 50%;
|
|
@@ -1394,9 +1447,18 @@ __decorateClass([
|
|
|
1394
1447
|
__decorateClass([
|
|
1395
1448
|
property3({ type: String })
|
|
1396
1449
|
], RelTextfield.prototype, "size", 2);
|
|
1450
|
+
__decorateClass([
|
|
1451
|
+
property3({ type: String, attribute: "label-style", reflect: true })
|
|
1452
|
+
], RelTextfield.prototype, "labelStyle", 2);
|
|
1397
1453
|
__decorateClass([
|
|
1398
1454
|
property3({ type: Boolean, reflect: true })
|
|
1399
1455
|
], RelTextfield.prototype, "floatingLabel", 2);
|
|
1456
|
+
__decorateClass([
|
|
1457
|
+
property3({ type: String, attribute: "field-aria-label" })
|
|
1458
|
+
], RelTextfield.prototype, "fieldAriaLabel", 2);
|
|
1459
|
+
__decorateClass([
|
|
1460
|
+
property3({ type: String, attribute: "field-aria-labelledby" })
|
|
1461
|
+
], RelTextfield.prototype, "fieldAriaLabelledby", 2);
|
|
1400
1462
|
__decorateClass([
|
|
1401
1463
|
property3({ type: String, reflect: true })
|
|
1402
1464
|
], RelTextfield.prototype, "variant", 2);
|
|
@@ -1509,6 +1571,11 @@ var RelAutocomplete = class extends LitElement4 {
|
|
|
1509
1571
|
this.panelStyle = `top:${top}px;left:${left}px;width:${t.width}px;`;
|
|
1510
1572
|
};
|
|
1511
1573
|
}
|
|
1574
|
+
/** Name used for the suggestion panel — the visible label, or fieldAriaLabel
|
|
1575
|
+
* once labelStyle="none" hides it. Mirrors rel-textfield's own resolution. */
|
|
1576
|
+
get accessibleName() {
|
|
1577
|
+
return (this.labelStyle === "none" ? this.fieldAriaLabel : this.label) || "";
|
|
1578
|
+
}
|
|
1512
1579
|
disconnectedCallback() {
|
|
1513
1580
|
super.disconnectedCallback();
|
|
1514
1581
|
this.removeGlobalListeners();
|
|
@@ -1613,17 +1680,17 @@ var RelAutocomplete = class extends LitElement4 {
|
|
|
1613
1680
|
this.syncPanel();
|
|
1614
1681
|
}
|
|
1615
1682
|
}
|
|
1616
|
-
async runSearch(
|
|
1683
|
+
async runSearch(query11) {
|
|
1617
1684
|
const seq = ++this.searchSeq;
|
|
1618
1685
|
try {
|
|
1619
|
-
const results = await this.searchFn(
|
|
1686
|
+
const results = await this.searchFn(query11);
|
|
1620
1687
|
if (seq !== this.searchSeq) return;
|
|
1621
1688
|
this.remoteItems = results ?? [];
|
|
1622
1689
|
} catch (err) {
|
|
1623
1690
|
if (seq !== this.searchSeq) return;
|
|
1624
1691
|
this.remoteItems = [];
|
|
1625
1692
|
this.dispatchEvent(
|
|
1626
|
-
new CustomEvent("rel-search-error", { detail: { query:
|
|
1693
|
+
new CustomEvent("rel-search-error", { detail: { query: query11, error: err }, bubbles: true, composed: true })
|
|
1627
1694
|
);
|
|
1628
1695
|
} finally {
|
|
1629
1696
|
if (seq === this.searchSeq) {
|
|
@@ -1769,7 +1836,10 @@ var RelAutocomplete = class extends LitElement4 {
|
|
|
1769
1836
|
.value="${this.inputText}"
|
|
1770
1837
|
.size="${this.size}"
|
|
1771
1838
|
.variant="${this.variant}"
|
|
1839
|
+
.labelStyle="${this.labelStyle}"
|
|
1772
1840
|
.floatingLabel="${this.floatingLabel}"
|
|
1841
|
+
.fieldAriaLabel="${this.fieldAriaLabel}"
|
|
1842
|
+
.fieldAriaLabelledby="${this.fieldAriaLabelledby}"
|
|
1773
1843
|
.prefixIcon="${this.prefixIcon}"
|
|
1774
1844
|
.disabled="${this.disabled}"
|
|
1775
1845
|
.readonly="${this.readonly}"
|
|
@@ -1791,7 +1861,7 @@ var RelAutocomplete = class extends LitElement4 {
|
|
|
1791
1861
|
part="panel list"
|
|
1792
1862
|
class="ac-panel"
|
|
1793
1863
|
role="listbox"
|
|
1794
|
-
aria-label="${this.
|
|
1864
|
+
aria-label="${this.accessibleName || "Suggestions"}"
|
|
1795
1865
|
style="${this.panelStyle}"
|
|
1796
1866
|
?data-open="${this.open}"
|
|
1797
1867
|
>
|
|
@@ -1970,9 +2040,18 @@ __decorateClass([
|
|
|
1970
2040
|
__decorateClass([
|
|
1971
2041
|
property4({ type: String })
|
|
1972
2042
|
], RelAutocomplete.prototype, "variant", 2);
|
|
2043
|
+
__decorateClass([
|
|
2044
|
+
property4({ type: String, attribute: "label-style", reflect: true })
|
|
2045
|
+
], RelAutocomplete.prototype, "labelStyle", 2);
|
|
1973
2046
|
__decorateClass([
|
|
1974
2047
|
property4({ type: Boolean, attribute: "floating-label" })
|
|
1975
2048
|
], RelAutocomplete.prototype, "floatingLabel", 2);
|
|
2049
|
+
__decorateClass([
|
|
2050
|
+
property4({ type: String, attribute: "field-aria-label" })
|
|
2051
|
+
], RelAutocomplete.prototype, "fieldAriaLabel", 2);
|
|
2052
|
+
__decorateClass([
|
|
2053
|
+
property4({ type: String, attribute: "field-aria-labelledby" })
|
|
2054
|
+
], RelAutocomplete.prototype, "fieldAriaLabelledby", 2);
|
|
1976
2055
|
__decorateClass([
|
|
1977
2056
|
property4({ type: String, attribute: "prefix-icon" })
|
|
1978
2057
|
], RelAutocomplete.prototype, "prefixIcon", 2);
|
|
@@ -2684,8 +2763,8 @@ RelButton.styles = css8`
|
|
|
2684
2763
|
pointer-events:none on the HOST would make it transparent to the
|
|
2685
2764
|
pointer and suppress the cursor entirely. */
|
|
2686
2765
|
cursor: not-allowed;
|
|
2687
|
-
/* The disabled tokens are full-contrast pairs (light:
|
|
2688
|
-
|
|
2766
|
+
/* The disabled tokens are full-contrast pairs (light: near-black on
|
|
2767
|
+
gray-300, dark: white on gray-700), so on their own they render a
|
|
2689
2768
|
crisp button that reads as enabled. Dulling happens here instead —
|
|
2690
2769
|
same opacity mechanism the rest of the library uses. */
|
|
2691
2770
|
opacity: 0.5;
|
|
@@ -8230,6 +8309,8 @@ var RelRadio = class extends LitElement23 {
|
|
|
8230
8309
|
super(...arguments);
|
|
8231
8310
|
this.options = [];
|
|
8232
8311
|
this.name = "radio-group";
|
|
8312
|
+
this.orientation = "vertical";
|
|
8313
|
+
this.size = "medium";
|
|
8233
8314
|
this.selectedValue = null;
|
|
8234
8315
|
this.disabled = false;
|
|
8235
8316
|
this.error = false;
|
|
@@ -8295,7 +8376,12 @@ var RelRadio = class extends LitElement23 {
|
|
|
8295
8376
|
}
|
|
8296
8377
|
render() {
|
|
8297
8378
|
return html23`
|
|
8298
|
-
<div
|
|
8379
|
+
<div
|
|
8380
|
+
part="base"
|
|
8381
|
+
class="rel-radio-group"
|
|
8382
|
+
role="radiogroup"
|
|
8383
|
+
aria-orientation="${this.orientation}"
|
|
8384
|
+
>
|
|
8299
8385
|
${this.options.map((o) => this.renderOption(o))}
|
|
8300
8386
|
</div>
|
|
8301
8387
|
${this.error && this.errorMessage ? html23`<div class="error-message">${this.errorMessage}</div>` : ""}
|
|
@@ -8316,6 +8402,15 @@ RelRadio.styles = css23`
|
|
|
8316
8402
|
gap: var(--rel-spacing-xs);
|
|
8317
8403
|
}
|
|
8318
8404
|
|
|
8405
|
+
/* Horizontal orientation — options flow in a wrapping row. Wider column
|
|
8406
|
+
gap than the vertical rhythm so adjacent option labels read as separate
|
|
8407
|
+
choices; row gap keeps the vertical value for wrapped lines. */
|
|
8408
|
+
:host([orientation="horizontal"]) .rel-radio-group {
|
|
8409
|
+
flex-direction: row;
|
|
8410
|
+
flex-wrap: wrap;
|
|
8411
|
+
gap: var(--rel-spacing-xs) var(--rel-spacing-xl);
|
|
8412
|
+
}
|
|
8413
|
+
|
|
8319
8414
|
.rel-radio-option {
|
|
8320
8415
|
display: inline-flex;
|
|
8321
8416
|
flex-direction: column;
|
|
@@ -8418,6 +8513,34 @@ RelRadio.styles = css23`
|
|
|
8418
8513
|
|
|
8419
8514
|
}
|
|
8420
8515
|
|
|
8516
|
+
/* Size variant (medium is the base scale above). Supporting-text indent =
|
|
8517
|
+
circle width + the radio-main gap, so it stays aligned under the label. */
|
|
8518
|
+
:host([size="small"]) .radio-custom {
|
|
8519
|
+
width: 16px;
|
|
8520
|
+
height: 16px;
|
|
8521
|
+
}
|
|
8522
|
+
|
|
8523
|
+
:host([size="small"]) input:checked + .radio-custom::after {
|
|
8524
|
+
inset: 3px;
|
|
8525
|
+
}
|
|
8526
|
+
|
|
8527
|
+
:host([size="small"]) .radio-label {
|
|
8528
|
+
font-family: var(--rel-body-md-regular-font-family);
|
|
8529
|
+
font-weight: var(--rel-body-md-regular-font-weight);
|
|
8530
|
+
font-size: var(--rel-body-md-regular-font-size);
|
|
8531
|
+
line-height: var(--rel-body-md-regular-line-height);
|
|
8532
|
+
letter-spacing: var(--rel-body-md-regular-letter-spacing);
|
|
8533
|
+
}
|
|
8534
|
+
|
|
8535
|
+
:host([size="small"]) .radio-supporting {
|
|
8536
|
+
margin-left: 24px;
|
|
8537
|
+
font-family: var(--rel-body-xs-regular-font-family);
|
|
8538
|
+
font-weight: var(--rel-body-xs-regular-font-weight);
|
|
8539
|
+
font-size: var(--rel-body-xs-regular-font-size);
|
|
8540
|
+
line-height: var(--rel-body-xs-regular-line-height);
|
|
8541
|
+
letter-spacing: var(--rel-body-xs-regular-letter-spacing);
|
|
8542
|
+
}
|
|
8543
|
+
|
|
8421
8544
|
/* Error state (Figma State=Error, both Is Checked values) */
|
|
8422
8545
|
.rel-radio-option.error-unchecked .radio-custom,
|
|
8423
8546
|
.rel-radio-option.error-checked .radio-custom {
|
|
@@ -8461,6 +8584,12 @@ __decorateClass([
|
|
|
8461
8584
|
__decorateClass([
|
|
8462
8585
|
property22({ type: String })
|
|
8463
8586
|
], RelRadio.prototype, "name", 2);
|
|
8587
|
+
__decorateClass([
|
|
8588
|
+
property22({ type: String, reflect: true })
|
|
8589
|
+
], RelRadio.prototype, "orientation", 2);
|
|
8590
|
+
__decorateClass([
|
|
8591
|
+
property22({ type: String, reflect: true })
|
|
8592
|
+
], RelRadio.prototype, "size", 2);
|
|
8464
8593
|
__decorateClass([
|
|
8465
8594
|
property22({ type: String, reflect: true })
|
|
8466
8595
|
], RelRadio.prototype, "selectedValue", 2);
|
|
@@ -13060,6 +13189,7 @@ var RelSelect = class extends LitElement46 {
|
|
|
13060
13189
|
this.internalState = "default";
|
|
13061
13190
|
this.visibleChipCount = Infinity;
|
|
13062
13191
|
this.uniqueId = `select-${Math.random().toString(36).slice(2)}`;
|
|
13192
|
+
this._warnedNoName = false;
|
|
13063
13193
|
this.onOutsidePointerDown = (e) => {
|
|
13064
13194
|
if (!this.open) return;
|
|
13065
13195
|
const path = e.composedPath();
|
|
@@ -13118,6 +13248,13 @@ var RelSelect = class extends LitElement46 {
|
|
|
13118
13248
|
}
|
|
13119
13249
|
}
|
|
13120
13250
|
updated(changed) {
|
|
13251
|
+
if (!this._warnedNoName && this.labelStyle === "none" && !this.fieldAriaLabel && !this.fieldAriaLabelledby && !this.getAttribute("aria-label")) {
|
|
13252
|
+
this._warnedNoName = true;
|
|
13253
|
+
console.warn(
|
|
13254
|
+
'<rel-select label-style="none"> has no accessible name. Set `field-aria-label` or `field-aria-labelledby` \u2014 a placeholder is not a valid naming mechanism.',
|
|
13255
|
+
this
|
|
13256
|
+
);
|
|
13257
|
+
}
|
|
13121
13258
|
if (changed.has("selectedValues") || changed.has("options") || changed.has("multiple") || changed.has("size")) {
|
|
13122
13259
|
requestAnimationFrame(() => this.computeVisibleChips());
|
|
13123
13260
|
}
|
|
@@ -13366,7 +13503,9 @@ var RelSelect = class extends LitElement46 {
|
|
|
13366
13503
|
const opts = this.normalizedOptions;
|
|
13367
13504
|
const filtered = this.filteredOptions;
|
|
13368
13505
|
const isDisabled = this.state === "disabled";
|
|
13506
|
+
const labelHidden = this.labelStyle === "none";
|
|
13369
13507
|
const showFloatingChrome = this.labelStyle === "floating" && !!this.label;
|
|
13508
|
+
const accessibleName = (labelHidden ? this.fieldAriaLabel : this.label) || "";
|
|
13370
13509
|
const wrapperClasses = [
|
|
13371
13510
|
"select-wrapper",
|
|
13372
13511
|
this.size,
|
|
@@ -13382,7 +13521,7 @@ var RelSelect = class extends LitElement46 {
|
|
|
13382
13521
|
const selectedSingle = opts.find((o) => o.value === this.selected);
|
|
13383
13522
|
return html46`
|
|
13384
13523
|
<div class="${wrapperClasses}">
|
|
13385
|
-
${!showFloatingChrome && this.label ? html46`<label part="label" class="select-label" for="${this.uniqueId}">${this.label}</label>` : ""}
|
|
13524
|
+
${labelHidden ? html46`<div class="label-reserve" aria-hidden="true"></div>` : !showFloatingChrome && this.label ? html46`<label part="label" class="select-label" for="${this.uniqueId}">${this.label}</label>` : ""}
|
|
13386
13525
|
|
|
13387
13526
|
<div class="select-container">
|
|
13388
13527
|
${showFloatingChrome ? html46`<span class="floating-label${this.leftIcon ? " has-left-icon" : ""}"
|
|
@@ -13399,7 +13538,8 @@ var RelSelect = class extends LitElement46 {
|
|
|
13399
13538
|
aria-controls="${listboxId}"
|
|
13400
13539
|
aria-activedescendant="${activeOptionId}"
|
|
13401
13540
|
aria-invalid="${this.state === "error"}"
|
|
13402
|
-
aria-label="${this.
|
|
13541
|
+
aria-label="${this.fieldAriaLabelledby ? nothing9 : accessibleName || this.placeholder}"
|
|
13542
|
+
aria-labelledby="${this.fieldAriaLabelledby ?? nothing9}"
|
|
13403
13543
|
?disabled="${isDisabled}"
|
|
13404
13544
|
@click="${this.onTriggerClick}"
|
|
13405
13545
|
@keydown="${this.onTriggerKeyDown}"
|
|
@@ -13436,7 +13576,7 @@ var RelSelect = class extends LitElement46 {
|
|
|
13436
13576
|
part="panel list" class="select-listbox"
|
|
13437
13577
|
role="listbox"
|
|
13438
13578
|
aria-multiselectable="${this.multiple}"
|
|
13439
|
-
aria-label="${
|
|
13579
|
+
aria-label="${accessibleName || "Options"}"
|
|
13440
13580
|
style="${this.panelStyle}"
|
|
13441
13581
|
?data-open="${this.open}"
|
|
13442
13582
|
>
|
|
@@ -13565,6 +13705,22 @@ RelSelect.styles = css43`
|
|
|
13565
13705
|
|
|
13566
13706
|
}
|
|
13567
13707
|
|
|
13708
|
+
/* ── labelStyle="none" ──
|
|
13709
|
+
No label renders. An empty spacer holds the label row's full height so the
|
|
13710
|
+
trigger still bottom-aligns with labelled siblings in a form row.
|
|
13711
|
+
The reserve covers label line-height + the wrapper gap, and the negative
|
|
13712
|
+
margin cancels the flex gap that would otherwise sit below it — so
|
|
13713
|
+
--rel-field-label-reserve: 0 collapses the row to genuinely zero
|
|
13714
|
+
(toolbars, table cells), not just to the leftover gap. */
|
|
13715
|
+
.label-reserve {
|
|
13716
|
+
flex: none;
|
|
13717
|
+
height: var(
|
|
13718
|
+
--rel-field-label-reserve,
|
|
13719
|
+
calc(var(--rel-body-sm-regular-line-height) + var(--rel-spacing-2xs))
|
|
13720
|
+
);
|
|
13721
|
+
margin-bottom: calc(-1 * var(--rel-spacing-2xs));
|
|
13722
|
+
}
|
|
13723
|
+
|
|
13568
13724
|
/* Error only colors the border/focus ring and the supporting text below
|
|
13569
13725
|
the field — label, floating label, placeholder, and icons stay normal. */
|
|
13570
13726
|
|
|
@@ -13958,8 +14114,14 @@ __decorateClass([
|
|
|
13958
14114
|
property40({ type: String })
|
|
13959
14115
|
], RelSelect.prototype, "label", 2);
|
|
13960
14116
|
__decorateClass([
|
|
13961
|
-
property40({ type: String, attribute: "label-style" })
|
|
14117
|
+
property40({ type: String, attribute: "label-style", reflect: true })
|
|
13962
14118
|
], RelSelect.prototype, "labelStyle", 2);
|
|
14119
|
+
__decorateClass([
|
|
14120
|
+
property40({ type: String, attribute: "field-aria-label" })
|
|
14121
|
+
], RelSelect.prototype, "fieldAriaLabel", 2);
|
|
14122
|
+
__decorateClass([
|
|
14123
|
+
property40({ type: String, attribute: "field-aria-labelledby" })
|
|
14124
|
+
], RelSelect.prototype, "fieldAriaLabelledby", 2);
|
|
13963
14125
|
__decorateClass([
|
|
13964
14126
|
property40({ type: Array })
|
|
13965
14127
|
], RelSelect.prototype, "options", 2);
|
|
@@ -16558,88 +16720,1030 @@ RelTab = __decorateClass([
|
|
|
16558
16720
|
customElement53("rel-tab")
|
|
16559
16721
|
], RelTab);
|
|
16560
16722
|
|
|
16561
|
-
// src/components/rel-
|
|
16562
|
-
import { LitElement as LitElement54, html as html54, css as css51 } from "lit";
|
|
16563
|
-
import { customElement as customElement54, property as property48, state as state21 } from "lit/decorators.js";
|
|
16564
|
-
|
|
16723
|
+
// src/components/rel-time-picker/rel-time-picker.ts
|
|
16724
|
+
import { LitElement as LitElement54, html as html54, css as css51, nothing as nothing10 } from "lit";
|
|
16725
|
+
import { customElement as customElement54, property as property48, state as state21, query as query10 } from "lit/decorators.js";
|
|
16726
|
+
import "@reliability-design/icons/rel-icon";
|
|
16727
|
+
var RelTimePicker = class extends LitElement54 {
|
|
16565
16728
|
constructor() {
|
|
16566
16729
|
super(...arguments);
|
|
16567
|
-
this.
|
|
16568
|
-
this.
|
|
16569
|
-
this.
|
|
16570
|
-
this.
|
|
16571
|
-
this.
|
|
16572
|
-
this.
|
|
16573
|
-
|
|
16574
|
-
|
|
16575
|
-
|
|
16576
|
-
|
|
16577
|
-
|
|
16578
|
-
|
|
16579
|
-
|
|
16580
|
-
|
|
16730
|
+
this.value = null;
|
|
16731
|
+
this.placeholder = "Select time";
|
|
16732
|
+
this.label = null;
|
|
16733
|
+
this.disabled = false;
|
|
16734
|
+
this.required = false;
|
|
16735
|
+
this.readonly = false;
|
|
16736
|
+
this.format = "12h";
|
|
16737
|
+
this.minuteStep = 1;
|
|
16738
|
+
this.minTime = null;
|
|
16739
|
+
this.maxTime = null;
|
|
16740
|
+
this.open = false;
|
|
16741
|
+
this.clearable = false;
|
|
16742
|
+
this.error = false;
|
|
16743
|
+
this.invalid = false;
|
|
16744
|
+
this.size = "medium";
|
|
16745
|
+
this.variant = "outlined";
|
|
16746
|
+
this.fullWidth = false;
|
|
16747
|
+
this.draftHour = null;
|
|
16748
|
+
this.draftMinute = null;
|
|
16749
|
+
this.handleClickOutside = (e) => {
|
|
16750
|
+
if (!this.open) return;
|
|
16751
|
+
const path = e.composedPath();
|
|
16752
|
+
if (!path.includes(this)) {
|
|
16753
|
+
this.cancelPopover();
|
|
16581
16754
|
}
|
|
16582
|
-
}
|
|
16583
|
-
|
|
16584
|
-
if (this.
|
|
16585
|
-
|
|
16586
|
-
this._dismissTimer = null;
|
|
16755
|
+
};
|
|
16756
|
+
this.handleEscapeKey = (e) => {
|
|
16757
|
+
if (e.key === "Escape" && this.open) {
|
|
16758
|
+
this.cancelPopover();
|
|
16587
16759
|
}
|
|
16588
|
-
}
|
|
16760
|
+
};
|
|
16761
|
+
}
|
|
16762
|
+
connectedCallback() {
|
|
16763
|
+
super.connectedCallback();
|
|
16764
|
+
document.addEventListener("click", this.handleClickOutside);
|
|
16765
|
+
document.addEventListener("keydown", this.handleEscapeKey);
|
|
16589
16766
|
}
|
|
16590
16767
|
disconnectedCallback() {
|
|
16591
16768
|
super.disconnectedCallback();
|
|
16592
|
-
|
|
16769
|
+
document.removeEventListener("click", this.handleClickOutside);
|
|
16770
|
+
document.removeEventListener("keydown", this.handleEscapeKey);
|
|
16593
16771
|
}
|
|
16594
|
-
|
|
16595
|
-
this.
|
|
16772
|
+
updated(changedProperties) {
|
|
16773
|
+
if (changedProperties.has("open") && this.open) {
|
|
16774
|
+
this.resetDraftFromValue();
|
|
16775
|
+
setTimeout(() => {
|
|
16776
|
+
this.scrollColumnsToSelection();
|
|
16777
|
+
const target = this.columnsElement?.querySelector(
|
|
16778
|
+
".option-button.selected:not(:disabled)"
|
|
16779
|
+
) ?? this.columnsElement?.querySelector(
|
|
16780
|
+
".option-button:not(:disabled)"
|
|
16781
|
+
);
|
|
16782
|
+
target?.focus();
|
|
16783
|
+
}, 0);
|
|
16784
|
+
}
|
|
16596
16785
|
}
|
|
16597
|
-
|
|
16598
|
-
|
|
16599
|
-
|
|
16600
|
-
|
|
16601
|
-
|
|
16602
|
-
|
|
16786
|
+
// ── Parsing / formatting ──
|
|
16787
|
+
parseTime(time) {
|
|
16788
|
+
if (!time) return null;
|
|
16789
|
+
const match = time.match(/^(\d{1,2}):(\d{2})$/);
|
|
16790
|
+
if (!match) return null;
|
|
16791
|
+
const hour = parseInt(match[1], 10);
|
|
16792
|
+
const minute = parseInt(match[2], 10);
|
|
16793
|
+
if (hour > 23 || minute > 59) return null;
|
|
16794
|
+
return { hour, minute };
|
|
16795
|
+
}
|
|
16796
|
+
formatValue(hour, minute) {
|
|
16797
|
+
return `${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}`;
|
|
16798
|
+
}
|
|
16799
|
+
formatDisplay(hour, minute) {
|
|
16800
|
+
const mm = String(minute).padStart(2, "0");
|
|
16801
|
+
if (this.format === "24h") {
|
|
16802
|
+
return `${String(hour).padStart(2, "0")}:${mm}`;
|
|
16803
|
+
}
|
|
16804
|
+
const period = hour < 12 ? "AM" : "PM";
|
|
16805
|
+
const hour12 = hour % 12 === 0 ? 12 : hour % 12;
|
|
16806
|
+
return `${hour12}:${mm} ${period}`;
|
|
16807
|
+
}
|
|
16808
|
+
toMinutes(hour, minute) {
|
|
16809
|
+
return hour * 60 + minute;
|
|
16810
|
+
}
|
|
16811
|
+
get minuteStepSafe() {
|
|
16812
|
+
const step = Math.floor(this.minuteStep);
|
|
16813
|
+
return step >= 1 && step <= 60 ? step : 1;
|
|
16814
|
+
}
|
|
16815
|
+
get minBoundMinutes() {
|
|
16816
|
+
const parsed = this.parseTime(this.minTime);
|
|
16817
|
+
return parsed ? this.toMinutes(parsed.hour, parsed.minute) : 0;
|
|
16818
|
+
}
|
|
16819
|
+
get maxBoundMinutes() {
|
|
16820
|
+
const parsed = this.parseTime(this.maxTime);
|
|
16821
|
+
return parsed ? this.toMinutes(parsed.hour, parsed.minute) : 23 * 60 + 59;
|
|
16822
|
+
}
|
|
16823
|
+
isTimeInRange(hour, minute) {
|
|
16824
|
+
const total = this.toMinutes(hour, minute);
|
|
16825
|
+
return total >= this.minBoundMinutes && total <= this.maxBoundMinutes;
|
|
16826
|
+
}
|
|
16827
|
+
/** Minutes shown in the minutes column (respecting minuteStep). */
|
|
16828
|
+
getMinuteOptions() {
|
|
16829
|
+
const minutes = [];
|
|
16830
|
+
for (let m = 0; m < 60; m += this.minuteStepSafe) {
|
|
16831
|
+
minutes.push(m);
|
|
16832
|
+
}
|
|
16833
|
+
return minutes;
|
|
16834
|
+
}
|
|
16835
|
+
/** An hour is enabled if at least one of its step minutes is in range. */
|
|
16836
|
+
isHourEnabled(hour) {
|
|
16837
|
+
return this.getMinuteOptions().some((m) => this.isTimeInRange(hour, m));
|
|
16838
|
+
}
|
|
16839
|
+
isPeriodEnabled(period) {
|
|
16840
|
+
const start = period === "AM" ? 0 : 12;
|
|
16841
|
+
for (let h = start; h < start + 12; h++) {
|
|
16842
|
+
if (this.isHourEnabled(h)) return true;
|
|
16843
|
+
}
|
|
16844
|
+
return false;
|
|
16845
|
+
}
|
|
16846
|
+
/** Hour column entries: display label + the 0–23 hour it maps to. */
|
|
16847
|
+
getHourOptions() {
|
|
16848
|
+
if (this.format === "24h") {
|
|
16849
|
+
return Array.from({ length: 24 }, (_, h) => ({
|
|
16850
|
+
label: String(h).padStart(2, "0"),
|
|
16851
|
+
hour24: h
|
|
16852
|
+
}));
|
|
16853
|
+
}
|
|
16854
|
+
const period = this.draftPeriod ?? "AM";
|
|
16855
|
+
const base = period === "AM" ? 0 : 12;
|
|
16856
|
+
return Array.from({ length: 12 }, (_, i) => ({
|
|
16857
|
+
label: i === 0 ? "12" : String(i).padStart(2, "0"),
|
|
16858
|
+
hour24: base + i
|
|
16859
|
+
}));
|
|
16860
|
+
}
|
|
16861
|
+
get draftPeriod() {
|
|
16862
|
+
if (this.draftHour === null) return null;
|
|
16863
|
+
return this.draftHour < 12 ? "AM" : "PM";
|
|
16864
|
+
}
|
|
16865
|
+
resetDraftFromValue() {
|
|
16866
|
+
const parsed = this.parseTime(this.value);
|
|
16867
|
+
this.draftHour = parsed ? parsed.hour : null;
|
|
16868
|
+
this.draftMinute = parsed ? parsed.minute : null;
|
|
16869
|
+
}
|
|
16870
|
+
/** Snap the draft minute to the nearest valid step minute for the hour. */
|
|
16871
|
+
clampDraftToRange() {
|
|
16872
|
+
if (this.draftHour === null || this.draftMinute === null) return;
|
|
16873
|
+
if (this.isTimeInRange(this.draftHour, this.draftMinute)) return;
|
|
16874
|
+
const hour = this.draftHour;
|
|
16875
|
+
const valid = this.getMinuteOptions().filter((m) => this.isTimeInRange(hour, m));
|
|
16876
|
+
if (valid.length === 0) {
|
|
16877
|
+
this.draftMinute = null;
|
|
16878
|
+
return;
|
|
16603
16879
|
}
|
|
16880
|
+
const current = this.draftMinute;
|
|
16881
|
+
this.draftMinute = valid.reduce(
|
|
16882
|
+
(best, m) => Math.abs(m - current) < Math.abs(best - current) ? m : best
|
|
16883
|
+
);
|
|
16884
|
+
}
|
|
16885
|
+
// ── Open / close / commit ──
|
|
16886
|
+
toggleTimePopover() {
|
|
16887
|
+
if (this.disabled || this.readonly) return;
|
|
16888
|
+
this.open = !this.open;
|
|
16604
16889
|
this.dispatchEvent(
|
|
16605
|
-
new CustomEvent("
|
|
16890
|
+
new CustomEvent("rel-open-change", {
|
|
16891
|
+
detail: { open: this.open },
|
|
16606
16892
|
bubbles: true,
|
|
16607
16893
|
composed: true
|
|
16608
16894
|
})
|
|
16609
16895
|
);
|
|
16610
16896
|
}
|
|
16611
|
-
|
|
16612
|
-
|
|
16897
|
+
/** Close discarding the draft (Cancel / Escape / outside click). */
|
|
16898
|
+
cancelPopover() {
|
|
16899
|
+
this.open = false;
|
|
16900
|
+
this.triggerButton?.focus();
|
|
16613
16901
|
this.dispatchEvent(
|
|
16614
|
-
new CustomEvent("
|
|
16902
|
+
new CustomEvent("rel-open-change", {
|
|
16903
|
+
detail: { open: false },
|
|
16615
16904
|
bubbles: true,
|
|
16616
16905
|
composed: true
|
|
16617
16906
|
})
|
|
16618
16907
|
);
|
|
16619
|
-
this.close();
|
|
16620
16908
|
}
|
|
16909
|
+
applySelection() {
|
|
16910
|
+
if (this.draftHour === null) return;
|
|
16911
|
+
const newValue = this.formatValue(this.draftHour, this.draftMinute ?? 0);
|
|
16912
|
+
this.value = newValue;
|
|
16913
|
+
this.dispatchEvent(
|
|
16914
|
+
new CustomEvent("rel-change", {
|
|
16915
|
+
detail: { value: newValue },
|
|
16916
|
+
bubbles: true,
|
|
16917
|
+
composed: true
|
|
16918
|
+
})
|
|
16919
|
+
);
|
|
16920
|
+
this.cancelPopover();
|
|
16921
|
+
}
|
|
16922
|
+
/** Set the draft to the current time, snapped to the minute step and range. */
|
|
16923
|
+
selectNow() {
|
|
16924
|
+
const now = /* @__PURE__ */ new Date();
|
|
16925
|
+
const step = this.minuteStepSafe;
|
|
16926
|
+
this.draftHour = now.getHours();
|
|
16927
|
+
this.draftMinute = Math.round(now.getMinutes() / step) * step;
|
|
16928
|
+
if (this.draftMinute >= 60) {
|
|
16929
|
+
this.draftMinute = 0;
|
|
16930
|
+
this.draftHour = (this.draftHour + 1) % 24;
|
|
16931
|
+
}
|
|
16932
|
+
if (!this.isHourEnabled(this.draftHour)) {
|
|
16933
|
+
const enabled = [];
|
|
16934
|
+
for (let h = 0; h < 24; h++) if (this.isHourEnabled(h)) enabled.push(h);
|
|
16935
|
+
if (enabled.length === 0) return;
|
|
16936
|
+
const currentHour = this.draftHour;
|
|
16937
|
+
this.draftHour = enabled.reduce(
|
|
16938
|
+
(best, h) => Math.abs(h - currentHour) < Math.abs(best - currentHour) ? h : best
|
|
16939
|
+
);
|
|
16940
|
+
}
|
|
16941
|
+
this.clampDraftToRange();
|
|
16942
|
+
this.scrollColumnsToSelection();
|
|
16943
|
+
}
|
|
16944
|
+
clearValue(e) {
|
|
16945
|
+
e.stopPropagation();
|
|
16946
|
+
this.value = null;
|
|
16947
|
+
this.draftHour = null;
|
|
16948
|
+
this.draftMinute = null;
|
|
16949
|
+
this.dispatchEvent(
|
|
16950
|
+
new CustomEvent("rel-change", {
|
|
16951
|
+
detail: { value: null },
|
|
16952
|
+
bubbles: true,
|
|
16953
|
+
composed: true
|
|
16954
|
+
})
|
|
16955
|
+
);
|
|
16956
|
+
}
|
|
16957
|
+
// ── Column selection ──
|
|
16958
|
+
selectHour(hour24) {
|
|
16959
|
+
if (!this.isHourEnabled(hour24)) return;
|
|
16960
|
+
this.draftHour = hour24;
|
|
16961
|
+
if (this.draftMinute === null) this.draftMinute = 0;
|
|
16962
|
+
this.clampDraftToRange();
|
|
16963
|
+
}
|
|
16964
|
+
selectMinute(minute) {
|
|
16965
|
+
if (this.draftHour === null) {
|
|
16966
|
+
for (let h = 0; h < 24; h++) {
|
|
16967
|
+
if (this.isHourEnabled(h)) {
|
|
16968
|
+
this.draftHour = h;
|
|
16969
|
+
break;
|
|
16970
|
+
}
|
|
16971
|
+
}
|
|
16972
|
+
if (this.draftHour === null) return;
|
|
16973
|
+
}
|
|
16974
|
+
if (!this.isTimeInRange(this.draftHour, minute)) return;
|
|
16975
|
+
this.draftMinute = minute;
|
|
16976
|
+
}
|
|
16977
|
+
selectPeriod(period) {
|
|
16978
|
+
if (!this.isPeriodEnabled(period)) return;
|
|
16979
|
+
const base = period === "AM" ? 0 : 12;
|
|
16980
|
+
const hour12 = this.draftHour === null ? 0 : this.draftHour % 12;
|
|
16981
|
+
this.draftHour = base + hour12;
|
|
16982
|
+
if (this.draftMinute === null) this.draftMinute = 0;
|
|
16983
|
+
if (!this.isHourEnabled(this.draftHour)) {
|
|
16984
|
+
const enabled = [];
|
|
16985
|
+
for (let h = base; h < base + 12; h++) if (this.isHourEnabled(h)) enabled.push(h);
|
|
16986
|
+
const currentHour = this.draftHour;
|
|
16987
|
+
this.draftHour = enabled.reduce(
|
|
16988
|
+
(best, h) => Math.abs(h - currentHour) < Math.abs(best - currentHour) ? h : best
|
|
16989
|
+
);
|
|
16990
|
+
}
|
|
16991
|
+
this.clampDraftToRange();
|
|
16992
|
+
this.scrollColumnsToSelection();
|
|
16993
|
+
}
|
|
16994
|
+
scrollColumnsToSelection() {
|
|
16995
|
+
this.updateComplete.then(() => {
|
|
16996
|
+
this.columnsElement?.querySelectorAll(".option-button.selected").forEach((btn) => btn.scrollIntoView({ block: "center" }));
|
|
16997
|
+
});
|
|
16998
|
+
}
|
|
16999
|
+
// ── Keyboard ──
|
|
17000
|
+
handleTriggerKeydown(e) {
|
|
17001
|
+
if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown") {
|
|
17002
|
+
e.preventDefault();
|
|
17003
|
+
if (!this.open) {
|
|
17004
|
+
this.toggleTimePopover();
|
|
17005
|
+
}
|
|
17006
|
+
}
|
|
17007
|
+
}
|
|
17008
|
+
/**
|
|
17009
|
+
* Column navigation: Up/Down move within the column (and select), Left/Right
|
|
17010
|
+
* jump between columns, Home/End go to the first/last option, Enter applies.
|
|
17011
|
+
*/
|
|
17012
|
+
handleOptionKeydown(e, column) {
|
|
17013
|
+
const list = e.currentTarget.closest(".column-list");
|
|
17014
|
+
if (!list) return;
|
|
17015
|
+
const buttons = Array.from(
|
|
17016
|
+
list.querySelectorAll(".option-button:not(:disabled)")
|
|
17017
|
+
);
|
|
17018
|
+
const index = buttons.indexOf(e.currentTarget);
|
|
17019
|
+
let target;
|
|
17020
|
+
switch (e.key) {
|
|
17021
|
+
case "ArrowUp":
|
|
17022
|
+
e.preventDefault();
|
|
17023
|
+
target = buttons[Math.max(0, index - 1)];
|
|
17024
|
+
break;
|
|
17025
|
+
case "ArrowDown":
|
|
17026
|
+
e.preventDefault();
|
|
17027
|
+
target = buttons[Math.min(buttons.length - 1, index + 1)];
|
|
17028
|
+
break;
|
|
17029
|
+
case "Home":
|
|
17030
|
+
e.preventDefault();
|
|
17031
|
+
target = buttons[0];
|
|
17032
|
+
break;
|
|
17033
|
+
case "End":
|
|
17034
|
+
e.preventDefault();
|
|
17035
|
+
target = buttons[buttons.length - 1];
|
|
17036
|
+
break;
|
|
17037
|
+
case "ArrowLeft":
|
|
17038
|
+
case "ArrowRight": {
|
|
17039
|
+
e.preventDefault();
|
|
17040
|
+
this.focusAdjacentColumn(column, e.key === "ArrowRight" ? 1 : -1);
|
|
17041
|
+
return;
|
|
17042
|
+
}
|
|
17043
|
+
case "Enter":
|
|
17044
|
+
e.preventDefault();
|
|
17045
|
+
e.currentTarget.click();
|
|
17046
|
+
this.applySelection();
|
|
17047
|
+
return;
|
|
17048
|
+
default:
|
|
17049
|
+
return;
|
|
17050
|
+
}
|
|
17051
|
+
if (target && target !== buttons[index]) {
|
|
17052
|
+
target.click();
|
|
17053
|
+
target.focus();
|
|
17054
|
+
target.scrollIntoView({ block: "nearest" });
|
|
17055
|
+
}
|
|
17056
|
+
}
|
|
17057
|
+
focusAdjacentColumn(from, direction) {
|
|
17058
|
+
const order = this.format === "12h" ? ["hour", "minute", "period"] : ["hour", "minute"];
|
|
17059
|
+
const nextIndex = order.indexOf(from) + direction;
|
|
17060
|
+
if (nextIndex < 0 || nextIndex >= order.length) return;
|
|
17061
|
+
const lists = this.columnsElement?.querySelectorAll(".column-list");
|
|
17062
|
+
const list = lists?.[nextIndex];
|
|
17063
|
+
const target = list?.querySelector(".option-button.selected:not(:disabled)") ?? list?.querySelector(".option-button:not(:disabled)");
|
|
17064
|
+
target?.focus();
|
|
17065
|
+
target?.scrollIntoView({ block: "nearest" });
|
|
17066
|
+
}
|
|
17067
|
+
// ── Render ──
|
|
16621
17068
|
render() {
|
|
17069
|
+
const isError = this.error || this.invalid;
|
|
17070
|
+
const belowText = isError && this.errorMessage ? this.errorMessage : this.helperText;
|
|
17071
|
+
const describedById = belowText ? "time-picker-desc" : void 0;
|
|
17072
|
+
const parsedValue = this.parseTime(this.value);
|
|
17073
|
+
const displayText = parsedValue ? this.formatDisplay(parsedValue.hour, parsedValue.minute) : "";
|
|
17074
|
+
const draftDisplay = this.draftHour !== null ? this.formatDisplay(this.draftHour, this.draftMinute ?? 0) : "";
|
|
17075
|
+
const hourOptions = this.getHourOptions();
|
|
17076
|
+
const minuteOptions = this.getMinuteOptions();
|
|
17077
|
+
const draftPeriod = this.draftPeriod;
|
|
17078
|
+
const wrapperClasses = [
|
|
17079
|
+
"time-picker-wrapper",
|
|
17080
|
+
this.size,
|
|
17081
|
+
isError ? "error" : "",
|
|
17082
|
+
this.readonly ? "readonly" : ""
|
|
17083
|
+
].filter(Boolean).join(" ");
|
|
16622
17084
|
return html54`
|
|
16623
|
-
<div part="base" class="
|
|
16624
|
-
|
|
17085
|
+
<div part="base" class="${wrapperClasses}">
|
|
17086
|
+
${this.label ? html54`<label part="label" class="label" id="time-picker-label"
|
|
17087
|
+
>${this.label}${this.required ? html54`<span class="required-mark" aria-hidden="true">*</span>` : nothing10}</label
|
|
17088
|
+
>` : nothing10}
|
|
16625
17089
|
|
|
16626
|
-
${this.
|
|
16627
|
-
part="action" class="toast-action"
|
|
16628
|
-
@click="${this.handleActionClick}"
|
|
16629
|
-
>
|
|
16630
|
-
${this.actionLabel}
|
|
16631
|
-
</button>` : ""}
|
|
17090
|
+
${this.name ? html54`<input type="hidden" name=${this.name} .value=${this.value ?? ""} />` : nothing10}
|
|
16632
17091
|
|
|
16633
|
-
<button
|
|
16634
|
-
|
|
16635
|
-
|
|
16636
|
-
|
|
16637
|
-
}
|
|
16638
|
-
|
|
16639
|
-
|
|
16640
|
-
|
|
16641
|
-
|
|
16642
|
-
|
|
17092
|
+
<button
|
|
17093
|
+
part="trigger"
|
|
17094
|
+
class="trigger-button ${isError ? "error" : ""}"
|
|
17095
|
+
type="button"
|
|
17096
|
+
?disabled=${this.disabled}
|
|
17097
|
+
@click=${this.toggleTimePopover}
|
|
17098
|
+
@keydown=${this.handleTriggerKeydown}
|
|
17099
|
+
@focus=${() => this.dispatchEvent(
|
|
17100
|
+
new CustomEvent("rel-focus", { bubbles: true, composed: true })
|
|
17101
|
+
)}
|
|
17102
|
+
@blur=${() => this.dispatchEvent(
|
|
17103
|
+
new CustomEvent("rel-blur", { bubbles: true, composed: true })
|
|
17104
|
+
)}
|
|
17105
|
+
aria-label=${this.label || "Select time"}
|
|
17106
|
+
aria-haspopup="dialog"
|
|
17107
|
+
aria-expanded=${this.open}
|
|
17108
|
+
aria-required=${this.required}
|
|
17109
|
+
aria-invalid=${isError}
|
|
17110
|
+
aria-readonly=${this.readonly}
|
|
17111
|
+
aria-describedby=${describedById ?? nothing10}
|
|
17112
|
+
aria-labelledby=${this.label ? "time-picker-label" : nothing10}
|
|
17113
|
+
>
|
|
17114
|
+
<span part="value" class="trigger-content ${displayText ? "" : "placeholder"}">
|
|
17115
|
+
${displayText || this.placeholder}
|
|
17116
|
+
</span>
|
|
17117
|
+
<span class="trigger-icons">
|
|
17118
|
+
${this.clearable && this.value ? html54`
|
|
17119
|
+
<button
|
|
17120
|
+
class="icon-button"
|
|
17121
|
+
type="button"
|
|
17122
|
+
aria-label="Clear time"
|
|
17123
|
+
@click=${this.clearValue}
|
|
17124
|
+
>
|
|
17125
|
+
<svg viewBox="0 0 16 16" fill="currentColor">
|
|
17126
|
+
<path
|
|
17127
|
+
d="M4 4l8 8M12 4l-8 8"
|
|
17128
|
+
stroke="currentColor"
|
|
17129
|
+
stroke-width="2"
|
|
17130
|
+
stroke-linecap="round"
|
|
17131
|
+
/>
|
|
17132
|
+
</svg>
|
|
17133
|
+
</button>
|
|
17134
|
+
` : nothing10}
|
|
17135
|
+
<rel-icon part="icon" name="time-filled" size="sm"></rel-icon>
|
|
17136
|
+
</span>
|
|
17137
|
+
</button>
|
|
17138
|
+
|
|
17139
|
+
<div
|
|
17140
|
+
part="panel"
|
|
17141
|
+
class="popover ${this.open ? "open" : ""}"
|
|
17142
|
+
role="dialog"
|
|
17143
|
+
aria-label="Choose time"
|
|
17144
|
+
>
|
|
17145
|
+
<div class="time-readout">
|
|
17146
|
+
<span class="time-readout-label">Time</span>
|
|
17147
|
+
<span class="time-readout-value ${draftDisplay ? "" : "empty"}">
|
|
17148
|
+
${draftDisplay || (this.format === "24h" ? "--:--" : "--:-- --")}
|
|
17149
|
+
</span>
|
|
17150
|
+
</div>
|
|
17151
|
+
|
|
17152
|
+
<div class="columns">
|
|
17153
|
+
<div class="column">
|
|
17154
|
+
<div class="column-header" aria-hidden="true">Hour</div>
|
|
17155
|
+
<div class="column-list" role="listbox" aria-label="Hours">
|
|
17156
|
+
${hourOptions.map(
|
|
17157
|
+
(opt, i) => html54`
|
|
17158
|
+
<button
|
|
17159
|
+
part="option"
|
|
17160
|
+
class="option-button ${this.draftHour === opt.hour24 ? "selected" : ""}"
|
|
17161
|
+
type="button"
|
|
17162
|
+
role="option"
|
|
17163
|
+
aria-selected=${this.draftHour === opt.hour24}
|
|
17164
|
+
?disabled=${!this.isHourEnabled(opt.hour24)}
|
|
17165
|
+
@click=${() => this.selectHour(opt.hour24)}
|
|
17166
|
+
@keydown=${(e) => this.handleOptionKeydown(e, "hour")}
|
|
17167
|
+
tabindex=${this.draftHour === opt.hour24 || this.draftHour === null && i === 0 ? 0 : -1}
|
|
17168
|
+
>
|
|
17169
|
+
${opt.label}
|
|
17170
|
+
</button>
|
|
17171
|
+
`
|
|
17172
|
+
)}
|
|
17173
|
+
</div>
|
|
17174
|
+
</div>
|
|
17175
|
+
|
|
17176
|
+
<div class="column">
|
|
17177
|
+
<div class="column-header" aria-hidden="true">Min</div>
|
|
17178
|
+
<div class="column-list" role="listbox" aria-label="Minutes">
|
|
17179
|
+
${minuteOptions.map(
|
|
17180
|
+
(m, i) => html54`
|
|
17181
|
+
<button
|
|
17182
|
+
part="option"
|
|
17183
|
+
class="option-button ${this.draftMinute === m ? "selected" : ""}"
|
|
17184
|
+
type="button"
|
|
17185
|
+
role="option"
|
|
17186
|
+
aria-selected=${this.draftMinute === m}
|
|
17187
|
+
?disabled=${this.draftHour !== null && !this.isTimeInRange(this.draftHour, m)}
|
|
17188
|
+
@click=${() => this.selectMinute(m)}
|
|
17189
|
+
@keydown=${(e) => this.handleOptionKeydown(e, "minute")}
|
|
17190
|
+
tabindex=${this.draftMinute === m || this.draftMinute === null && i === 0 ? 0 : -1}
|
|
17191
|
+
>
|
|
17192
|
+
${String(m).padStart(2, "0")}
|
|
17193
|
+
</button>
|
|
17194
|
+
`
|
|
17195
|
+
)}
|
|
17196
|
+
</div>
|
|
17197
|
+
</div>
|
|
17198
|
+
|
|
17199
|
+
${this.format === "12h" ? html54`
|
|
17200
|
+
<div class="column">
|
|
17201
|
+
<div class="column-header" aria-hidden="true">AM/PM</div>
|
|
17202
|
+
<div class="column-list" role="listbox" aria-label="AM or PM">
|
|
17203
|
+
${["AM", "PM"].map(
|
|
17204
|
+
(p) => html54`
|
|
17205
|
+
<button
|
|
17206
|
+
part="option"
|
|
17207
|
+
class="option-button ${draftPeriod === p ? "selected" : ""}"
|
|
17208
|
+
type="button"
|
|
17209
|
+
role="option"
|
|
17210
|
+
aria-selected=${draftPeriod === p}
|
|
17211
|
+
?disabled=${!this.isPeriodEnabled(p)}
|
|
17212
|
+
@click=${() => this.selectPeriod(p)}
|
|
17213
|
+
@keydown=${(e) => this.handleOptionKeydown(e, "period")}
|
|
17214
|
+
tabindex=${draftPeriod === p || draftPeriod === null && p === "AM" ? 0 : -1}
|
|
17215
|
+
>
|
|
17216
|
+
${p}
|
|
17217
|
+
</button>
|
|
17218
|
+
`
|
|
17219
|
+
)}
|
|
17220
|
+
</div>
|
|
17221
|
+
</div>
|
|
17222
|
+
` : nothing10}
|
|
17223
|
+
</div>
|
|
17224
|
+
|
|
17225
|
+
<div part="footer" class="time-footer">
|
|
17226
|
+
<rel-button
|
|
17227
|
+
variant="secondary"
|
|
17228
|
+
size="small"
|
|
17229
|
+
border="corner-radius"
|
|
17230
|
+
@click=${() => this.selectNow()}
|
|
17231
|
+
>
|
|
17232
|
+
Now
|
|
17233
|
+
</rel-button>
|
|
17234
|
+
<div class="footer-right">
|
|
17235
|
+
<rel-button
|
|
17236
|
+
variant="secondary"
|
|
17237
|
+
size="small"
|
|
17238
|
+
border="corner-radius"
|
|
17239
|
+
@click=${() => this.cancelPopover()}
|
|
17240
|
+
>
|
|
17241
|
+
Cancel
|
|
17242
|
+
</rel-button>
|
|
17243
|
+
<rel-button
|
|
17244
|
+
variant="primary"
|
|
17245
|
+
tone="default"
|
|
17246
|
+
size="small"
|
|
17247
|
+
border="corner-radius"
|
|
17248
|
+
?disabled=${this.draftHour === null}
|
|
17249
|
+
@click=${() => this.applySelection()}
|
|
17250
|
+
>
|
|
17251
|
+
Apply
|
|
17252
|
+
</rel-button>
|
|
17253
|
+
</div>
|
|
17254
|
+
</div>
|
|
17255
|
+
</div>
|
|
17256
|
+
|
|
17257
|
+
${belowText ? html54`<div
|
|
17258
|
+
part="helper"
|
|
17259
|
+
class="time-picker-support ${isError ? "error" : ""}"
|
|
17260
|
+
id=${describedById}
|
|
17261
|
+
>
|
|
17262
|
+
${belowText}
|
|
17263
|
+
</div>` : nothing10}
|
|
17264
|
+
</div>
|
|
17265
|
+
`;
|
|
17266
|
+
}
|
|
17267
|
+
};
|
|
17268
|
+
RelTimePicker.styles = css51`
|
|
17269
|
+
:host {
|
|
17270
|
+
color: var(--rel-text-default);
|
|
17271
|
+
display: block;
|
|
17272
|
+
font-family: var(--rel-fonts-font-family);
|
|
17273
|
+
position: relative;
|
|
17274
|
+
}
|
|
17275
|
+
|
|
17276
|
+
/* Intrinsic width by default; set fullWidth to fill the parent. */
|
|
17277
|
+
:host(:not([fullwidth])) {
|
|
17278
|
+
display: inline-block;
|
|
17279
|
+
width: auto;
|
|
17280
|
+
}
|
|
17281
|
+
|
|
17282
|
+
:host([disabled]) {
|
|
17283
|
+
opacity: 0.6;
|
|
17284
|
+
cursor: not-allowed;
|
|
17285
|
+
}
|
|
17286
|
+
|
|
17287
|
+
:host([disabled]) * {
|
|
17288
|
+
pointer-events: none;
|
|
17289
|
+
}
|
|
17290
|
+
|
|
17291
|
+
:host([readonly]) .trigger-button {
|
|
17292
|
+
cursor: default;
|
|
17293
|
+
}
|
|
17294
|
+
|
|
17295
|
+
.time-picker-wrapper {
|
|
17296
|
+
display: flex;
|
|
17297
|
+
flex-direction: column;
|
|
17298
|
+
gap: var(--rel-spacing-xs);
|
|
17299
|
+
}
|
|
17300
|
+
|
|
17301
|
+
.label {
|
|
17302
|
+
color: var(--rel-text-label);
|
|
17303
|
+
font-family: var(--rel-body-sm-medium-font-family);
|
|
17304
|
+
font-weight: var(--rel-body-sm-medium-font-weight);
|
|
17305
|
+
font-size: var(--rel-body-sm-medium-font-size);
|
|
17306
|
+
line-height: var(--rel-body-sm-medium-line-height);
|
|
17307
|
+
letter-spacing: var(--rel-body-sm-medium-letter-spacing);
|
|
17308
|
+
}
|
|
17309
|
+
|
|
17310
|
+
.required-mark {
|
|
17311
|
+
color: var(--rel-text-danger);
|
|
17312
|
+
margin-left: 2px;
|
|
17313
|
+
}
|
|
17314
|
+
|
|
17315
|
+
/* Support text (helperText / errorMessage) */
|
|
17316
|
+
.time-picker-support {
|
|
17317
|
+
color: var(--rel-text-placeholder);
|
|
17318
|
+
font-family: var(--rel-body-xs-regular-font-family);
|
|
17319
|
+
font-weight: var(--rel-body-xs-regular-font-weight);
|
|
17320
|
+
font-size: var(--rel-body-xs-regular-font-size);
|
|
17321
|
+
line-height: var(--rel-body-xs-regular-line-height);
|
|
17322
|
+
letter-spacing: var(--rel-body-xs-regular-letter-spacing);
|
|
17323
|
+
}
|
|
17324
|
+
|
|
17325
|
+
.time-picker-support.error {
|
|
17326
|
+
color: var(--rel-text-danger);
|
|
17327
|
+
}
|
|
17328
|
+
|
|
17329
|
+
.trigger-button {
|
|
17330
|
+
width: 100%;
|
|
17331
|
+
min-width: 160px;
|
|
17332
|
+
display: flex;
|
|
17333
|
+
align-items: center;
|
|
17334
|
+
justify-content: space-between;
|
|
17335
|
+
gap: var(--rel-spacing-xs);
|
|
17336
|
+
padding: var(--rel-spacing-sm) var(--rel-spacing-md);
|
|
17337
|
+
background: var(--rel-bg-surface);
|
|
17338
|
+
border: 1px solid var(--rel-border-default);
|
|
17339
|
+
border-radius: var(--rel-border-radius-sm);
|
|
17340
|
+
color: var(--rel-text-default);
|
|
17341
|
+
cursor: pointer;
|
|
17342
|
+
transition: all 0.15s ease;
|
|
17343
|
+
text-align: left;
|
|
17344
|
+
font-family: var(--rel-body-md-regular-font-family);
|
|
17345
|
+
font-weight: var(--rel-body-md-regular-font-weight);
|
|
17346
|
+
font-size: var(--rel-body-md-regular-font-size);
|
|
17347
|
+
line-height: var(--rel-body-md-regular-line-height);
|
|
17348
|
+
letter-spacing: var(--rel-body-md-regular-letter-spacing);
|
|
17349
|
+
}
|
|
17350
|
+
|
|
17351
|
+
.trigger-button:hover:not(:disabled) {
|
|
17352
|
+
border-color: var(--rel-border-strong);
|
|
17353
|
+
}
|
|
17354
|
+
|
|
17355
|
+
.trigger-button:focus-visible {
|
|
17356
|
+
outline: 2px solid var(--rel-border-focus);
|
|
17357
|
+
outline-offset: 2px;
|
|
17358
|
+
border-color: var(--rel-border-focus);
|
|
17359
|
+
}
|
|
17360
|
+
|
|
17361
|
+
.trigger-button:disabled {
|
|
17362
|
+
cursor: not-allowed;
|
|
17363
|
+
background: var(--rel-bg-surface-elevated);
|
|
17364
|
+
}
|
|
17365
|
+
|
|
17366
|
+
/* Error state */
|
|
17367
|
+
.trigger-button.error,
|
|
17368
|
+
.trigger-button.error:hover:not(:disabled) {
|
|
17369
|
+
border-color: var(--rel-border-danger);
|
|
17370
|
+
}
|
|
17371
|
+
|
|
17372
|
+
.trigger-button.error:focus-visible {
|
|
17373
|
+
outline-color: var(--rel-border-danger);
|
|
17374
|
+
border-color: var(--rel-border-danger);
|
|
17375
|
+
}
|
|
17376
|
+
|
|
17377
|
+
/* Size variants (medium is the base .trigger-button style) */
|
|
17378
|
+
.time-picker-wrapper.small .trigger-button {
|
|
17379
|
+
padding: var(--rel-spacing-xs) var(--rel-spacing-sm);
|
|
17380
|
+
font-family: var(--rel-body-sm-regular-font-family);
|
|
17381
|
+
font-weight: var(--rel-body-sm-regular-font-weight);
|
|
17382
|
+
font-size: var(--rel-body-sm-regular-font-size);
|
|
17383
|
+
line-height: var(--rel-body-sm-regular-line-height);
|
|
17384
|
+
letter-spacing: var(--rel-body-sm-regular-letter-spacing);
|
|
17385
|
+
}
|
|
17386
|
+
|
|
17387
|
+
.time-picker-wrapper.large .trigger-button {
|
|
17388
|
+
padding: var(--rel-spacing-md) var(--rel-spacing-md);
|
|
17389
|
+
font-family: var(--rel-body-lg-regular-font-family);
|
|
17390
|
+
font-weight: var(--rel-body-lg-regular-font-weight);
|
|
17391
|
+
font-size: var(--rel-body-lg-regular-font-size);
|
|
17392
|
+
line-height: var(--rel-body-lg-regular-line-height);
|
|
17393
|
+
letter-spacing: var(--rel-body-lg-regular-letter-spacing);
|
|
17394
|
+
}
|
|
17395
|
+
|
|
17396
|
+
.trigger-content {
|
|
17397
|
+
flex: 1;
|
|
17398
|
+
color: var(--rel-text-default);
|
|
17399
|
+
}
|
|
17400
|
+
|
|
17401
|
+
.trigger-content.placeholder {
|
|
17402
|
+
color: var(--rel-text-placeholder);
|
|
17403
|
+
}
|
|
17404
|
+
|
|
17405
|
+
.trigger-icons {
|
|
17406
|
+
display: flex;
|
|
17407
|
+
align-items: center;
|
|
17408
|
+
gap: var(--rel-spacing-2xs);
|
|
17409
|
+
color: var(--rel-text-placeholder);
|
|
17410
|
+
}
|
|
17411
|
+
|
|
17412
|
+
.icon-button {
|
|
17413
|
+
background: none;
|
|
17414
|
+
border: none;
|
|
17415
|
+
padding: var(--rel-spacing-3xs);
|
|
17416
|
+
cursor: pointer;
|
|
17417
|
+
color: var(--rel-text-placeholder);
|
|
17418
|
+
display: flex;
|
|
17419
|
+
align-items: center;
|
|
17420
|
+
justify-content: center;
|
|
17421
|
+
border-radius: var(--rel-border-radius-xs);
|
|
17422
|
+
transition: all 0.15s ease;
|
|
17423
|
+
}
|
|
17424
|
+
|
|
17425
|
+
.icon-button:hover {
|
|
17426
|
+
background: var(--rel-bg-surface-elevated);
|
|
17427
|
+
color: var(--rel-text-default);
|
|
17428
|
+
}
|
|
17429
|
+
|
|
17430
|
+
.icon-button svg {
|
|
17431
|
+
width: 16px;
|
|
17432
|
+
height: 16px;
|
|
17433
|
+
}
|
|
17434
|
+
|
|
17435
|
+
.popover {
|
|
17436
|
+
position: absolute;
|
|
17437
|
+
top: calc(100% + 4px);
|
|
17438
|
+
left: 0;
|
|
17439
|
+
z-index: 1000;
|
|
17440
|
+
background: var(--rel-bg-surface);
|
|
17441
|
+
border: 1px solid var(--rel-border-default);
|
|
17442
|
+
border-radius: var(--rel-border-radius-sm);
|
|
17443
|
+
box-shadow: var(--rel-shadows-shadow-300);
|
|
17444
|
+
padding: var(--rel-spacing-md);
|
|
17445
|
+
display: none;
|
|
17446
|
+
}
|
|
17447
|
+
|
|
17448
|
+
.popover.open {
|
|
17449
|
+
display: block;
|
|
17450
|
+
}
|
|
17451
|
+
|
|
17452
|
+
/* Draft readout (mirrors the range-picker FROM/TO block) */
|
|
17453
|
+
.time-readout {
|
|
17454
|
+
display: flex;
|
|
17455
|
+
align-items: baseline;
|
|
17456
|
+
gap: var(--rel-spacing-sm);
|
|
17457
|
+
padding-bottom: var(--rel-spacing-sm);
|
|
17458
|
+
margin-bottom: var(--rel-spacing-sm);
|
|
17459
|
+
border-bottom: 1px solid var(--rel-border-subtle);
|
|
17460
|
+
}
|
|
17461
|
+
|
|
17462
|
+
.time-readout-label {
|
|
17463
|
+
color: var(--rel-text-placeholder);
|
|
17464
|
+
text-transform: uppercase;
|
|
17465
|
+
font-family: var(--rel-body-xs-semibold-font-family);
|
|
17466
|
+
font-weight: var(--rel-body-xs-semibold-font-weight);
|
|
17467
|
+
font-size: var(--rel-body-xs-semibold-font-size);
|
|
17468
|
+
line-height: var(--rel-body-xs-semibold-line-height);
|
|
17469
|
+
letter-spacing: var(--rel-body-xs-semibold-letter-spacing);
|
|
17470
|
+
}
|
|
17471
|
+
|
|
17472
|
+
.time-readout-value {
|
|
17473
|
+
color: var(--rel-text-default);
|
|
17474
|
+
font-family: var(--rel-body-md-semibold-font-family);
|
|
17475
|
+
font-weight: var(--rel-body-md-semibold-font-weight);
|
|
17476
|
+
font-size: var(--rel-body-md-semibold-font-size);
|
|
17477
|
+
line-height: var(--rel-body-md-semibold-line-height);
|
|
17478
|
+
letter-spacing: var(--rel-body-md-semibold-letter-spacing);
|
|
17479
|
+
}
|
|
17480
|
+
|
|
17481
|
+
.time-readout-value.empty {
|
|
17482
|
+
color: var(--rel-text-placeholder);
|
|
17483
|
+
}
|
|
17484
|
+
|
|
17485
|
+
.columns {
|
|
17486
|
+
display: flex;
|
|
17487
|
+
gap: var(--rel-spacing-xs);
|
|
17488
|
+
}
|
|
17489
|
+
|
|
17490
|
+
/* Columns share the panel width equally, so the two-column 24h layout
|
|
17491
|
+
fills the same footprint the footer dictates instead of bunching left. */
|
|
17492
|
+
.column {
|
|
17493
|
+
display: flex;
|
|
17494
|
+
flex-direction: column;
|
|
17495
|
+
flex: 1;
|
|
17496
|
+
min-width: 64px;
|
|
17497
|
+
}
|
|
17498
|
+
|
|
17499
|
+
.column-header {
|
|
17500
|
+
text-align: center;
|
|
17501
|
+
color: var(--rel-text-placeholder);
|
|
17502
|
+
padding: var(--rel-spacing-xs);
|
|
17503
|
+
text-transform: uppercase;
|
|
17504
|
+
font-family: var(--rel-body-xs-semibold-font-family);
|
|
17505
|
+
font-weight: var(--rel-body-xs-semibold-font-weight);
|
|
17506
|
+
font-size: var(--rel-body-xs-semibold-font-size);
|
|
17507
|
+
line-height: var(--rel-body-xs-semibold-line-height);
|
|
17508
|
+
letter-spacing: var(--rel-body-xs-semibold-letter-spacing);
|
|
17509
|
+
}
|
|
17510
|
+
|
|
17511
|
+
.column-list {
|
|
17512
|
+
display: flex;
|
|
17513
|
+
flex-direction: column;
|
|
17514
|
+
gap: var(--rel-spacing-3xs);
|
|
17515
|
+
max-height: 224px;
|
|
17516
|
+
overflow-y: auto;
|
|
17517
|
+
padding: var(--rel-spacing-3xs);
|
|
17518
|
+
scrollbar-width: thin;
|
|
17519
|
+
}
|
|
17520
|
+
|
|
17521
|
+
.option-button {
|
|
17522
|
+
border: none;
|
|
17523
|
+
background: transparent;
|
|
17524
|
+
border-radius: var(--rel-border-radius-sm);
|
|
17525
|
+
color: var(--rel-text-default);
|
|
17526
|
+
cursor: pointer;
|
|
17527
|
+
padding: var(--rel-spacing-xs) var(--rel-spacing-sm);
|
|
17528
|
+
text-align: center;
|
|
17529
|
+
flex-shrink: 0;
|
|
17530
|
+
transition: all 0.15s ease;
|
|
17531
|
+
font-family: var(--rel-body-sm-medium-font-family);
|
|
17532
|
+
font-weight: var(--rel-body-sm-medium-font-weight);
|
|
17533
|
+
font-size: var(--rel-body-sm-medium-font-size);
|
|
17534
|
+
line-height: var(--rel-body-sm-medium-line-height);
|
|
17535
|
+
letter-spacing: var(--rel-body-sm-medium-letter-spacing);
|
|
17536
|
+
}
|
|
17537
|
+
|
|
17538
|
+
.option-button:hover:not(:disabled):not(.selected) {
|
|
17539
|
+
background: var(--rel-bg-surface-elevated);
|
|
17540
|
+
}
|
|
17541
|
+
|
|
17542
|
+
.option-button:focus-visible {
|
|
17543
|
+
outline: 2px solid var(--rel-border-focus);
|
|
17544
|
+
outline-offset: -2px;
|
|
17545
|
+
z-index: 1;
|
|
17546
|
+
}
|
|
17547
|
+
|
|
17548
|
+
.option-button.selected {
|
|
17549
|
+
background: var(--rel-action-brand-fill);
|
|
17550
|
+
color: var(--rel-action-brand-label-icons-primary);
|
|
17551
|
+
font-weight: var(--rel-body-md-semibold-font-weight);
|
|
17552
|
+
}
|
|
17553
|
+
|
|
17554
|
+
.option-button:disabled {
|
|
17555
|
+
color: var(--rel-text-disabled);
|
|
17556
|
+
cursor: not-allowed;
|
|
17557
|
+
opacity: 0.5;
|
|
17558
|
+
}
|
|
17559
|
+
|
|
17560
|
+
.option-button:disabled:hover {
|
|
17561
|
+
background: transparent;
|
|
17562
|
+
}
|
|
17563
|
+
|
|
17564
|
+
/* Footer (mirrors the range-picker calendar-footer) */
|
|
17565
|
+
.time-footer {
|
|
17566
|
+
display: flex;
|
|
17567
|
+
align-items: center;
|
|
17568
|
+
justify-content: space-between;
|
|
17569
|
+
gap: var(--rel-spacing-sm);
|
|
17570
|
+
padding-top: var(--rel-spacing-sm);
|
|
17571
|
+
margin-top: var(--rel-spacing-sm);
|
|
17572
|
+
border-top: 1px solid var(--rel-border-subtle);
|
|
17573
|
+
}
|
|
17574
|
+
|
|
17575
|
+
.footer-right {
|
|
17576
|
+
display: flex;
|
|
17577
|
+
align-items: center;
|
|
17578
|
+
gap: var(--rel-spacing-xs);
|
|
17579
|
+
}
|
|
17580
|
+
|
|
17581
|
+
@media (prefers-reduced-motion: reduce) {
|
|
17582
|
+
.trigger-button,
|
|
17583
|
+
.icon-button,
|
|
17584
|
+
.option-button {
|
|
17585
|
+
transition: none;
|
|
17586
|
+
}
|
|
17587
|
+
}
|
|
17588
|
+
`;
|
|
17589
|
+
__decorateClass([
|
|
17590
|
+
property48({ type: String, reflect: true })
|
|
17591
|
+
], RelTimePicker.prototype, "value", 2);
|
|
17592
|
+
__decorateClass([
|
|
17593
|
+
property48({ type: String })
|
|
17594
|
+
], RelTimePicker.prototype, "placeholder", 2);
|
|
17595
|
+
__decorateClass([
|
|
17596
|
+
property48({ type: String })
|
|
17597
|
+
], RelTimePicker.prototype, "label", 2);
|
|
17598
|
+
__decorateClass([
|
|
17599
|
+
property48({ type: Boolean, reflect: true })
|
|
17600
|
+
], RelTimePicker.prototype, "disabled", 2);
|
|
17601
|
+
__decorateClass([
|
|
17602
|
+
property48({ type: Boolean })
|
|
17603
|
+
], RelTimePicker.prototype, "required", 2);
|
|
17604
|
+
__decorateClass([
|
|
17605
|
+
property48({ type: Boolean, reflect: true })
|
|
17606
|
+
], RelTimePicker.prototype, "readonly", 2);
|
|
17607
|
+
__decorateClass([
|
|
17608
|
+
property48({ type: String })
|
|
17609
|
+
], RelTimePicker.prototype, "format", 2);
|
|
17610
|
+
__decorateClass([
|
|
17611
|
+
property48({ type: Number, attribute: "minute-step" })
|
|
17612
|
+
], RelTimePicker.prototype, "minuteStep", 2);
|
|
17613
|
+
__decorateClass([
|
|
17614
|
+
property48({ type: String, attribute: "min-time" })
|
|
17615
|
+
], RelTimePicker.prototype, "minTime", 2);
|
|
17616
|
+
__decorateClass([
|
|
17617
|
+
property48({ type: String, attribute: "max-time" })
|
|
17618
|
+
], RelTimePicker.prototype, "maxTime", 2);
|
|
17619
|
+
__decorateClass([
|
|
17620
|
+
property48({ type: Boolean, reflect: true })
|
|
17621
|
+
], RelTimePicker.prototype, "open", 2);
|
|
17622
|
+
__decorateClass([
|
|
17623
|
+
property48({ type: Boolean })
|
|
17624
|
+
], RelTimePicker.prototype, "clearable", 2);
|
|
17625
|
+
__decorateClass([
|
|
17626
|
+
property48({ type: Boolean })
|
|
17627
|
+
], RelTimePicker.prototype, "error", 2);
|
|
17628
|
+
__decorateClass([
|
|
17629
|
+
property48({ type: Boolean })
|
|
17630
|
+
], RelTimePicker.prototype, "invalid", 2);
|
|
17631
|
+
__decorateClass([
|
|
17632
|
+
property48({ type: String, attribute: "error-message" })
|
|
17633
|
+
], RelTimePicker.prototype, "errorMessage", 2);
|
|
17634
|
+
__decorateClass([
|
|
17635
|
+
property48({ type: String, attribute: "helper-text" })
|
|
17636
|
+
], RelTimePicker.prototype, "helperText", 2);
|
|
17637
|
+
__decorateClass([
|
|
17638
|
+
property48({ type: String })
|
|
17639
|
+
], RelTimePicker.prototype, "name", 2);
|
|
17640
|
+
__decorateClass([
|
|
17641
|
+
property48({ type: String })
|
|
17642
|
+
], RelTimePicker.prototype, "size", 2);
|
|
17643
|
+
__decorateClass([
|
|
17644
|
+
property48({ type: String, reflect: true })
|
|
17645
|
+
], RelTimePicker.prototype, "variant", 2);
|
|
17646
|
+
__decorateClass([
|
|
17647
|
+
property48({ type: Boolean, reflect: true })
|
|
17648
|
+
], RelTimePicker.prototype, "fullWidth", 2);
|
|
17649
|
+
__decorateClass([
|
|
17650
|
+
state21()
|
|
17651
|
+
], RelTimePicker.prototype, "draftHour", 2);
|
|
17652
|
+
__decorateClass([
|
|
17653
|
+
state21()
|
|
17654
|
+
], RelTimePicker.prototype, "draftMinute", 2);
|
|
17655
|
+
__decorateClass([
|
|
17656
|
+
query10(".trigger-button")
|
|
17657
|
+
], RelTimePicker.prototype, "triggerButton", 2);
|
|
17658
|
+
__decorateClass([
|
|
17659
|
+
query10(".columns")
|
|
17660
|
+
], RelTimePicker.prototype, "columnsElement", 2);
|
|
17661
|
+
RelTimePicker = __decorateClass([
|
|
17662
|
+
customElement54("rel-time-picker")
|
|
17663
|
+
], RelTimePicker);
|
|
17664
|
+
|
|
17665
|
+
// src/components/rel-toast/rel-toast.ts
|
|
17666
|
+
import { LitElement as LitElement55, html as html55, css as css52 } from "lit";
|
|
17667
|
+
import { customElement as customElement55, property as property49, state as state22 } from "lit/decorators.js";
|
|
17668
|
+
var RelToast = class extends LitElement55 {
|
|
17669
|
+
constructor() {
|
|
17670
|
+
super(...arguments);
|
|
17671
|
+
this.message = "";
|
|
17672
|
+
this.type = "default";
|
|
17673
|
+
this.duration = 4e3;
|
|
17674
|
+
this.show = false;
|
|
17675
|
+
this.visible = false;
|
|
17676
|
+
this._dismissTimer = null;
|
|
17677
|
+
}
|
|
17678
|
+
willUpdate(changed) {
|
|
17679
|
+
if (!changed.has("show")) return;
|
|
17680
|
+
if (this.show) {
|
|
17681
|
+
this.visible = true;
|
|
17682
|
+
if (this._dismissTimer) clearTimeout(this._dismissTimer);
|
|
17683
|
+
if (this.duration > 0) {
|
|
17684
|
+
this._dismissTimer = setTimeout(() => this.close(), this.duration);
|
|
17685
|
+
}
|
|
17686
|
+
} else {
|
|
17687
|
+
this.visible = false;
|
|
17688
|
+
if (this._dismissTimer) {
|
|
17689
|
+
clearTimeout(this._dismissTimer);
|
|
17690
|
+
this._dismissTimer = null;
|
|
17691
|
+
}
|
|
17692
|
+
}
|
|
17693
|
+
}
|
|
17694
|
+
disconnectedCallback() {
|
|
17695
|
+
super.disconnectedCallback();
|
|
17696
|
+
if (this._dismissTimer) clearTimeout(this._dismissTimer);
|
|
17697
|
+
}
|
|
17698
|
+
open() {
|
|
17699
|
+
this.show = true;
|
|
17700
|
+
}
|
|
17701
|
+
close() {
|
|
17702
|
+
this.show = false;
|
|
17703
|
+
this.visible = false;
|
|
17704
|
+
if (this._dismissTimer) {
|
|
17705
|
+
clearTimeout(this._dismissTimer);
|
|
17706
|
+
this._dismissTimer = null;
|
|
17707
|
+
}
|
|
17708
|
+
this.dispatchEvent(
|
|
17709
|
+
new CustomEvent("dismissed", {
|
|
17710
|
+
bubbles: true,
|
|
17711
|
+
composed: true
|
|
17712
|
+
})
|
|
17713
|
+
);
|
|
17714
|
+
}
|
|
17715
|
+
handleActionClick(event) {
|
|
17716
|
+
event.stopPropagation();
|
|
17717
|
+
this.dispatchEvent(
|
|
17718
|
+
new CustomEvent("action", {
|
|
17719
|
+
bubbles: true,
|
|
17720
|
+
composed: true
|
|
17721
|
+
})
|
|
17722
|
+
);
|
|
17723
|
+
this.close();
|
|
17724
|
+
}
|
|
17725
|
+
render() {
|
|
17726
|
+
return html55`
|
|
17727
|
+
<div part="base" class="toast ${this.type} ${this.visible ? "" : "hidden"}" role="status" aria-live="polite">
|
|
17728
|
+
<span part="label" class="toast-message">${this.message}</span>
|
|
17729
|
+
|
|
17730
|
+
${this.actionLabel ? html55`<button
|
|
17731
|
+
part="action" class="toast-action"
|
|
17732
|
+
@click="${this.handleActionClick}"
|
|
17733
|
+
>
|
|
17734
|
+
${this.actionLabel}
|
|
17735
|
+
</button>` : ""}
|
|
17736
|
+
|
|
17737
|
+
<button part="close-button" class="toast-close" @click="${this.close}" aria-label="Dismiss">✕</button>
|
|
17738
|
+
</div>
|
|
17739
|
+
`;
|
|
17740
|
+
}
|
|
17741
|
+
};
|
|
17742
|
+
RelToast.styles = css52`
|
|
17743
|
+
:host {
|
|
17744
|
+
color: var(--rel-text-default);
|
|
17745
|
+
position: fixed;
|
|
17746
|
+
bottom: 24px;
|
|
16643
17747
|
left: 50%;
|
|
16644
17748
|
transform: translateX(-50%);
|
|
16645
17749
|
display: flex;
|
|
@@ -16753,31 +17857,31 @@ RelToast.styles = css51`
|
|
|
16753
17857
|
}
|
|
16754
17858
|
`;
|
|
16755
17859
|
__decorateClass([
|
|
16756
|
-
|
|
17860
|
+
property49({ type: String })
|
|
16757
17861
|
], RelToast.prototype, "message", 2);
|
|
16758
17862
|
__decorateClass([
|
|
16759
|
-
|
|
17863
|
+
property49({ type: String })
|
|
16760
17864
|
], RelToast.prototype, "type", 2);
|
|
16761
17865
|
__decorateClass([
|
|
16762
|
-
|
|
17866
|
+
property49({ type: String })
|
|
16763
17867
|
], RelToast.prototype, "actionLabel", 2);
|
|
16764
17868
|
__decorateClass([
|
|
16765
|
-
|
|
17869
|
+
property49({ type: Number })
|
|
16766
17870
|
], RelToast.prototype, "duration", 2);
|
|
16767
17871
|
__decorateClass([
|
|
16768
|
-
|
|
17872
|
+
property49({ type: Boolean, reflect: true })
|
|
16769
17873
|
], RelToast.prototype, "show", 2);
|
|
16770
17874
|
__decorateClass([
|
|
16771
|
-
|
|
17875
|
+
state22()
|
|
16772
17876
|
], RelToast.prototype, "visible", 2);
|
|
16773
17877
|
RelToast = __decorateClass([
|
|
16774
|
-
|
|
17878
|
+
customElement55("rel-toast")
|
|
16775
17879
|
], RelToast);
|
|
16776
17880
|
|
|
16777
17881
|
// src/components/rel-tooltip/rel-tooltip.ts
|
|
16778
|
-
import { LitElement as
|
|
16779
|
-
import { customElement as
|
|
16780
|
-
var RelTooltip = class extends
|
|
17882
|
+
import { LitElement as LitElement56, html as html56, css as css53 } from "lit";
|
|
17883
|
+
import { customElement as customElement56, property as property50 } from "lit/decorators.js";
|
|
17884
|
+
var RelTooltip = class extends LitElement56 {
|
|
16781
17885
|
constructor() {
|
|
16782
17886
|
super(...arguments);
|
|
16783
17887
|
this.text = "";
|
|
@@ -16799,7 +17903,7 @@ var RelTooltip = class extends LitElement55 {
|
|
|
16799
17903
|
}
|
|
16800
17904
|
}
|
|
16801
17905
|
render() {
|
|
16802
|
-
return
|
|
17906
|
+
return html56`
|
|
16803
17907
|
<slot @slotchange="${this.linkTrigger}"></slot>
|
|
16804
17908
|
<div
|
|
16805
17909
|
part="panel" class="tooltip-bubble ${this.position}"
|
|
@@ -16807,12 +17911,12 @@ var RelTooltip = class extends LitElement55 {
|
|
|
16807
17911
|
role="tooltip"
|
|
16808
17912
|
>
|
|
16809
17913
|
${this.text}
|
|
16810
|
-
${this.showTail ?
|
|
17914
|
+
${this.showTail ? html56`<span part="indicator" class="tooltip-tail"></span>` : ""}
|
|
16811
17915
|
</div>
|
|
16812
17916
|
`;
|
|
16813
17917
|
}
|
|
16814
17918
|
};
|
|
16815
|
-
RelTooltip.styles =
|
|
17919
|
+
RelTooltip.styles = css53`
|
|
16816
17920
|
:host {
|
|
16817
17921
|
color: var(--rel-text-default);
|
|
16818
17922
|
display: inline-block;
|
|
@@ -16926,19 +18030,19 @@ RelTooltip.styles = css52`
|
|
|
16926
18030
|
}
|
|
16927
18031
|
`;
|
|
16928
18032
|
__decorateClass([
|
|
16929
|
-
|
|
18033
|
+
property50({ type: String })
|
|
16930
18034
|
], RelTooltip.prototype, "text", 2);
|
|
16931
18035
|
__decorateClass([
|
|
16932
|
-
|
|
18036
|
+
property50({ type: String })
|
|
16933
18037
|
], RelTooltip.prototype, "position", 2);
|
|
16934
18038
|
__decorateClass([
|
|
16935
|
-
|
|
18039
|
+
property50({ type: Boolean, attribute: "show-tail" })
|
|
16936
18040
|
], RelTooltip.prototype, "showTail", 2);
|
|
16937
18041
|
__decorateClass([
|
|
16938
|
-
|
|
18042
|
+
property50({ type: String, reflect: true })
|
|
16939
18043
|
], RelTooltip.prototype, "type", 2);
|
|
16940
18044
|
RelTooltip = __decorateClass([
|
|
16941
|
-
|
|
18045
|
+
customElement56("rel-tooltip")
|
|
16942
18046
|
], RelTooltip);
|
|
16943
18047
|
|
|
16944
18048
|
// src/index.ts
|
|
@@ -16991,6 +18095,7 @@ export {
|
|
|
16991
18095
|
RelTab,
|
|
16992
18096
|
RelTabItem,
|
|
16993
18097
|
RelTextfield,
|
|
18098
|
+
RelTimePicker,
|
|
16994
18099
|
RelToast,
|
|
16995
18100
|
RelTooltip,
|
|
16996
18101
|
RelTopbar,
|