@lekoala/date-picker 0.5.0 → 0.5.2

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.0 - https://github.com/lekoala/date-picker ***/
1
+ /*** @lekoala/date-picker v0.5.2 - 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})$/;
@@ -313,14 +316,37 @@
313
316
  function getDefaultControlClasses() {
314
317
  return { ...defaults };
315
318
  }
316
- function resolveControlClasses(classes) {
317
- const result = getDefaultControlClasses();
319
+ function normalizeControlClasses(classes) {
320
+ if (classes == null)
321
+ return null;
322
+ const result = {};
318
323
  for (const role of ["month", "year", "nav"]) {
319
- if (classes && Object.hasOwn(classes, role))
324
+ if (Object.hasOwn(classes, role))
320
325
  result[role] = normalizeControlClass(classes[role]);
321
326
  }
322
327
  return result;
323
328
  }
329
+ function usesDefaultControlClasses(classes) {
330
+ return classes == null || ["month", "year", "nav"].some((role) => !Object.hasOwn(classes, role));
331
+ }
332
+ function resolveControlClasses(classes) {
333
+ return { ...getDefaultControlClasses(), ...normalizeControlClasses(classes) };
334
+ }
335
+ function setDefaultControlClasses(classes) {
336
+ const next = resolveControlClasses(classes);
337
+ if (sameControlClasses(defaults, next))
338
+ return;
339
+ defaults = next;
340
+ for (const listener of listeners)
341
+ listener();
342
+ }
343
+ var listeners = new Set;
344
+ function subscribeDefaultControlClasses(listener) {
345
+ listeners.add(listener);
346
+ return () => {
347
+ listeners.delete(listener);
348
+ };
349
+ }
324
350
 
325
351
  // src/date-range.js
326
352
  function normalizeRange(range) {
@@ -703,6 +729,18 @@
703
729
  function getDefaultMessages() {
704
730
  return { ...defaults2 };
705
731
  }
732
+ function setDefaultMessages(messages) {
733
+ defaults2 = { ...defaults2, ...messages };
734
+ for (const listener of listeners2)
735
+ listener();
736
+ }
737
+ var listeners2 = new Set;
738
+ function subscribeDefaultMessages(listener) {
739
+ listeners2.add(listener);
740
+ return () => {
741
+ listeners2.delete(listener);
742
+ };
743
+ }
706
744
 
707
745
  // src/source.js
708
746
  function normalizeDateStates(payload) {
@@ -725,6 +763,17 @@
725
763
  return map;
726
764
  }
727
765
 
766
+ // src/upgrade-properties.js
767
+ function upgradeProperties(element, properties) {
768
+ for (const property of properties) {
769
+ if (!Object.hasOwn(element, property))
770
+ continue;
771
+ const value = Reflect.get(element, property);
772
+ Reflect.deleteProperty(element, property);
773
+ Reflect.set(element, property, value);
774
+ }
775
+ }
776
+
728
777
  // src/date-calendar.js
729
778
  var uid = 0;
730
779
  function escapeHtml(value) {
@@ -783,6 +832,18 @@
783
832
  "selection",
784
833
  "month-format"
785
834
  ];
835
+ static setDefaultControlClasses(classes) {
836
+ setDefaultControlClasses(classes);
837
+ }
838
+ static getDefaultControlClasses() {
839
+ return getDefaultControlClasses();
840
+ }
841
+ static setDefaultMessages(messages) {
842
+ setDefaultMessages(messages);
843
+ }
844
+ static getDefaultMessages() {
845
+ return getDefaultMessages();
846
+ }
786
847
  constructor() {
787
848
  super();
788
849
  this._id = `date-calendar-${++uid}`;
@@ -790,6 +851,7 @@
790
851
  this._connected = false;
791
852
  this._rendering = false;
792
853
  this._source = null;
854
+ this._hostIntent = null;
793
855
  this._sourceStates = new Map;
794
856
  this._loadedRanges = new Set;
795
857
  this._loadController = null;
@@ -802,8 +864,10 @@
802
864
  this._paintedRange = { start: "", end: "" };
803
865
  this._paintedPreview = false;
804
866
  this._renderedStates = new Map;
805
- this._messages = getDefaultMessages();
806
- this._controlClasses = getDefaultControlClasses();
867
+ this._messages = null;
868
+ this._controlClasses = null;
869
+ this._stopDefaultMessages = null;
870
+ this._stopDefaultControlClasses = null;
807
871
  this._onClick = this._onClick.bind(this);
808
872
  this._onChange = this._onChange.bind(this);
809
873
  this._onKeyDown = this._onKeyDown.bind(this);
@@ -812,15 +876,47 @@
812
876
  connectedCallback() {
813
877
  if (this._connected)
814
878
  return;
879
+ upgradeProperties(this, [
880
+ "locale",
881
+ "min",
882
+ "max",
883
+ "firstDay",
884
+ "selection",
885
+ "monthFormat",
886
+ "fixedWeeks",
887
+ "showWeekNumbers",
888
+ "controlClasses",
889
+ "messages",
890
+ "source",
891
+ "dateState",
892
+ "renderDay",
893
+ "isDateDisabled",
894
+ "highlightedRange",
895
+ "value",
896
+ "display"
897
+ ]);
898
+ this._syncModel();
899
+ upgradeProperties(this, ["focusedDate"]);
900
+ this._stopDefaultControlClasses = subscribeDefaultControlClasses(() => {
901
+ if (usesDefaultControlClasses(this._controlClasses))
902
+ this.render();
903
+ });
904
+ this._stopDefaultMessages = subscribeDefaultMessages(() => {
905
+ if (this._messages === null)
906
+ this.render();
907
+ });
815
908
  this._connected = true;
816
909
  this.addEventListener("click", this._onClick);
817
910
  this.addEventListener("change", this._onChange);
818
911
  this.addEventListener("keydown", this._onKeyDown);
819
912
  this.addEventListener("focusin", this._onFocusIn);
820
- this._syncModel();
821
913
  this.render();
822
914
  }
823
915
  disconnectedCallback() {
916
+ this._stopDefaultControlClasses?.();
917
+ this._stopDefaultControlClasses = null;
918
+ this._stopDefaultMessages?.();
919
+ this._stopDefaultMessages = null;
824
920
  this._connected = false;
825
921
  this._loadController?.abort();
826
922
  this._loadController = null;
@@ -941,21 +1037,21 @@
941
1037
  this.toggleAttribute("show-week-numbers", Boolean(value));
942
1038
  }
943
1039
  get controlClasses() {
944
- return { ...this._controlClasses };
1040
+ return resolveControlClasses(this._controlClasses);
945
1041
  }
946
1042
  set controlClasses(value) {
947
- const classes = resolveControlClasses(value);
948
- if (sameControlClasses(classes, this._controlClasses))
1043
+ const previous = this.controlClasses;
1044
+ this._controlClasses = normalizeControlClasses(value);
1045
+ if (sameControlClasses(previous, this.controlClasses))
949
1046
  return;
950
- this._controlClasses = classes;
951
1047
  if (this._connected)
952
1048
  this.render();
953
1049
  }
954
1050
  get messages() {
955
- return this._messages;
1051
+ return this._messages ?? getDefaultMessages();
956
1052
  }
957
1053
  set messages(value) {
958
- this._messages = { ...getDefaultMessages(), ...value || {} };
1054
+ this._messages = value == null ? null : { ...getDefaultMessages(), ...value };
959
1055
  if (this._connected)
960
1056
  this.render();
961
1057
  }
@@ -1192,6 +1288,15 @@
1192
1288
  this.focusGrid();
1193
1289
  }
1194
1290
  }
1291
+ _syncRovingDom(date) {
1292
+ for (const cell of this.querySelectorAll('.dp-day[tabindex="0"]')) {
1293
+ if (cell instanceof HTMLElement && cell.dataset.date !== date)
1294
+ cell.tabIndex = -1;
1295
+ }
1296
+ const cell = this.querySelector(`.dp-day[data-date="${CSS.escape(date)}"]`);
1297
+ if (cell instanceof HTMLElement)
1298
+ cell.tabIndex = 0;
1299
+ }
1195
1300
  _moveSelectionDom(previousValue, nextValue) {
1196
1301
  if (previousValue) {
1197
1302
  const prev = this.querySelector(`.dp-day[data-date="${CSS.escape(previousValue)}"]`);
@@ -1216,8 +1321,8 @@
1216
1321
  return [
1217
1322
  formatLongDate(date, this.locale),
1218
1323
  description,
1219
- rangeLabel ? this._messages[rangeLabel] : "",
1220
- state.disabled ? this._messages.unavailable : ""
1324
+ rangeLabel ? this.messages[rangeLabel] : "",
1325
+ state.disabled ? this.messages.unavailable : ""
1221
1326
  ].filter(Boolean).join(". ");
1222
1327
  }
1223
1328
  _updateRangeDom() {
@@ -1252,17 +1357,24 @@
1252
1357
  target.focus();
1253
1358
  }
1254
1359
  async activateDate(date) {
1360
+ const hostIntent = this._hostIntent?.date === date ? this._hostIntent.intent : undefined;
1361
+ this._hostIntent = null;
1362
+ const tagged = (event) => {
1363
+ if (hostIntent !== undefined)
1364
+ activationIntents.set(event, hostIntent);
1365
+ return event;
1366
+ };
1255
1367
  const confirmed = await this.ensureDate(date);
1256
1368
  const state = this.getDateState(date);
1257
1369
  if (!confirmed || state.disabled) {
1258
- this.dispatchEvent(new CustomEvent("dateinvalid", { detail: { date, state }, bubbles: true }));
1370
+ this.dispatchEvent(tagged(new CustomEvent("dateinvalid", { detail: { date, state }, bubbles: true })));
1259
1371
  return false;
1260
1372
  }
1261
- const accepted = this.dispatchEvent(new CustomEvent("dateactivate", {
1373
+ const accepted = this.dispatchEvent(tagged(new CustomEvent("dateactivate", {
1262
1374
  detail: { date, state },
1263
1375
  bubbles: true,
1264
1376
  cancelable: true
1265
- }));
1377
+ })));
1266
1378
  if (!accepted)
1267
1379
  return false;
1268
1380
  if (this.selection === "single") {
@@ -1307,6 +1419,8 @@
1307
1419
  this._reflect("display", this._model.display);
1308
1420
  if (previousDisplay !== this.display)
1309
1421
  this._emitDisplayChange();
1422
+ else
1423
+ this._syncRovingDom(this._model.focused);
1310
1424
  this.activateDate(date);
1311
1425
  }
1312
1426
  _onChange(event) {
@@ -1317,7 +1431,7 @@
1317
1431
  }
1318
1432
  if (target instanceof HTMLInputElement && target.matches(".dp-year-input")) {
1319
1433
  const year = Number(target.value);
1320
- if (!Number.isInteger(year) || year < 1 || year > 9998) {
1434
+ if (!Number.isInteger(year) || year < 1 || year > 9999) {
1321
1435
  if (this._connected)
1322
1436
  this.render(false);
1323
1437
  return;
@@ -1333,6 +1447,7 @@
1333
1447
  if (!isDate(date))
1334
1448
  return;
1335
1449
  this._model.focused = date;
1450
+ this._syncRovingDom(date);
1336
1451
  this.dispatchEvent(new CustomEvent("datefocus", { detail: { date, state: this.getDateState(date) }, bubbles: true }));
1337
1452
  }
1338
1453
  _onKeyDown(event) {
@@ -1395,10 +1510,10 @@
1395
1510
  const nextKey = stepMonthKey(display, 1);
1396
1511
  const prevDisabled = Boolean(minMonth && prevKey !== null && prevKey < minMonth);
1397
1512
  const nextDisabled = Boolean(maxMonth && nextKey !== null && nextKey > maxMonth);
1398
- const minYearAttr = this.min ? ` min="${Number(this.min.slice(0, 4))}"` : "";
1399
- const maxYearAttr = this.max ? ` max="${Number(this.max.slice(0, 4))}"` : "";
1513
+ const minYearAttr = ` min="${this.min ? Number(this.min.slice(0, 4)) : 1}"`;
1514
+ const maxYearAttr = ` max="${this.max ? Number(this.max.slice(0, 4)) : 9999}"`;
1400
1515
  const showWeeks = this.showWeekNumbers && this.firstDay === 1;
1401
- const weekHeader = showWeeks ? `<th scope="col" class="dp-week-heading" abbr="${escapeHtml(this._messages.week)}">#</th>` : "";
1516
+ const weekHeader = showWeeks ? `<th scope="col" class="dp-week-heading" abbr="${escapeHtml(this.messages.week)}">#</th>` : "";
1402
1517
  const monthOptions = names.map((name, index) => {
1403
1518
  const month = index + 1;
1404
1519
  const key = `${String(displayYear).padStart(4, "0")}-${String(month).padStart(2, "0")}`;
@@ -1426,22 +1541,22 @@
1426
1541
  const focusKey = focusedElement instanceof HTMLElement && this.contains(focusedElement) ? this._focusTargetKey(focusedElement) : "";
1427
1542
  const yearFirst = monthYearOrder(locale)[0] === "year";
1428
1543
  this.toggleAttribute("data-year-first", yearFirst);
1429
- const classes = this._controlClasses;
1544
+ const classes = this.controlClasses;
1430
1545
  const monthClass = escapeHtml(classes.month || "dp-skin");
1431
1546
  const yearClass = escapeHtml(classes.year || "dp-skin");
1432
1547
  const navClass = escapeHtml(classes.nav || "dp-skin");
1433
- const monthControl = `<label class="dp-visually-hidden" for="${this._id}-month">${escapeHtml(this._messages.month)}</label>
1434
- <select id="${this._id}-month" class="dp-month-select ${monthClass}" aria-label="${escapeHtml(this._messages.month)}">${monthOptions}</select>`;
1435
- const yearControl = `<label class="dp-visually-hidden" for="${this._id}-year">${escapeHtml(this._messages.year)}</label>
1436
- <input id="${this._id}-year" class="dp-year-input ${yearClass}" type="number" inputmode="numeric" value="${displayYear}"${minYearAttr}${maxYearAttr} aria-label="${escapeHtml(this._messages.year)}">`;
1548
+ const monthControl = `<label class="dp-visually-hidden" for="${this._id}-month">${escapeHtml(this.messages.month)}</label>
1549
+ <select id="${this._id}-month" class="dp-month-select ${monthClass}" aria-label="${escapeHtml(this.messages.month)}">${monthOptions}</select>`;
1550
+ const yearControl = `<label class="dp-visually-hidden" for="${this._id}-year">${escapeHtml(this.messages.year)}</label>
1551
+ <input id="${this._id}-year" class="dp-year-input ${yearClass}" type="number" inputmode="numeric" value="${displayYear}"${minYearAttr}${maxYearAttr} aria-label="${escapeHtml(this.messages.year)}">`;
1437
1552
  this._rendering = true;
1438
1553
  this.innerHTML = `
1439
1554
  <div class="dp-calendar-shell">
1440
1555
  <div class="dp-calendar-header">
1441
1556
  ${yearFirst ? yearControl : monthControl}
1442
1557
  ${yearFirst ? monthControl : yearControl}
1443
- <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>
1444
- <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>
1558
+ <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
+ <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>
1445
1560
  </div>
1446
1561
  <h2 id="${headingId}" class="dp-calendar-heading dp-visually-hidden" aria-live="polite">${escapeHtml(formatMonthYear(`${display}-15`, locale))}</h2>
1447
1562
  <table id="${gridId}" class="dp-grid" role="grid" aria-labelledby="${headingId}">
@@ -1854,6 +1969,10 @@
1854
1969
  }
1855
1970
 
1856
1971
  // src/date-field.js
1972
+ function isEditable(input) {
1973
+ return !input.matches(":disabled") && !input.readOnly;
1974
+ }
1975
+
1857
1976
  class DateFieldController {
1858
1977
  constructor(input, options = {}) {
1859
1978
  this.input = input;
@@ -1867,9 +1986,22 @@
1867
1986
  this._attributeObserver = null;
1868
1987
  this.onAttributesChanged = null;
1869
1988
  this.confirm = null;
1989
+ this._generatedDefault = null;
1870
1990
  }
1871
1991
  setLocale(locale) {
1992
+ const generated = this._generatedDefault;
1993
+ const previous = generated ? this.adapter.format(generated) : null;
1872
1994
  this.adapter = createDateAdapter(locale || "");
1995
+ if (generated && this.input.defaultValue === previous) {
1996
+ this.input.defaultValue = this.adapter.format(generated);
1997
+ }
1998
+ }
1999
+ adoptDefault() {
2000
+ const input = this.input;
2001
+ if (input.getAttribute("value") != null || input.defaultValue !== "")
2002
+ return;
2003
+ input.defaultValue = input.value;
2004
+ this._generatedDefault = this.canonical && input.value ? this.canonical : null;
1873
2005
  }
1874
2006
  setMessages(messages) {
1875
2007
  this.messages = messages || {};
@@ -1884,7 +2016,7 @@
1884
2016
  return this.adapter.placeholder;
1885
2017
  }
1886
2018
  get isDisabled() {
1887
- return this.input.disabled;
2019
+ return this.input.matches(":disabled");
1888
2020
  }
1889
2021
  get isReadOnly() {
1890
2022
  return this.input.readOnly;
@@ -2045,6 +2177,18 @@
2045
2177
 
2046
2178
  class DatePickerElement extends HTMLElement {
2047
2179
  static observedAttributes = ["value", "locale", "min", "max", "open-on-focus", "month-format"];
2180
+ static setDefaultControlClasses(classes) {
2181
+ setDefaultControlClasses(classes);
2182
+ }
2183
+ static getDefaultControlClasses() {
2184
+ return getDefaultControlClasses();
2185
+ }
2186
+ static setDefaultMessages(messages) {
2187
+ setDefaultMessages(messages);
2188
+ }
2189
+ static getDefaultMessages() {
2190
+ return getDefaultMessages();
2191
+ }
2048
2192
  constructor() {
2049
2193
  super();
2050
2194
  this._id = `date-picker-${++uid2}`;
@@ -2059,8 +2203,7 @@
2059
2203
  this._timeFields = [null, null];
2060
2204
  this._timeOrderOwned = new Set;
2061
2205
  this._lastFocusEndpoint = "";
2062
- this._rangeCommitId = 0;
2063
- this._pendingIntents = new Map;
2206
+ this._activationId = 0;
2064
2207
  this._previewDate = "";
2065
2208
  this._drag = null;
2066
2209
  this._suppressGridClick = false;
@@ -2076,8 +2219,9 @@
2076
2219
  this._stopTracking = null;
2077
2220
  this._open = false;
2078
2221
  this._suppressFocusOpen = false;
2079
- this._messages = getDefaultMessages();
2080
- this._controlClasses = getDefaultControlClasses();
2222
+ this._messages = null;
2223
+ this._controlClasses = null;
2224
+ this._stopDefaultMessages = null;
2081
2225
  this._source = null;
2082
2226
  this._dateState = null;
2083
2227
  this._renderDay = null;
@@ -2090,11 +2234,32 @@
2090
2234
  connectedCallback() {
2091
2235
  if (this._connected)
2092
2236
  return;
2093
- if (this.hasAttribute("range")) {
2237
+ upgradeProperties(this, [
2238
+ "locale",
2239
+ "min",
2240
+ "max",
2241
+ "monthFormat",
2242
+ "openOnFocus",
2243
+ "coordinateSpace",
2244
+ "controlClasses",
2245
+ "messages",
2246
+ "source",
2247
+ "dateState",
2248
+ "renderDay",
2249
+ "isDateDisabled"
2250
+ ]);
2251
+ if (this.hasAttribute("range"))
2094
2252
  this._connectRange();
2095
- return;
2253
+ else
2254
+ 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();
2096
2262
  }
2097
- this._connectSingle();
2098
2263
  }
2099
2264
  _connectSingle() {
2100
2265
  const input = this.querySelector(":scope > input:not([type=hidden]):not([type=time]):not([data-time-start]):not([data-time-end])");
@@ -2105,7 +2270,7 @@
2105
2270
  this._connected = true;
2106
2271
  this._input = input;
2107
2272
  this._discoverTimeFields();
2108
- this._field = new DateFieldController(input, { messages: this._messages, locale: this.locale });
2273
+ this._field = new DateFieldController(input, { messages: this.messages, locale: this.locale });
2109
2274
  this._field.onAttributesChanged = () => this._syncInputState();
2110
2275
  this._field.confirm = (date, options) => this._confirmDate(date, options);
2111
2276
  this._setupFormValue();
@@ -2120,12 +2285,10 @@
2120
2285
  else if (!input.value)
2121
2286
  this._setValue("", { emit: false, format: false });
2122
2287
  else
2123
- input.setCustomValidity(this._messages.invalidDate);
2288
+ input.setCustomValidity(this.messages.invalidDate);
2124
2289
  if (this._field.hidden)
2125
2290
  this._field.hidden.defaultValue = this._value;
2126
- if (input.getAttribute("value") == null && input.defaultValue === "") {
2127
- input.defaultValue = input.value;
2128
- }
2291
+ this._field.adoptDefault();
2129
2292
  }
2130
2293
  _discoverTimeFields() {
2131
2294
  this._timeFields = [null, null];
@@ -2161,8 +2324,8 @@
2161
2324
  this._connected = true;
2162
2325
  this._input = null;
2163
2326
  this._fields = [
2164
- new DateFieldController(startInput, { messages: this._messages, locale: this.locale }),
2165
- new DateFieldController(endInput, { messages: this._messages, locale: this.locale })
2327
+ new DateFieldController(startInput, { messages: this.messages, locale: this.locale }),
2328
+ new DateFieldController(endInput, { messages: this.messages, locale: this.locale })
2166
2329
  ];
2167
2330
  this._range = new DateRangeController;
2168
2331
  this._rangeOriginalDescribedBy = [
@@ -2182,23 +2345,28 @@
2182
2345
  const adapter = this._adapter();
2183
2346
  const initialStart = isDate(startInput.value) ? startInput.value : adapter.parse(startInput.value);
2184
2347
  const initialEnd = isDate(endInput.value) ? endInput.value : adapter.parse(endInput.value);
2348
+ const startText = startInput.value;
2349
+ const endText = endInput.value;
2185
2350
  this._setRange(initialStart || "", initialEnd || "", { emit: false });
2186
- if (startInput.value.trim() && !isDate(initialStart))
2187
- startInput.setCustomValidity(this._messages.invalidDate);
2188
- if (endInput.value.trim() && !isDate(initialEnd))
2189
- endInput.setCustomValidity(this._messages.invalidDate);
2351
+ for (const [input, text, initial] of [
2352
+ [startInput, startText, initialStart],
2353
+ [endInput, endText, initialEnd]
2354
+ ]) {
2355
+ if (!text.trim() || isDate(initial))
2356
+ continue;
2357
+ input.value = text;
2358
+ input.setCustomValidity(this.messages.invalidDate);
2359
+ }
2190
2360
  if (this._fields[0].hidden)
2191
2361
  this._fields[0].hidden.defaultValue = this._range.start;
2192
2362
  if (this._fields[1].hidden)
2193
2363
  this._fields[1].hidden.defaultValue = this._range.end;
2194
- if (startInput.getAttribute("value") == null && startInput.defaultValue === "") {
2195
- startInput.defaultValue = startInput.value;
2196
- }
2197
- if (endInput.getAttribute("value") == null && endInput.defaultValue === "") {
2198
- endInput.defaultValue = endInput.value;
2199
- }
2364
+ for (const field of this._fields)
2365
+ field.adoptDefault();
2200
2366
  }
2201
2367
  disconnectedCallback() {
2368
+ this._stopDefaultMessages?.();
2369
+ this._stopDefaultMessages = null;
2202
2370
  this._connected = false;
2203
2371
  this.hide(false);
2204
2372
  this._controller?.abort();
@@ -2245,8 +2413,10 @@
2245
2413
  if (name === "value") {
2246
2414
  if (this._rangeMode())
2247
2415
  return;
2248
- if (!newValue || isDate(newValue))
2416
+ if (!newValue || isDate(newValue)) {
2417
+ this._supersedeActivations();
2249
2418
  this._setValue(newValue || "", { emit: false, format: true, reflect: false });
2419
+ }
2250
2420
  return;
2251
2421
  }
2252
2422
  if (name === "locale")
@@ -2271,6 +2441,7 @@
2271
2441
  }
2272
2442
  if (value && !isDate(value))
2273
2443
  throw new TypeError(`Invalid date-picker value: ${value}`);
2444
+ this._supersedeActivations();
2274
2445
  this._setValue(value || "", { emit: false, format: true });
2275
2446
  }
2276
2447
  get range() {
@@ -2280,6 +2451,7 @@
2280
2451
  if (!this._rangeMode())
2281
2452
  throw new TypeError("range is only available on <date-picker range>");
2282
2453
  const { start, end } = normalizeRange(value);
2454
+ this._supersedeActivations();
2283
2455
  this._setRange(start, end);
2284
2456
  }
2285
2457
  get locale() {
@@ -2333,28 +2505,34 @@
2333
2505
  return this._open;
2334
2506
  }
2335
2507
  get controlClasses() {
2336
- return { ...this._controlClasses };
2508
+ return resolveControlClasses(this._controlClasses);
2337
2509
  }
2338
2510
  set controlClasses(value) {
2339
- const classes = resolveControlClasses(value);
2340
- if (sameControlClasses(classes, this._controlClasses))
2341
- return;
2342
- this._controlClasses = classes;
2511
+ this._controlClasses = normalizeControlClasses(value);
2343
2512
  if (this._calendar)
2344
2513
  this._calendar.controlClasses = this._controlClasses;
2345
2514
  }
2346
2515
  get messages() {
2347
- return this._messages;
2516
+ return this._messages ?? getDefaultMessages();
2348
2517
  }
2349
2518
  set messages(value) {
2350
- this._messages = { ...getDefaultMessages(), ...value || {} };
2519
+ this._messages = value == null ? null : { ...getDefaultMessages(), ...value };
2351
2520
  if (this._calendar)
2352
2521
  this._calendar.messages = this._messages;
2522
+ this._refreshMessages();
2523
+ }
2524
+ _refreshMessages() {
2525
+ const messages = this.messages;
2353
2526
  for (const field of this._fields || [])
2354
- field.setMessages(this._messages);
2355
- this._field?.setMessages(this._messages);
2527
+ field.setMessages(messages);
2528
+ this._field?.setMessages(messages);
2529
+ this._panel?.setAttribute("aria-label", messages.calendar);
2530
+ const field = this._field ?? this._fields?.[0];
2531
+ if (this._formatHint && field)
2532
+ this._formatHint.textContent = `${messages.formatHint}: ${field.placeholder}`;
2356
2533
  this._refreshButtonLabel();
2357
- this.validate();
2534
+ if (this._connected)
2535
+ this.validate();
2358
2536
  }
2359
2537
  get source() {
2360
2538
  return this._source;
@@ -2414,7 +2592,7 @@
2414
2592
  const button = this._buttons[0]?.button;
2415
2593
  if (button)
2416
2594
  button.disabled = input.disabled || input.readOnly;
2417
- if (this._open && (input.disabled || input.readOnly))
2595
+ if (this._open && !isEditable(input))
2418
2596
  this.hide(false);
2419
2597
  }
2420
2598
  _syncRangeFields() {
@@ -2429,9 +2607,9 @@
2429
2607
  }
2430
2608
  this._syncDragAffordance();
2431
2609
  const active = this._range?.activeEndpoint === "end" ? 1 : 0;
2432
- if (this._open && (this._fields[active]?.input.disabled || this._fields[active]?.input.readOnly)) {
2610
+ const activeInput = this._fields[active]?.input;
2611
+ if (this._open && activeInput && !isEditable(activeInput))
2433
2612
  this.hide(false);
2434
- }
2435
2613
  }
2436
2614
  _setRangeAria() {
2437
2615
  if (!this._fields || !this._panel || !this._formatHint)
@@ -2482,7 +2660,7 @@
2482
2660
  panel.className = "dp-picker-panel";
2483
2661
  panel.setAttribute("popover", "manual");
2484
2662
  panel.setAttribute("role", "dialog");
2485
- panel.setAttribute("aria-label", this._messages.calendar);
2663
+ panel.setAttribute("aria-label", this.messages.calendar);
2486
2664
  const calendar = new DateCalendarElement;
2487
2665
  calendar.selection = "none";
2488
2666
  calendar.fixedWeeks = true;
@@ -2568,15 +2746,23 @@
2568
2746
  this.show({ returnFocus: button });
2569
2747
  }, { signal });
2570
2748
  button.addEventListener("keydown", (event) => this._onTriggerKeyDown("", event), { signal });
2749
+ calendar.addEventListener("click", (event) => this._captureGridIntent(event), { capture: true, signal });
2750
+ calendar.addEventListener("keydown", (event) => this._captureGridIntent(event), {
2751
+ capture: true,
2752
+ signal
2753
+ });
2571
2754
  calendar.addEventListener("dateactivate", (event) => {
2572
2755
  const custom = event;
2573
2756
  const date = custom.detail?.date;
2574
2757
  if (!isDate(date))
2575
2758
  return;
2759
+ const intent = activationIntents.get(custom);
2576
2760
  queueMicrotask(() => {
2577
2761
  if (custom.defaultPrevented || !this._connected)
2578
2762
  return;
2579
- if (this._input?.disabled || this._input?.readOnly)
2763
+ if (intent && intent.id !== this._activationId)
2764
+ return;
2765
+ if (!this._input || !isEditable(this._input))
2580
2766
  return;
2581
2767
  this._field?.dirty();
2582
2768
  this._setValue(date, { emit: true, format: true });
@@ -2596,7 +2782,12 @@
2596
2782
  const { signal } = controller;
2597
2783
  for (const [index, field] of this._fields.entries()) {
2598
2784
  const endpoint = index === 1 ? "end" : "start";
2599
- field.input.addEventListener("input", () => field.handleInput(), { signal });
2785
+ field.input.addEventListener("input", () => {
2786
+ field.handleInput();
2787
+ this._orderTags.delete(index);
2788
+ if (!field.isDirty)
2789
+ this._revalidateRange(endpoint);
2790
+ }, { signal });
2600
2791
  field.input.addEventListener("change", () => void this._commitFieldText(endpoint), { signal });
2601
2792
  field.input.addEventListener("blur", () => void this._commitFieldText(endpoint), { signal });
2602
2793
  field.input.addEventListener("focus", (event) => this._onFieldFocus(endpoint, event), { signal });
@@ -2634,10 +2825,9 @@
2634
2825
  queueMicrotask(() => {
2635
2826
  if (custom.defaultPrevented || !this._connected || !this._range)
2636
2827
  return;
2637
- const intent = this._pendingIntents.get(date);
2638
- if (intent === undefined || intent.id !== this._rangeCommitId)
2828
+ const intent = activationIntents.get(custom);
2829
+ if (intent === undefined || intent.id !== this._activationId)
2639
2830
  return;
2640
- this._pendingIntents.delete(date);
2641
2831
  const editable = (bound) => this._boundEditable(bound);
2642
2832
  const result = intent.endpoint ? this._range.moveEndpoint(date, intent.endpoint, editable) : this._range.activate(date, editable);
2643
2833
  if (result.status === "refused") {
@@ -2672,8 +2862,7 @@
2672
2862
  if (!this._range || !calendar)
2673
2863
  return;
2674
2864
  this._range.focus(endpoint);
2675
- this._rangeCommitId++;
2676
- this._pendingIntents.clear();
2865
+ this._activationId++;
2677
2866
  this._lastFocusEndpoint = endpoint;
2678
2867
  const target = this._boundCanonical(endpoint) || todayISO();
2679
2868
  calendar.display = monthKey(target);
@@ -2735,8 +2924,7 @@
2735
2924
  this._lastFocusEndpoint = endpoint;
2736
2925
  this._range?.focus(endpoint);
2737
2926
  if (switched) {
2738
- this._rangeCommitId++;
2739
- this._pendingIntents.clear();
2927
+ this._activationId++;
2740
2928
  }
2741
2929
  if (!this.openOnFocus || this._suppressFocusOpen)
2742
2930
  return;
@@ -2747,25 +2935,34 @@
2747
2935
  this.show({ moveFocus: false });
2748
2936
  }
2749
2937
  _captureGridIntent(event) {
2750
- if (!this._rangeMode() || !this._range)
2938
+ if (this._rangeMode() ? !this._range : !this._field)
2751
2939
  return;
2752
2940
  if (event.type === "keydown") {
2753
2941
  const key = event.key;
2754
2942
  if (key !== "Enter" && key !== " ")
2755
2943
  return;
2756
2944
  }
2945
+ if (event.type === "click" && this._suppressGridClick)
2946
+ return;
2757
2947
  if (!(event.target instanceof Element))
2758
2948
  return;
2759
2949
  const cell = event.target.closest(".dp-day[data-date]");
2760
2950
  const date = cell?.getAttribute("data-date") || "";
2761
2951
  if (!isDate(date))
2762
2952
  return;
2763
- this._pendingIntents.clear();
2764
- this._pendingIntents.set(date, { id: ++this._rangeCommitId, endpoint: "" });
2953
+ this._attachIntent(date, "");
2954
+ }
2955
+ _attachIntent(date, endpoint) {
2956
+ if (!this._calendar)
2957
+ return;
2958
+ this._calendar._hostIntent = { date, intent: { id: ++this._activationId, endpoint } };
2959
+ }
2960
+ _supersedeActivations() {
2961
+ this._activationId++;
2765
2962
  }
2766
2963
  _boundEditable(bound) {
2767
2964
  const field = this._fields?.[bound === "end" ? 1 : 0];
2768
- return field != null && !field.input.disabled && !field.input.readOnly;
2965
+ return field != null && isEditable(field.input);
2769
2966
  }
2770
2967
  _applyRangeTransition(transition) {
2771
2968
  const fields = this._fields;
@@ -2913,8 +3110,7 @@
2913
3110
  return;
2914
3111
  }
2915
3112
  const target = drag.endpoint === "start" ? transition.range.start : transition.range.end;
2916
- this._pendingIntents.clear();
2917
- this._pendingIntents.set(target, { id: ++this._rangeCommitId, endpoint: drag.endpoint });
3113
+ this._attachIntent(target, drag.endpoint);
2918
3114
  this._calendar?.activateDate(target);
2919
3115
  }
2920
3116
  _onGridPointerCancel(event) {
@@ -2971,7 +3167,7 @@
2971
3167
  if (!this._rangeMode() && this._value)
2972
3168
  calendar.value = this._value;
2973
3169
  if (panel)
2974
- panel.setAttribute("aria-label", this._messages.calendar);
3170
+ panel.setAttribute("aria-label", this.messages.calendar);
2975
3171
  }
2976
3172
  _refreshLocale() {
2977
3173
  const hint = this._formatHint;
@@ -2980,7 +3176,7 @@
2980
3176
  if (this._rangeMode() && this._fields) {
2981
3177
  this._fields[0].setLocale(this.locale);
2982
3178
  this._fields[1].setLocale(this.locale);
2983
- hint.textContent = `${this._messages.formatHint}: ${this._fields[0].placeholder}`;
3179
+ hint.textContent = `${this.messages.formatHint}: ${this._fields[0].placeholder}`;
2984
3180
  for (const [index, field] of this._fields.entries()) {
2985
3181
  if (!field.input.hasAttribute("placeholder") || this._generatedPlaceholder) {
2986
3182
  field.input.placeholder = field.placeholder;
@@ -2997,7 +3193,7 @@
2997
3193
  if (!field)
2998
3194
  return;
2999
3195
  field.setLocale(this.locale);
3000
- hint.textContent = `${this._messages.formatHint}: ${field.placeholder}`;
3196
+ hint.textContent = `${this.messages.formatHint}: ${field.placeholder}`;
3001
3197
  if (!field.input.hasAttribute("placeholder") || this._generatedPlaceholder) {
3002
3198
  field.input.placeholder = field.placeholder;
3003
3199
  this._generatedPlaceholder = true;
@@ -3014,7 +3210,7 @@
3014
3210
  this._refreshRangeButtonLabel();
3015
3211
  return;
3016
3212
  }
3017
- const prefix = this._value ? this._messages.changeDate : this._messages.chooseDate;
3213
+ const prefix = this._value ? this.messages.changeDate : this.messages.chooseDate;
3018
3214
  const suffix = this._value ? `, ${formatLongDate(this._value, this.locale)}` : "";
3019
3215
  single.setAttribute("aria-label", `${prefix}${suffix}`);
3020
3216
  }
@@ -3024,9 +3220,9 @@
3024
3220
  const { start, end } = this._range;
3025
3221
  for (const { button, endpoint } of this._buttons) {
3026
3222
  const bound = endpoint === "end" ? end : start;
3027
- const prefix = bound ? this._messages.changeDate : this._messages.chooseDate;
3223
+ const prefix = bound ? this.messages.changeDate : this.messages.chooseDate;
3028
3224
  const suffix = bound ? `, ${formatLongDate(bound, this.locale)}` : "";
3029
- const boundLabel = endpoint === "end" ? this._messages.rangeEnd : this._messages.rangeStart;
3225
+ const boundLabel = endpoint === "end" ? this.messages.rangeEnd : this.messages.rangeStart;
3030
3226
  button.setAttribute("aria-label", `${prefix}${suffix}, ${boundLabel}`);
3031
3227
  }
3032
3228
  }
@@ -3073,7 +3269,7 @@
3073
3269
  const confirmed = await calendar.ensureDate(date);
3074
3270
  const state = calendar.getDateState(date);
3075
3271
  if (!confirmed || state.disabled)
3076
- return { ok: false, message: this._messages.unavailableDate };
3272
+ return { ok: false, message: this.messages.unavailableDate };
3077
3273
  return { ok: true };
3078
3274
  }
3079
3275
  async _commitText(emit, options = {}) {
@@ -3120,7 +3316,7 @@
3120
3316
  const field = fields[index];
3121
3317
  if (!field)
3122
3318
  return false;
3123
- if (options.user === true && (field.input.disabled || field.input.readOnly))
3319
+ if (options.user === true && !isEditable(field.input))
3124
3320
  return false;
3125
3321
  const next = value || "";
3126
3322
  const previous = field.canonical;
@@ -3189,7 +3385,7 @@
3189
3385
  for (const bound of bounds) {
3190
3386
  const index = bound === "end" ? 1 : 0;
3191
3387
  const input = this._fields[index].input;
3192
- const message = bound === "end" ? this._messages.rangeOrderEnd : this._messages.rangeOrderStart;
3388
+ const message = bound === "end" ? this.messages.rangeOrderEnd : this.messages.rangeOrderStart;
3193
3389
  const shouldHaveOrder = inverted && bound === which;
3194
3390
  if (shouldHaveOrder) {
3195
3391
  input.setCustomValidity(message);
@@ -3204,14 +3400,14 @@
3204
3400
  const [startInput, endInput] = this._timeFields;
3205
3401
  const bounds = ["start", "end"];
3206
3402
  const inputs = [startInput, endInput];
3207
- const startValue = startInput && !startInput.disabled ? startInput.value.trim() : "";
3208
- const endValue = endInput && !endInput.disabled ? endInput.value.trim() : "";
3403
+ const startValue = startInput && !startInput.matches(":disabled") ? startInput.value.trim() : "";
3404
+ const endValue = endInput && !endInput.matches(":disabled") ? endInput.value.trim() : "";
3209
3405
  const inverted = Boolean(startInput && endInput && startValue && endValue) && isTime(startValue) && isTime(endValue) && compareTimes(endValue, startValue) < 0;
3210
3406
  for (const [index, bound] of bounds.entries()) {
3211
3407
  const input = inputs[index];
3212
3408
  if (!input)
3213
3409
  continue;
3214
- const message = bound === "end" ? this._messages.rangeOrderEnd : this._messages.rangeOrderStart;
3410
+ const message = bound === "end" ? this.messages.rangeOrderEnd : this.messages.rangeOrderStart;
3215
3411
  const shouldHaveOrder = inverted && bound === which;
3216
3412
  if (shouldHaveOrder) {
3217
3413
  input.setCustomValidity(message);
@@ -3284,12 +3480,12 @@
3284
3480
  return "";
3285
3481
  const preferred = this._lastFocusEndpoint || "start";
3286
3482
  const preferredIndex = preferred === "end" ? 1 : 0;
3287
- if (!this._fields[preferredIndex].input.disabled && !this._fields[preferredIndex].input.readOnly) {
3483
+ if (isEditable(this._fields[preferredIndex].input)) {
3288
3484
  return preferred;
3289
3485
  }
3290
3486
  const alternative = preferred === "end" ? "start" : "end";
3291
3487
  const alternativeIndex = alternative === "end" ? 1 : 0;
3292
- if (!this._fields[alternativeIndex].input.disabled && !this._fields[alternativeIndex].input.readOnly) {
3488
+ if (isEditable(this._fields[alternativeIndex].input)) {
3293
3489
  return alternative;
3294
3490
  }
3295
3491
  return "";
@@ -3301,6 +3497,8 @@
3301
3497
  return true;
3302
3498
  for (const [index, field] of fields.entries()) {
3303
3499
  const result = await field.commit({ moveDisplay: false });
3500
+ if (result.status === "invalid")
3501
+ this._orderTags.delete(index);
3304
3502
  if (result.status === "ok" || result.status === "clear") {
3305
3503
  const which = index === 1 ? "end" : "start";
3306
3504
  this._setBound(which, result.value || "", { format: result.status === "ok" });
@@ -3313,11 +3511,7 @@
3313
3511
  const input = this._field?.input;
3314
3512
  if (!input)
3315
3513
  return true;
3316
- if (!input.value.trim()) {
3317
- input.setCustomValidity("");
3318
- } else {
3319
- await this._commitText(false, { force: true, moveDisplay: false });
3320
- }
3514
+ await this._commitText(false, { force: true, moveDisplay: false });
3321
3515
  this._revalidateTimeOrder("start");
3322
3516
  this._revalidateTimeOrder("end");
3323
3517
  const timesValid = this._timeFields.every((timeInput) => timeInput?.checkValidity() ?? true);
@@ -3335,7 +3529,7 @@
3335
3529
  this._activateRangeEndpoint(endpoint);
3336
3530
  } else {
3337
3531
  const input = this._input;
3338
- if (!input || input.disabled || input.readOnly)
3532
+ if (!input || !isEditable(input))
3339
3533
  return;
3340
3534
  const target = this._value || this._adapter().parse(input.value) || todayISO();
3341
3535
  calendar.display = monthKey(target);
@@ -3377,8 +3571,7 @@
3377
3571
  } catch {}
3378
3572
  this._open = false;
3379
3573
  this._setExpanded(false);
3380
- this._rangeCommitId++;
3381
- this._pendingIntents.clear();
3574
+ this._supersedeActivations();
3382
3575
  this._suppressGridClick = false;
3383
3576
  if (this._drag) {
3384
3577
  this._releaseDragCapture(this._drag.pointerId);
@@ -3401,7 +3594,7 @@
3401
3594
  if (active) {
3402
3595
  const index = active === "end" ? 1 : 0;
3403
3596
  const field = this._fields?.[index];
3404
- if (field && !field.input.disabled && !field.input.readOnly)
3597
+ if (field && isEditable(field.input))
3405
3598
  return active;
3406
3599
  }
3407
3600
  return this._resolveRangeEndpoint();