@lekoala/date-picker 0.5.2 → 0.6.0

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.2 - https://github.com/lekoala/date-picker ***/
1
+ /*** @lekoala/date-picker v0.6.0 - https://github.com/lekoala/date-picker ***/
2
2
  (() => {
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -78,9 +78,11 @@
78
78
  const text = String(value || "");
79
79
  const monthMatch = MONTH_RE.exec(text);
80
80
  if (monthMatch) {
81
+ const year = Number(monthMatch[1]);
81
82
  const month = Number(monthMatch[2]);
82
- if (month >= 1 && month <= 12)
83
+ if (year >= 1 && year <= 9999 && month >= 1 && month <= 12) {
83
84
  return `${monthMatch[1]}-${String(month).padStart(2, "0")}`;
85
+ }
84
86
  }
85
87
  const parsed = parseDate(text);
86
88
  if (!parsed)
@@ -471,28 +473,12 @@
471
473
  ...canContinue ? {} : { close: true }
472
474
  };
473
475
  }
474
- projectEndpoint(date, endpoint, editable = () => true) {
475
- if (!isDate(date))
476
- throw new TypeError(`Invalid range activation: ${date}`);
477
- if (!this.complete || !editable(endpoint))
478
- return this._refuse(endpoint);
479
- const { start, end } = this;
480
- const previous = endpoint === "start" ? start : end;
481
- const target = endpoint === "start" ? earlier(date, end) : later(date, start);
482
- return {
483
- status: "complete",
484
- endpoint,
485
- changedEndpoints: target === previous ? [] : [endpoint],
486
- range: endpoint === "start" ? { start: target, end } : { start, end: target },
487
- activeEndpoint: endpoint
488
- };
489
- }
490
- previewRange(date, endpoint = "", editable = () => true) {
476
+ previewRange(date, editable = () => true) {
491
477
  if (!isDate(date))
492
478
  return null;
493
- if (!endpoint && Boolean(this.start) === Boolean(this.end))
479
+ if (Boolean(this.start) === Boolean(this.end))
494
480
  return null;
495
- const transition = endpoint ? this.projectEndpoint(date, endpoint, editable) : this.project(date, editable);
481
+ const transition = this.project(date, editable);
496
482
  if (transition.status === "refused")
497
483
  return null;
498
484
  const { start, end } = transition.range;
@@ -511,23 +497,6 @@
511
497
  activate(date, editable = () => true) {
512
498
  return this._apply(this.project(date, editable));
513
499
  }
514
- moveEndpoint(date, endpoint, editable = () => true) {
515
- return this._apply(this.projectEndpoint(date, endpoint, editable));
516
- }
517
- }
518
- function later(a, b) {
519
- if (!a)
520
- return b;
521
- if (!b)
522
- return a;
523
- return compareDates(a, b) >= 0 ? a : b;
524
- }
525
- function earlier(a, b) {
526
- if (!a)
527
- return b;
528
- if (!b)
529
- return a;
530
- return compareDates(a, b) <= 0 ? a : b;
531
500
  }
532
501
 
533
502
  // src/intl.js
@@ -742,6 +711,17 @@
742
711
  };
743
712
  }
744
713
 
714
+ // src/session.js
715
+ function createSession() {
716
+ return new AbortController;
717
+ }
718
+ function guard(signal, callback) {
719
+ return () => {
720
+ if (!signal.aborted)
721
+ callback();
722
+ };
723
+ }
724
+
745
725
  // src/source.js
