@reliability-design/web 1.4.2 → 1.4.4
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 +4 -1
- package/dist/components/rel-autocomplete/rel-autocomplete.ts +80 -3
- package/dist/components/rel-button/rel-button.ts +2 -2
- package/dist/components/rel-checkbox/README.md +69 -22
- package/dist/components/rel-checkbox/rel-checkbox.ts +116 -9
- package/dist/components/rel-pagination/README.md +14 -0
- package/dist/components/rel-pagination/rel-pagination.ts +30 -14
- package/dist/components/rel-radio/README.md +16 -1
- package/dist/components/rel-radio/rel-radio.ts +52 -1
- package/dist/components/rel-range-picker/rel-range-picker.ts +117 -2
- package/dist/components/rel-select/README.md +7 -0
- package/dist/components/rel-select/rel-select.ts +1237 -1119
- 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/components/rel-upload/UPLOAD-DESIGN-CHANGES.md +134 -0
- package/dist/components/rel-upload/rel-upload-actions.ts +163 -0
- package/dist/components/rel-upload/rel-upload.ts +943 -0
- package/dist/index.d.mts +453 -39
- package/dist/index.d.ts +453 -39
- package/dist/index.js +2659 -339
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2663 -346
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -18,6 +18,13 @@ A text input field component with support for labels, icons, validation states,
|
|
|
18
18
|
supportingText="Must be at least 8 characters">
|
|
19
19
|
</rel-textfield>
|
|
20
20
|
|
|
21
|
+
<!-- No label — an accessible name is required in its place -->
|
|
22
|
+
<rel-textfield
|
|
23
|
+
label-style="none"
|
|
24
|
+
placeholder="Search orders"
|
|
25
|
+
field-aria-label="Search orders">
|
|
26
|
+
</rel-textfield>
|
|
27
|
+
|
|
21
28
|
<!-- Setting value via JavaScript -->
|
|
22
29
|
<rel-textfield id="myField" label="Username"></rel-textfield>
|
|
23
30
|
<script>
|
|
@@ -33,6 +40,10 @@ A text input field component with support for labels, icons, validation states,
|
|
|
33
40
|
| Name | Type | Default | Description |
|
|
34
41
|
|------|------|---------|-------------|
|
|
35
42
|
| `label` | `string` | - | Label text displayed above the input |
|
|
43
|
+
| `label-style` | `"default" \| "floating" \| "none"` | - | Label presentation. `"none"` renders no label at all. Unset, the deprecated `floatingLabel` boolean decides |
|
|
44
|
+
| `floatingLabel` | `boolean` | `false` | **Deprecated** — use `label-style="floating"`. Kept permanently as an alias |
|
|
45
|
+
| `field-aria-label` | `string` | - | Accessible name for the input. Required with `label-style="none"` |
|
|
46
|
+
| `field-aria-labelledby` | `string` | - | IDREF list of external element(s) naming the input. Wins over `field-aria-label` |
|
|
36
47
|
| `placeholder` | `string` | `""` | Placeholder text |
|
|
37
48
|
| `type` | `string` | `"text"` | Input type (text, email, password, etc.) |
|
|
38
49
|
| `value` | `string` | `""` | Current input value |
|
|
@@ -72,10 +83,15 @@ None exposed.
|
|
|
72
83
|
- `--rel-bg-neutral-elevated-1`: Disabled background
|
|
73
84
|
- `--rel-spacing-*`: Spacing tokens
|
|
74
85
|
- `--rel-border-radius-sm`: Border radius
|
|
86
|
+
- `--rel-field-label-reserve`: Height held for the absent label row under `label-style="none"`, so the input bottom-aligns with labelled siblings. Defaults to the label line-height plus the wrapper gap; set to `0` to collapse the row entirely in compact contexts (toolbars, table cells)
|
|
75
87
|
|
|
76
88
|
## Accessibility
|
|
77
89
|
|
|
78
90
|
- Associates `<label>` with input via `for`/`id`
|
|
91
|
+
- With `label-style="none"` no `<label>` renders, so `field-aria-label` (or
|
|
92
|
+
`field-aria-labelledby`) must supply the accessible name — a placeholder is
|
|
93
|
+
not a valid naming mechanism. The component logs a console warning when both
|
|
94
|
+
are missing.
|
|
79
95
|
- Supports native HTML5 input attributes (`required`, `disabled`, `readonly`, `maxlength`)
|
|
80
96
|
- Focus ring visible on keyboard navigation (`:focus-visible`)
|
|
81
97
|
- Error state communicated visually via border color
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LitElement, html, css, nothing } from "lit";
|
|
1
|
+
import { LitElement, html, css, nothing, PropertyValues } from "lit";
|
|
2
2
|
import { customElement, property, state } from "lit/decorators.js";
|
|
3
3
|
import "@reliability-design/icons/rel-icon";
|
|
4
4
|
|
|
@@ -6,6 +6,16 @@ export type TextFieldState = "default" | "error" | "disabled";
|
|
|
6
6
|
export type TextFieldSize = "small" | "medium" | "large";
|
|
7
7
|
export type TextFieldVariant = "default" | "underline" | "legend";
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* How a field presents its label. Shared by `<rel-textfield>`, `<rel-select>`
|
|
11
|
+
* and `<rel-autocomplete>` so the three stay in step.
|
|
12
|
+
* - `"default"` — static label above the control
|
|
13
|
+
* - `"floating"` — label rests over the placeholder, animates onto the border
|
|
14
|
+
* - `"none"` — no label renders; supply `fieldAriaLabel` /
|
|
15
|
+
* `fieldAriaLabelledby` to keep an accessible name
|
|
16
|
+
*/
|
|
17
|
+
export type FieldLabelStyle = "default" | "floating" | "none";
|
|
18
|
+
|
|
9
19
|
@customElement("rel-textfield")
|
|
10
20
|
export class RelTextfield extends LitElement {
|
|
11
21
|
@property({ type: String }) label?: string;
|
|
@@ -22,7 +32,23 @@ export class RelTextfield extends LitElement {
|
|
|
22
32
|
@property({ type: String }) supportingText?: string;
|
|
23
33
|
@property({ type: String }) state: TextFieldState = "default";
|
|
24
34
|
@property({ type: String }) size: TextFieldSize = "medium";
|
|
35
|
+
/**
|
|
36
|
+
* Label presentation. Left `undefined` the deprecated `floatingLabel` boolean
|
|
37
|
+
* decides, so existing markup keeps working; set explicitly and it wins.
|
|
38
|
+
* Declares `attribute` because Lit would otherwise only listen for the
|
|
39
|
+
* all-lowercase `labelstyle`.
|
|
40
|
+
*/
|
|
41
|
+
@property({ type: String, attribute: "label-style", reflect: true })
|
|
42
|
+
labelStyle?: FieldLabelStyle;
|
|
43
|
+
/** @deprecated Use `labelStyle="floating"`. Kept permanently as an alias. */
|
|
25
44
|
@property({ type: Boolean, reflect: true }) floatingLabel = false;
|
|
45
|
+
/**
|
|
46
|
+
* Accessible name for the input. Required when `labelStyle="none"`, since no
|
|
47
|
+
* `<label>` renders and a placeholder is not a valid naming mechanism.
|
|
48
|
+
*/
|
|
49
|
+
@property({ type: String, attribute: "field-aria-label" }) fieldAriaLabel?: string;
|
|
50
|
+
/** IDREF list of external element(s) naming the input. Wins over `fieldAriaLabel`. */
|
|
51
|
+
@property({ type: String, attribute: "field-aria-labelledby" }) fieldAriaLabelledby?: string;
|
|
26
52
|
@property({ type: String, reflect: true }) variant: TextFieldVariant = "default";
|
|
27
53
|
@property({ type: Boolean }) disabled = false;
|
|
28
54
|
@property({ type: Boolean }) required = false;
|
|
@@ -65,6 +91,17 @@ export class RelTextfield extends LitElement {
|
|
|
65
91
|
return this.helperText ?? this.supportingText;
|
|
66
92
|
}
|
|
67
93
|
|
|
94
|
+
/** labelStyle supersedes the deprecated floatingLabel boolean. */
|
|
95
|
+
private get resolvedLabelStyle(): FieldLabelStyle {
|
|
96
|
+
if (this.labelStyle) return this.labelStyle;
|
|
97
|
+
return this.floatingLabel ? "floating" : "default";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** True when no visible label renders and the input needs an ARIA name. */
|
|
101
|
+
private get labelHidden() {
|
|
102
|
+
return this.resolvedLabelStyle === "none";
|
|
103
|
+
}
|
|
104
|
+
|
|
68
105
|
static styles = css`
|
|
69
106
|
:host {
|
|
70
107
|
color: var(--rel-text-default);
|
|
@@ -101,6 +138,22 @@ export class RelTextfield extends LitElement {
|
|
|
101
138
|
margin-left: 2px;
|
|
102
139
|
}
|
|
103
140
|
|
|
141
|
+
/* ── labelStyle="none" ──
|
|
142
|
+
No label renders. An empty spacer holds the label row's full height so the
|
|
143
|
+
input box still bottom-aligns with labelled siblings in a form row.
|
|
144
|
+
The reserve covers label line-height + the wrapper gap, and the negative
|
|
145
|
+
margin cancels the flex gap that would otherwise sit below it — so
|
|
146
|
+
--rel-field-label-reserve: 0 collapses the row to genuinely zero
|
|
147
|
+
(toolbars, table cells), not just to the leftover gap. */
|
|
148
|
+
.label-reserve {
|
|
149
|
+
flex: none;
|
|
150
|
+
height: var(
|
|
151
|
+
--rel-field-label-reserve,
|
|
152
|
+
calc(var(--rel-body-sm-medium-line-height) + var(--rel-spacing-2xs))
|
|
153
|
+
);
|
|
154
|
+
margin-bottom: calc(-1 * var(--rel-spacing-2xs));
|
|
155
|
+
}
|
|
156
|
+
|
|
104
157
|
/* Floating label variant */
|
|
105
158
|
:host([floatinglabel]) .textfield-wrapper {
|
|
106
159
|
gap: 0;
|
|
@@ -477,6 +530,12 @@ export class RelTextfield extends LitElement {
|
|
|
477
530
|
box-sizing: border-box;
|
|
478
531
|
}
|
|
479
532
|
|
|
533
|
+
/* labelStyle="none" + legend: nothing to seat in the notch, so drop the
|
|
534
|
+
legend entirely and let the top border run continuous. */
|
|
535
|
+
:host([label-style="none"][variant="legend"]) .field-legend {
|
|
536
|
+
display: none;
|
|
537
|
+
}
|
|
538
|
+
|
|
480
539
|
/* Prefix icon (legend): vertically centered; input inset via has-left-icon */
|
|
481
540
|
:host([variant="legend"]) .icon.left {
|
|
482
541
|
top: 50%;
|
|
@@ -568,6 +627,29 @@ export class RelTextfield extends LitElement {
|
|
|
568
627
|
if (rightIcon) this.rightIcon = rightIcon;
|
|
569
628
|
}
|
|
570
629
|
|
|
630
|
+
willUpdate(changed: PropertyValues<this>) {
|
|
631
|
+
// Keep the deprecated boolean an accurate mirror of labelStyle. This is what
|
|
632
|
+
// lets every `:host([floatinglabel])` rule below stay untouched — the
|
|
633
|
+
// reflected attribute is still the single styling hook for the floating form.
|
|
634
|
+
if (changed.has("labelStyle") && this.labelStyle) {
|
|
635
|
+
this.floatingLabel = this.labelStyle === "floating";
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
private _warnedNoName = false;
|
|
640
|
+
|
|
641
|
+
updated() {
|
|
642
|
+
if (this._warnedNoName || !this.labelHidden) return;
|
|
643
|
+
if (this.fieldAriaLabel || this.fieldAriaLabelledby || this.getAttribute("aria-label")) return;
|
|
644
|
+
this._warnedNoName = true;
|
|
645
|
+
console.warn(
|
|
646
|
+
'<rel-textfield label-style="none"> has no accessible name. Set ' +
|
|
647
|
+
"`field-aria-label` or `field-aria-labelledby` — a placeholder is not a " +
|
|
648
|
+
"valid naming mechanism.",
|
|
649
|
+
this
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
|
|
571
653
|
/** Focuses the inner input/textarea (the host itself is not focusable). */
|
|
572
654
|
focus(options?: FocusOptions) {
|
|
573
655
|
(this.renderRoot.querySelector("input, textarea") as HTMLElement | null)?.focus(options);
|
|
@@ -649,8 +731,11 @@ export class RelTextfield extends LitElement {
|
|
|
649
731
|
.filter(Boolean)
|
|
650
732
|
.join(" ");
|
|
651
733
|
|
|
734
|
+
const labelStyle = this.resolvedLabelStyle;
|
|
735
|
+
const labelHidden = labelStyle === "none";
|
|
736
|
+
|
|
652
737
|
const labelTemplate = (extraClass = "") => {
|
|
653
|
-
if (!this.label) return "";
|
|
738
|
+
if (!this.label || labelHidden) return "";
|
|
654
739
|
return html`<label
|
|
655
740
|
part="label"
|
|
656
741
|
class="textfield-label${extraClass}"
|
|
@@ -658,7 +743,14 @@ export class RelTextfield extends LitElement {
|
|
|
658
743
|
>${this.label}${this.required ? html`<span class="required-mark" aria-hidden="true">*</span>` : ""}</label>`;
|
|
659
744
|
};
|
|
660
745
|
|
|
661
|
-
|
|
746
|
+
// With no visible label the <label for> / <legend> naming path is gone, so
|
|
747
|
+
// fieldAriaLabelledby → fieldAriaLabel take over. An explicit labelledby
|
|
748
|
+
// always wins; aria-label only fills in when nothing else names the input.
|
|
749
|
+
const legendLabelledby =
|
|
750
|
+
this.variant === "legend" && this.label && !labelHidden ? legendId : undefined;
|
|
751
|
+
const ariaLabelledby = this.fieldAriaLabelledby ?? legendLabelledby;
|
|
752
|
+
const hasVisibleLabel = !labelHidden && !!this.label;
|
|
753
|
+
const ariaLabel = !hasVisibleLabel && !ariaLabelledby ? this.fieldAriaLabel : undefined;
|
|
662
754
|
|
|
663
755
|
const inputTemplate = this.multiline
|
|
664
756
|
? html`<textarea part="input"
|
|
@@ -674,6 +766,7 @@ export class RelTextfield extends LitElement {
|
|
|
674
766
|
?required="${this.required}"
|
|
675
767
|
aria-invalid="${isError}"
|
|
676
768
|
aria-required="${this.required}"
|
|
769
|
+
aria-label="${ariaLabel ?? nothing}"
|
|
677
770
|
aria-labelledby="${ariaLabelledby ?? nothing}"
|
|
678
771
|
aria-describedby="${describedById ?? nothing}"
|
|
679
772
|
@focus="${this.handleFocus}"
|
|
@@ -693,6 +786,7 @@ export class RelTextfield extends LitElement {
|
|
|
693
786
|
?required="${this.required}"
|
|
694
787
|
aria-invalid="${isError}"
|
|
695
788
|
aria-required="${this.required}"
|
|
789
|
+
aria-label="${ariaLabel ?? nothing}"
|
|
696
790
|
aria-labelledby="${ariaLabelledby ?? nothing}"
|
|
697
791
|
aria-describedby="${describedById ?? nothing}"
|
|
698
792
|
@focus="${this.handleFocus}"
|
|
@@ -725,7 +819,7 @@ export class RelTextfield extends LitElement {
|
|
|
725
819
|
// is always present (only its content is dynamic).
|
|
726
820
|
return html`
|
|
727
821
|
<div class="${wrapperClasses}">
|
|
728
|
-
<fieldset part="control" class="${fieldsetClass}"><legend class="field-legend" id="${legendId}">${this.label ?? ""}${this.required ? html`<span class="required-mark" aria-hidden="true">*</span>` : ""}</legend>
|
|
822
|
+
<fieldset part="control" class="${fieldsetClass}"><legend class="field-legend" id="${legendId}">${labelHidden ? "" : this.label ?? ""}${this.required && !labelHidden ? html`<span class="required-mark" aria-hidden="true">*</span>` : ""}</legend>
|
|
729
823
|
${!this.multiline ? leadingIconTemplate : ""}
|
|
730
824
|
${inputTemplate}
|
|
731
825
|
</fieldset>
|
|
@@ -736,10 +830,14 @@ export class RelTextfield extends LitElement {
|
|
|
736
830
|
|
|
737
831
|
return html`
|
|
738
832
|
<div class="${wrapperClasses}">
|
|
739
|
-
${
|
|
833
|
+
${labelHidden
|
|
834
|
+
? html`<div class="label-reserve" aria-hidden="true"></div>`
|
|
835
|
+
: labelStyle !== "floating"
|
|
836
|
+
? labelTemplate()
|
|
837
|
+
: ""}
|
|
740
838
|
|
|
741
839
|
<div part="control" class="${["textfield-container", this.isDisabled ? "disabled" : "", this.multiline ? "multiline" : ""].filter(Boolean).join(" ")}">
|
|
742
|
-
${
|
|
840
|
+
${labelStyle === "floating"
|
|
743
841
|
? labelTemplate(leadingIcon && !this.multiline ? " has-left-icon" : "")
|
|
744
842
|
: ""}
|
|
745
843
|
${!this.multiline ? leadingIconTemplate : ""}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# rel-time-picker
|
|
2
|
+
|
|
3
|
+
A fully accessible time picker component built with Lit for the Reliability Design System. Features a trigger button with an hour/minute (and AM/PM) column popover. Selections are held in draft state until Apply is clicked, mirroring the rel-range-picker interaction model.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Trigger button with time-column popover (Hour / Min / AM-PM)
|
|
8
|
+
- 12-hour and 24-hour display formats
|
|
9
|
+
- Configurable minute intervals (1, 5, 10, 15, 30, …)
|
|
10
|
+
- Draft state with Now / Cancel / Apply footer
|
|
11
|
+
- Min/max time constraints
|
|
12
|
+
- Optional clear button
|
|
13
|
+
- Label, placeholder, helper text and error states
|
|
14
|
+
- Standardized input API (mirrors rel-textfield / rel-date-picker)
|
|
15
|
+
- Keyboard navigation and full ARIA support
|
|
16
|
+
- Token-driven styling with `::part` hooks
|
|
17
|
+
|
|
18
|
+
## Value format
|
|
19
|
+
|
|
20
|
+
The committed `value` is **always 24-hour `"HH:mm"`** (e.g. `"09:30"`, `"17:05"`) regardless of the display `format`, so consumers get a consistent machine-readable string.
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
### Basic
|
|
25
|
+
|
|
26
|
+
```html
|
|
27
|
+
<rel-time-picker></rel-time-picker>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### With Value
|
|
31
|
+
|
|
32
|
+
```html
|
|
33
|
+
<rel-time-picker value="09:30"></rel-time-picker>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### With Label
|
|
37
|
+
|
|
38
|
+
```html
|
|
39
|
+
<rel-time-picker label="Start time" placeholder="Select time"></rel-time-picker>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 24-hour format
|
|
43
|
+
|
|
44
|
+
```html
|
|
45
|
+
<rel-time-picker format="24h" value="17:05"></rel-time-picker>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Minute intervals
|
|
49
|
+
|
|
50
|
+
```html
|
|
51
|
+
<rel-time-picker minute-step="15"></rel-time-picker>
|
|
52
|
+
<rel-time-picker minute-step="30"></rel-time-picker>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### With Min/Max
|
|
56
|
+
|
|
57
|
+
```html
|
|
58
|
+
<rel-time-picker
|
|
59
|
+
min-time="09:00"
|
|
60
|
+
max-time="17:30"
|
|
61
|
+
placeholder="Business hours only"
|
|
62
|
+
></rel-time-picker>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Clearable
|
|
66
|
+
|
|
67
|
+
```html
|
|
68
|
+
<rel-time-picker value="09:30" clearable></rel-time-picker>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### States
|
|
72
|
+
|
|
73
|
+
```html
|
|
74
|
+
<rel-time-picker value="09:30" disabled></rel-time-picker>
|
|
75
|
+
<rel-time-picker value="09:30" readonly></rel-time-picker>
|
|
76
|
+
<rel-time-picker error error-message="Select a valid time"></rel-time-picker>
|
|
77
|
+
<rel-time-picker helper-text="Local timezone"></rel-time-picker>
|
|
78
|
+
<rel-time-picker label="Start time" required></rel-time-picker>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Sizes / layout
|
|
82
|
+
|
|
83
|
+
```html
|
|
84
|
+
<rel-time-picker size="small"></rel-time-picker>
|
|
85
|
+
<rel-time-picker size="large"></rel-time-picker>
|
|
86
|
+
<rel-time-picker fullWidth></rel-time-picker>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Form usage
|
|
90
|
+
|
|
91
|
+
```html
|
|
92
|
+
<form>
|
|
93
|
+
<rel-time-picker name="startTime" label="Start time" required></rel-time-picker>
|
|
94
|
+
</form>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The value is mirrored onto a hidden `<input name="...">` so native form submission works.
|
|
98
|
+
|
|
99
|
+
### Programmatic Control (JavaScript)
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
const picker = document.querySelector('rel-time-picker');
|
|
103
|
+
|
|
104
|
+
// Set value (always 24h HH:mm)
|
|
105
|
+
picker.value = '14:30';
|
|
106
|
+
|
|
107
|
+
// Open programmatically
|
|
108
|
+
picker.open = true;
|
|
109
|
+
|
|
110
|
+
// Listen to changes (fired on Apply, and on clear with value: null)
|
|
111
|
+
picker.addEventListener('rel-change', (e) => {
|
|
112
|
+
console.log('Selected time:', e.detail.value); // "14:30" | null
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
picker.addEventListener('rel-open-change', (e) => {
|
|
116
|
+
console.log('Open:', e.detail.open);
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Interaction model
|
|
121
|
+
|
|
122
|
+
- Clicking a column entry updates the **draft** shown in the readout row; nothing is committed yet.
|
|
123
|
+
- **Apply** commits the draft, emits `rel-change` and closes.
|
|
124
|
+
- **Cancel**, `Escape` or an outside click discards the draft (same as rel-range-picker).
|
|
125
|
+
- **Now** sets the draft to the current time, snapped to `minute-step` and clamped into `min-time`/`max-time`.
|
|
126
|
+
- Picking an hour with no minute selected defaults the minute to `00`.
|
|
127
|
+
|
|
128
|
+
## Keyboard
|
|
129
|
+
|
|
130
|
+
| Key | Context | Action |
|
|
131
|
+
| --- | --- | --- |
|
|
132
|
+
| `Enter` / `Space` / `ArrowDown` | Trigger | Open the popover |
|
|
133
|
+
| `ArrowUp` / `ArrowDown` | Column | Move and select the previous/next entry |
|
|
134
|
+
| `ArrowLeft` / `ArrowRight` | Column | Jump between Hour / Min / AM-PM columns |
|
|
135
|
+
| `Home` / `End` | Column | First / last entry |
|
|
136
|
+
| `Enter` | Column | Select the entry and Apply |
|
|
137
|
+
| `Escape` | Anywhere | Discard draft and close |
|
|
138
|
+
|
|
139
|
+
## Properties
|
|
140
|
+
|
|
141
|
+
| Property | Attribute | Type | Default | Description |
|
|
142
|
+
| --- | --- | --- | --- | --- |
|
|
143
|
+
| `value` | `value` | `string \| null` | `null` | Committed time, 24h `"HH:mm"` |
|
|
144
|
+
| `placeholder` | `placeholder` | `string` | `"Select time"` | Trigger text when empty |
|
|
145
|
+
| `label` | `label` | `string \| null` | `null` | Field label |
|
|
146
|
+
| `format` | `format` | `"12h" \| "24h"` | `"12h"` | Display format (value stays `HH:mm`) |
|
|
147
|
+
| `minuteStep` | `minute-step` | `number` | `1` | Minute column granularity |
|
|
148
|
+
| `minTime` | `min-time` | `string \| null` | `null` | Earliest selectable time (inclusive) |
|
|
149
|
+
| `maxTime` | `max-time` | `string \| null` | `null` | Latest selectable time (inclusive) |
|
|
150
|
+
| `disabled` | `disabled` | `boolean` | `false` | Disables all interaction |
|
|
151
|
+
| `readonly` | `readonly` | `boolean` | `false` | Value shown, popover cannot open |
|
|
152
|
+
| `required` | `required` | `boolean` | `false` | Renders `*` and sets `aria-required` |
|
|
153
|
+
| `clearable` | `clearable` | `boolean` | `false` | Clear button when a value is set |
|
|
154
|
+
| `open` | `open` | `boolean` | `false` | Controls / reflects popover state |
|
|
155
|
+
| `error` | `error` | `boolean` | `false` | Error styling |
|
|
156
|
+
| `invalid` | `invalid` | `boolean` | `false` | Alias for `error` |
|
|
157
|
+
| `errorMessage` | `error-message` | `string` | — | Shown below when error/invalid |
|
|
158
|
+
| `helperText` | `helper-text` | `string` | — | Assistive text below the field |
|
|
159
|
+
| `name` | `name` | `string` | — | Form field name (hidden input) |
|
|
160
|
+
| `size` | `size` | `"small" \| "medium" \| "large"` | `"medium"` | Trigger size |
|
|
161
|
+
| `fullWidth` | `fullWidth` | `boolean` | `false` | Fill the parent width |
|
|
162
|
+
| `variant` | `variant` | `"outlined"` | `"outlined"` | Visual style |
|
|
163
|
+
|
|
164
|
+
## Events
|
|
165
|
+
|
|
166
|
+
| Event | Detail | Description |
|
|
167
|
+
| --- | --- | --- |
|
|
168
|
+
| `rel-change` | `{ value: string \| null }` | Fired on Apply and on clear |
|
|
169
|
+
| `rel-open-change` | `{ open: boolean }` | Popover opened/closed |
|
|
170
|
+
| `rel-focus` | — | Trigger received focus |
|
|
171
|
+
| `rel-blur` | — | Trigger lost focus |
|
|
172
|
+
|
|
173
|
+
## CSS Parts
|
|
174
|
+
|
|
175
|
+
| Part | Element |
|
|
176
|
+
| --- | --- |
|
|
177
|
+
| `base` | Outer wrapper |
|
|
178
|
+
| `label` | Field label |
|
|
179
|
+
| `trigger` | Trigger button |
|
|
180
|
+
| `value` | Trigger value/placeholder text |
|
|
181
|
+
| `icon` | Trailing clock icon |
|
|
182
|
+
| `panel` | Popover panel |
|
|
183
|
+
| `option` | Hour/minute/period entries |
|
|
184
|
+
| `footer` | Now / Cancel / Apply row |
|
|
185
|
+
| `helper` | Helper / error text |
|
|
186
|
+
|
|
187
|
+
```css
|
|
188
|
+
/* Example: widen the popover and restyle options */
|
|
189
|
+
rel-time-picker::part(panel) {
|
|
190
|
+
box-shadow: var(--rel-shadows-shadow-400);
|
|
191
|
+
}
|
|
192
|
+
rel-time-picker::part(option) {
|
|
193
|
+
border-radius: var(--rel-border-radius-xs);
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Design tokens
|
|
198
|
+
|
|
199
|
+
Styling is fully token-driven: surfaces (`--rel-bg-surface`, `--rel-bg-surface-elevated`), borders (`--rel-border-default`, `--rel-border-strong`, `--rel-border-focus`, `--rel-border-danger`, `--rel-border-subtle`), text (`--rel-text-default`, `--rel-text-label`, `--rel-text-placeholder`, `--rel-text-danger`, `--rel-text-disabled`), selection (`--rel-action-brand-fill`, `--rel-action-brand-label-icons-primary`), spacing, radius, shadows and the `--rel-body-*` typography roles.
|