@antadesign/anta 0.3.15 → 0.3.17
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/README.md +75 -45
- package/dist/anta_helpers.d.ts +8 -13
- package/dist/anta_helpers.js +11 -3
- package/dist/components/Banner.js +1 -0
- package/dist/components/ButtonCopy.d.ts +9 -6
- package/dist/components/ButtonCopy.js +5 -3
- package/dist/components/Card.js +1 -15
- package/dist/components/InputAutocomplete.js +1 -1
- package/dist/components/InputTime.d.ts +5 -4
- package/dist/components/Slider.d.ts +89 -0
- package/dist/components/Slider.js +144 -0
- package/dist/components/Switch.d.ts +68 -0
- package/dist/components/Switch.js +90 -0
- package/dist/components/copy-props.d.ts +2 -0
- package/dist/components/copy-props.js +2 -1
- package/dist/elements/a-banner.css +1 -1
- package/dist/elements/a-button.css +1 -1
- package/dist/elements/a-card.css +1 -1
- package/dist/elements/a-checkbox.css +1 -1
- package/dist/elements/a-copy.d.ts +1 -1
- package/dist/elements/a-copy.js +103 -69
- package/dist/elements/a-dialog.css +1 -1
- package/dist/elements/a-dialog.js +1 -1
- package/dist/elements/a-expander.css +1 -1
- package/dist/elements/a-icon.css +1 -1
- package/dist/elements/a-icon.shapes.css +1 -1
- package/dist/elements/a-icon.shapes.d.ts +3 -1
- package/dist/elements/a-icon.shapes.js +4 -0
- package/dist/elements/a-input-time.css +1 -1
- package/dist/elements/a-input-time.js +298 -377
- package/dist/elements/a-input.css +1 -1
- package/dist/elements/a-input.d.ts +0 -3
- package/dist/elements/a-input.js +16 -46
- package/dist/elements/a-progress.css +1 -1
- package/dist/elements/a-progress.js +2 -0
- package/dist/elements/a-radio.css +1 -1
- package/dist/elements/a-slider.css +1 -0
- package/dist/elements/a-slider.d.ts +24 -0
- package/dist/elements/a-slider.js +479 -0
- package/dist/elements/a-switch.css +1 -0
- package/dist/elements/a-switch.d.ts +31 -0
- package/dist/elements/a-switch.js +109 -0
- package/dist/elements/a-tabs.css +1 -1
- package/dist/elements/a-tag.css +1 -1
- package/dist/elements/a-tooltip.css +1 -1
- package/dist/elements/copy-behavior.d.ts +3 -3
- package/dist/elements/index.d.ts +2 -0
- package/dist/elements/index.js +6 -0
- package/dist/general_types.d.ts +98 -4
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/jsx-runtime.d.ts +6 -1
- package/dist/theme-anta.css +1 -1
- package/package.json +2 -2
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { HTMLElementBase } from "../anta_helpers";
|
|
2
2
|
import "./a-input-time.css";
|
|
3
|
-
const FORWARDED = ["name", "aria-label", "required"];
|
|
4
3
|
const CLEAR_TRIGGER = "clearrequest";
|
|
5
4
|
const CLEAR_INPUT_EVENT = "clearinput";
|
|
6
5
|
const pad2 = (n) => String(n).padStart(2, "0");
|
|
6
|
+
const inputColumns = (value) => Array.from(value).reduce((columns, char) => columns + (/[ᄀ-ᅟ⺀-가-힣豈-︐-!-⦆¢-₩]/u.test(char) ? 2 : 1), 0);
|
|
7
7
|
const to24 = (h12, period) => period === "pm" ? h12 % 12 + 12 : h12 % 12;
|
|
8
|
+
const parseTime = (value) => {
|
|
9
|
+
const match = /^(\d{1,2}):(\d{2})$/.exec((value ?? "").trim());
|
|
10
|
+
return match ? { h: Math.min(23, Number(match[1])), min: Math.min(59, Number(match[2])) } : null;
|
|
11
|
+
};
|
|
8
12
|
const SHADOW_STYLE = `
|
|
9
13
|
:host {
|
|
10
|
-
--_fs: 15px;
|
|
11
|
-
--_lh: 20px;
|
|
12
|
-
|
|
13
14
|
display: grid;
|
|
14
15
|
grid-template-columns: minmax(0, 1fr);
|
|
15
16
|
row-gap: 4px;
|
|
@@ -20,11 +21,10 @@ const SHADOW_STYLE = `
|
|
|
20
21
|
display: none;
|
|
21
22
|
color: var(--input-time-label);
|
|
22
23
|
font-family: var(--sans-serif);
|
|
23
|
-
font-size: var(--
|
|
24
|
-
line-height: var(--
|
|
24
|
+
font-size: var(--input-time-font-size);
|
|
25
|
+
line-height: var(--input-time-line-height);
|
|
25
26
|
font-weight: 500;
|
|
26
27
|
}
|
|
27
|
-
.label.has-label { display: block; }
|
|
28
28
|
|
|
29
29
|
.field {
|
|
30
30
|
--_bc: var(--input-time-border);
|
|
@@ -33,41 +33,34 @@ const SHADOW_STYLE = `
|
|
|
33
33
|
display: flex;
|
|
34
34
|
align-items: center;
|
|
35
35
|
box-sizing: border-box;
|
|
36
|
-
min-height:
|
|
36
|
+
min-height: var(--input-time-field-height);
|
|
37
37
|
background: var(--input-time-bg);
|
|
38
|
-
border-radius:
|
|
38
|
+
border-radius: var(--input-time-field-radius);
|
|
39
39
|
box-shadow: inset 0 0 0 var(--_bw) var(--_bc);
|
|
40
40
|
transition: box-shadow 120ms ease;
|
|
41
41
|
}
|
|
42
42
|
:host([status]) .field { --_bw: 1px; }
|
|
43
|
-
:host([size="small"]) { --_fs: 13px; --_lh: 16px; }
|
|
44
|
-
:host([size="large"]) { --_fs: 17px; --_lh: 22px; }
|
|
45
|
-
:host([size="small"]) .field { min-height: 24px; }
|
|
46
|
-
:host([size="large"]) .field { min-height: 32px; }
|
|
47
|
-
:host([round]) .field { border-radius: var(--input-time-round, 999px); }
|
|
48
43
|
|
|
49
44
|
@media (hover: hover) and (pointer: fine) {
|
|
50
|
-
:host(:not(:disabled)) .field:hover {
|
|
45
|
+
:host(:not(:disabled)) .field:hover {
|
|
46
|
+
--_bc: var(--input-time-border-hover);
|
|
47
|
+
--_bw: 1px;
|
|
48
|
+
}
|
|
51
49
|
}
|
|
52
|
-
.field:
|
|
50
|
+
.field:focus-within {
|
|
53
51
|
--_bc: var(--input-time-border-hover);
|
|
52
|
+
--_bw: 1px;
|
|
54
53
|
outline: 1px solid var(--focus-ring);
|
|
55
54
|
outline-offset: 1px;
|
|
56
55
|
}
|
|
56
|
+
@media (forced-colors: active) { .field { border: 1px solid ButtonBorder; } }
|
|
57
57
|
|
|
58
58
|
slot[name="leading"] {
|
|
59
59
|
display: none;
|
|
60
60
|
color: var(--input-time-adornment);
|
|
61
|
-
font-size: var(--
|
|
62
|
-
line-height: var(--
|
|
61
|
+
font-size: var(--input-time-font-size);
|
|
62
|
+
line-height: var(--input-time-line-height);
|
|
63
63
|
}
|
|
64
|
-
.field.has-leading slot[name="leading"] {
|
|
65
|
-
display: flex;
|
|
66
|
-
align-items: center;
|
|
67
|
-
flex-shrink: 0;
|
|
68
|
-
margin-inline-start: 7px;
|
|
69
|
-
}
|
|
70
|
-
.field.has-leading .segments { padding-inline-start: 5px; }
|
|
71
64
|
:host([dim-actions]) slot[name="leading"] { opacity: 0.6; transition: opacity 120ms ease; }
|
|
72
65
|
:host([dim-actions]:not(:disabled)) .field:hover slot[name="leading"],
|
|
73
66
|
:host([dim-actions]:not(:disabled)) .field:focus-within slot[name="leading"] { opacity: 1; }
|
|
@@ -83,53 +76,50 @@ const SHADOW_STYLE = `
|
|
|
83
76
|
font-family: var(--sans-serif);
|
|
84
77
|
font-feature-settings: 'ss02', 'ss05', 'tnum';
|
|
85
78
|
font-variation-settings: 'wdth' 100, 'slnt' 0, 'ital' 0;
|
|
86
|
-
font-size: var(--
|
|
87
|
-
line-height: var(--
|
|
79
|
+
font-size: var(--input-time-font-size);
|
|
80
|
+
line-height: var(--input-time-line-height);
|
|
88
81
|
font-weight: 400;
|
|
89
82
|
}
|
|
90
83
|
|
|
91
84
|
.seg {
|
|
85
|
+
appearance: none;
|
|
92
86
|
display: inline-block;
|
|
93
87
|
box-sizing: border-box;
|
|
88
|
+
min-width: 0;
|
|
94
89
|
padding-inline: 1px;
|
|
90
|
+
border: 0;
|
|
95
91
|
border-radius: 2px;
|
|
92
|
+
background: transparent;
|
|
93
|
+
color: inherit;
|
|
94
|
+
font: inherit;
|
|
95
|
+
font-feature-settings: 'ss02', 'ss05', 'tnum';
|
|
96
|
+
font-variation-settings: 'wdth' 100, 'slnt' 0, 'ital' 0;
|
|
97
|
+
line-height: inherit;
|
|
96
98
|
text-align: center;
|
|
97
99
|
white-space: nowrap;
|
|
98
100
|
outline: none;
|
|
99
|
-
cursor:
|
|
101
|
+
cursor: text;
|
|
100
102
|
font-variant-numeric: tabular-nums;
|
|
101
|
-
caret-color: transparent;
|
|
102
103
|
}
|
|
103
|
-
.seg
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
.seg--period {
|
|
105
|
+
field-sizing: content;
|
|
106
|
+
width: auto;
|
|
107
|
+
color: var(--input-time-period-color);
|
|
107
108
|
}
|
|
108
|
-
.seg
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
.seg::placeholder {
|
|
110
|
+
color: var(--input-time-placeholder);
|
|
111
|
+
opacity: 1;
|
|
112
|
+
}
|
|
113
|
+
.lit {
|
|
114
|
+
margin-inline: -1px;
|
|
115
|
+
white-space: pre;
|
|
116
|
+
color: var(--input-time-text);
|
|
111
117
|
}
|
|
112
|
-
.seg[data-placeholder] { color: var(--input-time-placeholder); }
|
|
113
|
-
.lit { white-space: pre; color: var(--input-time-text); }
|
|
114
|
-
|
|
115
|
-
/* No text-selection highlight inside the field: the segments are spinbuttons,
|
|
116
|
-
and a selection painted over the focused segment's tint compounds into an
|
|
117
|
-
unreadable block. The focus tint alone marks the active segment. Scoped to
|
|
118
|
-
the shadow, so page selection is unaffected. */
|
|
119
|
-
::selection { background: transparent; }
|
|
120
118
|
|
|
121
119
|
:host(:disabled) .segments { cursor: not-allowed; }
|
|
122
120
|
:host(:disabled) .seg { cursor: not-allowed; }
|
|
123
121
|
|
|
124
122
|
slot[name="trailing"] { display: none; }
|
|
125
|
-
.field.has-trailing slot[name="trailing"] {
|
|
126
|
-
display: flex;
|
|
127
|
-
align-items: center;
|
|
128
|
-
gap: 2px;
|
|
129
|
-
flex-shrink: 0;
|
|
130
|
-
color: var(--input-time-adornment);
|
|
131
|
-
font-size: var(--_fs);
|
|
132
|
-
}
|
|
133
123
|
:host([dim-actions]) slot[name="trailing"],
|
|
134
124
|
:host([dim-actions]) slot[name="clear"] { opacity: 0.6; transition: opacity 120ms ease; }
|
|
135
125
|
:host([dim-actions]:not(:disabled)) .field:hover slot[name="trailing"],
|
|
@@ -143,7 +133,7 @@ const SHADOW_STYLE = `
|
|
|
143
133
|
flex-shrink: 0;
|
|
144
134
|
visibility: hidden;
|
|
145
135
|
}
|
|
146
|
-
|
|
136
|
+
:host(:state(filled)) slot[name="clear"] { visibility: visible; }
|
|
147
137
|
:host(:disabled) slot[name="clear"] { display: none; }
|
|
148
138
|
|
|
149
139
|
.hint {
|
|
@@ -153,33 +143,59 @@ const SHADOW_STYLE = `
|
|
|
153
143
|
padding-inline-start: 1px;
|
|
154
144
|
color: var(--input-time-hint);
|
|
155
145
|
font-family: var(--sans-serif);
|
|
156
|
-
font-size: calc(var(--
|
|
157
|
-
line-height: calc(var(--
|
|
146
|
+
font-size: calc(var(--input-time-font-size) - 1px);
|
|
147
|
+
line-height: calc(var(--input-time-line-height) - 2px);
|
|
158
148
|
}
|
|
159
|
-
.hint.has-hint { display: flex; }
|
|
160
149
|
`;
|
|
150
|
+
const INPUT_TIME_TEMPLATE = typeof document === "undefined" ? void 0 : (() => {
|
|
151
|
+
const template = document.createElement("template");
|
|
152
|
+
const style = document.createElement("style");
|
|
153
|
+
style.textContent = SHADOW_STYLE;
|
|
154
|
+
const label = document.createElement("slot");
|
|
155
|
+
label.className = "label";
|
|
156
|
+
label.part.add("label");
|
|
157
|
+
label.name = "label";
|
|
158
|
+
const field = document.createElement("div");
|
|
159
|
+
field.className = "field";
|
|
160
|
+
field.part.add("field");
|
|
161
|
+
const leadingSlot = document.createElement("slot");
|
|
162
|
+
leadingSlot.name = "leading";
|
|
163
|
+
leadingSlot.part.add("leading");
|
|
164
|
+
const segments = document.createElement("div");
|
|
165
|
+
segments.className = "segments";
|
|
166
|
+
segments.part.add("segments");
|
|
167
|
+
segments.setAttribute("role", "group");
|
|
168
|
+
const clearSlot = document.createElement("slot");
|
|
169
|
+
clearSlot.name = "clear";
|
|
170
|
+
clearSlot.part.add("clear");
|
|
171
|
+
const trailingSlot = document.createElement("slot");
|
|
172
|
+
trailingSlot.name = "trailing";
|
|
173
|
+
trailingSlot.part.add("trailing");
|
|
174
|
+
field.append(leadingSlot, segments, clearSlot, trailingSlot);
|
|
175
|
+
const hint = document.createElement("slot");
|
|
176
|
+
hint.className = "hint";
|
|
177
|
+
hint.part.add("hint");
|
|
178
|
+
hint.name = "hint";
|
|
179
|
+
template.content.append(style, label, field, document.createElement("slot"), hint);
|
|
180
|
+
return template;
|
|
181
|
+
})();
|
|
161
182
|
class AInputTimeElement extends HTMLElementBase {
|
|
162
183
|
static formAssociated = true;
|
|
163
184
|
static observedAttributes = [
|
|
164
|
-
...FORWARDED,
|
|
165
185
|
"value",
|
|
166
186
|
"defaultvalue",
|
|
167
187
|
"locale",
|
|
168
188
|
"hour12",
|
|
169
189
|
"status",
|
|
170
190
|
"disabled",
|
|
171
|
-
"size",
|
|
172
191
|
"min",
|
|
173
|
-
"max"
|
|
192
|
+
"max",
|
|
193
|
+
"aria-label",
|
|
194
|
+
"required"
|
|
174
195
|
];
|
|
175
196
|
#internals;
|
|
176
197
|
#field;
|
|
177
|
-
#labelBox;
|
|
178
198
|
#labelSlot;
|
|
179
|
-
#hintBox;
|
|
180
|
-
#hintSlot;
|
|
181
|
-
#leadingSlot;
|
|
182
|
-
#trailingSlot;
|
|
183
199
|
#segRow;
|
|
184
200
|
#ready = false;
|
|
185
201
|
#formDisabled = false;
|
|
@@ -196,9 +212,10 @@ class AInputTimeElement extends HTMLElementBase {
|
|
|
196
212
|
#minute = null;
|
|
197
213
|
#period = null;
|
|
198
214
|
#segs = [];
|
|
199
|
-
|
|
200
|
-
//
|
|
201
|
-
|
|
215
|
+
#literals = [];
|
|
216
|
+
// Pointer-focused inputs keep the browser's caret placement. Keyboard focus
|
|
217
|
+
// selects the segment so a new value can be typed immediately.
|
|
218
|
+
#pointerSegment;
|
|
202
219
|
constructor() {
|
|
203
220
|
super();
|
|
204
221
|
try {
|
|
@@ -207,51 +224,20 @@ class AInputTimeElement extends HTMLElementBase {
|
|
|
207
224
|
console.warn("a-input-time: ElementInternals unavailable \u2014 form association disabled.", err);
|
|
208
225
|
}
|
|
209
226
|
const shadow = this.attachShadow({ mode: "open", delegatesFocus: true });
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
this.#
|
|
213
|
-
this.#labelBox.className = "label";
|
|
214
|
-
this.#labelBox.setAttribute("part", "label");
|
|
215
|
-
this.#labelSlot = document.createElement("slot");
|
|
216
|
-
this.#labelSlot.name = "label";
|
|
217
|
-
this.#labelBox.append(this.#labelSlot);
|
|
227
|
+
if (!INPUT_TIME_TEMPLATE) throw new Error("a-input-time requires a DOM document");
|
|
228
|
+
shadow.append(INPUT_TIME_TEMPLATE.content.cloneNode(true));
|
|
229
|
+
this.#labelSlot = shadow.querySelector(".label");
|
|
218
230
|
this.#labelSlot.addEventListener("click", () => this.#segs[0]?.el.focus());
|
|
219
231
|
this.#labelSlot.addEventListener("slotchange", this.#onLabelSlotChange);
|
|
220
|
-
this.#field =
|
|
221
|
-
this.#
|
|
222
|
-
this.#field.
|
|
223
|
-
this.#segRow
|
|
224
|
-
this.#segRow.
|
|
225
|
-
this.#segRow.
|
|
226
|
-
this.#segRow.
|
|
227
|
-
this.#
|
|
228
|
-
if (e.target === this.#field || e.target === this.#segRow) {
|
|
229
|
-
e.preventDefault();
|
|
230
|
-
this.#segs[0]?.el.focus();
|
|
231
|
-
}
|
|
232
|
-
});
|
|
233
|
-
this.#leadingSlot = document.createElement("slot");
|
|
234
|
-
this.#leadingSlot.name = "leading";
|
|
235
|
-
this.#leadingSlot.setAttribute("part", "leading");
|
|
236
|
-
this.#leadingSlot.addEventListener("slotchange", () => this.#field.classList.toggle("has-leading", this.#leadingSlot.assignedNodes().length > 0));
|
|
237
|
-
const clearSlot = document.createElement("slot");
|
|
238
|
-
clearSlot.name = "clear";
|
|
239
|
-
clearSlot.setAttribute("part", "clear");
|
|
240
|
-
this.#trailingSlot = document.createElement("slot");
|
|
241
|
-
this.#trailingSlot.name = "trailing";
|
|
242
|
-
this.#trailingSlot.setAttribute("part", "trailing");
|
|
243
|
-
this.#trailingSlot.addEventListener("slotchange", () => this.#field.classList.toggle("has-trailing", this.#trailingSlot.assignedNodes().length > 0));
|
|
244
|
-
this.#field.append(this.#leadingSlot, this.#segRow, clearSlot, this.#trailingSlot);
|
|
232
|
+
this.#field = shadow.querySelector(".field");
|
|
233
|
+
this.#segRow = shadow.querySelector(".segments");
|
|
234
|
+
this.#field.addEventListener("mousedown", this.#onFieldMouseDown);
|
|
235
|
+
this.#segRow.addEventListener("input", this.#onInput);
|
|
236
|
+
this.#segRow.addEventListener("keydown", this.#onKeyDown);
|
|
237
|
+
this.#segRow.addEventListener("focusin", this.#onFocusIn);
|
|
238
|
+
this.#segRow.addEventListener("focusout", this.#onFocusOut);
|
|
239
|
+
this.#segRow.addEventListener("paste", this.#onPaste);
|
|
245
240
|
this.addEventListener(CLEAR_TRIGGER, () => this.clear());
|
|
246
|
-
this.#hintBox = document.createElement("div");
|
|
247
|
-
this.#hintBox.className = "hint";
|
|
248
|
-
this.#hintBox.setAttribute("part", "hint");
|
|
249
|
-
this.#hintSlot = document.createElement("slot");
|
|
250
|
-
this.#hintSlot.name = "hint";
|
|
251
|
-
this.#hintSlot.addEventListener("slotchange", this.#onHintSlotChange);
|
|
252
|
-
this.#hintBox.append(this.#hintSlot);
|
|
253
|
-
const extrasSlot = document.createElement("slot");
|
|
254
|
-
shadow.append(style, this.#labelBox, this.#field, extrasSlot, this.#hintBox);
|
|
255
241
|
}
|
|
256
242
|
getAnchorRect() {
|
|
257
243
|
return this.#field.getBoundingClientRect();
|
|
@@ -291,13 +277,7 @@ class AInputTimeElement extends HTMLElementBase {
|
|
|
291
277
|
this.#syncDisabled();
|
|
292
278
|
return;
|
|
293
279
|
}
|
|
294
|
-
if (name === "size") return;
|
|
295
280
|
if (name === "min" || name === "max") {
|
|
296
|
-
this.#clampIfComplete();
|
|
297
|
-
this.#iso = this.value;
|
|
298
|
-
this.#render();
|
|
299
|
-
this.#internals?.setFormValue(this.value);
|
|
300
|
-
this.#updateFilled();
|
|
301
281
|
this.#updateValidity();
|
|
302
282
|
return;
|
|
303
283
|
}
|
|
@@ -349,161 +329,211 @@ class AInputTimeElement extends HTMLElementBase {
|
|
|
349
329
|
{ type: "minute", value: "05" }
|
|
350
330
|
];
|
|
351
331
|
}
|
|
332
|
+
this.#amText = amText;
|
|
333
|
+
this.#pmText = pmText;
|
|
352
334
|
this.#segRow.replaceChildren();
|
|
353
335
|
this.#segs = [];
|
|
336
|
+
this.#literals = [];
|
|
354
337
|
for (const part of parts) {
|
|
355
338
|
if (part.type === "hour" || part.type === "minute" || part.type === "dayPeriod") {
|
|
356
|
-
const el = document.createElement("
|
|
357
|
-
el.className = "seg";
|
|
358
|
-
el.tabIndex = 0;
|
|
359
|
-
el.setAttribute("role", "spinbutton");
|
|
360
|
-
el.contentEditable = "true";
|
|
361
|
-
el.spellcheck = false;
|
|
362
|
-
el.setAttribute("autocorrect", "off");
|
|
363
|
-
el.setAttribute("autocapitalize", "off");
|
|
339
|
+
const el = document.createElement("input");
|
|
364
340
|
const kind = part.type === "dayPeriod" ? "period" : part.type;
|
|
341
|
+
el.className = `seg seg--${kind}`;
|
|
342
|
+
el.type = "text";
|
|
343
|
+
el.spellcheck = false;
|
|
344
|
+
el.part.add("segment", kind);
|
|
345
|
+
el.autocomplete = "off";
|
|
346
|
+
el.autocorrect = false;
|
|
347
|
+
el.autocapitalize = "off";
|
|
365
348
|
el.setAttribute("aria-label", kind === "hour" ? "Hour" : kind === "minute" ? "Minute" : "AM/PM");
|
|
366
|
-
el.
|
|
349
|
+
el.inputMode = kind === "period" ? "text" : "numeric";
|
|
350
|
+
if (kind === "period") {
|
|
351
|
+
const length = Math.max(2, inputColumns(this.#amText), inputColumns(this.#pmText));
|
|
352
|
+
el.maxLength = length;
|
|
353
|
+
el.size = length;
|
|
354
|
+
} else {
|
|
355
|
+
el.maxLength = 2;
|
|
356
|
+
el.size = 2;
|
|
357
|
+
}
|
|
367
358
|
const seg = kind === "hour" ? { kind, el, min: hour12 ? 1 : 0, max: hour12 ? 12 : 23 } : kind === "minute" ? { kind, el, min: 0, max: 59 } : { kind, el, min: 0, max: 1 };
|
|
368
|
-
el.addEventListener("keydown", (e) => this.#onSegKeyDown(e, seg));
|
|
369
|
-
el.addEventListener("beforeinput", (e) => this.#onSegBeforeInput(e, seg));
|
|
370
|
-
el.addEventListener("focus", () => {
|
|
371
|
-
this.#buf = "";
|
|
372
|
-
});
|
|
373
|
-
el.addEventListener("blur", () => this.#onSegBlur());
|
|
374
359
|
this.#segRow.append(el);
|
|
375
360
|
this.#segs.push(seg);
|
|
376
361
|
} else {
|
|
362
|
+
if (!part.value.trim()) continue;
|
|
377
363
|
const lit = document.createElement("span");
|
|
378
364
|
lit.className = "lit";
|
|
365
|
+
lit.part.add("literal");
|
|
379
366
|
lit.setAttribute("aria-hidden", "true");
|
|
380
367
|
lit.textContent = part.value;
|
|
381
368
|
this.#segRow.append(lit);
|
|
369
|
+
this.#literals.push(part.value);
|
|
382
370
|
}
|
|
383
371
|
}
|
|
384
|
-
this.#amText = amText;
|
|
385
|
-
this.#pmText = pmText;
|
|
386
372
|
this.#applyValue(keep);
|
|
387
373
|
this.#applyGroupLabel();
|
|
388
374
|
this.#syncStatus();
|
|
389
375
|
this.#syncDisabled();
|
|
390
|
-
this.#
|
|
391
|
-
this.#internals?.setFormValue(this.value);
|
|
392
|
-
this.#updateFilled();
|
|
393
|
-
this.#updateValidity();
|
|
376
|
+
this.#commitEdit({ dispatch: false });
|
|
394
377
|
}
|
|
395
378
|
#amText = "AM";
|
|
396
379
|
#pmText = "PM";
|
|
397
|
-
// ---
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
380
|
+
// --- Native segment editing + navigation ---
|
|
381
|
+
#segFromTarget(target) {
|
|
382
|
+
if (!(target instanceof HTMLInputElement)) return void 0;
|
|
383
|
+
return this.#segs.find((seg) => seg.el === target);
|
|
384
|
+
}
|
|
385
|
+
// Clicking the field's padding (rather than a native input) moves focus into
|
|
386
|
+
// the editor. A real pointer interaction leaves caret placement to the browser.
|
|
387
|
+
#onFieldMouseDown = (e) => {
|
|
388
|
+
const seg = this.#segFromTarget(e.target);
|
|
389
|
+
if (seg) {
|
|
390
|
+
this.#pointerSegment = seg.el;
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
if (e.target === this.#field || e.target === this.#segRow) {
|
|
406
394
|
e.preventDefault();
|
|
407
|
-
this.#
|
|
408
|
-
}
|
|
395
|
+
this.#focusSegment(this.#segs[0]);
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
#onFocusIn = (e) => {
|
|
399
|
+
const seg = this.#segFromTarget(e.target);
|
|
400
|
+
if (!seg) return;
|
|
401
|
+
const pointerFocused = this.#pointerSegment === seg.el;
|
|
402
|
+
this.#pointerSegment = void 0;
|
|
403
|
+
if (!pointerFocused) {
|
|
404
|
+
queueMicrotask(() => {
|
|
405
|
+
if (this.shadowRoot?.activeElement === seg.el) seg.el.select();
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
#onFocusOut = (_e) => {
|
|
410
|
+
queueMicrotask(() => {
|
|
411
|
+
const active = this.shadowRoot?.activeElement;
|
|
412
|
+
if (this.#segs.some((seg) => seg.el === active)) return;
|
|
413
|
+
if (this.#clampIfComplete()) this.#commitEdit();
|
|
414
|
+
this.#dispatch("change");
|
|
415
|
+
});
|
|
416
|
+
};
|
|
417
|
+
#onKeyDown = (e) => {
|
|
418
|
+
const seg = this.#segFromTarget(e.target);
|
|
419
|
+
if (!seg) return;
|
|
420
|
+
const { el } = seg;
|
|
421
|
+
const key = e.key;
|
|
422
|
+
if (key === "ArrowUp" || key === "ArrowDown") {
|
|
409
423
|
e.preventDefault();
|
|
410
|
-
this.#step(seg,
|
|
411
|
-
|
|
424
|
+
this.#step(seg, key === "ArrowUp" ? 1 : -1);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
if (key === "PageUp" || key === "PageDown") {
|
|
412
428
|
e.preventDefault();
|
|
413
|
-
this.#
|
|
414
|
-
|
|
429
|
+
this.#step(seg, (key === "PageUp" ? 1 : -1) * (seg.kind === "minute" ? 5 : 1));
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
if (key === "ArrowLeft" || key === "ArrowRight") {
|
|
433
|
+
const start = el.selectionStart ?? 0;
|
|
434
|
+
const end = el.selectionEnd ?? start;
|
|
435
|
+
const boundary = key === "ArrowLeft" ? start === 0 && end === 0 : start === el.value.length && end === el.value.length;
|
|
436
|
+
if (boundary) {
|
|
437
|
+
e.preventDefault();
|
|
438
|
+
this.#render();
|
|
439
|
+
this.#moveFocus(seg, key === "ArrowRight" ? 1 : -1);
|
|
440
|
+
}
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
if (key === "Backspace" && el.value === "") {
|
|
415
444
|
e.preventDefault();
|
|
416
|
-
this.#
|
|
417
|
-
|
|
445
|
+
this.#moveFocus(seg, -1);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
if (seg.kind !== "period" && this.#literals.some((literal) => literal.includes(key))) {
|
|
418
449
|
e.preventDefault();
|
|
419
|
-
this.#
|
|
450
|
+
this.#render();
|
|
451
|
+
this.#moveFocus(seg, 1, { numeric: true });
|
|
420
452
|
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
#onSegBeforeInput(e, seg) {
|
|
453
|
+
};
|
|
454
|
+
#onPaste = (e) => {
|
|
455
|
+
const text = e.clipboardData?.getData("text/plain").trim() ?? "";
|
|
456
|
+
if (!/^(?:[01]?\d|2[0-3]):[0-5]\d$/.test(text)) return;
|
|
426
457
|
e.preventDefault();
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
458
|
+
this.#applyValue(text);
|
|
459
|
+
this.#commitEdit();
|
|
460
|
+
this.#dispatch("change");
|
|
461
|
+
};
|
|
462
|
+
#onInput = (e) => {
|
|
463
|
+
const seg = this.#segFromTarget(e.target);
|
|
464
|
+
if (!seg || e.isComposing) return;
|
|
465
|
+
if (seg.kind === "period") this.#onPeriodInput(e, seg);
|
|
466
|
+
else this.#onNumericInput(seg);
|
|
467
|
+
};
|
|
468
|
+
#onNumericInput(seg) {
|
|
469
|
+
const { el } = seg;
|
|
470
|
+
const typed = el.value.replace(/\D/g, "");
|
|
471
|
+
if (typed !== el.value) el.value = typed;
|
|
472
|
+
const hour12Hour = seg.kind === "hour" && this.#hour12;
|
|
473
|
+
const typeMax = hour12Hour ? 23 : seg.max;
|
|
474
|
+
if (typed === "") {
|
|
475
|
+
if (seg.kind === "hour") this.#hour = null;
|
|
476
|
+
else this.#minute = null;
|
|
477
|
+
this.#commitEdit({ dispatch: false, preserve: el });
|
|
433
478
|
return;
|
|
434
479
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
this.#commitEdit();
|
|
443
|
-
this.#dispatch("change");
|
|
444
|
-
} else {
|
|
445
|
-
for (const ch of text) this.#typeChar(seg, ch);
|
|
446
|
-
}
|
|
447
|
-
return;
|
|
480
|
+
let raw = typed.slice(0, 2);
|
|
481
|
+
let value = Number(raw);
|
|
482
|
+
let normalized = false;
|
|
483
|
+
if (value > typeMax) {
|
|
484
|
+
raw = raw.at(-1);
|
|
485
|
+
value = Number(raw);
|
|
486
|
+
normalized = true;
|
|
448
487
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
let s = (text ?? "").trim().toLowerCase();
|
|
459
|
-
if (!s) return null;
|
|
460
|
-
let mer = null;
|
|
461
|
-
const m = s.match(/([ap])\.?m?\.?$/);
|
|
462
|
-
if (m) {
|
|
463
|
-
mer = m[1] === "p" ? "pm" : "am";
|
|
464
|
-
s = s.slice(0, m.index).trim();
|
|
465
|
-
}
|
|
466
|
-
let h;
|
|
467
|
-
let min;
|
|
468
|
-
if (/[:.]/.test(s)) {
|
|
469
|
-
const [hp, mp] = s.split(/[:.]/);
|
|
470
|
-
h = Number(hp);
|
|
471
|
-
min = Number(mp ?? "0");
|
|
472
|
-
} else {
|
|
473
|
-
const d = s.replace(/\D/g, "");
|
|
474
|
-
if (!d) return null;
|
|
475
|
-
if (d.length <= 2) {
|
|
476
|
-
h = Number(d);
|
|
477
|
-
min = 0;
|
|
488
|
+
const advance = raw.length === 2 || Number(`${value}0`) > typeMax;
|
|
489
|
+
if (hour12Hour) {
|
|
490
|
+
if (value === 0 && raw.length === 1) this.#hour = null;
|
|
491
|
+
else if (value === 0) {
|
|
492
|
+
this.#hour = 12;
|
|
493
|
+
this.#period = "am";
|
|
494
|
+
} else if (value > 12) {
|
|
495
|
+
this.#hour = value - 12;
|
|
496
|
+
this.#period = "pm";
|
|
478
497
|
} else {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
min = Number(d.slice(cut));
|
|
498
|
+
this.#hour = value;
|
|
499
|
+
if (this.#period == null) this.#period = "am";
|
|
482
500
|
}
|
|
501
|
+
} else {
|
|
502
|
+
this.#assign(seg, Math.max(seg.min, value));
|
|
483
503
|
}
|
|
484
|
-
|
|
485
|
-
if (
|
|
486
|
-
if (mer === "am" && h === 12) h = 0;
|
|
487
|
-
if (h < 0 || h > 23 || min < 0 || min > 59) return null;
|
|
488
|
-
return { h, min };
|
|
504
|
+
this.#commitEdit({ dispatch: false, preserve: normalized || advance ? void 0 : el });
|
|
505
|
+
if (advance) this.#moveFocus(seg, 1);
|
|
489
506
|
}
|
|
490
|
-
#
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
507
|
+
#onPeriodInput(e, seg) {
|
|
508
|
+
const { el } = seg;
|
|
509
|
+
const raw = el.value.trim();
|
|
510
|
+
if (!raw) {
|
|
511
|
+
this.#period = null;
|
|
512
|
+
this.#commitEdit({ dispatch: false, preserve: el });
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
const lower = raw.toLowerCase();
|
|
516
|
+
const am = this.#amText.toLowerCase();
|
|
517
|
+
const pm = this.#pmText.toLowerCase();
|
|
518
|
+
const period = lower === "a" || lower === "am" || lower === am ? "am" : lower === "p" || lower === "pm" || lower === pm ? "pm" : null;
|
|
519
|
+
if (period) {
|
|
520
|
+
this.#period = period;
|
|
521
|
+
this.#commitEdit({ dispatch: false });
|
|
522
|
+
this.#moveFocus(seg, 1);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
if (am.startsWith(lower) || pm.startsWith(lower)) {
|
|
526
|
+
this.#period = null;
|
|
527
|
+
this.#commitEdit({ dispatch: false, preserve: el });
|
|
498
528
|
return;
|
|
499
529
|
}
|
|
500
|
-
|
|
530
|
+
e.stopPropagation();
|
|
531
|
+
el.value = this.#period === "pm" ? this.#pmText : this.#period === "am" ? this.#amText : "";
|
|
501
532
|
}
|
|
502
|
-
/** Arrow / page
|
|
533
|
+
/** Arrow / page increments wrap within a segment's [min, max];
|
|
503
534
|
* from empty, ↑ lands on min and ↓ on max. Clamps the resulting complete value
|
|
504
535
|
* into the field's `min`/`max` range (so ↑ can't step past `max`). */
|
|
505
536
|
#step(seg, delta) {
|
|
506
|
-
this.#buf = "";
|
|
507
537
|
if (seg.kind === "period") {
|
|
508
538
|
this.#period = this.#period == null ? "am" : this.#period === "am" ? "pm" : "am";
|
|
509
539
|
this.#clampIfComplete();
|
|
@@ -519,22 +549,16 @@ class AInputTimeElement extends HTMLElementBase {
|
|
|
519
549
|
this.#clampIfComplete();
|
|
520
550
|
this.#commitEdit();
|
|
521
551
|
}
|
|
522
|
-
/** Mutate a segment
|
|
552
|
+
/** Mutate a numeric segment without committing (so callers can clamp first). */
|
|
523
553
|
#assign(seg, val) {
|
|
524
|
-
if (seg.kind === "period") return;
|
|
525
554
|
if (seg.kind === "hour") this.#hour = val;
|
|
526
555
|
else this.#minute = val;
|
|
527
556
|
if (seg.kind === "hour" && this.#hour12 && this.#period == null) this.#period = "am";
|
|
528
557
|
}
|
|
529
|
-
#set(seg, val) {
|
|
530
|
-
this.#assign(seg, val);
|
|
531
|
-
this.#commitEdit();
|
|
532
|
-
}
|
|
533
558
|
// --- min / max range (total minutes since 00:00, or null when unbounded) ---
|
|
534
559
|
#parseTotal(v) {
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
return Math.min(23, Number(m[1])) * 60 + Math.min(59, Number(m[2]));
|
|
560
|
+
const time = parseTime(v);
|
|
561
|
+
return time ? time.h * 60 + time.min : null;
|
|
538
562
|
}
|
|
539
563
|
get #minTotal() {
|
|
540
564
|
return this.#parseTotal(this.getAttribute("min"));
|
|
@@ -561,75 +585,20 @@ class AInputTimeElement extends HTMLElementBase {
|
|
|
561
585
|
this.#applyValue(`${pad2(Math.floor(clamped / 60))}:${pad2(clamped % 60)}`);
|
|
562
586
|
return true;
|
|
563
587
|
}
|
|
564
|
-
#
|
|
565
|
-
if (seg
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
const candidate = Number(this.#buf + d);
|
|
576
|
-
let raw;
|
|
577
|
-
let advance = false;
|
|
578
|
-
if (candidate > typeMax) {
|
|
579
|
-
raw = Number(d);
|
|
580
|
-
this.#buf = d;
|
|
581
|
-
} else {
|
|
582
|
-
raw = candidate;
|
|
583
|
-
this.#buf += d;
|
|
584
|
-
}
|
|
585
|
-
if (Number(`${raw}0`) > typeMax || this.#buf.length >= String(typeMax).length) {
|
|
586
|
-
advance = true;
|
|
587
|
-
this.#buf = "";
|
|
588
|
-
}
|
|
589
|
-
if (hour12Hour) {
|
|
590
|
-
if (raw === 0 && !advance) {
|
|
591
|
-
seg.el.textContent = "0";
|
|
592
|
-
seg.el.setAttribute("data-placeholder", "");
|
|
593
|
-
seg.el.removeAttribute("aria-valuenow");
|
|
594
|
-
seg.el.removeAttribute("aria-valuetext");
|
|
588
|
+
#focusSegment(seg) {
|
|
589
|
+
if (!seg) return;
|
|
590
|
+
seg.el.focus();
|
|
591
|
+
seg.el.select();
|
|
592
|
+
}
|
|
593
|
+
#moveFocus(from, dir, options = {}) {
|
|
594
|
+
const index = this.#segs.indexOf(from);
|
|
595
|
+
for (let i = index + dir; i >= 0 && i < this.#segs.length; i += dir) {
|
|
596
|
+
const next = this.#segs[i];
|
|
597
|
+
if (!options.numeric || next.kind !== "period") {
|
|
598
|
+
this.#focusSegment(next);
|
|
595
599
|
return;
|
|
596
600
|
}
|
|
597
|
-
if (raw === 0) {
|
|
598
|
-
this.#hour = 12;
|
|
599
|
-
this.#period = "am";
|
|
600
|
-
} else if (raw > 12) {
|
|
601
|
-
this.#hour = raw - 12;
|
|
602
|
-
this.#period = "pm";
|
|
603
|
-
} else {
|
|
604
|
-
this.#hour = raw;
|
|
605
|
-
if (this.#period == null) this.#period = "am";
|
|
606
|
-
}
|
|
607
|
-
this.#commitEdit();
|
|
608
|
-
if (advance) this.#moveFocus(seg, 1);
|
|
609
|
-
return;
|
|
610
601
|
}
|
|
611
|
-
this.#assign(seg, Math.max(seg.min, raw));
|
|
612
|
-
this.#commitEdit();
|
|
613
|
-
if (advance) this.#moveFocus(seg, 1);
|
|
614
|
-
}
|
|
615
|
-
#moveFocus(from, dir) {
|
|
616
|
-
const i = this.#segs.indexOf(from);
|
|
617
|
-
const next = this.#segs[i + dir];
|
|
618
|
-
if (next) next.el.focus();
|
|
619
|
-
}
|
|
620
|
-
/** On losing a segment, reset the digit buffer; when focus has left the whole
|
|
621
|
-
* group (not just hopped to a sibling segment), clamp into `min`/`max` and
|
|
622
|
-
* fire `change`. `shadowRoot.activeElement` reliably reports the focused
|
|
623
|
-
* segment even across the shadow boundary (blur `relatedTarget` is retargeted
|
|
624
|
-
* to null), so defer one microtask to read where focus landed. */
|
|
625
|
-
#onSegBlur() {
|
|
626
|
-
this.#buf = "";
|
|
627
|
-
queueMicrotask(() => {
|
|
628
|
-
const active = this.shadowRoot?.activeElement;
|
|
629
|
-
if (this.#segs.some((s) => s.el === active)) return;
|
|
630
|
-
if (this.#clampIfComplete()) this.#commitEdit();
|
|
631
|
-
this.#dispatch("change");
|
|
632
|
-
});
|
|
633
602
|
}
|
|
634
603
|
// --- Value + rendering ---
|
|
635
604
|
/** Canonical 0–23 hour from the display state, or null when incomplete. */
|
|
@@ -639,70 +608,48 @@ class AInputTimeElement extends HTMLElementBase {
|
|
|
639
608
|
if (this.#period == null) return null;
|
|
640
609
|
return to24(this.#hour, this.#period);
|
|
641
610
|
}
|
|
642
|
-
#commitEdit() {
|
|
643
|
-
this.#render();
|
|
611
|
+
#commitEdit({ dispatch = true, preserve } = {}) {
|
|
612
|
+
this.#render(preserve);
|
|
644
613
|
this.#iso = this.value;
|
|
645
614
|
this.#internals?.setFormValue(this.value);
|
|
646
615
|
this.#updateFilled();
|
|
647
616
|
this.#updateValidity();
|
|
648
|
-
this.#dispatch("input");
|
|
617
|
+
if (dispatch) this.#dispatch("input");
|
|
649
618
|
}
|
|
650
|
-
#render() {
|
|
619
|
+
#render(preserve) {
|
|
651
620
|
for (const seg of this.#segs) {
|
|
652
|
-
|
|
653
|
-
else this.#renderSeg(seg, seg.kind === "hour" ? this.#hour : this.#minute);
|
|
621
|
+
this.#renderSeg(seg, preserve === seg.el);
|
|
654
622
|
}
|
|
655
623
|
}
|
|
656
|
-
/** Paint
|
|
657
|
-
*
|
|
658
|
-
#renderSeg(seg,
|
|
624
|
+
/** Paint a segment's committed value without disturbing a focused native input
|
|
625
|
+
* that already owns valid in-progress text and selection. */
|
|
626
|
+
#renderSeg(seg, preserve) {
|
|
659
627
|
const el = seg.el;
|
|
660
628
|
if (seg.kind === "period") {
|
|
661
|
-
const
|
|
662
|
-
el.
|
|
663
|
-
el.
|
|
664
|
-
el.setAttribute("aria-valuemin", "0");
|
|
665
|
-
el.setAttribute("aria-valuemax", "1");
|
|
666
|
-
if (set2) {
|
|
667
|
-
el.setAttribute("aria-valuenow", this.#period === "pm" ? "1" : "0");
|
|
668
|
-
el.setAttribute("aria-valuetext", this.#period === "pm" ? this.#pmText : this.#amText);
|
|
669
|
-
} else {
|
|
670
|
-
el.removeAttribute("aria-valuenow");
|
|
671
|
-
el.removeAttribute("aria-valuetext");
|
|
672
|
-
}
|
|
629
|
+
const value2 = this.#period === "pm" ? this.#pmText : this.#period === "am" ? this.#amText : "";
|
|
630
|
+
if (!preserve) el.value = value2;
|
|
631
|
+
el.placeholder = this.#amText;
|
|
673
632
|
return;
|
|
674
633
|
}
|
|
675
|
-
const
|
|
676
|
-
|
|
677
|
-
el.
|
|
678
|
-
el.toggleAttribute("data-placeholder", !set);
|
|
679
|
-
el.setAttribute("aria-valuemin", String(seg.min));
|
|
680
|
-
el.setAttribute("aria-valuemax", String(seg.max));
|
|
681
|
-
if (set) {
|
|
682
|
-
el.setAttribute("aria-valuenow", String(val));
|
|
683
|
-
el.setAttribute("aria-valuetext", pad2(val));
|
|
684
|
-
} else {
|
|
685
|
-
el.removeAttribute("aria-valuenow");
|
|
686
|
-
el.removeAttribute("aria-valuetext");
|
|
687
|
-
}
|
|
634
|
+
const value = seg.kind === "hour" ? this.#hour : this.#minute;
|
|
635
|
+
if (!preserve) el.value = value == null ? "" : pad2(value);
|
|
636
|
+
el.placeholder = "\u2013\u2013";
|
|
688
637
|
}
|
|
689
638
|
/** Parse a `"HH:mm"` value into the display segments (24h → display units). */
|
|
690
639
|
#applyValue(v) {
|
|
691
|
-
const
|
|
692
|
-
if (!
|
|
640
|
+
const time = parseTime(v);
|
|
641
|
+
if (!time) {
|
|
693
642
|
this.#hour = null;
|
|
694
643
|
this.#minute = null;
|
|
695
644
|
this.#period = null;
|
|
696
645
|
return;
|
|
697
646
|
}
|
|
698
|
-
|
|
699
|
-
const min = Math.min(59, Number(m[2]));
|
|
700
|
-
this.#minute = min;
|
|
647
|
+
this.#minute = time.min;
|
|
701
648
|
if (this.#hour12) {
|
|
702
|
-
this.#hour = (h + 11) % 12 + 1;
|
|
703
|
-
this.#period = h < 12 ? "am" : "pm";
|
|
649
|
+
this.#hour = (time.h + 11) % 12 + 1;
|
|
650
|
+
this.#period = time.h < 12 ? "am" : "pm";
|
|
704
651
|
} else {
|
|
705
|
-
this.#hour = h;
|
|
652
|
+
this.#hour = time.h;
|
|
706
653
|
this.#period = null;
|
|
707
654
|
}
|
|
708
655
|
}
|
|
@@ -715,20 +662,11 @@ class AInputTimeElement extends HTMLElementBase {
|
|
|
715
662
|
else this.#segRow.removeAttribute("aria-label");
|
|
716
663
|
}
|
|
717
664
|
#onLabelSlotChange = () => {
|
|
718
|
-
this.#labelBox.classList.toggle("has-label", this.#labelSlot.assignedNodes().length > 0);
|
|
719
665
|
this.#applyGroupLabel();
|
|
720
666
|
};
|
|
721
|
-
#onHintSlotChange = () => {
|
|
722
|
-
this.#hintBox.classList.toggle("has-hint", this.#hintSlot.assignedNodes().length > 0);
|
|
723
|
-
};
|
|
724
667
|
#syncDisabled() {
|
|
725
668
|
const off = this.hasAttribute("disabled") || this.#formDisabled;
|
|
726
|
-
for (const seg of this.#segs)
|
|
727
|
-
if (off) seg.el.setAttribute("aria-disabled", "true");
|
|
728
|
-
else seg.el.removeAttribute("aria-disabled");
|
|
729
|
-
seg.el.tabIndex = off ? -1 : 0;
|
|
730
|
-
seg.el.contentEditable = off ? "false" : "true";
|
|
731
|
-
}
|
|
669
|
+
for (const seg of this.#segs) seg.el.disabled = off;
|
|
732
670
|
}
|
|
733
671
|
#syncStatus() {
|
|
734
672
|
const critical = this.getAttribute("status") === "critical";
|
|
@@ -739,13 +677,8 @@ class AInputTimeElement extends HTMLElementBase {
|
|
|
739
677
|
}
|
|
740
678
|
}
|
|
741
679
|
#updateFilled() {
|
|
742
|
-
|
|
743
|
-
this.#
|
|
744
|
-
try {
|
|
745
|
-
if (filled) this.#internals?.states.add("filled");
|
|
746
|
-
else this.#internals?.states.delete("filled");
|
|
747
|
-
} catch {
|
|
748
|
-
}
|
|
680
|
+
if (this.value) this.#internals?.states.add("filled");
|
|
681
|
+
else this.#internals?.states.delete("filled");
|
|
749
682
|
}
|
|
750
683
|
#updateValidity() {
|
|
751
684
|
if (!this.#internals) return;
|
|
@@ -778,12 +711,10 @@ class AInputTimeElement extends HTMLElementBase {
|
|
|
778
711
|
const next = v ?? "";
|
|
779
712
|
if (next === this.value) return;
|
|
780
713
|
this.#applyValue(next);
|
|
781
|
-
this.#iso = this.value;
|
|
782
714
|
if (this.#ready) {
|
|
783
|
-
this.#
|
|
784
|
-
|
|
785
|
-
this.#
|
|
786
|
-
this.#updateValidity();
|
|
715
|
+
this.#commitEdit({ dispatch: false });
|
|
716
|
+
} else {
|
|
717
|
+
this.#iso = this.value;
|
|
787
718
|
}
|
|
788
719
|
}
|
|
789
720
|
/** Empty the field, refocus the first segment, and fire input + change. */
|
|
@@ -791,12 +722,7 @@ class AInputTimeElement extends HTMLElementBase {
|
|
|
791
722
|
this.#hour = null;
|
|
792
723
|
this.#minute = null;
|
|
793
724
|
this.#period = null;
|
|
794
|
-
this.#
|
|
795
|
-
this.#render();
|
|
796
|
-
this.#internals?.setFormValue("");
|
|
797
|
-
this.#updateFilled();
|
|
798
|
-
this.#updateValidity();
|
|
799
|
-
this.#dispatch("input");
|
|
725
|
+
this.#commitEdit();
|
|
800
726
|
this.#dispatch("change");
|
|
801
727
|
this.dispatchEvent(new CustomEvent(CLEAR_INPUT_EVENT, { bubbles: true }));
|
|
802
728
|
this.#segs[0]?.el.focus();
|
|
@@ -828,12 +754,7 @@ class AInputTimeElement extends HTMLElementBase {
|
|
|
828
754
|
}
|
|
829
755
|
formResetCallback() {
|
|
830
756
|
this.#applyValue(this.getAttribute("defaultvalue") ?? "");
|
|
831
|
-
this.#
|
|
832
|
-
this.#render();
|
|
833
|
-
this.#internals?.setFormValue(this.value);
|
|
834
|
-
this.#updateFilled();
|
|
835
|
-
this.#updateValidity();
|
|
836
|
-
this.#dispatch("input");
|
|
757
|
+
this.#commitEdit();
|
|
837
758
|
this.#dispatch("change");
|
|
838
759
|
}
|
|
839
760
|
formDisabledCallback(disabled) {
|