@lekoala/date-picker 0.5.1 → 0.5.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.
@@ -1,4 +1,4 @@
1
- /*** @lekoala/date-picker v0.5.1 - https://github.com/lekoala/date-picker ***/
1
+ /*** @lekoala/date-picker v0.5.3 - https://github.com/lekoala/date-picker ***/
2
2
  (() => {
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -44,6 +44,9 @@
44
44
  defineDatePicker: () => defineDatePicker
45
45
  });
46
46
 
47
+ // src/activation-intents.js
48
+ var activationIntents = new WeakMap;
49
+
47
50
  // src/date.js
48
51
  var DATE_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
49
52
  var MONTH_RE = /^(\d{4})-(\d{2})$/;
@@ -75,9 +78,11 @@
75
78
  const text = String(value || "");
76
79
  const monthMatch = MONTH_RE.exec(text);
77
80
  if (monthMatch) {
81
+ const year = Number(monthMatch[1]);
78
82
  const month = Number(monthMatch[2]);
79
- if (month >= 1 && month <= 12)
83
+ if (year >= 1 && year <= 9999 && month >= 1 && month <= 12) {
80
84
  return `${monthMatch[1]}-${String(month).padStart(2, "0")}`;
85
+ }
81
86
  }
82
87
  const parsed = parseDate(text);
83
88
  if (!parsed)
@@ -846,8 +851,12 @@
846
851
  this._id = `date-calendar-${++uid}`;
847
852
  this._model = new CalendarModel;
848
853
  this._connected = false;
854
+ this._lifecycleId = 0;
849
855
  this._rendering = false;
856
+ this._heading = null;
857
+ this._renderRoot = null;
850
858
  this._source = null;
859
+ this._hostIntent = null;
851
860
  this._sourceStates = new Map;
852
861
  this._loadedRanges = new Set;
853
862
  this._loadController = null;
@@ -872,6 +881,7 @@
872
881
  connectedCallback() {
873
882
  if (this._connected)
874
883
  return;
884
+ this._lifecycleId++;
875
885
  upgradeProperties(this, [
876
886
  "locale",
877
887
  "min",
@@ -914,6 +924,7 @@
914
924
  this._stopDefaultMessages?.();
915
925
  this._stopDefaultMessages = null;
916
926
  this._connected = false;
927
+ this._lifecycleId++;
917
928
  this._loadController?.abort();
918
929
  this._loadController = null;
919
930
  this._loadingKey = "";
@@ -923,12 +934,43 @@
923
934
  this.removeEventListener("keydown", this._onKeyDown);
924
935
  this.removeEventListener("focusin", this._onFocusIn);
925
936
  }
926
- attributeChangedCallback() {
927
- if (!this._connected || this._rendering)
937
+ attributeChangedCallback(name, _oldValue, newValue) {
938
+ if (this._rendering)
939
+ return;
940
+ if (name === "value" && newValue && !isDate(newValue)) {
941
+ this._reflect("value", this._model.value);
942
+ return;
943
+ }
944
+ if ((name === "min" || name === "max") && newValue && !isDate(newValue)) {
945
+ this._reflect(name, "");
946
+ if (this._connected) {
947
+ this._syncModel();
948
+ this.render();
949
+ }
950
+ return;
951
+ }
952
+ if (name === "display" && newValue) {
953
+ try {
954
+ if (monthKey(newValue) !== newValue)
955
+ throw new TypeError("Non-canonical month");
956
+ } catch {
957
+ this._reflect("display", this._model.display);
958
+ return;
959
+ }
960
+ }
961
+ if (!this._connected)
928
962
  return;
929
963
  this._syncModel();
930
964
  this.render();
931
965
  }
966
+ _queueFocus(callback) {
967
+ const lifecycleId = this._lifecycleId;
968
+ queueMicrotask(() => {
969
+ if (!this._connected || lifecycleId !== this._lifecycleId)
970
+ return;
971
+ callback();
972
+ });
973
+ }
932
974
  get locale() {
933
975
  return resolveLocale(this.getAttribute("locale") || "");
934
976
  }
@@ -1269,7 +1311,7 @@
1269
1311
  this._moveFocusDom(previousFocused, this._model.focused, moveFocus);
1270
1312
  }
1271
1313
  if (moveFocus)
1272
- queueMicrotask(() => this.focusGrid());
1314
+ this._queueFocus(() => this.focusGrid());
1273
1315
  }
1274
1316
  _moveFocusDom(previous, next, moveFocus = true) {
1275
1317
  const prevCell = previous ? this.querySelector(`.dp-day[data-date="${CSS.escape(previous)}"]`) : null;
@@ -1284,6 +1326,15 @@
1284
1326
  this.focusGrid();
1285
1327
  }
1286
1328
  }
