@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.js
CHANGED
|
@@ -76,6 +76,7 @@ __export(index_exports, {
|
|
|
76
76
|
RelTab: () => RelTab,
|
|
77
77
|
RelTabItem: () => RelTabItem,
|
|
78
78
|
RelTextfield: () => RelTextfield,
|
|
79
|
+
RelTimePicker: () => RelTimePicker,
|
|
79
80
|
RelToast: () => RelToast,
|
|
80
81
|
RelTooltip: () => RelTooltip,
|
|
81
82
|
RelTopbar: () => RelTopbar,
|
|
@@ -760,6 +761,7 @@ var RelTextfield = class extends import_lit3.LitElement {
|
|
|
760
761
|
this.internalState = "default";
|
|
761
762
|
this._wasError = false;
|
|
762
763
|
this.uniqueId = `textfield-${Math.random().toString(36).slice(2)}`;
|
|
764
|
+
this._warnedNoName = false;
|
|
763
765
|
}
|
|
764
766
|
get isDisabled() {
|
|
765
767
|
return this.disabled || this.state === "disabled";
|
|
@@ -775,6 +777,15 @@ var RelTextfield = class extends import_lit3.LitElement {
|
|
|
775
777
|
get helper() {
|
|
776
778
|
return this.helperText ?? this.supportingText;
|
|
777
779
|
}
|
|
780
|
+
/** labelStyle supersedes the deprecated floatingLabel boolean. */
|
|
781
|
+
get resolvedLabelStyle() {
|
|
782
|
+
if (this.labelStyle) return this.labelStyle;
|
|
783
|
+
return this.floatingLabel ? "floating" : "default";
|
|
784
|
+
}
|
|
785
|
+
/** True when no visible label renders and the input needs an ARIA name. */
|
|
786
|
+
get labelHidden() {
|
|
787
|
+
return this.resolvedLabelStyle === "none";
|
|
788
|
+
}
|
|
778
789
|
connectedCallback() {
|
|
779
790
|
super.connectedCallback();
|
|
780
791
|
const leftIcon = this.getAttribute("lefticon") || this.getAttribute("leftIcon");
|
|
@@ -782,6 +793,20 @@ var RelTextfield = class extends import_lit3.LitElement {
|
|
|
782
793
|
const rightIcon = this.getAttribute("righticon") || this.getAttribute("rightIcon");
|
|
783
794
|
if (rightIcon) this.rightIcon = rightIcon;
|
|
784
795
|
}
|
|
796
|
+
willUpdate(changed) {
|
|
797
|
+
if (changed.has("labelStyle") && this.labelStyle) {
|
|
798
|
+
this.floatingLabel = this.labelStyle === "floating";
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
updated() {
|
|
802
|
+
if (this._warnedNoName || !this.labelHidden) return;
|
|
803
|
+
if (this.fieldAriaLabel || this.fieldAriaLabelledby || this.getAttribute("aria-label")) return;
|
|
804
|
+
this._warnedNoName = true;
|
|
805
|
+
console.warn(
|
|
806
|
+
'<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.',
|
|
807
|
+
this
|
|
808
|
+
);
|
|
809
|
+
}
|
|
785
810
|
/** Focuses the inner input/textarea (the host itself is not focusable). */
|
|
786
811
|
focus(options) {
|
|
787
812
|
this.renderRoot.querySelector("input, textarea")?.focus(options);
|
|
@@ -849,15 +874,20 @@ var RelTextfield = class extends import_lit3.LitElement {
|
|
|
849
874
|
!this.multiline && leadingIcon ? "has-left-icon" : "",
|
|
850
875
|
!this.multiline && this.rightIcon && this.variant !== "legend" ? "has-right-icon" : ""
|
|
851
876
|
].filter(Boolean).join(" ");
|
|
877
|
+
const labelStyle = this.resolvedLabelStyle;
|
|
878
|
+
const labelHidden = labelStyle === "none";
|
|
852
879
|
const labelTemplate = (extraClass = "") => {
|
|
853
|
-
if (!this.label) return "";
|
|
880
|
+
if (!this.label || labelHidden) return "";
|
|
854
881
|
return import_lit3.html`<label
|
|
855
882
|
part="label"
|
|
856
883
|
class="textfield-label${extraClass}"
|
|
857
884
|
for="${inputId}"
|
|
858
885
|
>${this.label}${this.required ? import_lit3.html`<span class="required-mark" aria-hidden="true">*</span>` : ""}</label>`;
|
|
859
886
|
};
|
|
860
|
-
const
|
|
887
|
+
const legendLabelledby = this.variant === "legend" && this.label && !labelHidden ? legendId : void 0;
|
|
888
|
+
const ariaLabelledby = this.fieldAriaLabelledby ?? legendLabelledby;
|
|
889
|
+
const hasVisibleLabel = !labelHidden && !!this.label;
|
|
890
|
+
const ariaLabel = !hasVisibleLabel && !ariaLabelledby ? this.fieldAriaLabel : void 0;
|
|
861
891
|
const inputTemplate = this.multiline ? import_lit3.html`<textarea part="input"
|
|
862
892
|
id="${inputId}"
|
|
863
893
|
class="${inputClasses}"
|
|
@@ -871,6 +901,7 @@ var RelTextfield = class extends import_lit3.LitElement {
|
|
|
871
901
|
?required="${this.required}"
|
|
872
902
|
aria-invalid="${isError}"
|
|
873
903
|
aria-required="${this.required}"
|
|
904
|
+
aria-label="${ariaLabel ?? import_lit3.nothing}"
|
|
874
905
|
aria-labelledby="${ariaLabelledby ?? import_lit3.nothing}"
|
|
875
906
|
aria-describedby="${describedById ?? import_lit3.nothing}"
|
|
876
907
|
@focus="${this.handleFocus}"
|
|
@@ -889,6 +920,7 @@ var RelTextfield = class extends import_lit3.LitElement {
|
|
|
889
920
|
?required="${this.required}"
|
|
890
921
|
aria-invalid="${isError}"
|
|
891
922
|
aria-required="${this.required}"
|
|
923
|
+
aria-label="${ariaLabel ?? import_lit3.nothing}"
|
|
892
924
|
aria-labelledby="${ariaLabelledby ?? import_lit3.nothing}"
|
|
893
925
|
aria-describedby="${describedById ?? import_lit3.nothing}"
|
|
894
926
|
@focus="${this.handleFocus}"
|
|
@@ -910,7 +942,7 @@ var RelTextfield = class extends import_lit3.LitElement {
|
|
|
910
942
|
const fieldsetClass = ["field-fieldset", this.multiline ? "multiline" : ""].filter(Boolean).join(" ");
|
|
911
943
|
return import_lit3.html`
|
|
912
944
|
<div class="${wrapperClasses}">
|
|
913
|
-
<fieldset part="control" class="${fieldsetClass}"><legend class="field-legend" id="${legendId}">${this.label ?? ""}${this.required ? import_lit3.html`<span class="required-mark" aria-hidden="true">*</span>` : ""}</legend>
|
|
945
|
+
<fieldset part="control" class="${fieldsetClass}"><legend class="field-legend" id="${legendId}">${labelHidden ? "" : this.label ?? ""}${this.required && !labelHidden ? import_lit3.html`<span class="required-mark" aria-hidden="true">*</span>` : ""}</legend>
|
|
914
946
|
${!this.multiline ? leadingIconTemplate : ""}
|
|
915
947
|
${inputTemplate}
|
|
916
948
|
</fieldset>
|
|
@@ -920,10 +952,10 @@ var RelTextfield = class extends import_lit3.LitElement {
|
|
|
920
952
|
}
|
|
921
953
|
return import_lit3.html`
|
|
922
954
|
<div class="${wrapperClasses}">
|
|
923
|
-
${
|
|
955
|
+
${labelHidden ? import_lit3.html`<div class="label-reserve" aria-hidden="true"></div>` : labelStyle !== "floating" ? labelTemplate() : ""}
|
|
924
956
|
|
|
925
957
|
<div part="control" class="${["textfield-container", this.isDisabled ? "disabled" : "", this.multiline ? "multiline" : ""].filter(Boolean).join(" ")}">
|
|
926
|
-
${
|
|
958
|
+
${labelStyle === "floating" ? labelTemplate(leadingIcon && !this.multiline ? " has-left-icon" : "") : ""}
|
|
927
959
|
${!this.multiline ? leadingIconTemplate : ""}
|
|
928
960
|
|
|
929
961
|
${inputTemplate}
|
|
@@ -982,6 +1014,22 @@ RelTextfield.styles = import_lit3.css`
|
|
|
982
1014
|
margin-left: 2px;
|
|
983
1015
|
}
|
|
984
1016
|
|
|
1017
|
+
/* ── labelStyle="none" ──
|
|
1018
|
+
No label renders. An empty spacer holds the label row's full height so the
|
|
1019
|
+
input box still bottom-aligns with labelled siblings in a form row.
|
|
1020
|
+
The reserve covers label line-height + the wrapper gap, and the negative
|
|
1021
|
+
margin cancels the flex gap that would otherwise sit below it — so
|
|
1022
|
+
--rel-field-label-reserve: 0 collapses the row to genuinely zero
|
|
1023
|
+
(toolbars, table cells), not just to the leftover gap. */
|
|
1024
|
+
.label-reserve {
|
|
1025
|
+
flex: none;
|
|
1026
|
+
height: var(
|
|
1027
|
+
--rel-field-label-reserve,
|
|
1028
|
+
calc(var(--rel-body-sm-medium-line-height) + var(--rel-spacing-2xs))
|
|
1029
|
+
);
|
|
1030
|
+
margin-bottom: calc(-1 * var(--rel-spacing-2xs));
|
|
1031
|
+
}
|
|
1032
|
+
|
|
985
1033
|
/* Floating label variant */
|
|
986
1034
|
:host([floatinglabel]) .textfield-wrapper {
|
|
987
1035
|
gap: 0;
|
|
@@ -1358,6 +1406,12 @@ RelTextfield.styles = import_lit3.css`
|
|
|
1358
1406
|
box-sizing: border-box;
|
|
1359
1407
|
}
|
|
1360
1408
|
|
|
1409
|
+
/* labelStyle="none" + legend: nothing to seat in the notch, so drop the
|
|
1410
|
+
legend entirely and let the top border run continuous. */
|
|
1411
|
+
:host([label-style="none"][variant="legend"]) .field-legend {
|
|
1412
|
+
display: none;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1361
1415
|
/* Prefix icon (legend): vertically centered; input inset via has-left-icon */
|
|
1362
1416
|
:host([variant="legend"]) .icon.left {
|
|
1363
1417
|
top: 50%;
|
|
@@ -1473,9 +1527,18 @@ __decorateClass([
|
|
|
1473
1527
|
__decorateClass([
|
|
1474
1528
|
(0, import_decorators3.property)({ type: String })
|
|
1475
1529
|
], RelTextfield.prototype, "size", 2);
|
|
1530
|
+
__decorateClass([
|
|
1531
|
+
(0, import_decorators3.property)({ type: String, attribute: "label-style", reflect: true })
|
|
1532
|
+
], RelTextfield.prototype, "labelStyle", 2);
|
|
1476
1533
|
__decorateClass([
|
|
1477
1534
|
(0, import_decorators3.property)({ type: Boolean, reflect: true })
|
|
1478
1535
|
], RelTextfield.prototype, "floatingLabel", 2);
|
|
1536
|
+
__decorateClass([
|
|
1537
|
+
(0, import_decorators3.property)({ type: String, attribute: "field-aria-label" })
|
|
1538
|
+
], RelTextfield.prototype, "fieldAriaLabel", 2);
|
|
1539
|
+
__decorateClass([
|
|
1540
|
+
(0, import_decorators3.property)({ type: String, attribute: "field-aria-labelledby" })
|
|
1541
|
+
], RelTextfield.prototype, "fieldAriaLabelledby", 2);
|
|
1479
1542
|
__decorateClass([
|
|
1480
1543
|
(0, import_decorators3.property)({ type: String, reflect: true })
|
|
1481
1544
|
], RelTextfield.prototype, "variant", 2);
|
|
@@ -1588,6 +1651,11 @@ var RelAutocomplete = class extends import_lit4.LitElement {
|
|
|
1588
1651
|
this.panelStyle = `top:${top}px;left:${left}px;width:${t.width}px;`;
|
|
1589
1652
|
};
|
|
1590
1653
|
}
|
|
1654
|
+
/** Name used for the suggestion panel — the visible label, or fieldAriaLabel
|
|
1655
|
+
* once labelStyle="none" hides it. Mirrors rel-textfield's own resolution. */
|
|
1656
|
+
get accessibleName() {
|
|
1657
|
+
return (this.labelStyle === "none" ? this.fieldAriaLabel : this.label) || "";
|
|
1658
|
+
}
|
|
1591
1659
|
disconnectedCallback() {
|
|
1592
1660
|
super.disconnectedCallback();
|
|
1593
1661
|
this.removeGlobalListeners();
|
|
@@ -1692,17 +1760,17 @@ var RelAutocomplete = class extends import_lit4.LitElement {
|
|
|
1692
1760
|
this.syncPanel();
|
|
1693
1761
|
}
|
|
1694
1762
|
}
|
|
1695
|
-
async runSearch(
|
|
1763
|
+
async runSearch(query11) {
|
|
1696
1764
|
const seq = ++this.searchSeq;
|
|
1697
1765
|
try {
|
|
1698
|
-
const results = await this.searchFn(
|
|
1766
|
+
const results = await this.searchFn(query11);
|
|
1699
1767
|
if (seq !== this.searchSeq) return;
|
|
1700
1768
|
this.remoteItems = results ?? [];
|
|
1701
1769
|
} catch (err) {
|
|
1702
1770
|
if (seq !== this.searchSeq) return;
|
|
1703
1771
|
this.remoteItems = [];
|
|
1704
1772
|
this.dispatchEvent(
|
|
1705
|
-
new CustomEvent("rel-search-error", { detail: { query:
|
|
1773
|
+
new CustomEvent("rel-search-error", { detail: { query: query11, error: err }, bubbles: true, composed: true })
|
|
1706
1774
|
);
|
|
1707
1775
|
} finally {
|
|
1708
1776
|
if (seq === this.searchSeq) {
|
|
@@ -1848,7 +1916,10 @@ var RelAutocomplete = class extends import_lit4.LitElement {
|
|
|
1848
1916
|
.value="${this.inputText}"
|
|
1849
1917
|
.size="${this.size}"
|
|
1850
1918
|
.variant="${this.variant}"
|
|
1919
|
+
.labelStyle="${this.labelStyle}"
|
|
1851
1920
|
.floatingLabel="${this.floatingLabel}"
|
|
1921
|
+
.fieldAriaLabel="${this.fieldAriaLabel}"
|
|
1922
|
+
.fieldAriaLabelledby="${this.fieldAriaLabelledby}"
|
|
1852
1923
|
.prefixIcon="${this.prefixIcon}"
|
|
1853
1924
|
.disabled="${this.disabled}"
|
|
1854
1925
|
.readonly="${this.readonly}"
|
|
@@ -1870,7 +1941,7 @@ var RelAutocomplete = class extends import_lit4.LitElement {
|
|
|
1870
1941
|
part="panel list"
|
|
1871
1942
|
class="ac-panel"
|
|
1872
1943
|
role="listbox"
|
|
1873
|
-
aria-label="${this.
|
|
1944
|
+
aria-label="${this.accessibleName || "Suggestions"}"
|
|
1874
1945
|
style="${this.panelStyle}"
|
|
1875
1946
|
?data-open="${this.open}"
|
|
1876
1947
|
>
|
|
@@ -2049,9 +2120,18 @@ __decorateClass([
|
|
|
2049
2120
|
__decorateClass([
|
|
2050
2121
|
(0, import_decorators4.property)({ type: String })
|
|
2051
2122
|
], RelAutocomplete.prototype, "variant", 2);
|
|
2123
|
+
__decorateClass([
|
|
2124
|
+
(0, import_decorators4.property)({ type: String, attribute: "label-style", reflect: true })
|
|
2125
|
+
], RelAutocomplete.prototype, "labelStyle", 2);
|
|
2052
2126
|
__decorateClass([
|
|
2053
2127
|
(0, import_decorators4.property)({ type: Boolean, attribute: "floating-label" })
|
|
2054
2128
|
], RelAutocomplete.prototype, "floatingLabel", 2);
|
|
2129
|
+
__decorateClass([
|
|
2130
|
+
(0, import_decorators4.property)({ type: String, attribute: "field-aria-label" })
|
|
2131
|
+
], RelAutocomplete.prototype, "fieldAriaLabel", 2);
|
|
2132
|
+
__decorateClass([
|
|
2133
|
+
(0, import_decorators4.property)({ type: String, attribute: "field-aria-labelledby" })
|
|
2134
|
+
], RelAutocomplete.prototype, "fieldAriaLabelledby", 2);
|
|
2055
2135
|
__decorateClass([
|
|
2056
2136
|
(0, import_decorators4.property)({ type: String, attribute: "prefix-icon" })
|
|
2057
2137
|
], RelAutocomplete.prototype, "prefixIcon", 2);
|
|
@@ -2763,8 +2843,8 @@ RelButton.styles = import_lit8.css`
|
|
|
2763
2843
|
pointer-events:none on the HOST would make it transparent to the
|
|
2764
2844
|
pointer and suppress the cursor entirely. */
|
|
2765
2845
|
cursor: not-allowed;
|
|
2766
|
-
/* The disabled tokens are full-contrast pairs (light:
|
|
2767
|
-
|
|
2846
|
+
/* The disabled tokens are full-contrast pairs (light: near-black on
|
|
2847
|
+
gray-300, dark: white on gray-700), so on their own they render a
|
|
2768
2848
|
crisp button that reads as enabled. Dulling happens here instead —
|
|
2769
2849
|
same opacity mechanism the rest of the library uses. */
|
|
2770
2850
|
opacity: 0.5;
|
|
@@ -8309,6 +8389,8 @@ var RelRadio = class extends import_lit23.LitElement {
|
|
|
8309
8389
|
super(...arguments);
|
|
8310
8390
|
this.options = [];
|
|
8311
8391
|
this.name = "radio-group";
|
|
8392
|
+
this.orientation = "vertical";
|
|
8393
|
+
this.size = "medium";
|
|
8312
8394
|
this.selectedValue = null;
|
|
8313
8395
|
this.disabled = false;
|
|
8314
8396
|
this.error = false;
|
|
@@ -8374,7 +8456,12 @@ var RelRadio = class extends import_lit23.LitElement {
|
|
|
8374
8456
|
}
|
|
8375
8457
|
render() {
|
|
8376
8458
|
return import_lit23.html`
|
|
8377
|
-
<div
|
|
8459
|
+
<div
|
|
8460
|
+
part="base"
|
|
8461
|
+
class="rel-radio-group"
|
|
8462
|
+
role="radiogroup"
|
|
8463
|
+
aria-orientation="${this.orientation}"
|
|
8464
|
+
>
|
|
8378
8465
|
${this.options.map((o) => this.renderOption(o))}
|
|
8379
8466
|
</div>
|
|
8380
8467
|
${this.error && this.errorMessage ? import_lit23.html`<div class="error-message">${this.errorMessage}</div>` : ""}
|
|
@@ -8395,6 +8482,15 @@ RelRadio.styles = import_lit23.css`
|
|
|
8395
8482
|
gap: var(--rel-spacing-xs);
|
|
8396
8483
|
}
|
|
8397
8484
|
|
|
8485
|
+
/* Horizontal orientation — options flow in a wrapping row. Wider column
|
|
8486
|
+
gap than the vertical rhythm so adjacent option labels read as separate
|
|
8487
|
+
choices; row gap keeps the vertical value for wrapped lines. */
|
|
8488
|
+
:host([orientation="horizontal"]) .rel-radio-group {
|
|
8489
|
+
flex-direction: row;
|
|
8490
|
+
flex-wrap: wrap;
|
|
8491
|
+
gap: var(--rel-spacing-xs) var(--rel-spacing-xl);
|
|
8492
|
+
}
|
|
8493
|
+
|
|
8398
8494
|
.rel-radio-option {
|
|
8399
8495
|
display: inline-flex;
|
|
8400
8496
|
flex-direction: column;
|
|
@@ -8497,6 +8593,34 @@ RelRadio.styles = import_lit23.css`
|
|
|
8497
8593
|
|
|
8498
8594
|
}
|
|
8499
8595
|
|
|
8596
|
+
/* Size variant (medium is the base scale above). Supporting-text indent =
|
|
8597
|
+
circle width + the radio-main gap, so it stays aligned under the label. */
|
|
8598
|
+
:host([size="small"]) .radio-custom {
|
|
8599
|
+
width: 16px;
|
|
8600
|
+
height: 16px;
|
|
8601
|
+
}
|
|
8602
|
+
|
|
8603
|
+
:host([size="small"]) input:checked + .radio-custom::after {
|
|
8604
|
+
inset: 3px;
|
|
8605
|
+
}
|
|
8606
|
+
|
|
8607
|
+
:host([size="small"]) .radio-label {
|
|
8608
|
+
font-family: var(--rel-body-md-regular-font-family);
|
|
8609
|
+
font-weight: var(--rel-body-md-regular-font-weight);
|
|
8610
|
+
font-size: var(--rel-body-md-regular-font-size);
|
|
8611
|
+
line-height: var(--rel-body-md-regular-line-height);
|
|
8612
|
+
letter-spacing: var(--rel-body-md-regular-letter-spacing);
|
|
8613
|
+
}
|
|
8614
|
+
|
|
8615
|
+
:host([size="small"]) .radio-supporting {
|
|
8616
|
+
margin-left: 24px;
|
|
8617
|
+
font-family: var(--rel-body-xs-regular-font-family);
|
|
8618
|
+
font-weight: var(--rel-body-xs-regular-font-weight);
|
|
8619
|
+
font-size: var(--rel-body-xs-regular-font-size);
|
|
8620
|
+
line-height: var(--rel-body-xs-regular-line-height);
|
|
8621
|
+
letter-spacing: var(--rel-body-xs-regular-letter-spacing);
|
|
8622
|
+
}
|
|
8623
|
+
|
|
8500
8624
|
/* Error state (Figma State=Error, both Is Checked values) */
|
|
8501
8625
|
.rel-radio-option.error-unchecked .radio-custom,
|
|
8502
8626
|
.rel-radio-option.error-checked .radio-custom {
|
|
@@ -8540,6 +8664,12 @@ __decorateClass([
|
|
|
8540
8664
|
__decorateClass([
|
|
8541
8665
|
(0, import_decorators23.property)({ type: String })
|
|
8542
8666
|
], RelRadio.prototype, "name", 2);
|
|
8667
|
+
__decorateClass([
|
|
8668
|
+
(0, import_decorators23.property)({ type: String, reflect: true })
|
|
8669
|
+
], RelRadio.prototype, "orientation", 2);
|
|
8670
|
+
__decorateClass([
|
|
8671
|
+
(0, import_decorators23.property)({ type: String, reflect: true })
|
|
8672
|
+
], RelRadio.prototype, "size", 2);
|
|
8543
8673
|
__decorateClass([
|
|
8544
8674
|
(0, import_decorators23.property)({ type: String, reflect: true })
|
|
8545
8675
|
], RelRadio.prototype, "selectedValue", 2);
|
|
@@ -13139,6 +13269,7 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13139
13269
|
this.internalState = "default";
|
|
13140
13270
|
this.visibleChipCount = Infinity;
|
|
13141
13271
|
this.uniqueId = `select-${Math.random().toString(36).slice(2)}`;
|
|
13272
|
+
this._warnedNoName = false;
|
|
13142
13273
|
this.onOutsidePointerDown = (e) => {
|
|
13143
13274
|
if (!this.open) return;
|
|
13144
13275
|
const path = e.composedPath();
|
|
@@ -13197,6 +13328,13 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13197
13328
|
}
|
|
13198
13329
|
}
|
|
13199
13330
|
updated(changed) {
|
|
13331
|
+
if (!this._warnedNoName && this.labelStyle === "none" && !this.fieldAriaLabel && !this.fieldAriaLabelledby && !this.getAttribute("aria-label")) {
|
|
13332
|
+
this._warnedNoName = true;
|
|
13333
|
+
console.warn(
|
|
13334
|
+
'<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.',
|
|
13335
|
+
this
|
|
13336
|
+
);
|
|
13337
|
+
}
|
|
13200
13338
|
if (changed.has("selectedValues") || changed.has("options") || changed.has("multiple") || changed.has("size")) {
|
|
13201
13339
|
requestAnimationFrame(() => this.computeVisibleChips());
|
|
13202
13340
|
}
|
|
@@ -13445,7 +13583,9 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13445
13583
|
const opts = this.normalizedOptions;
|
|
13446
13584
|
const filtered = this.filteredOptions;
|
|
13447
13585
|
const isDisabled = this.state === "disabled";
|
|
13586
|
+
const labelHidden = this.labelStyle === "none";
|
|
13448
13587
|
const showFloatingChrome = this.labelStyle === "floating" && !!this.label;
|
|
13588
|
+
const accessibleName = (labelHidden ? this.fieldAriaLabel : this.label) || "";
|
|
13449
13589
|
const wrapperClasses = [
|
|
13450
13590
|
"select-wrapper",
|
|
13451
13591
|
this.size,
|
|
@@ -13461,7 +13601,7 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13461
13601
|
const selectedSingle = opts.find((o) => o.value === this.selected);
|
|
13462
13602
|
return import_lit46.html`
|
|
13463
13603
|
<div class="${wrapperClasses}">
|
|
13464
|
-
${!showFloatingChrome && this.label ? import_lit46.html`<label part="label" class="select-label" for="${this.uniqueId}">${this.label}</label>` : ""}
|
|
13604
|
+
${labelHidden ? import_lit46.html`<div class="label-reserve" aria-hidden="true"></div>` : !showFloatingChrome && this.label ? import_lit46.html`<label part="label" class="select-label" for="${this.uniqueId}">${this.label}</label>` : ""}
|
|
13465
13605
|
|
|
13466
13606
|
<div class="select-container">
|
|
13467
13607
|
${showFloatingChrome ? import_lit46.html`<span class="floating-label${this.leftIcon ? " has-left-icon" : ""}"
|
|
@@ -13478,7 +13618,8 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13478
13618
|
aria-controls="${listboxId}"
|
|
13479
13619
|
aria-activedescendant="${activeOptionId}"
|
|
13480
13620
|
aria-invalid="${this.state === "error"}"
|
|
13481
|
-
aria-label="${this.
|
|
13621
|
+
aria-label="${this.fieldAriaLabelledby ? import_lit46.nothing : accessibleName || this.placeholder}"
|
|
13622
|
+
aria-labelledby="${this.fieldAriaLabelledby ?? import_lit46.nothing}"
|
|
13482
13623
|
?disabled="${isDisabled}"
|
|
13483
13624
|
@click="${this.onTriggerClick}"
|
|
13484
13625
|
@keydown="${this.onTriggerKeyDown}"
|
|
@@ -13515,7 +13656,7 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13515
13656
|
part="panel list" class="select-listbox"
|
|
13516
13657
|
role="listbox"
|
|
13517
13658
|
aria-multiselectable="${this.multiple}"
|
|
13518
|
-
aria-label="${
|
|
13659
|
+
aria-label="${accessibleName || "Options"}"
|
|
13519
13660
|
style="${this.panelStyle}"
|
|
13520
13661
|
?data-open="${this.open}"
|
|
13521
13662
|
>
|
|
@@ -13644,6 +13785,22 @@ RelSelect.styles = import_lit46.css`
|
|
|
13644
13785
|
|
|
13645
13786
|
}
|
|
13646
13787
|
|
|
13788
|
+
/* ── labelStyle="none" ──
|
|
13789
|
+
No label renders. An empty spacer holds the label row's full height so the
|
|
13790
|
+
trigger still bottom-aligns with labelled siblings in a form row.
|
|
13791
|
+
The reserve covers label line-height + the wrapper gap, and the negative
|
|
13792
|
+
margin cancels the flex gap that would otherwise sit below it — so
|
|
13793
|
+
--rel-field-label-reserve: 0 collapses the row to genuinely zero
|
|
13794
|
+
(toolbars, table cells), not just to the leftover gap. */
|
|
13795
|
+
.label-reserve {
|
|
13796
|
+
flex: none;
|
|
13797
|
+
height: var(
|
|
13798
|
+
--rel-field-label-reserve,
|
|
13799
|
+
calc(var(--rel-body-sm-regular-line-height) + var(--rel-spacing-2xs))
|
|
13800
|
+
);
|
|
13801
|
+
margin-bottom: calc(-1 * var(--rel-spacing-2xs));
|
|
13802
|
+
}
|
|
13803
|
+
|
|
13647
13804
|
/* Error only colors the border/focus ring and the supporting text below
|
|
13648
13805
|
the field — label, floating label, placeholder, and icons stay normal. */
|
|
13649
13806
|
|
|
@@ -14037,8 +14194,14 @@ __decorateClass([
|
|
|
14037
14194
|
(0, import_decorators46.property)({ type: String })
|
|
14038
14195
|
], RelSelect.prototype, "label", 2);
|
|
14039
14196
|
__decorateClass([
|
|
14040
|
-
(0, import_decorators46.property)({ type: String, attribute: "label-style" })
|
|
14197
|
+
(0, import_decorators46.property)({ type: String, attribute: "label-style", reflect: true })
|
|
14041
14198
|
], RelSelect.prototype, "labelStyle", 2);
|
|
14199
|
+
__decorateClass([
|
|
14200
|
+
(0, import_decorators46.property)({ type: String, attribute: "field-aria-label" })
|
|
14201
|
+
], RelSelect.prototype, "fieldAriaLabel", 2);
|
|
14202
|
+
__decorateClass([
|
|
14203
|
+
(0, import_decorators46.property)({ type: String, attribute: "field-aria-labelledby" })
|
|
14204
|
+
], RelSelect.prototype, "fieldAriaLabelledby", 2);
|
|
14042
14205
|
__decorateClass([
|
|
14043
14206
|
(0, import_decorators46.property)({ type: Array })
|
|
14044
14207
|
], RelSelect.prototype, "options", 2);
|
|
@@ -16637,89 +16800,1031 @@ RelTab = __decorateClass([
|
|
|
16637
16800
|
(0, import_decorators53.customElement)("rel-tab")
|
|
16638
16801
|
], RelTab);
|
|
16639
16802
|
|
|
16640
|
-
// src/components/rel-
|
|
16803
|
+
// src/components/rel-time-picker/rel-time-picker.ts
|
|
16641
16804
|
var import_lit54 = require("lit");
|
|
16642
16805
|
var import_decorators54 = require("lit/decorators.js");
|
|
16643
|
-
var
|
|
16806
|
+
var import_rel_icon16 = require("@reliability-design/icons/rel-icon");
|
|
16807
|
+
var RelTimePicker = class extends import_lit54.LitElement {
|
|
16644
16808
|
constructor() {
|
|
16645
16809
|
super(...arguments);
|
|
16646
|
-
this.
|
|
16647
|
-
this.
|
|
16648
|
-
this.
|
|
16649
|
-
this.
|
|
16650
|
-
this.
|
|
16651
|
-
this.
|
|
16652
|
-
|
|
16653
|
-
|
|
16654
|
-
|
|
16655
|
-
|
|
16656
|
-
|
|
16657
|
-
|
|
16658
|
-
|
|
16659
|
-
|
|
16810
|
+
this.value = null;
|
|
16811
|
+
this.placeholder = "Select time";
|
|
16812
|
+
this.label = null;
|
|
16813
|
+
this.disabled = false;
|
|
16814
|
+
this.required = false;
|
|
16815
|
+
this.readonly = false;
|
|
16816
|
+
this.format = "12h";
|
|
16817
|
+
this.minuteStep = 1;
|
|
16818
|
+
this.minTime = null;
|
|
16819
|
+
this.maxTime = null;
|
|
16820
|
+
this.open = false;
|
|
16821
|
+
this.clearable = false;
|
|
16822
|
+
this.error = false;
|
|
16823
|
+
this.invalid = false;
|
|
16824
|
+
this.size = "medium";
|
|
16825
|
+
this.variant = "outlined";
|
|
16826
|
+
this.fullWidth = false;
|
|
16827
|
+
this.draftHour = null;
|
|
16828
|
+
this.draftMinute = null;
|
|
16829
|
+
this.handleClickOutside = (e) => {
|
|
16830
|
+
if (!this.open) return;
|
|
16831
|
+
const path = e.composedPath();
|
|
16832
|
+
if (!path.includes(this)) {
|
|
16833
|
+
this.cancelPopover();
|
|
16660
16834
|
}
|
|
16661
|
-
}
|
|
16662
|
-
|
|
16663
|
-
if (this.
|
|
16664
|
-
|
|
16665
|
-
this._dismissTimer = null;
|
|
16835
|
+
};
|
|
16836
|
+
this.handleEscapeKey = (e) => {
|
|
16837
|
+
if (e.key === "Escape" && this.open) {
|
|
16838
|
+
this.cancelPopover();
|
|
16666
16839
|
}
|
|
16667
|
-
}
|
|
16840
|
+
};
|
|
16841
|
+
}
|
|
16842
|
+
connectedCallback() {
|
|
16843
|
+
super.connectedCallback();
|
|
16844
|
+
document.addEventListener("click", this.handleClickOutside);
|
|
16845
|
+
document.addEventListener("keydown", this.handleEscapeKey);
|
|
16668
16846
|
}
|
|
16669
16847
|
disconnectedCallback() {
|
|
16670
16848
|
super.disconnectedCallback();
|
|
16671
|
-
|
|
16849
|
+
document.removeEventListener("click", this.handleClickOutside);
|
|
16850
|
+
document.removeEventListener("keydown", this.handleEscapeKey);
|
|
16672
16851
|
}
|
|
16673
|
-
|
|
16674
|
-
this.
|
|
16852
|
+
updated(changedProperties) {
|
|
16853
|
+
if (changedProperties.has("open") && this.open) {
|
|
16854
|
+
this.resetDraftFromValue();
|
|
16855
|
+
setTimeout(() => {
|
|
16856
|
+
this.scrollColumnsToSelection();
|
|
16857
|
+
const target = this.columnsElement?.querySelector(
|
|
16858
|
+
".option-button.selected:not(:disabled)"
|
|
16859
|
+
) ?? this.columnsElement?.querySelector(
|
|
16860
|
+
".option-button:not(:disabled)"
|
|
16861
|
+
);
|
|
16862
|
+
target?.focus();
|
|
16863
|
+
}, 0);
|
|
16864
|
+
}
|
|
16675
16865
|
}
|
|
16676
|
-
|
|
16677
|
-
|
|
16678
|
-
|
|
16679
|
-
|
|
16680
|
-
|
|
16681
|
-
|
|
16866
|
+
// ── Parsing / formatting ──
|
|
16867
|
+
parseTime(time) {
|
|
16868
|
+
if (!time) return null;
|
|
16869
|
+
const match = time.match(/^(\d{1,2}):(\d{2})$/);
|
|
16870
|
+
if (!match) return null;
|
|
16871
|
+
const hour = parseInt(match[1], 10);
|
|
16872
|
+
const minute = parseInt(match[2], 10);
|
|
16873
|
+
if (hour > 23 || minute > 59) return null;
|
|
16874
|
+
return { hour, minute };
|
|
16875
|
+
}
|
|
16876
|
+
formatValue(hour, minute) {
|
|
16877
|
+
return `${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}`;
|
|
16878
|
+
}
|
|
16879
|
+
formatDisplay(hour, minute) {
|
|
16880
|
+
const mm = String(minute).padStart(2, "0");
|
|
16881
|
+
if (this.format === "24h") {
|
|
16882
|
+
return `${String(hour).padStart(2, "0")}:${mm}`;
|
|
16883
|
+
}
|
|
16884
|
+
const period = hour < 12 ? "AM" : "PM";
|
|
16885
|
+
const hour12 = hour % 12 === 0 ? 12 : hour % 12;
|
|
16886
|
+
return `${hour12}:${mm} ${period}`;
|
|
16887
|
+
}
|
|
16888
|
+
toMinutes(hour, minute) {
|
|
16889
|
+
return hour * 60 + minute;
|
|
16890
|
+
}
|
|
16891
|
+
get minuteStepSafe() {
|
|
16892
|
+
const step = Math.floor(this.minuteStep);
|
|
16893
|
+
return step >= 1 && step <= 60 ? step : 1;
|
|
16894
|
+
}
|
|
16895
|
+
get minBoundMinutes() {
|
|
16896
|
+
const parsed = this.parseTime(this.minTime);
|
|
16897
|
+
return parsed ? this.toMinutes(parsed.hour, parsed.minute) : 0;
|
|
16898
|
+
}
|
|
16899
|
+
get maxBoundMinutes() {
|
|
16900
|
+
const parsed = this.parseTime(this.maxTime);
|
|
16901
|
+
return parsed ? this.toMinutes(parsed.hour, parsed.minute) : 23 * 60 + 59;
|
|
16902
|
+
}
|
|
16903
|
+
isTimeInRange(hour, minute) {
|
|
16904
|
+
const total = this.toMinutes(hour, minute);
|
|
16905
|
+
return total >= this.minBoundMinutes && total <= this.maxBoundMinutes;
|
|
16906
|
+
}
|
|
16907
|
+
/** Minutes shown in the minutes column (respecting minuteStep). */
|
|
16908
|
+
getMinuteOptions() {
|
|
16909
|
+
const minutes = [];
|
|
16910
|
+
for (let m = 0; m < 60; m += this.minuteStepSafe) {
|
|
16911
|
+
minutes.push(m);
|
|
16912
|
+
}
|
|
16913
|
+
return minutes;
|
|
16914
|
+
}
|
|
16915
|
+
/** An hour is enabled if at least one of its step minutes is in range. */
|
|
16916
|
+
isHourEnabled(hour) {
|
|
16917
|
+
return this.getMinuteOptions().some((m) => this.isTimeInRange(hour, m));
|
|
16918
|
+
}
|
|
16919
|
+
isPeriodEnabled(period) {
|
|
16920
|
+
const start = period === "AM" ? 0 : 12;
|
|
16921
|
+
for (let h = start; h < start + 12; h++) {
|
|
16922
|
+
if (this.isHourEnabled(h)) return true;
|
|
16923
|
+
}
|
|
16924
|
+
return false;
|
|
16925
|
+
}
|
|
16926
|
+
/** Hour column entries: display label + the 0–23 hour it maps to. */
|
|
16927
|
+
getHourOptions() {
|
|
16928
|
+
if (this.format === "24h") {
|
|
16929
|
+
return Array.from({ length: 24 }, (_, h) => ({
|
|
16930
|
+
label: String(h).padStart(2, "0"),
|
|
16931
|
+
hour24: h
|
|
16932
|
+
}));
|
|
16933
|
+
}
|
|
16934
|
+
const period = this.draftPeriod ?? "AM";
|
|
16935
|
+
const base = period === "AM" ? 0 : 12;
|
|
16936
|
+
return Array.from({ length: 12 }, (_, i) => ({
|
|
16937
|
+
label: i === 0 ? "12" : String(i).padStart(2, "0"),
|
|
16938
|
+
hour24: base + i
|
|
16939
|
+
}));
|
|
16940
|
+
}
|
|
16941
|
+
get draftPeriod() {
|
|
16942
|
+
if (this.draftHour === null) return null;
|
|
16943
|
+
return this.draftHour < 12 ? "AM" : "PM";
|
|
16944
|
+
}
|
|
16945
|
+
resetDraftFromValue() {
|
|
16946
|
+
const parsed = this.parseTime(this.value);
|
|
16947
|
+
this.draftHour = parsed ? parsed.hour : null;
|
|
16948
|
+
this.draftMinute = parsed ? parsed.minute : null;
|
|
16949
|
+
}
|
|
16950
|
+
/** Snap the draft minute to the nearest valid step minute for the hour. */
|
|
16951
|
+
clampDraftToRange() {
|
|
16952
|
+
if (this.draftHour === null || this.draftMinute === null) return;
|
|
16953
|
+
if (this.isTimeInRange(this.draftHour, this.draftMinute)) return;
|
|
16954
|
+
const hour = this.draftHour;
|
|
16955
|
+
const valid = this.getMinuteOptions().filter((m) => this.isTimeInRange(hour, m));
|
|
16956
|
+
if (valid.length === 0) {
|
|
16957
|
+
this.draftMinute = null;
|
|
16958
|
+
return;
|
|
16682
16959
|
}
|
|
16960
|
+
const current = this.draftMinute;
|
|
16961
|
+
this.draftMinute = valid.reduce(
|
|
16962
|
+
(best, m) => Math.abs(m - current) < Math.abs(best - current) ? m : best
|
|
16963
|
+
);
|
|
16964
|
+
}
|
|
16965
|
+
// ── Open / close / commit ──
|
|
16966
|
+
toggleTimePopover() {
|
|
16967
|
+
if (this.disabled || this.readonly) return;
|
|
16968
|
+
this.open = !this.open;
|
|
16683
16969
|
this.dispatchEvent(
|
|
16684
|
-
new CustomEvent("
|
|
16970
|
+
new CustomEvent("rel-open-change", {
|
|
16971
|
+
detail: { open: this.open },
|
|
16685
16972
|
bubbles: true,
|
|
16686
16973
|
composed: true
|
|
16687
16974
|
})
|
|
16688
16975
|
);
|
|
16689
16976
|
}
|
|
16690
|
-
|
|
16691
|
-
|
|
16977
|
+
/** Close discarding the draft (Cancel / Escape / outside click). */
|
|
16978
|
+
cancelPopover() {
|
|
16979
|
+
this.open = false;
|
|
16980
|
+
this.triggerButton?.focus();
|
|
16692
16981
|
this.dispatchEvent(
|
|
16693
|
-
new CustomEvent("
|
|
16982
|
+
new CustomEvent("rel-open-change", {
|
|
16983
|
+
detail: { open: false },
|
|
16694
16984
|
bubbles: true,
|
|
16695
16985
|
composed: true
|
|
16696
16986
|
})
|
|
16697
16987
|
);
|
|
16698
|
-
this.close();
|
|
16699
16988
|
}
|
|
16989
|
+
applySelection() {
|
|
16990
|
+
if (this.draftHour === null) return;
|
|
16991
|
+
const newValue = this.formatValue(this.draftHour, this.draftMinute ?? 0);
|
|
16992
|
+
this.value = newValue;
|
|
16993
|
+
this.dispatchEvent(
|
|
16994
|
+
new CustomEvent("rel-change", {
|
|
16995
|
+
detail: { value: newValue },
|
|
16996
|
+
bubbles: true,
|
|
16997
|
+
composed: true
|
|
16998
|
+
})
|
|
16999
|
+
);
|
|
17000
|
+
this.cancelPopover();
|
|
17001
|
+
}
|
|
17002
|
+
/** Set the draft to the current time, snapped to the minute step and range. */
|
|
17003
|
+
selectNow() {
|
|
17004
|
+
const now = /* @__PURE__ */ new Date();
|
|
17005
|
+
const step = this.minuteStepSafe;
|
|
17006
|
+
this.draftHour = now.getHours();
|
|
17007
|
+
this.draftMinute = Math.round(now.getMinutes() / step) * step;
|
|
17008
|
+
if (this.draftMinute >= 60) {
|
|
17009
|
+
this.draftMinute = 0;
|
|
17010
|
+
this.draftHour = (this.draftHour + 1) % 24;
|
|
17011
|
+
}
|
|
17012
|
+
if (!this.isHourEnabled(this.draftHour)) {
|
|
17013
|
+
const enabled = [];
|
|
17014
|
+
for (let h = 0; h < 24; h++) if (this.isHourEnabled(h)) enabled.push(h);
|
|
17015
|
+
if (enabled.length === 0) return;
|
|
17016
|
+
const currentHour = this.draftHour;
|
|
17017
|
+
this.draftHour = enabled.reduce(
|
|
17018
|
+
(best, h) => Math.abs(h - currentHour) < Math.abs(best - currentHour) ? h : best
|
|
17019
|
+
);
|
|
17020
|
+
}
|
|
17021
|
+
this.clampDraftToRange();
|
|
17022
|
+
this.scrollColumnsToSelection();
|
|
17023
|
+
}
|
|
17024
|
+
clearValue(e) {
|
|
17025
|
+
e.stopPropagation();
|
|
17026
|
+
this.value = null;
|
|
17027
|
+
this.draftHour = null;
|
|
17028
|
+
this.draftMinute = null;
|
|
17029
|
+
this.dispatchEvent(
|
|
17030
|
+
new CustomEvent("rel-change", {
|
|
17031
|
+
detail: { value: null },
|
|
17032
|
+
bubbles: true,
|
|
17033
|
+
composed: true
|
|
17034
|
+
})
|
|
17035
|
+
);
|
|
17036
|
+
}
|
|
17037
|
+
// ── Column selection ──
|
|
17038
|
+
selectHour(hour24) {
|
|
17039
|
+
if (!this.isHourEnabled(hour24)) return;
|
|
17040
|
+
this.draftHour = hour24;
|
|
17041
|
+
if (this.draftMinute === null) this.draftMinute = 0;
|
|
17042
|
+
this.clampDraftToRange();
|
|
17043
|
+
}
|
|
17044
|
+
selectMinute(minute) {
|
|
17045
|
+
if (this.draftHour === null) {
|
|
17046
|
+
for (let h = 0; h < 24; h++) {
|
|
17047
|
+
if (this.isHourEnabled(h)) {
|
|
17048
|
+
this.draftHour = h;
|
|
17049
|
+
break;
|
|
17050
|
+
}
|
|
17051
|
+
}
|
|
17052
|
+
if (this.draftHour === null) return;
|
|
17053
|
+
}
|
|
17054
|
+
if (!this.isTimeInRange(this.draftHour, minute)) return;
|
|
17055
|
+
this.draftMinute = minute;
|
|
17056
|
+
}
|
|
17057
|
+
selectPeriod(period) {
|
|
17058
|
+
if (!this.isPeriodEnabled(period)) return;
|
|
17059
|
+
const base = period === "AM" ? 0 : 12;
|
|
17060
|
+
const hour12 = this.draftHour === null ? 0 : this.draftHour % 12;
|
|
17061
|
+
this.draftHour = base + hour12;
|
|
17062
|
+
if (this.draftMinute === null) this.draftMinute = 0;
|
|
17063
|
+
if (!this.isHourEnabled(this.draftHour)) {
|
|
17064
|
+
const enabled = [];
|
|
17065
|
+
for (let h = base; h < base + 12; h++) if (this.isHourEnabled(h)) enabled.push(h);
|
|
17066
|
+
const currentHour = this.draftHour;
|
|
17067
|
+
this.draftHour = enabled.reduce(
|
|
17068
|
+
(best, h) => Math.abs(h - currentHour) < Math.abs(best - currentHour) ? h : best
|
|
17069
|
+
);
|
|
17070
|
+
}
|
|
17071
|
+
this.clampDraftToRange();
|
|
17072
|
+
this.scrollColumnsToSelection();
|
|
17073
|
+
}
|
|
17074
|
+
scrollColumnsToSelection() {
|
|
17075
|
+
this.updateComplete.then(() => {
|
|
17076
|
+
this.columnsElement?.querySelectorAll(".option-button.selected").forEach((btn) => btn.scrollIntoView({ block: "center" }));
|
|
17077
|
+
});
|
|
17078
|
+
}
|
|
17079
|
+
// ── Keyboard ──
|
|
17080
|
+
handleTriggerKeydown(e) {
|
|
17081
|
+
if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown") {
|
|
17082
|
+
e.preventDefault();
|
|
17083
|
+
if (!this.open) {
|
|
17084
|
+
this.toggleTimePopover();
|
|
17085
|
+
}
|
|
17086
|
+
}
|
|
17087
|
+
}
|
|
17088
|
+
/**
|
|
17089
|
+
* Column navigation: Up/Down move within the column (and select), Left/Right
|
|
17090
|
+
* jump between columns, Home/End go to the first/last option, Enter applies.
|
|
17091
|
+
*/
|
|
17092
|
+
handleOptionKeydown(e, column) {
|
|
17093
|
+
const list = e.currentTarget.closest(".column-list");
|
|
17094
|
+
if (!list) return;
|
|
17095
|
+
const buttons = Array.from(
|
|
17096
|
+
list.querySelectorAll(".option-button:not(:disabled)")
|
|
17097
|
+
);
|
|
17098
|
+
const index = buttons.indexOf(e.currentTarget);
|
|
17099
|
+
let target;
|
|
17100
|
+
switch (e.key) {
|
|
17101
|
+
case "ArrowUp":
|
|
17102
|
+
e.preventDefault();
|
|
17103
|
+
target = buttons[Math.max(0, index - 1)];
|
|
17104
|
+
break;
|
|
17105
|
+
case "ArrowDown":
|
|
17106
|
+
e.preventDefault();
|
|
17107
|
+
target = buttons[Math.min(buttons.length - 1, index + 1)];
|
|
17108
|
+
break;
|
|
17109
|
+
case "Home":
|
|
17110
|
+
e.preventDefault();
|
|
17111
|
+
target = buttons[0];
|
|
17112
|
+
break;
|
|
17113
|
+
case "End":
|
|
17114
|
+
e.preventDefault();
|
|
17115
|
+
target = buttons[buttons.length - 1];
|
|
17116
|
+
break;
|
|
17117
|
+
case "ArrowLeft":
|
|
17118
|
+
case "ArrowRight": {
|
|
17119
|
+
e.preventDefault();
|
|
17120
|
+
this.focusAdjacentColumn(column, e.key === "ArrowRight" ? 1 : -1);
|
|
17121
|
+
return;
|
|
17122
|
+
}
|
|
17123
|
+
case "Enter":
|
|
17124
|
+
e.preventDefault();
|
|
17125
|
+
e.currentTarget.click();
|
|
17126
|
+
this.applySelection();
|
|
17127
|
+
return;
|
|
17128
|
+
default:
|
|
17129
|
+
return;
|
|
17130
|
+
}
|
|
17131
|
+
if (target && target !== buttons[index]) {
|
|
17132
|
+
target.click();
|
|
17133
|
+
target.focus();
|
|
17134
|
+
target.scrollIntoView({ block: "nearest" });
|
|
17135
|
+
}
|
|
17136
|
+
}
|
|
17137
|
+
focusAdjacentColumn(from, direction) {
|
|
17138
|
+
const order = this.format === "12h" ? ["hour", "minute", "period"] : ["hour", "minute"];
|
|
17139
|
+
const nextIndex = order.indexOf(from) + direction;
|
|
17140
|
+
if (nextIndex < 0 || nextIndex >= order.length) return;
|
|
17141
|
+
const lists = this.columnsElement?.querySelectorAll(".column-list");
|
|
17142
|
+
const list = lists?.[nextIndex];
|
|
17143
|
+
const target = list?.querySelector(".option-button.selected:not(:disabled)") ?? list?.querySelector(".option-button:not(:disabled)");
|
|
17144
|
+
target?.focus();
|
|
17145
|
+
target?.scrollIntoView({ block: "nearest" });
|
|
17146
|
+
}
|
|
17147
|
+
// ── Render ──
|
|
16700
17148
|
render() {
|
|
17149
|
+
const isError = this.error || this.invalid;
|
|
17150
|
+
const belowText = isError && this.errorMessage ? this.errorMessage : this.helperText;
|
|
17151
|
+
const describedById = belowText ? "time-picker-desc" : void 0;
|
|
17152
|
+
const parsedValue = this.parseTime(this.value);
|
|
17153
|
+
const displayText = parsedValue ? this.formatDisplay(parsedValue.hour, parsedValue.minute) : "";
|
|
17154
|
+
const draftDisplay = this.draftHour !== null ? this.formatDisplay(this.draftHour, this.draftMinute ?? 0) : "";
|
|
17155
|
+
const hourOptions = this.getHourOptions();
|
|
17156
|
+
const minuteOptions = this.getMinuteOptions();
|
|
17157
|
+
const draftPeriod = this.draftPeriod;
|
|
17158
|
+
const wrapperClasses = [
|
|
17159
|
+
"time-picker-wrapper",
|
|
17160
|
+
this.size,
|
|
17161
|
+
isError ? "error" : "",
|
|
17162
|
+
this.readonly ? "readonly" : ""
|
|
17163
|
+
].filter(Boolean).join(" ");
|
|
16701
17164
|
return import_lit54.html`
|
|
16702
|
-
<div part="base" class="
|
|
16703
|
-
|
|
17165
|
+
<div part="base" class="${wrapperClasses}">
|
|
17166
|
+
${this.label ? import_lit54.html`<label part="label" class="label" id="time-picker-label"
|
|
17167
|
+
>${this.label}${this.required ? import_lit54.html`<span class="required-mark" aria-hidden="true">*</span>` : import_lit54.nothing}</label
|
|
17168
|
+
>` : import_lit54.nothing}
|
|
16704
17169
|
|
|
16705
|
-
${this.
|
|
16706
|
-
part="action" class="toast-action"
|
|
16707
|
-
@click="${this.handleActionClick}"
|
|
16708
|
-
>
|
|
16709
|
-
${this.actionLabel}
|
|
16710
|
-
</button>` : ""}
|
|
17170
|
+
${this.name ? import_lit54.html`<input type="hidden" name=${this.name} .value=${this.value ?? ""} />` : import_lit54.nothing}
|
|
16711
17171
|
|
|
16712
|
-
<button
|
|
16713
|
-
|
|
16714
|
-
|
|
16715
|
-
|
|
16716
|
-
}
|
|
16717
|
-
|
|
16718
|
-
|
|
16719
|
-
|
|
16720
|
-
|
|
16721
|
-
|
|
16722
|
-
|
|
17172
|
+
<button
|
|
17173
|
+
part="trigger"
|
|
17174
|
+
class="trigger-button ${isError ? "error" : ""}"
|
|
17175
|
+
type="button"
|
|
17176
|
+
?disabled=${this.disabled}
|
|
17177
|
+
@click=${this.toggleTimePopover}
|
|
17178
|
+
@keydown=${this.handleTriggerKeydown}
|
|
17179
|
+
@focus=${() => this.dispatchEvent(
|
|
17180
|
+
new CustomEvent("rel-focus", { bubbles: true, composed: true })
|
|
17181
|
+
)}
|
|
17182
|
+
@blur=${() => this.dispatchEvent(
|
|
17183
|
+
new CustomEvent("rel-blur", { bubbles: true, composed: true })
|
|
17184
|
+
)}
|
|
17185
|
+
aria-label=${this.label || "Select time"}
|
|
17186
|
+
aria-haspopup="dialog"
|
|
17187
|
+
aria-expanded=${this.open}
|
|
17188
|
+
aria-required=${this.required}
|
|
17189
|
+
aria-invalid=${isError}
|
|
17190
|
+
aria-readonly=${this.readonly}
|
|
17191
|
+
aria-describedby=${describedById ?? import_lit54.nothing}
|
|
17192
|
+
aria-labelledby=${this.label ? "time-picker-label" : import_lit54.nothing}
|
|
17193
|
+
>
|
|
17194
|
+
<span part="value" class="trigger-content ${displayText ? "" : "placeholder"}">
|
|
17195
|
+
${displayText || this.placeholder}
|
|
17196
|
+
</span>
|
|
17197
|
+
<span class="trigger-icons">
|
|
17198
|
+
${this.clearable && this.value ? import_lit54.html`
|
|
17199
|
+
<button
|
|
17200
|
+
class="icon-button"
|
|
17201
|
+
type="button"
|
|
17202
|
+
aria-label="Clear time"
|
|
17203
|
+
@click=${this.clearValue}
|
|
17204
|
+
>
|
|
17205
|
+
<svg viewBox="0 0 16 16" fill="currentColor">
|
|
17206
|
+
<path
|
|
17207
|
+
d="M4 4l8 8M12 4l-8 8"
|
|
17208
|
+
stroke="currentColor"
|
|
17209
|
+
stroke-width="2"
|
|
17210
|
+
stroke-linecap="round"
|
|
17211
|
+
/>
|
|
17212
|
+
</svg>
|
|
17213
|
+
</button>
|
|
17214
|
+
` : import_lit54.nothing}
|
|
17215
|
+
<rel-icon part="icon" name="time-filled" size="sm"></rel-icon>
|
|
17216
|
+
</span>
|
|
17217
|
+
</button>
|
|
17218
|
+
|
|
17219
|
+
<div
|
|
17220
|
+
part="panel"
|
|
17221
|
+
class="popover ${this.open ? "open" : ""}"
|
|
17222
|
+
role="dialog"
|
|
17223
|
+
aria-label="Choose time"
|
|
17224
|
+
>
|
|
17225
|
+
<div class="time-readout">
|
|
17226
|
+
<span class="time-readout-label">Time</span>
|
|
17227
|
+
<span class="time-readout-value ${draftDisplay ? "" : "empty"}">
|
|
17228
|
+
${draftDisplay || (this.format === "24h" ? "--:--" : "--:-- --")}
|
|
17229
|
+
</span>
|
|
17230
|
+
</div>
|
|
17231
|
+
|
|
17232
|
+
<div class="columns">
|
|
17233
|
+
<div class="column">
|
|
17234
|
+
<div class="column-header" aria-hidden="true">Hour</div>
|
|
17235
|
+
<div class="column-list" role="listbox" aria-label="Hours">
|
|
17236
|
+
${hourOptions.map(
|
|
17237
|
+
(opt, i) => import_lit54.html`
|
|
17238
|
+
<button
|
|
17239
|
+
part="option"
|
|
17240
|
+
class="option-button ${this.draftHour === opt.hour24 ? "selected" : ""}"
|
|
17241
|
+
type="button"
|
|
17242
|
+
role="option"
|
|
17243
|
+
aria-selected=${this.draftHour === opt.hour24}
|
|
17244
|
+
?disabled=${!this.isHourEnabled(opt.hour24)}
|
|
17245
|
+
@click=${() => this.selectHour(opt.hour24)}
|
|
17246
|
+
@keydown=${(e) => this.handleOptionKeydown(e, "hour")}
|
|
17247
|
+
tabindex=${this.draftHour === opt.hour24 || this.draftHour === null && i === 0 ? 0 : -1}
|
|
17248
|
+
>
|
|
17249
|
+
${opt.label}
|
|
17250
|
+
</button>
|
|
17251
|
+
`
|
|
17252
|
+
)}
|
|
17253
|
+
</div>
|
|
17254
|
+
</div>
|
|
17255
|
+
|
|
17256
|
+
<div class="column">
|
|
17257
|
+
<div class="column-header" aria-hidden="true">Min</div>
|
|
17258
|
+
<div class="column-list" role="listbox" aria-label="Minutes">
|
|
17259
|
+
${minuteOptions.map(
|
|
17260
|
+
(m, i) => import_lit54.html`
|
|
17261
|
+
<button
|
|
17262
|
+
part="option"
|
|
17263
|
+
class="option-button ${this.draftMinute === m ? "selected" : ""}"
|
|
17264
|
+
type="button"
|
|
17265
|
+
role="option"
|
|
17266
|
+
aria-selected=${this.draftMinute === m}
|
|
17267
|
+
?disabled=${this.draftHour !== null && !this.isTimeInRange(this.draftHour, m)}
|
|
17268
|
+
@click=${() => this.selectMinute(m)}
|
|
17269
|
+
@keydown=${(e) => this.handleOptionKeydown(e, "minute")}
|
|
17270
|
+
tabindex=${this.draftMinute === m || this.draftMinute === null && i === 0 ? 0 : -1}
|
|
17271
|
+
>
|
|
17272
|
+
${String(m).padStart(2, "0")}
|
|
17273
|
+
</button>
|
|
17274
|
+
`
|
|
17275
|
+
)}
|
|
17276
|
+
</div>
|
|
17277
|
+
</div>
|
|
17278
|
+
|
|
17279
|
+
${this.format === "12h" ? import_lit54.html`
|
|
17280
|
+
<div class="column">
|
|
17281
|
+
<div class="column-header" aria-hidden="true">AM/PM</div>
|
|
17282
|
+
<div class="column-list" role="listbox" aria-label="AM or PM">
|
|
17283
|
+
${["AM", "PM"].map(
|
|
17284
|
+
(p) => import_lit54.html`
|
|
17285
|
+
<button
|
|
17286
|
+
part="option"
|
|
17287
|
+
class="option-button ${draftPeriod === p ? "selected" : ""}"
|
|
17288
|
+
type="button"
|
|
17289
|
+
role="option"
|
|
17290
|
+
aria-selected=${draftPeriod === p}
|
|
17291
|
+
?disabled=${!this.isPeriodEnabled(p)}
|
|
17292
|
+
@click=${() => this.selectPeriod(p)}
|
|
17293
|
+
@keydown=${(e) => this.handleOptionKeydown(e, "period")}
|
|
17294
|
+
tabindex=${draftPeriod === p || draftPeriod === null && p === "AM" ? 0 : -1}
|
|
17295
|
+
>
|
|
17296
|
+
${p}
|
|
17297
|
+
</button>
|
|
17298
|
+
`
|
|
17299
|
+
)}
|
|
17300
|
+
</div>
|
|
17301
|
+
</div>
|
|
17302
|
+
` : import_lit54.nothing}
|
|
17303
|
+
</div>
|
|
17304
|
+
|
|
17305
|
+
<div part="footer" class="time-footer">
|
|
17306
|
+
<rel-button
|
|
17307
|
+
variant="secondary"
|
|
17308
|
+
size="small"
|
|
17309
|
+
border="corner-radius"
|
|
17310
|
+
@click=${() => this.selectNow()}
|
|
17311
|
+
>
|
|
17312
|
+
Now
|
|
17313
|
+
</rel-button>
|
|
17314
|
+
<div class="footer-right">
|
|
17315
|
+
<rel-button
|
|
17316
|
+
variant="secondary"
|
|
17317
|
+
size="small"
|
|
17318
|
+
border="corner-radius"
|
|
17319
|
+
@click=${() => this.cancelPopover()}
|
|
17320
|
+
>
|
|
17321
|
+
Cancel
|
|
17322
|
+
</rel-button>
|
|
17323
|
+
<rel-button
|
|
17324
|
+
variant="primary"
|
|
17325
|
+
tone="default"
|
|
17326
|
+
size="small"
|
|
17327
|
+
border="corner-radius"
|
|
17328
|
+
?disabled=${this.draftHour === null}
|
|
17329
|
+
@click=${() => this.applySelection()}
|
|
17330
|
+
>
|
|
17331
|
+
Apply
|
|
17332
|
+
</rel-button>
|
|
17333
|
+
</div>
|
|
17334
|
+
</div>
|
|
17335
|
+
</div>
|
|
17336
|
+
|
|
17337
|
+
${belowText ? import_lit54.html`<div
|
|
17338
|
+
part="helper"
|
|
17339
|
+
class="time-picker-support ${isError ? "error" : ""}"
|
|
17340
|
+
id=${describedById}
|
|
17341
|
+
>
|
|
17342
|
+
${belowText}
|
|
17343
|
+
</div>` : import_lit54.nothing}
|
|
17344
|
+
</div>
|
|
17345
|
+
`;
|
|
17346
|
+
}
|
|
17347
|
+
};
|
|
17348
|
+
RelTimePicker.styles = import_lit54.css`
|
|
17349
|
+
:host {
|
|
17350
|
+
color: var(--rel-text-default);
|
|
17351
|
+
display: block;
|
|
17352
|
+
font-family: var(--rel-fonts-font-family);
|
|
17353
|
+
position: relative;
|
|
17354
|
+
}
|
|
17355
|
+
|
|
17356
|
+
/* Intrinsic width by default; set fullWidth to fill the parent. */
|
|
17357
|
+
:host(:not([fullwidth])) {
|
|
17358
|
+
display: inline-block;
|
|
17359
|
+
width: auto;
|
|
17360
|
+
}
|
|
17361
|
+
|
|
17362
|
+
:host([disabled]) {
|
|
17363
|
+
opacity: 0.6;
|
|
17364
|
+
cursor: not-allowed;
|
|
17365
|
+
}
|
|
17366
|
+
|
|
17367
|
+
:host([disabled]) * {
|
|
17368
|
+
pointer-events: none;
|
|
17369
|
+
}
|
|
17370
|
+
|
|
17371
|
+
:host([readonly]) .trigger-button {
|
|
17372
|
+
cursor: default;
|
|
17373
|
+
}
|
|
17374
|
+
|
|
17375
|
+
.time-picker-wrapper {
|
|
17376
|
+
display: flex;
|
|
17377
|
+
flex-direction: column;
|
|
17378
|
+
gap: var(--rel-spacing-xs);
|
|
17379
|
+
}
|
|
17380
|
+
|
|
17381
|
+
.label {
|
|
17382
|
+
color: var(--rel-text-label);
|
|
17383
|
+
font-family: var(--rel-body-sm-medium-font-family);
|
|
17384
|
+
font-weight: var(--rel-body-sm-medium-font-weight);
|
|
17385
|
+
font-size: var(--rel-body-sm-medium-font-size);
|
|
17386
|
+
line-height: var(--rel-body-sm-medium-line-height);
|
|
17387
|
+
letter-spacing: var(--rel-body-sm-medium-letter-spacing);
|
|
17388
|
+
}
|
|
17389
|
+
|
|
17390
|
+
.required-mark {
|
|
17391
|
+
color: var(--rel-text-danger);
|
|
17392
|
+
margin-left: 2px;
|
|
17393
|
+
}
|
|
17394
|
+
|
|
17395
|
+
/* Support text (helperText / errorMessage) */
|
|
17396
|
+
.time-picker-support {
|
|
17397
|
+
color: var(--rel-text-placeholder);
|
|
17398
|
+
font-family: var(--rel-body-xs-regular-font-family);
|
|
17399
|
+
font-weight: var(--rel-body-xs-regular-font-weight);
|
|
17400
|
+
font-size: var(--rel-body-xs-regular-font-size);
|
|
17401
|
+
line-height: var(--rel-body-xs-regular-line-height);
|
|
17402
|
+
letter-spacing: var(--rel-body-xs-regular-letter-spacing);
|
|
17403
|
+
}
|
|
17404
|
+
|
|
17405
|
+
.time-picker-support.error {
|
|
17406
|
+
color: var(--rel-text-danger);
|
|
17407
|
+
}
|
|
17408
|
+
|
|
17409
|
+
.trigger-button {
|
|
17410
|
+
width: 100%;
|
|
17411
|
+
min-width: 160px;
|
|
17412
|
+
display: flex;
|
|
17413
|
+
align-items: center;
|
|
17414
|
+
justify-content: space-between;
|
|
17415
|
+
gap: var(--rel-spacing-xs);
|
|
17416
|
+
padding: var(--rel-spacing-sm) var(--rel-spacing-md);
|
|
17417
|
+
background: var(--rel-bg-surface);
|
|
17418
|
+
border: 1px solid var(--rel-border-default);
|
|
17419
|
+
border-radius: var(--rel-border-radius-sm);
|
|
17420
|
+
color: var(--rel-text-default);
|
|
17421
|
+
cursor: pointer;
|
|
17422
|
+
transition: all 0.15s ease;
|
|
17423
|
+
text-align: left;
|
|
17424
|
+
font-family: var(--rel-body-md-regular-font-family);
|
|
17425
|
+
font-weight: var(--rel-body-md-regular-font-weight);
|
|
17426
|
+
font-size: var(--rel-body-md-regular-font-size);
|
|
17427
|
+
line-height: var(--rel-body-md-regular-line-height);
|
|
17428
|
+
letter-spacing: var(--rel-body-md-regular-letter-spacing);
|
|
17429
|
+
}
|
|
17430
|
+
|
|
17431
|
+
.trigger-button:hover:not(:disabled) {
|
|
17432
|
+
border-color: var(--rel-border-strong);
|
|
17433
|
+
}
|
|
17434
|
+
|
|
17435
|
+
.trigger-button:focus-visible {
|
|
17436
|
+
outline: 2px solid var(--rel-border-focus);
|
|
17437
|
+
outline-offset: 2px;
|
|
17438
|
+
border-color: var(--rel-border-focus);
|
|
17439
|
+
}
|
|
17440
|
+
|
|
17441
|
+
.trigger-button:disabled {
|
|
17442
|
+
cursor: not-allowed;
|
|
17443
|
+
background: var(--rel-bg-surface-elevated);
|
|
17444
|
+
}
|
|
17445
|
+
|
|
17446
|
+
/* Error state */
|
|
17447
|
+
.trigger-button.error,
|
|
17448
|
+
.trigger-button.error:hover:not(:disabled) {
|
|
17449
|
+
border-color: var(--rel-border-danger);
|
|
17450
|
+
}
|
|
17451
|
+
|
|
17452
|
+
.trigger-button.error:focus-visible {
|
|
17453
|
+
outline-color: var(--rel-border-danger);
|
|
17454
|
+
border-color: var(--rel-border-danger);
|
|
17455
|
+
}
|
|
17456
|
+
|
|
17457
|
+
/* Size variants (medium is the base .trigger-button style) */
|
|
17458
|
+
.time-picker-wrapper.small .trigger-button {
|
|
17459
|
+
padding: var(--rel-spacing-xs) var(--rel-spacing-sm);
|
|
17460
|
+
font-family: var(--rel-body-sm-regular-font-family);
|
|
17461
|
+
font-weight: var(--rel-body-sm-regular-font-weight);
|
|
17462
|
+
font-size: var(--rel-body-sm-regular-font-size);
|
|
17463
|
+
line-height: var(--rel-body-sm-regular-line-height);
|
|
17464
|
+
letter-spacing: var(--rel-body-sm-regular-letter-spacing);
|
|
17465
|
+
}
|
|
17466
|
+
|
|
17467
|
+
.time-picker-wrapper.large .trigger-button {
|
|
17468
|
+
padding: var(--rel-spacing-md) var(--rel-spacing-md);
|
|
17469
|
+
font-family: var(--rel-body-lg-regular-font-family);
|
|
17470
|
+
font-weight: var(--rel-body-lg-regular-font-weight);
|
|
17471
|
+
font-size: var(--rel-body-lg-regular-font-size);
|
|
17472
|
+
line-height: var(--rel-body-lg-regular-line-height);
|
|
17473
|
+
letter-spacing: var(--rel-body-lg-regular-letter-spacing);
|
|
17474
|
+
}
|
|
17475
|
+
|
|
17476
|
+
.trigger-content {
|
|
17477
|
+
flex: 1;
|
|
17478
|
+
color: var(--rel-text-default);
|
|
17479
|
+
}
|
|
17480
|
+
|
|
17481
|
+
.trigger-content.placeholder {
|
|
17482
|
+
color: var(--rel-text-placeholder);
|
|
17483
|
+
}
|
|
17484
|
+
|
|
17485
|
+
.trigger-icons {
|
|
17486
|
+
display: flex;
|
|
17487
|
+
align-items: center;
|
|
17488
|
+
gap: var(--rel-spacing-2xs);
|
|
17489
|
+
color: var(--rel-text-placeholder);
|
|
17490
|
+
}
|
|
17491
|
+
|
|
17492
|
+
.icon-button {
|
|
17493
|
+
background: none;
|
|
17494
|
+
border: none;
|
|
17495
|
+
padding: var(--rel-spacing-3xs);
|
|
17496
|
+
cursor: pointer;
|
|
17497
|
+
color: var(--rel-text-placeholder);
|
|
17498
|
+
display: flex;
|
|
17499
|
+
align-items: center;
|
|
17500
|
+
justify-content: center;
|
|
17501
|
+
border-radius: var(--rel-border-radius-xs);
|
|
17502
|
+
transition: all 0.15s ease;
|
|
17503
|
+
}
|
|
17504
|
+
|
|
17505
|
+
.icon-button:hover {
|
|
17506
|
+
background: var(--rel-bg-surface-elevated);
|
|
17507
|
+
color: var(--rel-text-default);
|
|
17508
|
+
}
|
|
17509
|
+
|
|
17510
|
+
.icon-button svg {
|
|
17511
|
+
width: 16px;
|
|
17512
|
+
height: 16px;
|
|
17513
|
+
}
|
|
17514
|
+
|
|
17515
|
+
.popover {
|
|
17516
|
+
position: absolute;
|
|
17517
|
+
top: calc(100% + 4px);
|
|
17518
|
+
left: 0;
|
|
17519
|
+
z-index: 1000;
|
|
17520
|
+
background: var(--rel-bg-surface);
|
|
17521
|
+
border: 1px solid var(--rel-border-default);
|
|
17522
|
+
border-radius: var(--rel-border-radius-sm);
|
|
17523
|
+
box-shadow: var(--rel-shadows-shadow-300);
|
|
17524
|
+
padding: var(--rel-spacing-md);
|
|
17525
|
+
display: none;
|
|
17526
|
+
}
|
|
17527
|
+
|
|
17528
|
+
.popover.open {
|
|
17529
|
+
display: block;
|
|
17530
|
+
}
|
|
17531
|
+
|
|
17532
|
+
/* Draft readout (mirrors the range-picker FROM/TO block) */
|
|
17533
|
+
.time-readout {
|
|
17534
|
+
display: flex;
|
|
17535
|
+
align-items: baseline;
|
|
17536
|
+
gap: var(--rel-spacing-sm);
|
|
17537
|
+
padding-bottom: var(--rel-spacing-sm);
|
|
17538
|
+
margin-bottom: var(--rel-spacing-sm);
|
|
17539
|
+
border-bottom: 1px solid var(--rel-border-subtle);
|
|
17540
|
+
}
|
|
17541
|
+
|
|
17542
|
+
.time-readout-label {
|
|
17543
|
+
color: var(--rel-text-placeholder);
|
|
17544
|
+
text-transform: uppercase;
|
|
17545
|
+
font-family: var(--rel-body-xs-semibold-font-family);
|
|
17546
|
+
font-weight: var(--rel-body-xs-semibold-font-weight);
|
|
17547
|
+
font-size: var(--rel-body-xs-semibold-font-size);
|
|
17548
|
+
line-height: var(--rel-body-xs-semibold-line-height);
|
|
17549
|
+
letter-spacing: var(--rel-body-xs-semibold-letter-spacing);
|
|
17550
|
+
}
|
|
17551
|
+
|
|
17552
|
+
.time-readout-value {
|
|
17553
|
+
color: var(--rel-text-default);
|
|
17554
|
+
font-family: var(--rel-body-md-semibold-font-family);
|
|
17555
|
+
font-weight: var(--rel-body-md-semibold-font-weight);
|
|
17556
|
+
font-size: var(--rel-body-md-semibold-font-size);
|
|
17557
|
+
line-height: var(--rel-body-md-semibold-line-height);
|
|
17558
|
+
letter-spacing: var(--rel-body-md-semibold-letter-spacing);
|
|
17559
|
+
}
|
|
17560
|
+
|
|
17561
|
+
.time-readout-value.empty {
|
|
17562
|
+
color: var(--rel-text-placeholder);
|
|
17563
|
+
}
|
|
17564
|
+
|
|
17565
|
+
.columns {
|
|
17566
|
+
display: flex;
|
|
17567
|
+
gap: var(--rel-spacing-xs);
|
|
17568
|
+
}
|
|
17569
|
+
|
|
17570
|
+
/* Columns share the panel width equally, so the two-column 24h layout
|
|
17571
|
+
fills the same footprint the footer dictates instead of bunching left. */
|
|
17572
|
+
.column {
|
|
17573
|
+
display: flex;
|
|
17574
|
+
flex-direction: column;
|
|
17575
|
+
flex: 1;
|
|
17576
|
+
min-width: 64px;
|
|
17577
|
+
}
|
|
17578
|
+
|
|
17579
|
+
.column-header {
|
|
17580
|
+
text-align: center;
|
|
17581
|
+
color: var(--rel-text-placeholder);
|
|
17582
|
+
padding: var(--rel-spacing-xs);
|
|
17583
|
+
text-transform: uppercase;
|
|
17584
|
+
font-family: var(--rel-body-xs-semibold-font-family);
|
|
17585
|
+
font-weight: var(--rel-body-xs-semibold-font-weight);
|
|
17586
|
+
font-size: var(--rel-body-xs-semibold-font-size);
|
|
17587
|
+
line-height: var(--rel-body-xs-semibold-line-height);
|
|
17588
|
+
letter-spacing: var(--rel-body-xs-semibold-letter-spacing);
|
|
17589
|
+
}
|
|
17590
|
+
|
|
17591
|
+
.column-list {
|
|
17592
|
+
display: flex;
|
|
17593
|
+
flex-direction: column;
|
|
17594
|
+
gap: var(--rel-spacing-3xs);
|
|
17595
|
+
max-height: 224px;
|
|
17596
|
+
overflow-y: auto;
|
|
17597
|
+
padding: var(--rel-spacing-3xs);
|
|
17598
|
+
scrollbar-width: thin;
|
|
17599
|
+
}
|
|
17600
|
+
|
|
17601
|
+
.option-button {
|
|
17602
|
+
border: none;
|
|
17603
|
+
background: transparent;
|
|
17604
|
+
border-radius: var(--rel-border-radius-sm);
|
|
17605
|
+
color: var(--rel-text-default);
|
|
17606
|
+
cursor: pointer;
|
|
17607
|
+
padding: var(--rel-spacing-xs) var(--rel-spacing-sm);
|
|
17608
|
+
text-align: center;
|
|
17609
|
+
flex-shrink: 0;
|
|
17610
|
+
transition: all 0.15s ease;
|
|
17611
|
+
font-family: var(--rel-body-sm-medium-font-family);
|
|
17612
|
+
font-weight: var(--rel-body-sm-medium-font-weight);
|
|
17613
|
+
font-size: var(--rel-body-sm-medium-font-size);
|
|
17614
|
+
line-height: var(--rel-body-sm-medium-line-height);
|
|
17615
|
+
letter-spacing: var(--rel-body-sm-medium-letter-spacing);
|
|
17616
|
+
}
|
|
17617
|
+
|
|
17618
|
+
.option-button:hover:not(:disabled):not(.selected) {
|
|
17619
|
+
background: var(--rel-bg-surface-elevated);
|
|
17620
|
+
}
|
|
17621
|
+
|
|
17622
|
+
.option-button:focus-visible {
|
|
17623
|
+
outline: 2px solid var(--rel-border-focus);
|
|
17624
|
+
outline-offset: -2px;
|
|
17625
|
+
z-index: 1;
|
|
17626
|
+
}
|
|
17627
|
+
|
|
17628
|
+
.option-button.selected {
|
|
17629
|
+
background: var(--rel-action-brand-fill);
|
|
17630
|
+
color: var(--rel-action-brand-label-icons-primary);
|
|
17631
|
+
font-weight: var(--rel-body-md-semibold-font-weight);
|
|
17632
|
+
}
|
|
17633
|
+
|
|
17634
|
+
.option-button:disabled {
|
|
17635
|
+
color: var(--rel-text-disabled);
|
|
17636
|
+
cursor: not-allowed;
|
|
17637
|
+
opacity: 0.5;
|
|
17638
|
+
}
|
|
17639
|
+
|
|
17640
|
+
.option-button:disabled:hover {
|
|
17641
|
+
background: transparent;
|
|
17642
|
+
}
|
|
17643
|
+
|
|
17644
|
+
/* Footer (mirrors the range-picker calendar-footer) */
|
|
17645
|
+
.time-footer {
|
|
17646
|
+
display: flex;
|
|
17647
|
+
align-items: center;
|
|
17648
|
+
justify-content: space-between;
|
|
17649
|
+
gap: var(--rel-spacing-sm);
|
|
17650
|
+
padding-top: var(--rel-spacing-sm);
|
|
17651
|
+
margin-top: var(--rel-spacing-sm);
|
|
17652
|
+
border-top: 1px solid var(--rel-border-subtle);
|
|
17653
|
+
}
|
|
17654
|
+
|
|
17655
|
+
.footer-right {
|
|
17656
|
+
display: flex;
|
|
17657
|
+
align-items: center;
|
|
17658
|
+
gap: var(--rel-spacing-xs);
|
|
17659
|
+
}
|
|
17660
|
+
|
|
17661
|
+
@media (prefers-reduced-motion: reduce) {
|
|
17662
|
+
.trigger-button,
|
|
17663
|
+
.icon-button,
|
|
17664
|
+
.option-button {
|
|
17665
|
+
transition: none;
|
|
17666
|
+
}
|
|
17667
|
+
}
|
|
17668
|
+
`;
|
|
17669
|
+
__decorateClass([
|
|
17670
|
+
(0, import_decorators54.property)({ type: String, reflect: true })
|
|
17671
|
+
], RelTimePicker.prototype, "value", 2);
|
|
17672
|
+
__decorateClass([
|
|
17673
|
+
(0, import_decorators54.property)({ type: String })
|
|
17674
|
+
], RelTimePicker.prototype, "placeholder", 2);
|
|
17675
|
+
__decorateClass([
|
|
17676
|
+
(0, import_decorators54.property)({ type: String })
|
|
17677
|
+
], RelTimePicker.prototype, "label", 2);
|
|
17678
|
+
__decorateClass([
|
|
17679
|
+
(0, import_decorators54.property)({ type: Boolean, reflect: true })
|
|
17680
|
+
], RelTimePicker.prototype, "disabled", 2);
|
|
17681
|
+
__decorateClass([
|
|
17682
|
+
(0, import_decorators54.property)({ type: Boolean })
|
|
17683
|
+
], RelTimePicker.prototype, "required", 2);
|
|
17684
|
+
__decorateClass([
|
|
17685
|
+
(0, import_decorators54.property)({ type: Boolean, reflect: true })
|
|
17686
|
+
], RelTimePicker.prototype, "readonly", 2);
|
|
17687
|
+
__decorateClass([
|
|
17688
|
+
(0, import_decorators54.property)({ type: String })
|
|
17689
|
+
], RelTimePicker.prototype, "format", 2);
|
|
17690
|
+
__decorateClass([
|
|
17691
|
+
(0, import_decorators54.property)({ type: Number, attribute: "minute-step" })
|
|
17692
|
+
], RelTimePicker.prototype, "minuteStep", 2);
|
|
17693
|
+
__decorateClass([
|
|
17694
|
+
(0, import_decorators54.property)({ type: String, attribute: "min-time" })
|
|
17695
|
+
], RelTimePicker.prototype, "minTime", 2);
|
|
17696
|
+
__decorateClass([
|
|
17697
|
+
(0, import_decorators54.property)({ type: String, attribute: "max-time" })
|
|
17698
|
+
], RelTimePicker.prototype, "maxTime", 2);
|
|
17699
|
+
__decorateClass([
|
|
17700
|
+
(0, import_decorators54.property)({ type: Boolean, reflect: true })
|
|
17701
|
+
], RelTimePicker.prototype, "open", 2);
|
|
17702
|
+
__decorateClass([
|
|
17703
|
+
(0, import_decorators54.property)({ type: Boolean })
|
|
17704
|
+
], RelTimePicker.prototype, "clearable", 2);
|
|
17705
|
+
__decorateClass([
|
|
17706
|
+
(0, import_decorators54.property)({ type: Boolean })
|
|
17707
|
+
], RelTimePicker.prototype, "error", 2);
|
|
17708
|
+
__decorateClass([
|
|
17709
|
+
(0, import_decorators54.property)({ type: Boolean })
|
|
17710
|
+
], RelTimePicker.prototype, "invalid", 2);
|
|
17711
|
+
__decorateClass([
|
|
17712
|
+
(0, import_decorators54.property)({ type: String, attribute: "error-message" })
|
|
17713
|
+
], RelTimePicker.prototype, "errorMessage", 2);
|
|
17714
|
+
__decorateClass([
|
|
17715
|
+
(0, import_decorators54.property)({ type: String, attribute: "helper-text" })
|
|
17716
|
+
], RelTimePicker.prototype, "helperText", 2);
|
|
17717
|
+
__decorateClass([
|
|
17718
|
+
(0, import_decorators54.property)({ type: String })
|
|
17719
|
+
], RelTimePicker.prototype, "name", 2);
|
|
17720
|
+
__decorateClass([
|
|
17721
|
+
(0, import_decorators54.property)({ type: String })
|
|
17722
|
+
], RelTimePicker.prototype, "size", 2);
|
|
17723
|
+
__decorateClass([
|
|
17724
|
+
(0, import_decorators54.property)({ type: String, reflect: true })
|
|
17725
|
+
], RelTimePicker.prototype, "variant", 2);
|
|
17726
|
+
__decorateClass([
|
|
17727
|
+
(0, import_decorators54.property)({ type: Boolean, reflect: true })
|
|
17728
|
+
], RelTimePicker.prototype, "fullWidth", 2);
|
|
17729
|
+
__decorateClass([
|
|
17730
|
+
(0, import_decorators54.state)()
|
|
17731
|
+
], RelTimePicker.prototype, "draftHour", 2);
|
|
17732
|
+
__decorateClass([
|
|
17733
|
+
(0, import_decorators54.state)()
|
|
17734
|
+
], RelTimePicker.prototype, "draftMinute", 2);
|
|
17735
|
+
__decorateClass([
|
|
17736
|
+
(0, import_decorators54.query)(".trigger-button")
|
|
17737
|
+
], RelTimePicker.prototype, "triggerButton", 2);
|
|
17738
|
+
__decorateClass([
|
|
17739
|
+
(0, import_decorators54.query)(".columns")
|
|
17740
|
+
], RelTimePicker.prototype, "columnsElement", 2);
|
|
17741
|
+
RelTimePicker = __decorateClass([
|
|
17742
|
+
(0, import_decorators54.customElement)("rel-time-picker")
|
|
17743
|
+
], RelTimePicker);
|
|
17744
|
+
|
|
17745
|
+
// src/components/rel-toast/rel-toast.ts
|
|
17746
|
+
var import_lit55 = require("lit");
|
|
17747
|
+
var import_decorators55 = require("lit/decorators.js");
|
|
17748
|
+
var RelToast = class extends import_lit55.LitElement {
|
|
17749
|
+
constructor() {
|
|
17750
|
+
super(...arguments);
|
|
17751
|
+
this.message = "";
|
|
17752
|
+
this.type = "default";
|
|
17753
|
+
this.duration = 4e3;
|
|
17754
|
+
this.show = false;
|
|
17755
|
+
this.visible = false;
|
|
17756
|
+
this._dismissTimer = null;
|
|
17757
|
+
}
|
|
17758
|
+
willUpdate(changed) {
|
|
17759
|
+
if (!changed.has("show")) return;
|
|
17760
|
+
if (this.show) {
|
|
17761
|
+
this.visible = true;
|
|
17762
|
+
if (this._dismissTimer) clearTimeout(this._dismissTimer);
|
|
17763
|
+
if (this.duration > 0) {
|
|
17764
|
+
this._dismissTimer = setTimeout(() => this.close(), this.duration);
|
|
17765
|
+
}
|
|
17766
|
+
} else {
|
|
17767
|
+
this.visible = false;
|
|
17768
|
+
if (this._dismissTimer) {
|
|
17769
|
+
clearTimeout(this._dismissTimer);
|
|
17770
|
+
this._dismissTimer = null;
|
|
17771
|
+
}
|
|
17772
|
+
}
|
|
17773
|
+
}
|
|
17774
|
+
disconnectedCallback() {
|
|
17775
|
+
super.disconnectedCallback();
|
|
17776
|
+
if (this._dismissTimer) clearTimeout(this._dismissTimer);
|
|
17777
|
+
}
|
|
17778
|
+
open() {
|
|
17779
|
+
this.show = true;
|
|
17780
|
+
}
|
|
17781
|
+
close() {
|
|
17782
|
+
this.show = false;
|
|
17783
|
+
this.visible = false;
|
|
17784
|
+
if (this._dismissTimer) {
|
|
17785
|
+
clearTimeout(this._dismissTimer);
|
|
17786
|
+
this._dismissTimer = null;
|
|
17787
|
+
}
|
|
17788
|
+
this.dispatchEvent(
|
|
17789
|
+
new CustomEvent("dismissed", {
|
|
17790
|
+
bubbles: true,
|
|
17791
|
+
composed: true
|
|
17792
|
+
})
|
|
17793
|
+
);
|
|
17794
|
+
}
|
|
17795
|
+
handleActionClick(event) {
|
|
17796
|
+
event.stopPropagation();
|
|
17797
|
+
this.dispatchEvent(
|
|
17798
|
+
new CustomEvent("action", {
|
|
17799
|
+
bubbles: true,
|
|
17800
|
+
composed: true
|
|
17801
|
+
})
|
|
17802
|
+
);
|
|
17803
|
+
this.close();
|
|
17804
|
+
}
|
|
17805
|
+
render() {
|
|
17806
|
+
return import_lit55.html`
|
|
17807
|
+
<div part="base" class="toast ${this.type} ${this.visible ? "" : "hidden"}" role="status" aria-live="polite">
|
|
17808
|
+
<span part="label" class="toast-message">${this.message}</span>
|
|
17809
|
+
|
|
17810
|
+
${this.actionLabel ? import_lit55.html`<button
|
|
17811
|
+
part="action" class="toast-action"
|
|
17812
|
+
@click="${this.handleActionClick}"
|
|
17813
|
+
>
|
|
17814
|
+
${this.actionLabel}
|
|
17815
|
+
</button>` : ""}
|
|
17816
|
+
|
|
17817
|
+
<button part="close-button" class="toast-close" @click="${this.close}" aria-label="Dismiss">✕</button>
|
|
17818
|
+
</div>
|
|
17819
|
+
`;
|
|
17820
|
+
}
|
|
17821
|
+
};
|
|
17822
|
+
RelToast.styles = import_lit55.css`
|
|
17823
|
+
:host {
|
|
17824
|
+
color: var(--rel-text-default);
|
|
17825
|
+
position: fixed;
|
|
17826
|
+
bottom: 24px;
|
|
17827
|
+
left: 50%;
|
|
16723
17828
|
transform: translateX(-50%);
|
|
16724
17829
|
display: flex;
|
|
16725
17830
|
justify-content: center;
|
|
@@ -16832,31 +17937,31 @@ RelToast.styles = import_lit54.css`
|
|
|
16832
17937
|
}
|
|
16833
17938
|
`;
|
|
16834
17939
|
__decorateClass([
|
|
16835
|
-
(0,
|
|
17940
|
+
(0, import_decorators55.property)({ type: String })
|
|
16836
17941
|
], RelToast.prototype, "message", 2);
|
|
16837
17942
|
__decorateClass([
|
|
16838
|
-
(0,
|
|
17943
|
+
(0, import_decorators55.property)({ type: String })
|
|
16839
17944
|
], RelToast.prototype, "type", 2);
|
|
16840
17945
|
__decorateClass([
|
|
16841
|
-
(0,
|
|
17946
|
+
(0, import_decorators55.property)({ type: String })
|
|
16842
17947
|
], RelToast.prototype, "actionLabel", 2);
|
|
16843
17948
|
__decorateClass([
|
|
16844
|
-
(0,
|
|
17949
|
+
(0, import_decorators55.property)({ type: Number })
|
|
16845
17950
|
], RelToast.prototype, "duration", 2);
|
|
16846
17951
|
__decorateClass([
|
|
16847
|
-
(0,
|
|
17952
|
+
(0, import_decorators55.property)({ type: Boolean, reflect: true })
|
|
16848
17953
|
], RelToast.prototype, "show", 2);
|
|
16849
17954
|
__decorateClass([
|
|
16850
|
-
(0,
|
|
17955
|
+
(0, import_decorators55.state)()
|
|
16851
17956
|
], RelToast.prototype, "visible", 2);
|
|
16852
17957
|
RelToast = __decorateClass([
|
|
16853
|
-
(0,
|
|
17958
|
+
(0, import_decorators55.customElement)("rel-toast")
|
|
16854
17959
|
], RelToast);
|
|
16855
17960
|
|
|
16856
17961
|
// src/components/rel-tooltip/rel-tooltip.ts
|
|
16857
|
-
var
|
|
16858
|
-
var
|
|
16859
|
-
var RelTooltip = class extends
|
|
17962
|
+
var import_lit56 = require("lit");
|
|
17963
|
+
var import_decorators56 = require("lit/decorators.js");
|
|
17964
|
+
var RelTooltip = class extends import_lit56.LitElement {
|
|
16860
17965
|
constructor() {
|
|
16861
17966
|
super(...arguments);
|
|
16862
17967
|
this.text = "";
|
|
@@ -16878,7 +17983,7 @@ var RelTooltip = class extends import_lit55.LitElement {
|
|
|
16878
17983
|
}
|
|
16879
17984
|
}
|
|
16880
17985
|
render() {
|
|
16881
|
-
return
|
|
17986
|
+
return import_lit56.html`
|
|
16882
17987
|
<slot @slotchange="${this.linkTrigger}"></slot>
|
|
16883
17988
|
<div
|
|
16884
17989
|
part="panel" class="tooltip-bubble ${this.position}"
|
|
@@ -16886,12 +17991,12 @@ var RelTooltip = class extends import_lit55.LitElement {
|
|
|
16886
17991
|
role="tooltip"
|
|
16887
17992
|
>
|
|
16888
17993
|
${this.text}
|
|
16889
|
-
${this.showTail ?
|
|
17994
|
+
${this.showTail ? import_lit56.html`<span part="indicator" class="tooltip-tail"></span>` : ""}
|
|
16890
17995
|
</div>
|
|
16891
17996
|
`;
|
|
16892
17997
|
}
|
|
16893
17998
|
};
|
|
16894
|
-
RelTooltip.styles =
|
|
17999
|
+
RelTooltip.styles = import_lit56.css`
|
|
16895
18000
|
:host {
|
|
16896
18001
|
color: var(--rel-text-default);
|
|
16897
18002
|
display: inline-block;
|
|
@@ -17005,19 +18110,19 @@ RelTooltip.styles = import_lit55.css`
|
|
|
17005
18110
|
}
|
|
17006
18111
|
`;
|
|
17007
18112
|
__decorateClass([
|
|
17008
|
-
(0,
|
|
18113
|
+
(0, import_decorators56.property)({ type: String })
|
|
17009
18114
|
], RelTooltip.prototype, "text", 2);
|
|
17010
18115
|
__decorateClass([
|
|
17011
|
-
(0,
|
|
18116
|
+
(0, import_decorators56.property)({ type: String })
|
|
17012
18117
|
], RelTooltip.prototype, "position", 2);
|
|
17013
18118
|
__decorateClass([
|
|
17014
|
-
(0,
|
|
18119
|
+
(0, import_decorators56.property)({ type: Boolean, attribute: "show-tail" })
|
|
17015
18120
|
], RelTooltip.prototype, "showTail", 2);
|
|
17016
18121
|
__decorateClass([
|
|
17017
|
-
(0,
|
|
18122
|
+
(0, import_decorators56.property)({ type: String, reflect: true })
|
|
17018
18123
|
], RelTooltip.prototype, "type", 2);
|
|
17019
18124
|
RelTooltip = __decorateClass([
|
|
17020
|
-
(0,
|
|
18125
|
+
(0, import_decorators56.customElement)("rel-tooltip")
|
|
17021
18126
|
], RelTooltip);
|
|
17022
18127
|
|
|
17023
18128
|
// src/index.ts
|
|
@@ -17071,6 +18176,7 @@ __reExport(index_exports, require("@reliability-design/icons"), module.exports);
|
|
|
17071
18176
|
RelTab,
|
|
17072
18177
|
RelTabItem,
|
|
17073
18178
|
RelTextfield,
|
|
18179
|
+
RelTimePicker,
|
|
17074
18180
|
RelToast,
|
|
17075
18181
|
RelTooltip,
|
|
17076
18182
|
RelTopbar,
|