@lekoala/date-picker 0.5.2 → 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.2 - 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;
@@ -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)
@@ -849,7 +851,10 @@
849
851
  this._id = `date-calendar-${++uid}`;
850
852
  this._model = new CalendarModel;
851
853
  this._connected = false;
854
+ this._lifecycleId = 0;
852
855
  this._rendering = false;
856
+ this._heading = null;
857
+ this._renderRoot = null;
853
858
  this._source = null;
854
859
  this._hostIntent = null;
855
860
  this._sourceStates = new Map;
@@ -876,6 +881,7 @@
876
881
  connectedCallback() {
877
882
  if (this._connected)
878
883
  return;
884
+ this._lifecycleId++;
879
885
  upgradeProperties(this, [
880
886
  "locale",
881
887
  "min",
@@ -918,6 +924,7 @@
918
924
  this._stopDefaultMessages?.();
919
925
  this._stopDefaultMessages = null;
920
926
  this._connected = false;
927
+ this._lifecycleId++;
921
928
  this._loadController?.abort();
922
929
  this._loadController = null;
923
930
  this._loadingKey = "";
@@ -927,12 +934,43 @@
927
934
  this.removeEventListener("keydown", this._onKeyDown);
928
935
  this.removeEventListener("focusin", this._onFocusIn);
929
936
  }
930
- attributeChangedCallback() {
931
- 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)
932
962
  return;
933
963
  this._syncModel();
934
964
  this.render();
935
965
  }
966
+ _queueFocus(callback) {
967
+ const lifecycleId = this._lifecycleId;
968
+ queueMicrotask(() => {
969
+ if (!this._connected || lifecycleId !== this._lifecycleId)
970
+ return;
971
+ callback();
972
+ });
973
+ }
936
974
  get locale() {
937
975
  return resolveLocale(this.getAttribute("locale") || "");
938
976
  }
@@ -1273,7 +1311,7 @@
1273
1311
  this._moveFocusDom(previousFocused, this._model.focused, moveFocus);
1274
1312
  }
1275
1313
  if (moveFocus)
1276
- queueMicrotask(() => this.focusGrid());
1314
+ this._queueFocus(() => this.focusGrid());
1277
1315
  }
1278
1316
  _moveFocusDom(previous, next, moveFocus = true) {
1279
1317
  const prevCell = previous ? this.querySelector(`.dp-day[data-date="${CSS.escape(previous)}"]`) : null;
@@ -1357,6 +1395,9 @@
1357
1395
  target.focus();
1358
1396
  }