746
726
  function normalizeDateStates(payload) {
747
727
  const raw = payload && typeof payload === "object" && !Array.isArray(payload) && "dates" in payload ? payload.dates : payload;
@@ -849,7 +829,10 @@
849
829
  this._id = `date-calendar-${++uid}`;
850
830
  this._model = new CalendarModel;
851
831
  this._connected = false;
832
+ this._session = null;
852
833
  this._rendering = false;
834
+ this._heading = null;
835
+ this._renderRoot = null;
853
836
  this._source = null;
854
837
  this._hostIntent = null;
855
838
  this._sourceStates = new Map;
@@ -876,6 +859,7 @@
876
859
  connectedCallback() {
877
860
  if (this._connected)
878
861
  return;
862
+ this._session = createSession();
879
863
  upgradeProperties(this, [
880
864
  "locale",
881
865
  "min",
@@ -895,7 +879,10 @@
895
879
  "value",
896
880
  "display"
897
881
  ]);
882
+ this._normalizeAttributes();
898
883
  this._syncModel();
884
+ if (this.hasAttribute("display"))
885
+ this._reflect("display", this._model.display);
899
886
  upgradeProperties(this, ["focusedDate"]);
900
887
  this._stopDefaultControlClasses = subscribeDefaultControlClasses(() => {
901
888
  if (usesDefaultControlClasses(this._controlClasses))
@@ -906,10 +893,11 @@
906
893
  this.render();
907
894
  });
908
895
  this._connected = true;
909
- this.addEventListener("click", this._onClick);
910
- this.addEventListener("change", this._onChange);
911
- this.addEventListener("keydown", this._onKeyDown);
912
- this.addEventListener("focusin", this._onFocusIn);
896
+ const { signal } = this._session;
897
+ this.addEventListener("click", this._onClick, { signal });
898
+ this.addEventListener("change", this._onChange, { signal });
899
+ this.addEventListener("keydown", this._onKeyDown, { signal });
900
+ this.addEventListener("focusin", this._onFocusIn, { signal });
913
901
  this.render();
914
902
  }
915
903
  disconnectedCallback() {
@@ -922,17 +910,55 @@
922
910
  this._loadController = null;
923
911
  this._loadingKey = "";
924
912
  this._loadingPromise = null;
925
- this.removeEventListener("click", this._onClick);
926
- this.removeEventListener("change", this._onChange);
927
- this.removeEventListener("keydown", this._onKeyDown);
928
- this.removeEventListener("focusin", this._onFocusIn);
913
+ this.removeAttribute("data-loading");
914
+ this._session?.abort();
915
+ this._session = null;
916
+ }
917
+ _normalizeAttributes() {
918
+ const value = this.getAttribute("value");
919
+ if (value && !isDate(value))
920
+ this.removeAttribute("value");
921
+ for (const name of ["min", "max"]) {
922
+ const raw = this.getAttribute(name);
923
+ if (raw && !isDate(raw))
924
+ this.removeAttribute(name);
925
+ }
929
926
  }
930
- attributeChangedCallback() {
931
- if (!this._connected || this._rendering)
927
+ attributeChangedCallback(name, _oldValue, newValue) {
928
+ if (this._rendering)
929
+ return;
930
+ if (!this._connected)
931
+ return;
932
+ if (name === "value" && newValue && !isDate(newValue)) {
933
+ this._reflect("value", this._model.value);
934
+ return;
935
+ }
936
+ if ((name === "min" || name === "max") && newValue && !isDate(newValue)) {
937
+ this._reflect(name, "");
938
+ if (this._connected) {
939
+ this._syncModel();
940
+ this.render();
941
+ }
932
942
  return;
943
+ }
944
+ if (name === "display" && newValue) {
945
+ try {
946
+ if (monthKey(newValue) !== newValue)
947
+ throw new TypeError("Non-canonical month");
948
+ } catch {
949
+ this._reflect("display", this._model.display);
950
+ return;
951
+ }
952
+ }
933
953
  this._syncModel();
934
954
  this.render();
935
955
  }
956
+ _queueFocus(callback) {
957
+ const session = this._session;
958
+ if (!session)
959
+ return;
960
+ queueMicrotask(guard(session.signal, callback));
961
+ }
936
962
  get locale() {
937
963
  return resolveLocale(this.getAttribute("locale") || "");
938
964
  }
@@ -1220,11 +1246,9 @@
1220
1246
  } finally {
1221
1247
  if (this._loadController === controller) {
1222
1248
  this._loadController = null;
1223
- this.removeAttribute("data-loading");
1224
- }
1225
- if (this._loadingKey === key) {
1226
1249
  this._loadingKey = "";
1227
1250
  this._loadingPromise = null;
1251
+ this.removeAttribute("data-loading");
1228
1252
  }
1229
1253
  }
1230
1254
  })();
@@ -1273,7 +1297,7 @@
1273
1297
  this._moveFocusDom(previousFocused, this._model.focused, moveFocus);
1274
1298
  }
1275
1299
  if (moveFocus)
1276
- queueMicrotask(() => this.focusGrid());
1300
+ this._queueFocus(() => this.focusGrid());
1277
1301
  }
1278
1302
  _moveFocusDom(previous, next, moveFocus = true) {
1279
1303
  const prevCell = previous ? this.querySelector(`.dp-day[data-date="${CSS.escape(previous)}"]`) : null;
@@ -1357,6 +1381,9 @@
1357
1381
  target.focus();
1358
1382
  }
1359
1383
  async activateDate(date) {
1384
+ if (!this._connected)
1385
+ return false;
1386
+ const session = this._session;
1360
1387
  const hostIntent = this._hostIntent?.date === date ? this._hostIntent.intent : undefined;
1361
1388
  this._hostIntent = null;
1362
1389
  const tagged = (event) => {
@@ -1365,6 +1392,8 @@
1365
1392
  return event;
1366
1393
  };
1367
1394
  const confirmed = await this.ensureDate(date);
1395
+ if (!session || session.signal.aborted)
1396
+ return false;
1368
1397
  const state = this.getDateState(date);
1369
1398
  if (!confirmed || state.disabled) {
1370
1399
  this.dispatchEvent(tagged(new CustomEvent("dateinvalid", { detail: { date, state }, bubbles: true })));
@@ -1375,7 +1404,7 @@
1375
1404
  bubbles: true,
1376
1405
  cancelable: true
1377
1406
  })));
1378
- if (!accepted)
1407
+ if (!accepted || session.signal.aborted)
1379
1408
  return false;