1329
+ _syncRovingDom(date) {
1330
+ for (const cell of this.querySelectorAll('.dp-day[tabindex="0"]')) {
1331
+ if (cell instanceof HTMLElement && cell.dataset.date !== date)
1332
+ cell.tabIndex = -1;
1333
+ }
1334
+ const cell = this.querySelector(`.dp-day[data-date="${CSS.escape(date)}"]`);
1335
+ if (cell instanceof HTMLElement)
1336
+ cell.tabIndex = 0;
1337
+ }
1287
1338
  _moveSelectionDom(previousValue, nextValue) {
1288
1339
  if (previousValue) {
1289
1340
  const prev = this.querySelector(`.dp-day[data-date="${CSS.escape(previousValue)}"]`);
@@ -1344,17 +1395,29 @@
1344
1395
  target.focus();
1345
1396
  }
1346
1397
  async activateDate(date) {
1398
+ if (!this._connected)
1399
+ return false;
1400
+ const lifecycleId = this._lifecycleId;
1401
+ const hostIntent = this._hostIntent?.date === date ? this._hostIntent.intent : undefined;
1402
+ this._hostIntent = null;
1403
+ const tagged = (event) => {
1404
+ if (hostIntent !== undefined)
1405
+ activationIntents.set(event, hostIntent);
1406
+ return event;
1407
+ };
1347
1408
  const confirmed = await this.ensureDate(date);
1409
+ if (!this._connected || lifecycleId !== this._lifecycleId)
1410
+ return false;
1348
1411
  const state = this.getDateState(date);
1349
1412
  if (!confirmed || state.disabled) {
1350
- this.dispatchEvent(new CustomEvent("dateinvalid", { detail: { date, state }, bubbles: true }));
1413
+ this.dispatchEvent(tagged(new CustomEvent("dateinvalid", { detail: { date, state }, bubbles: true })));
1351
1414
  return false;
1352
1415
  }
1353
- const accepted = this.dispatchEvent(new CustomEvent("dateactivate", {
1416
+ const accepted = this.dispatchEvent(tagged(new CustomEvent("dateactivate", {
1354
1417
  detail: { date, state },
1355
1418
  bubbles: true,
1356
1419
  cancelable: true
1357
- }));
1420
+ })));
1358
1421
  if (!accepted)
1359
1422
  return false;
1360
1423
  if (this.selection === "single") {
@@ -1399,6 +1462,8 @@
1399
1462
  this._reflect("display", this._model.display);
1400
1463
  if (previousDisplay !== this.display)
1401
1464
  this._emitDisplayChange();
1465
+ else
1466
+ this._syncRovingDom(this._model.focused);
1402
1467
  this.activateDate(date);
1403
1468
  }
1404
1469
  _onChange(event) {
@@ -1409,7 +1474,7 @@
1409
1474
  }
1410
1475
  if (target instanceof HTMLInputElement && target.matches(".dp-year-input")) {
1411
1476
  const year = Number(target.value);
1412
- if (!Number.isInteger(year) || year < 1 || year > 9998) {
1477
+ if (!Number.isInteger(year) || year < 1 || year > 9999) {
1413
1478
  if (this._connected)
1414
1479
  this.render(false);
1415
1480
  return;
@@ -1425,6 +1490,7 @@
1425
1490
  if (!isDate(date))
1426
1491
  return;
1427
1492
  this._model.focused = date;
1493
+ this._syncRovingDom(date);
1428
1494
  this.dispatchEvent(new CustomEvent("datefocus", { detail: { date, state: this.getDateState(date) }, bubbles: true }));
1429
1495
  }
1430
1496
  _onKeyDown(event) {
@@ -1487,8 +1553,8 @@
1487
1553
  const nextKey = stepMonthKey(display, 1);
1488
1554
  const prevDisabled = Boolean(minMonth && prevKey !== null && prevKey < minMonth);
1489
1555
  const nextDisabled = Boolean(maxMonth && nextKey !== null && nextKey > maxMonth);
1490
- const minYearAttr = this.min ? ` min="${Number(this.min.slice(0, 4))}"` : "";
1491
- const maxYearAttr = this.max ? ` max="${Number(this.max.slice(0, 4))}"` : "";
1556
+ const minYearAttr = ` min="${this.min ? Number(this.min.slice(0, 4)) : 1}"`;
1557
+ const maxYearAttr = ` max="${this.max ? Number(this.max.slice(0, 4)) : 9999}"`;
1492
1558
  const showWeeks = this.showWeekNumbers && this.firstDay === 1;
1493
1559
  const weekHeader = showWeeks ? `<th scope="col" class="dp-week-heading" abbr="${escapeHtml(this.messages.week)}">#</th>` : "";
1494
1560
  const monthOptions = names.map((name, index) => {
@@ -1526,8 +1592,20 @@
1526
1592
  <select id="${this._id}-month" class="dp-month-select ${monthClass}" aria-label="${escapeHtml(this.messages.month)}">${monthOptions}</select>`;
1527
1593
  const yearControl = `<label class="dp-visually-hidden" for="${this._id}-year">${escapeHtml(this.messages.year)}</label>
1528
1594
  <input id="${this._id}-year" class="dp-year-input ${yearClass}" type="number" inputmode="numeric" value="${displayYear}"${minYearAttr}${maxYearAttr} aria-label="${escapeHtml(this.messages.year)}">`;
1595
+ if (!this._heading?.isConnected || !this._renderRoot?.isConnected) {
1596
+ this.replaceChildren();
1597
+ const heading = document.createElement("h2");
1598
+ heading.id = headingId;
1599
+ heading.className = "dp-calendar-heading dp-visually-hidden";
1600
+ heading.setAttribute("aria-live", "polite");
1601
+ const renderRoot = document.createElement("div");
1602
+ renderRoot.className = "dp-calendar-render";
1603
+ this.append(heading, renderRoot);
1604
+ this._heading = heading;
1605
+ this._renderRoot = renderRoot;
1606
+ }
1529
1607
  this._rendering = true;
1530
- this.innerHTML = `
1608
+ this._renderRoot.innerHTML = `
1531
1609
  <div class="dp-calendar-shell">
1532
1610
  <div class="dp-calendar-header">
1533
1611
  ${yearFirst ? yearControl : monthControl}
@@ -1535,17 +1613,17 @@
1535
1613
  <button type="button" class="dp-nav dp-prev ${navClass}" data-calendar-action="previous" aria-label="${escapeHtml(this.messages.previousMonth)}"${prevDisabled ? " disabled" : ""}><svg aria-hidden="true" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="m4 10 4-4 4 4"/></svg></button>
1536
1614
  <button type="button" class="dp-nav dp-next ${navClass}" data-calendar-action="next" aria-label="${escapeHtml(this.messages.nextMonth)}"${nextDisabled ? " disabled" : ""}><svg aria-hidden="true" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="m4 6 4 4 4-4"/></svg></button>
1537
1615
  </div>
1538
- <h2 id="${headingId}" class="dp-calendar-heading dp-visually-hidden" aria-live="polite">${escapeHtml(formatMonthYear(`${display}-15`, locale))}</h2>
1539
1616
  <table id="${gridId}" class="dp-grid" role="grid" aria-labelledby="${headingId}">
1540
1617
  <thead><tr>${weekHeader}${dayHeaders}</tr></thead>
1541
1618
  <tbody>${rows}</tbody>
1542
1619
  </table>
1543
1620
  </div>`;
1621
+ this._heading.textContent = formatMonthYear(`${display}-15`, locale);
1544
1622
  this._rendering = false;
1545
1623
  this._paintedRange = { ...this._highlightedRange };
1546
1624
  this._paintedPreview = this.hasAttribute("data-range-preview");
1547
1625
  if (focusKey) {
1548
- queueMicrotask(() => {
1626
+ this._queueFocus(() => {
1549
1627
  let selector = focusKey;
1550
1628
  if (selector.startsWith(".dp-day[data-date=")) {
1551
1629
  selector = `.dp-day[data-date="${CSS.escape(this._model.focused)}"]`;
@@ -1946,6 +2024,10 @@
1946
2024
  }
1947
2025
 
1948
2026
  // src/date-field.js
2027
+ function isEditable(input) {
2028
+ return !input.matches(":disabled") && !input.readOnly;
2029
+ }
2030
+
1949
2031
  class DateFieldController {
1950
2032
  constructor(input, options = {}) {
1951
2033
  this.input = input;
@@ -1955,13 +2037,27 @@
1955
2037
  this.canonical = "";
1956
2038
  this._dirty = false;
1957
2039
  this._commitId = 0;
2040
+ this._disposed = false;
1958
2041
  this._originalName = input.getAttribute("name") || "";
1959
2042
  this._attributeObserver = null;
1960
2043
  this.onAttributesChanged = null;
1961
2044
  this.confirm = null;
2045
+ this._generatedDefault = null;
1962
2046
  }
1963
2047
  setLocale(locale) {
2048
+ const generated = this._generatedDefault;
2049
+ const previous = generated ? this.adapter.format(generated) : null;
1964
2050
  this.adapter = createDateAdapter(locale || "");
2051
+ if (generated && this.input.defaultValue === previous) {
2052
+ this.input.defaultValue = this.adapter.format(generated);
2053
+ }
2054
+ }
2055
+ adoptDefault() {
2056
+ const input = this.input;
2057
+ if (input.getAttribute("value") != null || input.defaultValue !== "")
2058
+ return;
2059
+ input.defaultValue = input.value;
2060
+ this._generatedDefault = this.canonical && input.value ? this.canonical : null;
1965
2061
  }
1966
2062
  setMessages(messages) {
1967
2063
  this.messages = messages || {};
@@ -1976,7 +2072,7 @@
1976
2072
  return this.adapter.placeholder;
1977
2073
  }
1978
2074
  get isDisabled() {
1979
- return this.input.disabled;
2075
+ return this.input.matches(":disabled");
1980
2076
  }
1981
2077
  get isReadOnly() {
1982
2078
  return this.input.readOnly;
@@ -2044,34 +2140,39 @@
2044
2140
  async commit(options = {}) {
2045
2141
  const input = this.input;
2046
2142
  const commitId = ++this._commitId;
2143
+ if (this._disposed)
2144
+ return { status: "stale", commitId };
2047
2145
  const text = input.value.trim();
2048
2146
  if (!text) {
2049
2147
  if (commitId !== this._commitId)
2050
- return { status: "stale" };
2148
+ return { status: "stale", commitId };
2051
2149
  input.setCustomValidity("");
2052
- return { status: "clear", value: "" };
2150
+ return { status: "clear", value: "", commitId };
2053
2151
  }
2054
2152
  const parsed = this.adapter.parse(text);
2055
2153
  if (!parsed) {
2056
2154
  if (commitId !== this._commitId)
2057
- return { status: "stale" };
2155
+ return { status: "stale", commitId };
2058
2156
  if (this.hidden)
2059
2157
  this.hidden.value = "";
2060
2158
  input.setCustomValidity(this.messages.invalidDate);
2061
- return { status: "invalid" };
2159
+ return { status: "invalid", commitId };
2062
2160
  }
2063
2161
  if (this.confirm) {
2064
2162
  const result = await this.confirm(parsed, options);
2065
- if (commitId !== this._commitId)
2066
- return { status: "stale" };
2163
+ if (this._disposed || commitId !== this._commitId)
2164
+ return { status: "stale", commitId };
2067
2165
  if (!result?.ok) {
2068
2166
  if (this.hidden)
2069
2167
  this.hidden.value = "";
2070
2168
  input.setCustomValidity(result?.message || this.messages.unavailableDate);
2071
- return { status: "invalid" };
2169
+ return { status: "invalid", commitId };
2072
2170
  }
2073
2171
  }
2074
- return { status: "ok", value: parsed };
2172
+ return { status: "ok", value: parsed, commitId };
2173
+ }
2174
+ isCommitCurrent(commitId) {
2175
+ return commitId === this._commitId;
2075
2176
  }
2076
2177
  setCanonical(value, options = {}) {
2077
2178
  const next = value || "";
@@ -2092,6 +2193,8 @@
2092
2193
  return parsed && isDate(parsed) ? parsed : "";
2093
2194
  }
2094
2195
  teardown() {
2196
+ this.dirty();
2197
+ this._disposed = true;
2095
2198
  this._attributeObserver?.disconnect();
2096
2199
  this._attributeObserver = null;
2097
2200
  const hiddenName = this.hidden?.getAttribute("name") || this._originalName;
@@ -2163,8 +2266,7 @@
2163
2266
  this._timeFields = [null, null];
2164
2267
  this._timeOrderOwned = new Set;
2165
2268
  this._lastFocusEndpoint = "";
2166
- this._rangeCommitId = 0;
2167
- this._pendingIntents = new Map;
2269
+ this._activationId = 0;
2168
2270
  this._previewDate = "";
2169
2271
  this._drag = null;
2170
2272
  this._suppressGridClick = false;
@@ -2175,7 +2277,7 @@
2175
2277
  this._originalDescribedBy = "";
2176
2278
  this._rangeOriginalDescribedBy = ["", ""];
2177
2279
  this._formatHint = null;
2178
- this._generatedPlaceholder = false;
2280
+ this._generatedPlaceholders = new WeakSet;
2179
2281
  this._controller = null;
2180
2282
  this._stopTracking = null;
2181
2283
  this._open = false;
@@ -2192,6 +2294,9 @@
2192
2294
  _rangeMode() {
2193
2295
  return this.hasAttribute("range") && Boolean(this._range);
2194
2296
  }
2297
+ _ownsField(field) {
2298
+ return this._connected && (field === this._field || Boolean(this._fields?.includes(field)));
2299
+ }
2195
2300
  connectedCallback() {
2196
2301
  if (this._connected)
2197
2302
  return;
@@ -2219,6 +2324,7 @@
2219
2324
  if (this._messages === null)
2220
2325
  this._refreshMessages();
2221
2326
  });
2327
+ this.validate();
2222
2328
  }
2223
2329
  }
2224
2330
  _connectSingle() {
@@ -2248,9 +2354,7 @@
2248
2354
  input.setCustomValidity(this.messages.invalidDate);
2249
2355
  if (this._field.hidden)
2250
2356
  this._field.hidden.defaultValue = this._value;
2251
- if (input.getAttribute("value") == null && input.defaultValue === "") {
2252
- input.defaultValue = input.value;
2253
- }
2357
+ this._field.adoptDefault();
2254
2358
  }
2255
2359
  _discoverTimeFields() {
2256
2360
  this._timeFields = [null, null];
@@ -2307,26 +2411,30 @@
2307
2411
  const adapter = this._adapter();
2308
2412
  const initialStart = isDate(startInput.value) ? startInput.value : adapter.parse(startInput.value);
2309
2413
  const initialEnd = isDate(endInput.value) ? endInput.value : adapter.parse(endInput.value);
2414
+ const startText = startInput.value;
2415
+ const endText = endInput.value;
2310
2416
  this._setRange(initialStart || "", initialEnd || "", { emit: false });
2311
- if (startInput.value.trim() && !isDate(initialStart))
2312
- startInput.setCustomValidity(this.messages.invalidDate);
2313
- if (endInput.value.trim() && !isDate(initialEnd))
2314
- endInput.setCustomValidity(this.messages.invalidDate);
2417
+ for (const [input, text, initial] of [
2418
+ [startInput, startText, initialStart],
2419
+ [endInput, endText, initialEnd]
2420
+ ]) {
2421
+ if (!text.trim() || isDate(initial))
2422
+ continue;
2423
+ input.value = text;
2424
+ input.setCustomValidity(this.messages.invalidDate);
2425
+ }
2315
2426
  if (this._fields[0].hidden)
2316
2427
  this._fields[0].hidden.defaultValue = this._range.start;
2317
2428
  if (this._fields[1].hidden)
2318
2429
  this._fields[1].hidden.defaultValue = this._range.end;
2319
- if (startInput.getAttribute("value") == null && startInput.defaultValue === "") {
2320
- startInput.defaultValue = startInput.value;
2321
- }
2322
- if (endInput.getAttribute("value") == null && endInput.defaultValue === "") {
2323
- endInput.defaultValue = endInput.value;
2324
- }
2430
+ for (const field of this._fields)
2431
+ field.adoptDefault();
2325
2432
  }
2326
2433
  disconnectedCallback() {
2327
2434
  this._stopDefaultMessages?.();
2328
2435
  this._stopDefaultMessages = null;
2329
2436
  this._connected = false;
2437
+ this._suppressFocusOpen = false;
2330
2438
  this.hide(false);
2331
2439
  this._controller?.abort();
2332
2440
  this._controller = null;
@@ -2367,13 +2475,29 @@
2367
2475
  input.removeAttribute("aria-controls");
2368
2476
  }
2369
2477
  attributeChangedCallback(name, oldValue, newValue) {
2370
- if (!this._connected || this._reflecting || oldValue === newValue)
2478
+ if (this._reflecting || oldValue === newValue)
2479
+ return;
2480
+ if (name === "value" && newValue && !isDate(newValue)) {
2481
+ this._reflectValue(this._value);
2482
+ return;
2483
+ }
2484
+ if ((name === "min" || name === "max") && newValue && !isDate(newValue)) {
2485
+ this.removeAttribute(name);
2486
+ if (this._connected) {
2487
+ this._syncCalendarOptions();
2488
+ this.validate();
2489
+ }
2490
+ return;
2491
+ }
2492
+ if (!this._connected)
2371
2493
  return;
2372
2494
  if (name === "value") {
2373
2495
  if (this._rangeMode())
2374
2496
  return;
2375
- if (!newValue || isDate(newValue))
2497
+ if (!newValue || isDate(newValue)) {
2498
+ this._supersedeActivations();
2376
2499
  this._setValue(newValue || "", { emit: false, format: true, reflect: false });
2500
+ }
2377
2501
  return;
2378
2502
  }
2379
2503
  if (name === "locale")
@@ -2398,6 +2522,7 @@
2398
2522
  }
2399
2523
  if (value && !isDate(value))
2400
2524
  throw new TypeError(`Invalid date-picker value: ${value}`);
2525
+ this._supersedeActivations();
2401
2526
  this._setValue(value || "", { emit: false, format: true });
2402
2527
  }
2403
2528
  get range() {
@@ -2407,6 +2532,7 @@
2407
2532
  if (!this._rangeMode())
2408
2533
  throw new TypeError("range is only available on <date-picker range>");
2409
2534
  const { start, end } = normalizeRange(value);
2535
+ this._supersedeActivations();
2410
2536
  this._setRange(start, end);
2411
2537
  }
2412
2538
  get locale() {
@@ -2547,7 +2673,7 @@
2547
2673
  const button = this._buttons[0]?.button;
2548
2674
  if (button)
2549
2675
  button.disabled = input.disabled || input.readOnly;
2550
- if (this._open && (input.disabled || input.readOnly))
2676
+ if (this._open && !isEditable(input))
2551
2677
  this.hide(false);
2552
2678
  }
2553
2679
  _syncRangeFields() {
@@ -2562,9 +2688,9 @@
2562
2688
  }
2563
2689
  this._syncDragAffordance();
2564
2690
  const active = this._range?.activeEndpoint === "end" ? 1 : 0;
2565
- if (this._open && (this._fields[active]?.input.disabled || this._fields[active]?.input.readOnly)) {
2691
+ const activeInput = this._fields[active]?.input;
2692
+ if (this._open && activeInput && !isEditable(activeInput))
2566
2693
  this.hide(false);
2567
- }
2568
2694
  }
2569
2695
  _setRangeAria() {
2570
2696
  if (!this._fields || !this._panel || !this._formatHint)
@@ -2650,7 +2776,7 @@
2650
2776
  return;
2651
2777
  this.hide(false);
2652
2778
  queueMicrotask(() => {
2653
- if (!event.defaultPrevented)
2779
+ if (!signal.aborted && !event.defaultPrevented)
2654
2780
  restore();
2655
2781
  });
2656
2782
  }, { capture: true, signal });
@@ -2692,7 +2818,7 @@
2692
2818
  button.addEventListener("click", (event) => {
2693
2819
  if (this._open) {
2694
2820
  if (event.detail === 0) {
2695
- setTimeout(() => this._calendar?.focusGrid(), 0);
2821
+ this._defer(() => this._calendar?.focusGrid());
2696
2822
  return;
2697
2823
  }
2698
2824
  this.hide(false);
@@ -2701,15 +2827,23 @@
2701
2827
  this.show({ returnFocus: button });
2702
2828
  }, { signal });
2703
2829
  button.addEventListener("keydown", (event) => this._onTriggerKeyDown("", event), { signal });
2830
+ calendar.addEventListener("click", (event) => this._captureGridIntent(event), { capture: true, signal });
2831
+ calendar.addEventListener("keydown", (event) => this._captureGridIntent(event), {
2832
+ capture: true,
2833
+ signal
2834
+ });
2704
2835
  calendar.addEventListener("dateactivate", (event) => {
2705
2836
  const custom = event;
2706
2837
  const date = custom.detail?.date;
2707
2838
  if (!isDate(date))
2708
2839
  return;
2840
+ const intent = activationIntents.get(custom);
2709
2841
  queueMicrotask(() => {
2710
- if (custom.defaultPrevented || !this._connected)
2842
+ if (signal.aborted || custom.defaultPrevented || !this._connected)
2711
2843
  return;
2712
- if (this._input?.disabled || this._input?.readOnly)
2844
+ if (intent && intent.id !== this._activationId)
2845
+ return;
2846
+ if (!this._input || !isEditable(this._input))
2713
2847
  return;
2714
2848
  this._field?.dirty();
2715
2849
  this._setValue(date, { emit: true, format: true });
@@ -2729,7 +2863,12 @@
2729
2863
  const { signal } = controller;
2730
2864
  for (const [index, field] of this._fields.entries()) {
2731
2865
  const endpoint = index === 1 ? "end" : "start";
2732
- field.input.addEventListener("input", () => field.handleInput(), { signal });
2866
+ field.input.addEventListener("input", () => {
2867
+ field.handleInput();
2868
+ this._orderTags.delete(index);
2869
+ if (!field.isDirty)
2870
+ this._revalidateRange(endpoint);
2871
+ }, { signal });
2733
2872
  field.input.addEventListener("change", () => void this._commitFieldText(endpoint), { signal });
2734
2873
  field.input.addEventListener("blur", () => void this._commitFieldText(endpoint), { signal });
2735
2874
  field.input.addEventListener("focus", (event) => this._onFieldFocus(endpoint, event), { signal });
@@ -2765,12 +2904,11 @@
2765
2904
  if (!isDate(date))
2766
2905
  return;
2767
2906
  queueMicrotask(() => {
2768
- if (custom.defaultPrevented || !this._connected || !this._range)
2907
+ if (signal.aborted || custom.defaultPrevented || !this._connected || !this._range)
2769
2908
  return;
2770
- const intent = this._pendingIntents.get(date);
2771
- if (intent === undefined || intent.id !== this._rangeCommitId)
2909
+ const intent = activationIntents.get(custom);
2910
+ if (intent === undefined || intent.id !== this._activationId)
2772
2911
  return;
2773
- this._pendingIntents.delete(date);
2774
2912
  const editable = (bound) => this._boundEditable(bound);
2775
2913
  const result = intent.endpoint ? this._range.moveEndpoint(date, intent.endpoint, editable) : this._range.activate(date, editable);
2776
2914
  if (result.status === "refused") {
@@ -2805,8 +2943,7 @@
2805
2943
  if (!this._range || !calendar)
2806
2944
  return;
2807
2945
  this._range.focus(endpoint);
2808
- this._rangeCommitId++;
2809
- this._pendingIntents.clear();
2946
+ this._activationId++;
2810
2947
  this._lastFocusEndpoint = endpoint;
2811
2948
  const target = this._boundCanonical(endpoint) || todayISO();
2812
2949
  calendar.display = monthKey(target);
@@ -2820,7 +2957,7 @@
2820
2957
  }
2821
2958
  if (event.detail === 0) {
2822
2959
  this._activateRangeEndpoint(endpoint);
2823
- setTimeout(() => this._calendar?.focusGrid(), 0);
2960
+ this._defer(() => this._calendar?.focusGrid());
2824
2961
  return;
2825
2962
  }
2826
2963
  if (endpoint !== this._range?.activeEndpoint) {
@@ -2836,7 +2973,7 @@
2836
2973
  if (this._open) {
2837
2974
  if (this._rangeMode() && endpoint)
2838
2975
  this._activateRangeEndpoint(endpoint);
2839
- setTimeout(() => this._calendar?.focusGrid(), 0);
2976
+ this._defer(() => this._calendar?.focusGrid());
2840
2977
  return;
2841
2978
  }
2842
2979
  if (this._rangeMode())
@@ -2852,7 +2989,7 @@
2852
2989
  this._range?.focus(endpoint);
2853
2990
  }
2854
2991
  if (this._open) {
2855
- setTimeout(() => this._calendar?.focusGrid(), 0);
2992
+ this._defer(() => this._calendar?.focusGrid());
2856
2993
  } else {
2857
2994
  this.show();
2858
2995
  }
@@ -2868,8 +3005,7 @@
2868
3005
  this._lastFocusEndpoint = endpoint;
2869
3006
  this._range?.focus(endpoint);
2870
3007
  if (switched) {
2871
- this._rangeCommitId++;
2872
- this._pendingIntents.clear();
3008
+ this._activationId++;
2873
3009
  }
2874
3010
  if (!this.openOnFocus || this._suppressFocusOpen)
2875
3011
  return;
@@ -2880,25 +3016,34 @@
2880
3016
  this.show({ moveFocus: false });
2881
3017
  }
2882
3018
  _captureGridIntent(event) {
2883
- if (!this._rangeMode() || !this._range)
3019
+ if (this._rangeMode() ? !this._range : !this._field)
2884
3020
  return;
2885
3021
  if (event.type === "keydown") {
2886
3022
  const key = event.key;
2887
3023
  if (key !== "Enter" && key !== " ")
2888
3024
  return;
2889
3025
  }
3026
+ if (event.type === "click" && this._suppressGridClick)
3027
+ return;
2890
3028
  if (!(event.target instanceof Element))
2891
3029
  return;
2892
3030
  const cell = event.target.closest(".dp-day[data-date]");
2893
3031
  const date = cell?.getAttribute("data-date") || "";
2894
3032
  if (!isDate(date))
2895
3033
  return;
2896
- this._pendingIntents.clear();
2897
- this._pendingIntents.set(date, { id: ++this._rangeCommitId, endpoint: "" });
3034
+ this._attachIntent(date, "");
3035
+ }
3036
+ _attachIntent(date, endpoint) {
3037
+ if (!this._calendar)
3038
+ return;
3039
+ this._calendar._hostIntent = { date, intent: { id: ++this._activationId, endpoint } };
3040
+ }
3041
+ _supersedeActivations() {
3042
+ this._activationId++;
2898
3043
  }
2899
3044
  _boundEditable(bound) {
2900
3045
  const field = this._fields?.[bound === "end" ? 1 : 0];
2901
- return field != null && !field.input.disabled && !field.input.readOnly;
3046
+ return field != null && isEditable(field.input);
2902
3047
  }
2903
3048
  _applyRangeTransition(transition) {
2904
3049
  const fields = this._fields;
@@ -3046,8 +3191,7 @@
3046
3191
  return;
3047
3192
  }
3048
3193
  const target = drag.endpoint === "start" ? transition.range.start : transition.range.end;
3049
- this._pendingIntents.clear();
3050
- this._pendingIntents.set(target, { id: ++this._rangeCommitId, endpoint: drag.endpoint });
3194
+ this._attachIntent(target, drag.endpoint);
3051
3195
  this._calendar?.activateDate(target);
3052
3196
  }
3053
3197
  _onGridPointerCancel(event) {
@@ -3115,9 +3259,9 @@
3115
3259
  this._fields[1].setLocale(this.locale);
3116
3260
  hint.textContent = `${this.messages.formatHint}: ${this._fields[0].placeholder}`;
3117
3261
  for (const [index, field] of this._fields.entries()) {
3118
- if (!field.input.hasAttribute("placeholder") || this._generatedPlaceholder) {
3262
+ if (!field.input.hasAttribute("placeholder") || this._generatedPlaceholders.has(field.input)) {
3119
3263
  field.input.placeholder = field.placeholder;
3120
- this._generatedPlaceholder = true;
3264
+ this._generatedPlaceholders.add(field.input);
3121
3265
  }
3122
3266
  const canonical = index === 1 ? this._range?.end || "" : this._range?.start || "";
3123
3267
  if (canonical)
@@ -3131,9 +3275,9 @@
3131
3275
  return;
3132
3276
  field.setLocale(this.locale);
3133
3277
  hint.textContent = `${this.messages.formatHint}: ${field.placeholder}`;
3134
- if (!field.input.hasAttribute("placeholder") || this._generatedPlaceholder) {
3278
+ if (!field.input.hasAttribute("placeholder") || this._generatedPlaceholders.has(field.input)) {
3135
3279
  field.input.placeholder = field.placeholder;
3136
- this._generatedPlaceholder = true;
3280
+ this._generatedPlaceholders.add(field.input);
3137
3281
  }
3138
3282
  if (this._value)
3139
3283
  field.input.value = field.format(this._value);
@@ -3216,6 +3360,9 @@
3216
3360
  if (!options.force && !field.isDirty)
3217
3361
  return false;
3218
3362
  const result = await field.commit(options);
3363
+ if (!this._ownsField(field) || !field.isCommitCurrent(result.commitId)) {
3364
+ return false;
3365
+ }
3219
3366
  if (result.status === "stale" || result.status === "invalid")
3220
3367
  return false;
3221
3368
  this._setValue(result.value || "", {
@@ -3234,6 +3381,8 @@
3234
3381
  if (!field?.isDirty)
3235
3382
  return false;
3236
3383
  const result = await field.commit();
3384
+ if (!this._ownsField(field) || !field.isCommitCurrent(result.commitId))
3385
+ return false;
3237
3386
  if (result.status === "stale")
3238
3387
  return false;
3239
3388
  if (result.status === "invalid") {
@@ -3253,7 +3402,7 @@
3253
3402
  const field = fields[index];
3254
3403
  if (!field)
3255
3404
  return false;
3256
- if (options.user === true && (field.input.disabled || field.input.readOnly))
3405
+ if (options.user === true && !isEditable(field.input))
3257
3406
  return false;
3258
3407
  const next = value || "";
3259
3408
  const previous = field.canonical;
@@ -3337,8 +3486,8 @@
3337
3486
  const [startInput, endInput] = this._timeFields;
3338
3487
  const bounds = ["start", "end"];
3339
3488
  const inputs = [startInput, endInput];
3340
- const startValue = startInput && !startInput.disabled ? startInput.value.trim() : "";
3341
- const endValue = endInput && !endInput.disabled ? endInput.value.trim() : "";
3489
+ const startValue = startInput && !startInput.matches(":disabled") ? startInput.value.trim() : "";
3490
+ const endValue = endInput && !endInput.matches(":disabled") ? endInput.value.trim() : "";
3342
3491
  const inverted = Boolean(startInput && endInput && startValue && endValue) && isTime(startValue) && isTime(endValue) && compareTimes(endValue, startValue) < 0;
3343
3492
  for (const [index, bound] of bounds.entries()) {
3344
3493
  const input = inputs[index];
@@ -3360,6 +3509,14 @@
3360
3509
  input.setCustomValidity("");
3361
3510
  this._timeOrderOwned.clear();
3362
3511
  }
3512
+ _defer(callback) {
3513
+ const controller = this._controller;
3514
+ setTimeout(() => {
3515
+ if (!this._connected || !controller || controller.signal.aborted || this._controller !== controller)
3516
+ return;
3517
+ callback();
3518
+ }, 0);
3519
+ }
3363
3520
  _restoreDefault() {
3364
3521
  const field = this._field;
3365
3522
  if (!field)
@@ -3396,10 +3553,10 @@
3396
3553
  if (!input)
3397
3554
  return;
3398
3555
  this._suppressFocusOpen = true;
3399
- setTimeout(() => {
3556
+ this._defer(() => {
3400
3557
  input.focus();
3401
3558
  this._suppressFocusOpen = false;
3402
- }, 0);
3559
+ });
3403
3560
  }
3404
3561
  _restoreFocus() {
3405
3562
  const target = this._returnFocus;
@@ -3407,22 +3564,22 @@
3407
3564
  if (!(target instanceof HTMLElement) || !target.isConnected || target.matches(":disabled"))
3408
3565
  return;
3409
3566
  this._suppressFocusOpen = true;
3410
- setTimeout(() => {
3567
+ this._defer(() => {
3411
3568
  target.focus();
3412
3569
  this._suppressFocusOpen = false;
3413
- }, 0);
3570
+ });
3414
3571
  }
3415
3572
  _resolveRangeEndpoint() {
3416
3573
  if (!this._fields)
3417
3574
  return "";
3418
3575
  const preferred = this._lastFocusEndpoint || "start";
3419
3576
  const preferredIndex = preferred === "end" ? 1 : 0;
3420
- if (!this._fields[preferredIndex].input.disabled && !this._fields[preferredIndex].input.readOnly) {
3577
+ if (isEditable(this._fields[preferredIndex].input)) {
3421
3578
  return preferred;
3422
3579
  }
3423
3580
  const alternative = preferred === "end" ? "start" : "end";
3424
3581
  const alternativeIndex = alternative === "end" ? 1 : 0;
3425
- if (!this._fields[alternativeIndex].input.disabled && !this._fields[alternativeIndex].input.readOnly) {
3582
+ if (isEditable(this._fields[alternativeIndex].input)) {
3426
3583
  return alternative;
3427
3584
  }
3428
3585
  return "";
@@ -3434,6 +3591,10 @@
3434
3591
  return true;
3435
3592
  for (const [index, field] of fields.entries()) {
3436
3593
  const result = await field.commit({ moveDisplay: false });
3594
+ if (!this._ownsField(field) || !field.isCommitCurrent(result.commitId))
3595
+ continue;
3596
+ if (result.status === "invalid")
3597
+ this._orderTags.delete(index);
3437
3598
  if (result.status === "ok" || result.status === "clear") {
3438
3599
  const which = index === 1 ? "end" : "start";
3439
3600
  this._setBound(which, result.value || "", { format: result.status === "ok" });
@@ -3444,13 +3605,12 @@
3444
3605
  return fields.every((field) => field.input.checkValidity());
3445
3606
  }
3446
3607
  const input = this._field?.input;
3447
- if (!input)
3448
- return true;
3449
- if (!input.value.trim()) {
3450
- input.setCustomValidity("");
3451
- } else {
3452
- await this._commitText(false, { force: true, moveDisplay: false });
3608
+ if (!input) {
3609
+ const dateInputs = this.hasAttribute("range") ? this.querySelectorAll(":scope > input[data-range-start], :scope > input[data-range-end]") : this._input ? [this._input] : [];
3610
+ const timeInputs = this.querySelectorAll(":scope > input[type=time][data-time-start], :scope > input[type=time][data-time-end]");
3611
+ return [...dateInputs, ...timeInputs].every((candidate) => candidate instanceof HTMLInputElement && candidate.checkValidity());
3453
3612
  }
3613
+ await this._commitText(false, { force: true, moveDisplay: false });
3454
3614
  this._revalidateTimeOrder("start");
3455
3615
  this._revalidateTimeOrder("end");
3456
3616
  const timesValid = this._timeFields.every((timeInput) => timeInput?.checkValidity() ?? true);
@@ -3468,7 +3628,7 @@
3468
3628
  this._activateRangeEndpoint(endpoint);
3469
3629
  } else {
3470
3630
  const input = this._input;
3471
- if (!input || input.disabled || input.readOnly)
3631
+ if (!input || !isEditable(input))
3472
3632
  return;
3473
3633
  const target = this._value || this._adapter().parse(input.value) || todayISO();
3474
3634
  calendar.display = monthKey(target);
@@ -3488,8 +3648,14 @@
3488
3648
  });
3489
3649
  position();
3490
3650
  this._stopTracking = autoUpdate(this, panel, position);
3491
- if (options.moveFocus !== false)
3492
- queueMicrotask(() => calendar.focusGrid());
3651
+ if (options.moveFocus !== false) {
3652
+ const controller = this._controller;
3653
+ queueMicrotask(() => {
3654
+ if (!this._connected || !controller || controller.signal.aborted || this._controller !== controller)
3655
+ return;
3656
+ calendar.focusGrid();
3657
+ });
3658
+ }
3493
3659
  this.dispatchEvent(new Event("open", { bubbles: true }));
3494
3660
  }
3495
3661
  _setExpanded(expanded) {
@@ -3510,8 +3676,7 @@
3510
3676
  } catch {}
3511
3677
  this._open = false;
3512
3678
  this._setExpanded(false);
3513
- this._rangeCommitId++;
3514
- this._pendingIntents.clear();
3679
+ this._supersedeActivations();
3515
3680
  this._suppressGridClick = false;
3516
3681
  if (this._drag) {
3517
3682
  this._releaseDragCapture(this._drag.pointerId);
@@ -3534,7 +3699,7 @@
3534
3699
  if (active) {
3535
3700
  const index = active === "end" ? 1 : 0;
3536
3701
  const field = this._fields?.[index];
3537
- if (field && !field.input.disabled && !field.input.readOnly)
3702
+ if (field && isEditable(field.input))
3538
3703
  return active;
3539
3704
  }
3540
3705
  return this._resolveRangeEndpoint();