1359
1397
  async activateDate(date) {
1398
+ if (!this._connected)
1399
+ return false;
1400
+ const lifecycleId = this._lifecycleId;
1360
1401
  const hostIntent = this._hostIntent?.date === date ? this._hostIntent.intent : undefined;
1361
1402
  this._hostIntent = null;
1362
1403
  const tagged = (event) => {
@@ -1365,6 +1406,8 @@
1365
1406
  return event;
1366
1407
  };
1367
1408
  const confirmed = await this.ensureDate(date);
1409
+ if (!this._connected || lifecycleId !== this._lifecycleId)
1410
+ return false;
1368
1411
  const state = this.getDateState(date);
1369
1412
  if (!confirmed || state.disabled) {
1370
1413
  this.dispatchEvent(tagged(new CustomEvent("dateinvalid", { detail: { date, state }, bubbles: true })));
@@ -1549,8 +1592,20 @@
1549
1592
  <select id="${this._id}-month" class="dp-month-select ${monthClass}" aria-label="${escapeHtml(this.messages.month)}">${monthOptions}</select>`;
1550
1593
  const yearControl = `<label class="dp-visually-hidden" for="${this._id}-year">${escapeHtml(this.messages.year)}</label>
1551
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
+ }
1552
1607
  this._rendering = true;
1553
- this.innerHTML = `
1608
+ this._renderRoot.innerHTML = `
1554
1609
  <div class="dp-calendar-shell">
1555
1610
  <div class="dp-calendar-header">
1556
1611
  ${yearFirst ? yearControl : monthControl}
@@ -1558,17 +1613,17 @@
1558
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>
1559
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>
1560
1615
  </div>
1561
- <h2 id="${headingId}" class="dp-calendar-heading dp-visually-hidden" aria-live="polite">${escapeHtml(formatMonthYear(`${display}-15`, locale))}</h2>
1562
1616
  <table id="${gridId}" class="dp-grid" role="grid" aria-labelledby="${headingId}">
1563
1617
  <thead><tr>${weekHeader}${dayHeaders}</tr></thead>
1564
1618
  <tbody>${rows}</tbody>
1565
1619
  </table>
1566
1620
  </div>`;
1621
+ this._heading.textContent = formatMonthYear(`${display}-15`, locale);
1567
1622
  this._rendering = false;
1568
1623
  this._paintedRange = { ...this._highlightedRange };
1569
1624
  this._paintedPreview = this.hasAttribute("data-range-preview");
1570
1625
  if (focusKey) {
1571
- queueMicrotask(() => {
1626
+ this._queueFocus(() => {
1572
1627
  let selector = focusKey;
1573
1628
  if (selector.startsWith(".dp-day[data-date=")) {
1574
1629
  selector = `.dp-day[data-date="${CSS.escape(this._model.focused)}"]`;
@@ -1982,6 +2037,7 @@
1982
2037
  this.canonical = "";
1983
2038
  this._dirty = false;
1984
2039
  this._commitId = 0;
2040
+ this._disposed = false;
1985
2041
  this._originalName = input.getAttribute("name") || "";
1986
2042
  this._attributeObserver = null;
1987
2043
  this.onAttributesChanged = null;
@@ -2084,34 +2140,39 @@
2084
2140
  async commit(options = {}) {
2085
2141
  const input = this.input;
2086
2142
  const commitId = ++this._commitId;
2143
+ if (this._disposed)
2144
+ return { status: "stale", commitId };
2087
2145
  const text = input.value.trim();
2088
2146
  if (!text) {
2089
2147
  if (commitId !== this._commitId)
2090
- return { status: "stale" };
2148
+ return { status: "stale", commitId };
2091
2149
  input.setCustomValidity("");
2092
- return { status: "clear", value: "" };
2150
+ return { status: "clear", value: "", commitId };
2093
2151
  }
2094
2152
  const parsed = this.adapter.parse(text);
2095
2153
  if (!parsed) {
2096
2154
  if (commitId !== this._commitId)
2097
- return { status: "stale" };
2155
+ return { status: "stale", commitId };
2098
2156
  if (this.hidden)
2099
2157
  this.hidden.value = "";
2100
2158
  input.setCustomValidity(this.messages.invalidDate);
2101
- return { status: "invalid" };
2159
+ return { status: "invalid", commitId };
2102
2160
  }
2103
2161
  if (this.confirm) {
2104
2162
  const result = await this.confirm(parsed, options);
2105
- if (commitId !== this._commitId)
2106
- return { status: "stale" };
2163
+ if (this._disposed || commitId !== this._commitId)
2164
+ return { status: "stale", commitId };
2107
2165
  if (!result?.ok) {
2108
2166
  if (this.hidden)
2109
2167
  this.hidden.value = "";
2110
2168
  input.setCustomValidity(result?.message || this.messages.unavailableDate);
2111
- return { status: "invalid" };
2169
+ return { status: "invalid", commitId };
2112
2170
  }
2113
2171
  }
2114
- return { status: "ok", value: parsed };
2172
+ return { status: "ok", value: parsed, commitId };
2173
+ }
2174
+ isCommitCurrent(commitId) {
2175
+ return commitId === this._commitId;
2115
2176
  }
2116
2177
  setCanonical(value, options = {}) {
2117
2178
  const next = value || "";
@@ -2132,6 +2193,8 @@
2132
2193
  return parsed && isDate(parsed) ? parsed : "";
2133
2194
  }
2134
2195
  teardown() {
2196
+ this.dirty();
2197
+ this._disposed = true;
2135
2198
  this._attributeObserver?.disconnect();
2136
2199
  this._attributeObserver = null;
2137
2200
  const hiddenName = this.hidden?.getAttribute("name") || this._originalName;
@@ -2214,7 +2277,7 @@
2214
2277
  this._originalDescribedBy = "";
2215
2278
  this._rangeOriginalDescribedBy = ["", ""];
2216
2279
  this._formatHint = null;
2217
- this._generatedPlaceholder = false;
2280
+ this._generatedPlaceholders = new WeakSet;
2218
2281
  this._controller = null;
2219
2282
  this._stopTracking = null;
2220
2283
  this._open = false;
@@ -2231,6 +2294,9 @@
2231
2294
  _rangeMode() {
2232
2295
  return this.hasAttribute("range") && Boolean(this._range);
2233
2296
  }
2297
+ _ownsField(field) {
2298
+ return this._connected && (field === this._field || Boolean(this._fields?.includes(field)));
2299
+ }
2234
2300
  connectedCallback() {
2235
2301
  if (this._connected)
2236
2302
  return;
@@ -2368,6 +2434,7 @@
2368
2434
  this._stopDefaultMessages?.();
2369
2435
  this._stopDefaultMessages = null;
2370
2436
  this._connected = false;
2437
+ this._suppressFocusOpen = false;
2371
2438
  this.hide(false);
2372
2439
  this._controller?.abort();
2373
2440
  this._controller = null;
@@ -2408,7 +2475,21 @@
2408
2475
  input.removeAttribute("aria-controls");
2409
2476
  }
2410
2477
  attributeChangedCallback(name, oldValue, newValue) {
2411
- 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)
2412
2493
  return;
2413
2494
  if (name === "value") {
2414
2495
  if (this._rangeMode())
@@ -2695,7 +2776,7 @@
2695
2776
  return;
2696
2777
  this.hide(false);
2697
2778
  queueMicrotask(() => {
2698
- if (!event.defaultPrevented)
2779
+ if (!signal.aborted && !event.defaultPrevented)
2699
2780
  restore();
2700
2781
  });
2701
2782
  }, { capture: true, signal });
@@ -2737,7 +2818,7 @@
2737
2818
  button.addEventListener("click", (event) => {
2738
2819
  if (this._open) {
2739
2820
  if (event.detail === 0) {
2740
- setTimeout(() => this._calendar?.focusGrid(), 0);
2821
+ this._defer(() => this._calendar?.focusGrid());
2741
2822
  return;
2742
2823
  }
2743
2824
  this.hide(false);
@@ -2758,7 +2839,7 @@
2758
2839
  return;
2759
2840
  const intent = activationIntents.get(custom);
2760
2841
  queueMicrotask(() => {
2761
- if (custom.defaultPrevented || !this._connected)
2842
+ if (signal.aborted || custom.defaultPrevented || !this._connected)
2762
2843
  return;
2763
2844
  if (intent && intent.id !== this._activationId)
2764
2845
  return;
@@ -2823,7 +2904,7 @@
2823
2904
  if (!isDate(date))
2824
2905
  return;
2825
2906
  queueMicrotask(() => {
2826
- if (custom.defaultPrevented || !this._connected || !this._range)
2907
+ if (signal.aborted || custom.defaultPrevented || !this._connected || !this._range)
2827
2908
  return;
2828
2909
  const intent = activationIntents.get(custom);
2829
2910
  if (intent === undefined || intent.id !== this._activationId)
@@ -2876,7 +2957,7 @@
2876
2957
  }
2877
2958
  if (event.detail === 0) {
2878
2959
  this._activateRangeEndpoint(endpoint);
2879
- setTimeout(() => this._calendar?.focusGrid(), 0);
2960
+ this._defer(() => this._calendar?.focusGrid());
2880
2961
  return;
2881
2962
  }
2882
2963
  if (endpoint !== this._range?.activeEndpoint) {
@@ -2892,7 +2973,7 @@
2892
2973
  if (this._open) {
2893
2974
  if (this._rangeMode() && endpoint)
2894
2975
  this._activateRangeEndpoint(endpoint);
2895
- setTimeout(() => this._calendar?.focusGrid(), 0);
2976
+ this._defer(() => this._calendar?.focusGrid());
2896
2977
  return;
2897
2978
  }
2898
2979
  if (this._rangeMode())
@@ -2908,7 +2989,7 @@
2908
2989
  this._range?.focus(endpoint);
2909
2990
  }
2910
2991
  if (this._open) {
2911
- setTimeout(() => this._calendar?.focusGrid(), 0);
2992
+ this._defer(() => this._calendar?.focusGrid());
2912
2993
  } else {
2913
2994
  this.show();
2914
2995
  }
@@ -3178,9 +3259,9 @@
3178
3259
  this._fields[1].setLocale(this.locale);
3179
3260
  hint.textContent = `${this.messages.formatHint}: ${this._fields[0].placeholder}`;
3180
3261
  for (const [index, field] of this._fields.entries()) {
3181
- if (!field.input.hasAttribute("placeholder") || this._generatedPlaceholder) {
3262
+ if (!field.input.hasAttribute("placeholder") || this._generatedPlaceholders.has(field.input)) {
3182
3263
  field.input.placeholder = field.placeholder;
3183
- this._generatedPlaceholder = true;
3264
+ this._generatedPlaceholders.add(field.input);
3184
3265
  }
3185
3266
  const canonical = index === 1 ? this._range?.end || "" : this._range?.start || "";
3186
3267
  if (canonical)
@@ -3194,9 +3275,9 @@
3194
3275
  return;
3195
3276
  field.setLocale(this.locale);
3196
3277
  hint.textContent = `${this.messages.formatHint}: ${field.placeholder}`;
3197
- if (!field.input.hasAttribute("placeholder") || this._generatedPlaceholder) {
3278
+ if (!field.input.hasAttribute("placeholder") || this._generatedPlaceholders.has(field.input)) {
3198
3279
  field.input.placeholder = field.placeholder;
3199
- this._generatedPlaceholder = true;
3280
+ this._generatedPlaceholders.add(field.input);
3200
3281
  }
3201
3282
  if (this._value)
3202
3283
  field.input.value = field.format(this._value);
@@ -3279,6 +3360,9 @@
3279
3360
  if (!options.force && !field.isDirty)
3280
3361
  return false;
3281
3362
  const result = await field.commit(options);
3363
+ if (!this._ownsField(field) || !field.isCommitCurrent(result.commitId)) {
3364
+ return false;
3365
+ }
3282
3366
  if (result.status === "stale" || result.status === "invalid")
3283
3367
  return false;
3284
3368
  this._setValue(result.value || "", {
@@ -3297,6 +3381,8 @@
3297
3381
  if (!field?.isDirty)
3298
3382
  return false;
3299
3383
  const result = await field.commit();
3384
+ if (!this._ownsField(field) || !field.isCommitCurrent(result.commitId))
3385
+ return false;
3300
3386
  if (result.status === "stale")
3301
3387
  return false;
3302
3388
  if (result.status === "invalid") {
@@ -3423,6 +3509,14 @@
3423
3509
  input.setCustomValidity("");
3424
3510
  this._timeOrderOwned.clear();
3425
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
+ }
3426
3520
  _restoreDefault() {
3427
3521
  const field = this._field;
3428
3522
  if (!field)
@@ -3459,10 +3553,10 @@
3459
3553
  if (!input)
3460
3554
  return;
3461
3555
  this._suppressFocusOpen = true;
3462
- setTimeout(() => {
3556
+ this._defer(() => {
3463
3557
  input.focus();
3464
3558
  this._suppressFocusOpen = false;
3465
- }, 0);
3559
+ });
3466
3560
  }
3467
3561
  _restoreFocus() {
3468
3562
  const target = this._returnFocus;
@@ -3470,10 +3564,10 @@
3470
3564
  if (!(target instanceof HTMLElement) || !target.isConnected || target.matches(":disabled"))
3471
3565
  return;
3472
3566
  this._suppressFocusOpen = true;
3473
- setTimeout(() => {
3567
+ this._defer(() => {
3474
3568
  target.focus();
3475
3569
  this._suppressFocusOpen = false;
3476
- }, 0);
3570
+ });
3477
3571
  }
3478
3572
  _resolveRangeEndpoint() {
3479
3573
  if (!this._fields)
@@ -3497,6 +3591,8 @@
3497
3591
  return true;
3498
3592
  for (const [index, field] of fields.entries()) {
3499
3593
  const result = await field.commit({ moveDisplay: false });
3594
+ if (!this._ownsField(field) || !field.isCommitCurrent(result.commitId))
3595
+ continue;
3500
3596
  if (result.status === "invalid")
3501
3597
  this._orderTags.delete(index);
3502
3598
  if (result.status === "ok" || result.status === "clear") {
@@ -3509,8 +3605,11 @@
3509
3605
  return fields.every((field) => field.input.checkValidity());
3510
3606
  }
3511
3607
  const input = this._field?.input;
3512
- if (!input)
3513
- return true;
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());
3612
+ }
3514
3613
  await this._commitText(false, { force: true, moveDisplay: false });
3515
3614
  this._revalidateTimeOrder("start");
3516
3615
  this._revalidateTimeOrder("end");
@@ -3549,8 +3648,14 @@
3549
3648
  });
3550
3649
  position();
3551
3650
  this._stopTracking = autoUpdate(this, panel, position);
3552
- if (options.moveFocus !== false)
3553
- 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
+ }
3554
3659
  this.dispatchEvent(new Event("open", { bubbles: true }));
3555
3660
  }
3556
3661
  _setExpanded(expanded) {