1380
1409
  if (this.selection === "single") {
1381
1410
  const previousValue = this._model.value;
@@ -1549,8 +1578,20 @@
1549
1578
  <select id="${this._id}-month" class="dp-month-select ${monthClass}" aria-label="${escapeHtml(this.messages.month)}">${monthOptions}</select>`;
1550
1579
  const yearControl = `<label class="dp-visually-hidden" for="${this._id}-year">${escapeHtml(this.messages.year)}</label>
1551
1580
  <input id="${this._id}-year" class="dp-year-input ${yearClass}" type="number" inputmode="numeric" value="${displayYear}"${minYearAttr}${maxYearAttr} aria-label="${escapeHtml(this.messages.year)}">`;
1581
+ if (!this._heading?.isConnected || !this._renderRoot?.isConnected) {
1582
+ this.replaceChildren();
1583
+ const heading = document.createElement("h2");
1584
+ heading.id = headingId;
1585
+ heading.className = "dp-calendar-heading dp-visually-hidden";
1586
+ heading.setAttribute("aria-live", "polite");
1587
+ const renderRoot = document.createElement("div");
1588
+ renderRoot.className = "dp-calendar-render";
1589
+ this.append(heading, renderRoot);
1590
+ this._heading = heading;
1591
+ this._renderRoot = renderRoot;
1592
+ }
1552
1593
  this._rendering = true;
1553
- this.innerHTML = `
1594
+ this._renderRoot.innerHTML = `
1554
1595
  <div class="dp-calendar-shell">
1555
1596
  <div class="dp-calendar-header">
1556
1597
  ${yearFirst ? yearControl : monthControl}
@@ -1558,17 +1599,17 @@
1558
1599
  <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>
1559
1600
  <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>
1560
1601
  </div>
1561
- <h2 id="${headingId}" class="dp-calendar-heading dp-visually-hidden" aria-live="polite">${escapeHtml(formatMonthYear(`${display}-15`, locale))}</h2>
1562
1602
  <table id="${gridId}" class="dp-grid" role="grid" aria-labelledby="${headingId}">
1563
1603
  <thead><tr>${weekHeader}${dayHeaders}</tr></thead>
1564
1604
  <tbody>${rows}</tbody>
1565
1605
  </table>
1566
1606
  </div>`;
1607
+ this._heading.textContent = formatMonthYear(`${display}-15`, locale);
1567
1608
  this._rendering = false;
1568
1609
  this._paintedRange = { ...this._highlightedRange };
1569
1610
  this._paintedPreview = this.hasAttribute("data-range-preview");
1570
1611
  if (focusKey) {
1571
- queueMicrotask(() => {
1612
+ this._queueFocus(() => {
1572
1613
  let selector = focusKey;
1573
1614
  if (selector.startsWith(".dp-day[data-date=")) {
1574
1615
  selector = `.dp-day[data-date="${CSS.escape(this._model.focused)}"]`;
@@ -1982,6 +2023,7 @@
1982
2023
  this.canonical = "";
1983
2024
  this._dirty = false;
1984
2025
  this._commitId = 0;
2026
+ this._session = createSession();
1985
2027
  this._originalName = input.getAttribute("name") || "";
1986
2028
  this._attributeObserver = null;
1987
2029
  this.onAttributesChanged = null;
@@ -2084,34 +2126,41 @@
2084
2126
  async commit(options = {}) {
2085
2127
  const input = this.input;
2086
2128
  const commitId = ++this._commitId;
2129
+ const session = this._session;
2130
+ if (session.signal.aborted)
2131
+ return { status: "stale", commitId };
2087
2132
  const text = input.value.trim();
2088
2133
  if (!text) {
2089
2134
  if (commitId !== this._commitId)
2090
- return { status: "stale" };
2135
+ return { status: "stale", commitId };
2091
2136
  input.setCustomValidity("");
2092
- return { status: "clear", value: "" };
2137
+ return { status: "clear", value: "", commitId };
2093
2138
  }
2094
2139
  const parsed = this.adapter.parse(text);
2095
2140
  if (!parsed) {
2096
2141
  if (commitId !== this._commitId)
2097
- return { status: "stale" };
2142
+ return { status: "stale", commitId };
2098
2143
  if (this.hidden)
2099
2144
  this.hidden.value = "";
2100
2145
  input.setCustomValidity(this.messages.invalidDate);
2101
- return { status: "invalid" };
2146
+ return { status: "invalid", commitId };
2102
2147
  }
2103
2148
  if (this.confirm) {
2104
2149
  const result = await this.confirm(parsed, options);
2105
- if (commitId !== this._commitId)
2106
- return { status: "stale" };
2150
+ if (session.signal.aborted || commitId !== this._commitId) {
2151
+ return { status: "stale", commitId };
2152
+ }
2107
2153
  if (!result?.ok) {
2108
2154
  if (this.hidden)
2109
2155
  this.hidden.value = "";
2110
2156
  input.setCustomValidity(result?.message || this.messages.unavailableDate);
2111
- return { status: "invalid" };
2157
+ return { status: "invalid", commitId };
2112
2158
  }
2113
2159
  }
2114
- return { status: "ok", value: parsed };
2160
+ return { status: "ok", value: parsed, commitId };
2161
+ }
2162
+ isCommitCurrent(commitId) {
2163
+ return commitId === this._commitId;
2115
2164
  }
2116
2165
  setCanonical(value, options = {}) {
2117
2166
  const next = value || "";
@@ -2132,6 +2181,8 @@
2132
2181
  return parsed && isDate(parsed) ? parsed : "";
2133
2182
  }
2134
2183
  teardown() {
2184
+ this.dirty();
2185
+ this._session.abort();
2135
2186
  this._attributeObserver?.disconnect();
2136
2187
  this._attributeObserver = null;
2137
2188
  const hiddenName = this.hidden?.getAttribute("name") || this._originalName;
@@ -2173,7 +2224,6 @@
2173
2224
 
2174
2225
  // src/date-picker.js
2175
2226
  var uid2 = 0;
2176
- var DRAG_THRESHOLD = 4;
2177
2227
 
2178
2228
  class DatePickerElement extends HTMLElement {
2179
2229
  static observedAttributes = ["value", "locale", "min", "max", "open-on-focus", "month-format"];
@@ -2205,8 +2255,6 @@
2205
2255
  this._lastFocusEndpoint = "";
2206
2256
  this._activationId = 0;
2207
2257
  this._previewDate = "";
2208
- this._drag = null;
2209
- this._suppressGridClick = false;
2210
2258
  this._buttons = [];
2211
2259
  this._returnFocus = null;
2212
2260
  this._panel = null;
@@ -2214,8 +2262,8 @@
2214
2262
  this._originalDescribedBy = "";
2215
2263
  this._rangeOriginalDescribedBy = ["", ""];
2216
2264
  this._formatHint = null;
2217
- this._generatedPlaceholder = false;
2218
- this._controller = null;
2265
+ this._generatedPlaceholders = new WeakSet;
2266
+ this._session = null;
2219
2267
  this._stopTracking = null;
2220
2268
  this._open = false;
2221
2269
  this._suppressFocusOpen = false;
@@ -2234,6 +2282,7 @@
2234
2282
  connectedCallback() {
2235
2283
  if (this._connected)
2236
2284
  return;
2285
+ this._session = createSession();
2237
2286
  upgradeProperties(this, [
2238
2287
  "locale",
2239
2288
  "min",
@@ -2248,17 +2297,31 @@
2248
2297
  "renderDay",
2249
2298
  "isDateDisabled"
2250
2299
  ]);
2300
+ this._normalizeAttributes();
2251
2301
  if (this.hasAttribute("range"))
2252
2302
  this._connectRange();
2253
2303
  else
2254
2304
  this._connectSingle();
2255
- if (this._connected) {
2256
- upgradeProperties(this, ["value", "range"]);
2257
- this._stopDefaultMessages = subscribeDefaultMessages(() => {
2258
- if (this._messages === null)
2259
- this._refreshMessages();
2260
- });
2261
- this.validate();
2305
+ if (!this._connected) {
2306
+ this._session.abort();
2307
+ this._session = null;
2308
+ return;
2309
+ }
2310
+ upgradeProperties(this, ["value", "range"]);
2311
+ this._stopDefaultMessages = subscribeDefaultMessages(() => {
2312
+ if (this._messages === null)
2313
+ this._refreshMessages();
2314
+ });
2315
+ this.validate();
2316
+ }
2317
+ _normalizeAttributes() {
2318
+ const value = this.getAttribute("value");
2319
+ if (value && !isDate(value))
2320
+ this.removeAttribute("value");
2321
+ for (const name of ["min", "max"]) {
2322
+ const raw = this.getAttribute(name);
2323
+ if (raw && !isDate(raw))
2324
+ this.removeAttribute(name);
2262
2325
  }
2263
2326
  }
2264
2327
  _connectSingle() {
@@ -2368,9 +2431,10 @@
2368
2431
  this._stopDefaultMessages?.();
2369
2432
  this._stopDefaultMessages = null;
2370
2433
  this._connected = false;
2434
+ this._suppressFocusOpen = false;
2371
2435
  this.hide(false);
2372
- this._controller?.abort();
2373
- this._controller = null;
2436
+ this._session?.abort();
2437
+ this._session = null;
2374
2438
  this._clearTimeOrderValidity();
2375
2439
  this._timeFields = [null, null];
2376
2440
  if (this._rangeMode()) {
@@ -2408,8 +2472,22 @@
2408
2472
  input.removeAttribute("aria-controls");
2409
2473
  }
2410
2474
  attributeChangedCallback(name, oldValue, newValue) {
2411
- if (!this._connected || this._reflecting || oldValue === newValue)
2475
+ if (this._reflecting || oldValue === newValue)
2476
+ return;
2477
+ if (!this._connected)
2412
2478
  return;
2479
+ if (name === "value" && newValue && !isDate(newValue)) {
2480
+ this._reflectValue(this._value);
2481
+ return;
2482
+ }
2483
+ if ((name === "min" || name === "max") && newValue && !isDate(newValue)) {
2484
+ this.removeAttribute(name);
2485
+ if (this._connected) {
2486
+ this._syncCalendarOptions();
2487
+ this.validate();
2488
+ }
2489
+ return;
2490
+ }
2413
2491
  if (name === "value") {
2414
2492
  if (this._rangeMode())
2415
2493
  return;
@@ -2605,7 +2683,6 @@
2605
2683
  const field = this._fields[index];
2606
2684
  button.disabled = Boolean(field?.input.disabled || field?.input.readOnly);
2607
2685
  }
2608
- this._syncDragAffordance();
2609
2686
  const active = this._range?.activeEndpoint === "end" ? 1 : 0;
2610
2687
  const activeInput = this._fields[active]?.input;
2611
2688
  if (this._open && activeInput && !isEditable(activeInput))
@@ -2694,10 +2771,10 @@
2694
2771
  if (!(event.target instanceof HTMLFormElement) || !input || event.target !== input.form)
2695
2772
  return;
2696
2773
  this.hide(false);
2697
- queueMicrotask(() => {
2774
+ queueMicrotask(guard(signal, () => {
2698
2775
  if (!event.defaultPrevented)
2699
2776
  restore();
2700
- });
2777
+ }));
2701
2778
  }, { capture: true, signal });
2702
2779
  this.addEventListener("keydown", (event) => this._onEscape(event), { signal });
2703
2780
  }
@@ -2707,9 +2784,10 @@
2707
2784
  const calendar = this._calendar;
2708
2785
  if (!input || !button || !calendar)
2709
2786
  return;
2710
- const controller = new AbortController;
2711
- this._controller = controller;
2712
- const { signal } = controller;
2787
+ const session = this._session;
2788
+ if (!session)
2789
+ return;
2790
+ const { signal } = session;
2713
2791
  input.addEventListener("input", () => {
2714
2792
  this._field?.handleInput();
2715
2793
  }, { signal });
@@ -2737,7 +2815,7 @@
2737
2815
  button.addEventListener("click", (event) => {
2738
2816
  if (this._open) {
2739
2817
  if (event.detail === 0) {
2740
- setTimeout(() => this._calendar?.focusGrid(), 0);
2818
+ this._defer(() => this._calendar?.focusGrid());
2741
2819
  return;
2742
2820
  }
2743
2821
  this.hide(false);
@@ -2758,7 +2836,7 @@
2758
2836
  return;
2759
2837
  const intent = activationIntents.get(custom);
2760
2838
  queueMicrotask(() => {
2761
- if (custom.defaultPrevented || !this._connected)
2839
+ if (signal.aborted || custom.defaultPrevented)
2762
2840
  return;
2763
2841
  if (intent && intent.id !== this._activationId)
2764
2842
  return;
@@ -2777,9 +2855,10 @@
2777
2855
  const calendar = this._calendar;
2778
2856
  if (!this._fields || !this._buttons.length || !calendar)
2779
2857
  return;
2780
- const controller = new AbortController;
2781
- this._controller = controller;
2782
- const { signal } = controller;
2858
+ const session = this._session;
2859
+ if (!session)
2860
+ return;
2861
+ const { signal } = session;
2783
2862
  for (const [index, field] of this._fields.entries()) {
2784
2863
  const endpoint = index === 1 ? "end" : "start";
2785
2864
  field.input.addEventListener("input", () => {
@@ -2805,12 +2884,6 @@
2805
2884
  });
2806
2885
  }
2807
2886
  calendar.addEventListener("click", (event) => {
2808
- if (this._suppressGridClick) {
2809
- this._suppressGridClick = false;
2810
- event.preventDefault();
2811
- event.stopImmediatePropagation();
2812
- return;
2813
- }
2814
2887
  this._captureGridIntent(event);
2815
2888
  }, { capture: true, signal });
2816
2889
  calendar.addEventListener("keydown", (event) => this._captureGridIntent(event), {
@@ -2823,13 +2896,13 @@
2823
2896
  if (!isDate(date))
2824
2897
  return;
2825
2898
  queueMicrotask(() => {
2826
- if (custom.defaultPrevented || !this._connected || !this._range)
2899
+ if (signal.aborted || custom.defaultPrevented || !this._range)
2827
2900
  return;
2828
2901
  const intent = activationIntents.get(custom);
2829
2902
  if (intent === undefined || intent.id !== this._activationId)
2830
2903
  return;
2831
2904
  const editable = (bound) => this._boundEditable(bound);
2832
- const result = intent.endpoint ? this._range.moveEndpoint(date, intent.endpoint, editable) : this._range.activate(date, editable);
2905
+ const result = this._range.activate(date, editable);
2833
2906
  if (result.status === "refused") {
2834
2907
  this._calendar?.dispatchEvent(new CustomEvent("dateinvalid", {
2835
2908
  detail: { date, state: this._calendar.getDateState(date) },
@@ -2838,8 +2911,6 @@
2838
2911
  return;
2839
2912
  }
2840
2913
  this._applyRangeTransition(result);
2841
- if (intent.endpoint)
2842
- return;
2843
2914
  if (result.close || result.status === "complete") {
2844
2915
  this.hide(false);
2845
2916
  this._focusField(result.endpoint);
@@ -2849,11 +2920,6 @@
2849
2920
  calendar.addEventListener("datefocus", (event) => this._onGridDateFocus(event), { signal });
2850
2921
  calendar.addEventListener("pointerover", (event) => this._onGridPointerOver(event), { signal });
2851
2922
  calendar.addEventListener("pointerleave", () => this._onGridPointerLeave(), { signal });
2852
- calendar.addEventListener("pointerdown", (event) => this._onGridPointerDown(event), { signal });
2853
- calendar.addEventListener("pointermove", (event) => this._onGridPointerMove(event), { signal });
2854
- calendar.addEventListener("pointerup", (event) => this._onGridPointerUp(event), { signal });
2855
- calendar.addEventListener("pointercancel", (event) => this._onGridPointerCancel(event), { signal });
2856
- calendar.addEventListener("lostpointercapture", (event) => this._onGridPointerCancel(event), { signal });
2857
2923
  calendar.addEventListener("dateloadend", () => void this.validate(), { signal });
2858
2924
  this._bindPopup(signal, this._fields?.[0]?.input, () => this._restoreRangeDefault());
2859
2925
  }
@@ -2876,7 +2942,7 @@
2876
2942
  }
2877
2943
  if (event.detail === 0) {
2878
2944
  this._activateRangeEndpoint(endpoint);
2879
- setTimeout(() => this._calendar?.focusGrid(), 0);
2945
+ this._defer(() => this._calendar?.focusGrid());
2880
2946
  return;
2881
2947
  }
2882
2948
  if (endpoint !== this._range?.activeEndpoint) {
@@ -2892,7 +2958,7 @@
2892
2958
  if (this._open) {
2893
2959
  if (this._rangeMode() && endpoint)
2894
2960
  this._activateRangeEndpoint(endpoint);
2895
- setTimeout(() => this._calendar?.focusGrid(), 0);
2961
+ this._defer(() => this._calendar?.focusGrid());
2896
2962
  return;
2897
2963
  }
2898
2964
  if (this._rangeMode())
@@ -2908,7 +2974,7 @@
2908
2974
  this._range?.focus(endpoint);
2909
2975
  }
2910
2976
  if (this._open) {
2911
- setTimeout(() => this._calendar?.focusGrid(), 0);
2977
+ this._defer(() => this._calendar?.focusGrid());
2912
2978
  } else {
2913
2979
  this.show();
2914
2980
  }
@@ -2942,20 +3008,18 @@
2942
3008
  if (key !== "Enter" && key !== " ")
2943
3009
  return;
2944
3010
  }
2945
- if (event.type === "click" && this._suppressGridClick)
2946
- return;
2947
3011
  if (!(event.target instanceof Element))
2948
3012
  return;
2949
3013
  const cell = event.target.closest(".dp-day[data-date]");
2950
3014
  const date = cell?.getAttribute("data-date") || "";
2951
3015
  if (!isDate(date))
2952
3016
  return;
2953
- this._attachIntent(date, "");
3017
+ this._attachIntent(date);
2954
3018
  }
2955
- _attachIntent(date, endpoint) {
3019
+ _attachIntent(date) {
2956
3020
  if (!this._calendar)
2957
3021
  return;
2958
- this._calendar._hostIntent = { date, intent: { id: ++this._activationId, endpoint } };
3022
+ this._calendar._hostIntent = { date, intent: { id: ++this._activationId } };
2959
3023
  }
2960
3024
  _supersedeActivations() {
2961
3025
  this._activationId++;
@@ -2988,12 +3052,12 @@
2988
3052
  field.input.dispatchEvent(new Event("change", { bubbles: true }));
2989
3053
  }
2990
3054
  }
2991
- _setPreview(date, endpoint = "") {
3055
+ _setPreview(date) {
2992
3056
  const calendar = this._calendar;
2993
3057
  if (!calendar || !this._range || !this._open)
2994
3058
  return;
2995
3059
  const candidate = isDate(date) && !calendar.getDateState(date).disabled ? date : "";
2996
- const preview = candidate ? this._range.previewRange(candidate, endpoint, (bound) => this._boundEditable(bound)) : null;
3060
+ const preview = candidate ? this._range.previewRange(candidate, (bound) => this._boundEditable(bound)) : null;
2997
3061
  if (!preview) {
2998
3062
  this._clearPreview();
2999
3063
  return;
@@ -3008,23 +3072,17 @@
3008
3072
  this._syncHighlight();
3009
3073
  }
3010
3074
  _onGridDateFocus(event) {
3011
- if (this._drag)
3012
- return;
3013
3075
  const date = event.detail?.date;
3014
3076
  if (isDate(date))
3015
3077
  this._setPreview(date);
3016
3078
  }
3017
3079
  _onGridPointerOver(event) {
3018
3080
  const pointer = event;
3019
- if (this._drag || pointer.pointerType === "touch")
3081
+ if (pointer.pointerType === "touch")
3020
3082
  return;
3021
3083
  this._setPreview(this._cellDate(pointer.target));
3022
3084
  }
3023
3085
  _onGridPointerLeave() {
3024
- if (this._drag?.active === false)
3025
- this._drag = null;
3026
- if (this._drag)
3027
- return;
3028
3086
  this._clearPreview();
3029
3087
  }
3030
3088
  _cellDate(target) {
@@ -3032,116 +3090,6 @@
3032
3090
  const date = cell?.getAttribute("data-date") || "";
3033
3091
  return isDate(date) ? date : "";
3034
3092
  }
3035
- _dateAtPoint(x, y) {
3036
- const element = this.ownerDocument.elementFromPoint(x, y);
3037
- if (!(element instanceof Element) || !this._calendar?.contains(element))
3038
- return "";
3039
- return this._cellDate(element);
3040
- }
3041
- _onGridPointerDown(event) {
3042
- const pointer = event;
3043
- this._suppressGridClick = false;
3044
- if (this._drag?.active === false)
3045
- this._drag = null;
3046
- if (!this._open || !this._range || this._drag)
3047
- return;
3048
- if (!pointer.isPrimary || pointer.button !== 0)
3049
- return;
3050
- if (pointer.pointerType !== "mouse" && pointer.pointerType !== "pen")
3051
- return;
3052
- if (!this._range.complete)
3053
- return;
3054
- const cell = pointer.target instanceof Element ? pointer.target.closest(".dp-day[data-date]") : null;
3055
- if (!(cell instanceof HTMLElement))
3056
- return;
3057
- const isStart = cell.hasAttribute("data-range-start");
3058
- const isEnd = cell.hasAttribute("data-range-end");
3059
- if (isStart === isEnd)
3060
- return;
3061
- const endpoint = isStart ? "start" : "end";
3062
- const date = this._cellDate(cell);
3063
- if (!date || !this._boundEditable(endpoint))
3064
- return;
3065
- this._drag = {
3066
- pointerId: pointer.pointerId,
3067
- endpoint,
3068
- x: pointer.clientX,
3069
- y: pointer.clientY,
3070
- date,
3071
- active: false
3072
- };
3073
- }
3074
- _onGridPointerMove(event) {
3075
- const pointer = event;
3076
- const drag = this._drag;
3077
- if (!drag || pointer.pointerId !== drag.pointerId)
3078
- return;
3079
- if (!drag.active) {
3080
- const travelled = Math.abs(pointer.clientX - drag.x) > DRAG_THRESHOLD || Math.abs(pointer.clientY - drag.y) > DRAG_THRESHOLD;
3081
- if (!travelled)
3082
- return;
3083
- drag.active = true;
3084
- try {
3085
- this._calendar?.setPointerCapture(drag.pointerId);
3086
- } catch {}
3087
- this._syncDragAffordance();
3088
- }
3089
- const date = this._dateAtPoint(pointer.clientX, pointer.clientY);
3090
- if (!date || this._calendar?.getDateState(date).disabled)
3091
- return;
3092
- drag.date = date;
3093
- this._setPreview(date, drag.endpoint);
3094
- }
3095
- _onGridPointerUp(event) {
3096
- const pointer = event;
3097
- const drag = this._drag;
3098
- if (!drag || pointer.pointerId !== drag.pointerId)
3099
- return;
3100
- this._drag = null;
3101
- this._releaseDragCapture(drag.pointerId);
3102
- if (!drag.active) {
3103
- this._syncDragAffordance();
3104
- return;
3105
- }
3106
- this._suppressGridClick = true;
3107
- const transition = this._range?.projectEndpoint(drag.date, drag.endpoint, (bound) => this._boundEditable(bound));
3108
- if (!transition || transition.status === "refused" || !transition.changedEndpoints.length) {
3109
- this._syncHighlight();
3110
- return;
3111
- }
3112
- const target = drag.endpoint === "start" ? transition.range.start : transition.range.end;
3113
- this._attachIntent(target, drag.endpoint);
3114
- this._calendar?.activateDate(target);
3115
- }
3116
- _onGridPointerCancel(event) {
3117
- const pointer = event;
3118
- const drag = this._drag;
3119
- if (!drag || pointer.pointerId !== drag.pointerId)
3120
- return;
3121
- this._drag = null;
3122
- this._releaseDragCapture(drag.pointerId);
3123
- this._syncHighlight();
3124
- }
3125
- _releaseDragCapture(pointerId) {
3126
- const calendar = this._calendar;
3127
- if (calendar?.hasPointerCapture(pointerId))
3128
- calendar.releasePointerCapture(pointerId);
3129
- }
3130
- _syncDragAffordance() {
3131
- const calendar = this._calendar;
3132
- if (!calendar || !this._range)
3133
- return;
3134
- if (this._drag?.active) {
3135
- calendar.setAttribute("data-range-drag", "active");
3136
- return;
3137
- }
3138
- const { start, end } = this._range;
3139
- const draggable = this._range.complete && start !== end && (this._boundEditable("start") || this._boundEditable("end"));
3140
- if (draggable)
3141
- calendar.setAttribute("data-range-drag", "ready");
3142
- else
3143
- calendar.removeAttribute("data-range-drag");
3144
- }
3145
3093
  _onEscape(event) {
3146
3094
  if (event.key === "Escape" && this._open) {
3147
3095
  event.preventDefault();
@@ -3178,9 +3126,9 @@
3178
3126
  this._fields[1].setLocale(this.locale);
3179
3127
  hint.textContent = `${this.messages.formatHint}: ${this._fields[0].placeholder}`;
3180
3128
  for (const [index, field] of this._fields.entries()) {
3181
- if (!field.input.hasAttribute("placeholder") || this._generatedPlaceholder) {
3129
+ if (!field.input.hasAttribute("placeholder") || this._generatedPlaceholders.has(field.input)) {
3182
3130
  field.input.placeholder = field.placeholder;
3183
- this._generatedPlaceholder = true;
3131
+ this._generatedPlaceholders.add(field.input);
3184
3132
  }
3185
3133
  const canonical = index === 1 ? this._range?.end || "" : this._range?.start || "";
3186
3134
  if (canonical)
@@ -3194,9 +3142,9 @@
3194
3142
  return;
3195
3143
  field.setLocale(this.locale);
3196
3144
  hint.textContent = `${this.messages.formatHint}: ${field.placeholder}`;
3197
- if (!field.input.hasAttribute("placeholder") || this._generatedPlaceholder) {
3145
+ if (!field.input.hasAttribute("placeholder") || this._generatedPlaceholders.has(field.input)) {
3198
3146
  field.input.placeholder = field.placeholder;
3199
- this._generatedPlaceholder = true;
3147
+ this._generatedPlaceholders.add(field.input);
3200
3148
  }
3201
3149
  if (this._value)
3202
3150
  field.input.value = field.format(this._value);
@@ -3274,11 +3222,15 @@
3274
3222
  }
3275
3223
  async _commitText(emit, options = {}) {
3276
3224
  const field = this._field;
3277
- if (!field)
3225
+ const session = this._session;
3226
+ if (!field || !session)
3278
3227
  return false;
3279
3228
  if (!options.force && !field.isDirty)
3280
3229
  return false;
3281
3230
  const result = await field.commit(options);
3231
+ if (session.signal.aborted || !field.isCommitCurrent(result.commitId)) {
3232
+ return false;
3233
+ }
3282
3234
  if (result.status === "stale" || result.status === "invalid")
3283
3235
  return false;
3284
3236
  this._setValue(result.value || "", {
@@ -3290,13 +3242,16 @@
3290
3242
  }
3291
3243
  async _commitFieldText(which) {
3292
3244
  const fields = this._fields;
3293
- if (!fields)
3245
+ const session = this._session;
3246
+ if (!fields || !session)
3294
3247
  return false;
3295
3248
  const index = which === "end" ? 1 : 0;
3296
3249
  const field = fields[index];
3297
3250
  if (!field?.isDirty)
3298
3251
  return false;
3299
3252
  const result = await field.commit();
3253
+ if (session.signal.aborted || !field.isCommitCurrent(result.commitId))
3254
+ return false;
3300
3255
  if (result.status === "stale")
3301
3256
  return false;
3302
3257
  if (result.status === "invalid") {
@@ -3374,7 +3329,6 @@
3374
3329
  this._previewDate = "";
3375
3330
  this._calendar.removeAttribute("data-range-preview");
3376
3331
  this._calendar.highlightedRange = this._displayRange();
3377
- this._syncDragAffordance();
3378
3332
  }
3379
3333
  _revalidateRange(which) {
3380
3334
  if (!this._fields || !this._range)
@@ -3423,6 +3377,12 @@
3423
3377
  input.setCustomValidity("");
3424
3378
  this._timeOrderOwned.clear();
3425
3379
  }
3380
+ _defer(callback) {
3381
+ const session = this._session;
3382
+ if (!session)
3383
+ return;
3384
+ setTimeout(guard(session.signal, callback), 0);
3385
+ }
3426
3386
  _restoreDefault() {
3427
3387
  const field = this._field;
3428
3388
  if (!field)
@@ -3459,10 +3419,10 @@
3459
3419
  if (!input)
3460
3420
  return;
3461
3421
  this._suppressFocusOpen = true;
3462
- setTimeout(() => {
3422
+ this._defer(() => {
3463
3423
  input.focus();
3464
3424
  this._suppressFocusOpen = false;
3465
- }, 0);
3425
+ });
3466
3426
  }
3467
3427
  _restoreFocus() {
3468
3428
  const target = this._returnFocus;
@@ -3470,10 +3430,10 @@
3470
3430
  if (!(target instanceof HTMLElement) || !target.isConnected || target.matches(":disabled"))
3471
3431
  return;
3472
3432
  this._suppressFocusOpen = true;
3473
- setTimeout(() => {
3433
+ this._defer(() => {
3474
3434
  target.focus();
3475
3435
  this._suppressFocusOpen = false;
3476
- }, 0);
3436
+ });
3477
3437
  }
3478
3438
  _resolveRangeEndpoint() {
3479
3439
  if (!this._fields)
@@ -3491,12 +3451,18 @@
3491
3451
  return "";
3492
3452
  }
3493
3453
  async validate() {
3454
+ const session = this._session;
3494
3455
  if (this._rangeMode()) {
3495
3456
  const fields = this._fields;
3496
3457
  if (!fields)
3497
3458
  return true;
3498
3459
  for (const [index, field] of fields.entries()) {
3499
3460
  const result = await field.commit({ moveDisplay: false });
3461
+ if (!session || session.signal.aborted) {
3462
+ return fields.every((field) => field.input.checkValidity());
3463
+ }
3464
+ if (!field.isCommitCurrent(result.commitId))
3465
+ continue;
3500
3466
  if (result.status === "invalid")
3501
3467
  this._orderTags.delete(index);
3502
3468
  if (result.status === "ok" || result.status === "clear") {
@@ -3509,9 +3475,15 @@
3509
3475
  return fields.every((field) => field.input.checkValidity());
3510
3476
  }
3511
3477
  const input = this._field?.input;
3512
- if (!input)
3513
- return true;
3478
+ if (!input) {
3479
+ const dateInputs = this.hasAttribute("range") ? this.querySelectorAll(":scope > input[data-range-start], :scope > input[data-range-end]") : this._input ? [this._input] : [];
3480
+ const timeInputs = this.querySelectorAll(":scope > input[type=time][data-time-start], :scope > input[type=time][data-time-end]");
3481
+ return [...dateInputs, ...timeInputs].every((candidate) => candidate instanceof HTMLInputElement && candidate.checkValidity());
3482
+ }
3514
3483
  await this._commitText(false, { force: true, moveDisplay: false });
3484
+ if (!session || session.signal.aborted) {
3485
+ return input.checkValidity() && this._timeFields.every((timeInput) => timeInput?.checkValidity() ?? true);
3486
+ }
3515
3487
  this._revalidateTimeOrder("start");
3516
3488
  this._revalidateTimeOrder("end");
3517
3489
  const timesValid = this._timeFields.every((timeInput) => timeInput?.checkValidity() ?? true);
@@ -3549,8 +3521,11 @@
3549
3521
  });
3550
3522
  position();
3551
3523
  this._stopTracking = autoUpdate(this, panel, position);
3552
- if (options.moveFocus !== false)
3553
- queueMicrotask(() => calendar.focusGrid());
3524
+ if (options.moveFocus !== false) {
3525
+ const session = this._session;
3526
+ if (session)
3527
+ queueMicrotask(guard(session.signal, () => calendar.focusGrid()));
3528
+ }
3554
3529
  this.dispatchEvent(new Event("open", { bubbles: true }));
3555
3530
  }
3556
3531
  _setExpanded(expanded) {
@@ -3572,11 +3547,6 @@
3572
3547
  this._open = false;
3573
3548
  this._setExpanded(false);
3574
3549
  this._supersedeActivations();
3575
- this._suppressGridClick = false;
3576
- if (this._drag) {
3577
- this._releaseDragCapture(this._drag.pointerId);
3578
- this._drag = null;
3579
- }
3580
3550
  this._syncHighlight();
3581
3551
  if (restoreFocus) {
3582
3552
  if (this._rangeMode()) {