tyrell-components 1.0.0-TC57 → 1.0.0-TC58

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.
Files changed (200) hide show
  1. package/dist/tyrell.js +1 -1
  2. package/dist/tyrell.js.map +1 -0
  3. package/lib/base/ty-component.d.ts +12 -3
  4. package/lib/base/ty-component.js +21 -17
  5. package/lib/components/button.d.ts +4 -0
  6. package/lib/components/button.js +33 -6
  7. package/lib/components/calendar-month.js +3 -10
  8. package/lib/components/calendar.d.ts +3 -1
  9. package/lib/components/calendar.js +19 -34
  10. package/lib/components/checkbox.d.ts +1 -1
  11. package/lib/components/checkbox.js +2 -1
  12. package/lib/components/date-picker.d.ts +60 -8
  13. package/lib/components/date-picker.js +268 -134
  14. package/lib/components/file-upload.d.ts +2 -1
  15. package/lib/components/file-upload.js +2 -1
  16. package/lib/components/input.d.ts +13 -6
  17. package/lib/components/input.js +58 -60
  18. package/lib/components/modal.js +37 -1
  19. package/lib/components/option.js +2 -5
  20. package/lib/components/popup.js +43 -6
  21. package/lib/components/radio.d.ts +2 -1
  22. package/lib/components/radio.js +2 -1
  23. package/lib/components/resize-observer.js +2 -0
  24. package/lib/components/scroll-container.js +2 -0
  25. package/lib/components/select.d.ts +32 -3
  26. package/lib/components/select.js +123 -11
  27. package/lib/components/switch.d.ts +3 -1
  28. package/lib/components/switch.js +2 -1
  29. package/lib/components/textarea.d.ts +44 -4
  30. package/lib/components/textarea.js +54 -10
  31. package/lib/styles/button.js +20 -19
  32. package/lib/styles/date-picker.js +17 -0
  33. package/lib/styles/select-base.js +26 -0
  34. package/lib/styles/textarea.d.ts +1 -1
  35. package/lib/styles/textarea.js +7 -0
  36. package/lib/utils/error-flavor.d.ts +8 -0
  37. package/lib/utils/error-flavor.js +1 -0
  38. package/lib/utils/upgrade-properties.d.ts +21 -0
  39. package/lib/utils/upgrade-properties.js +47 -0
  40. package/lib/utils/validity.d.ts +63 -5
  41. package/lib/utils/validity.js +145 -24
  42. package/lib/version.d.ts +1 -1
  43. package/lib/version.js +1 -1
  44. package/package.json +2 -1
  45. package/lib/base/ty-component.d.ts.map +0 -1
  46. package/lib/base/ty-component.js.map +0 -1
  47. package/lib/components/button.d.ts.map +0 -1
  48. package/lib/components/button.js.map +0 -1
  49. package/lib/components/calendar-month.d.ts.map +0 -1
  50. package/lib/components/calendar-month.js.map +0 -1
  51. package/lib/components/calendar-navigation.d.ts.map +0 -1
  52. package/lib/components/calendar-navigation.js.map +0 -1
  53. package/lib/components/calendar.d.ts.map +0 -1
  54. package/lib/components/calendar.js.map +0 -1
  55. package/lib/components/checkbox.d.ts.map +0 -1
  56. package/lib/components/checkbox.js.map +0 -1
  57. package/lib/components/copy.d.ts.map +0 -1
  58. package/lib/components/copy.js.map +0 -1
  59. package/lib/components/date-picker.d.ts.map +0 -1
  60. package/lib/components/date-picker.js.map +0 -1
  61. package/lib/components/dropdown.d.ts +0 -403
  62. package/lib/components/dropdown.d.ts.map +0 -1
  63. package/lib/components/dropdown.js +0 -1315
  64. package/lib/components/dropdown.js.map +0 -1
  65. package/lib/components/icon.d.ts.map +0 -1
  66. package/lib/components/icon.js.map +0 -1
  67. package/lib/components/input.d.ts.map +0 -1
  68. package/lib/components/input.js.map +0 -1
  69. package/lib/components/modal.d.ts.map +0 -1
  70. package/lib/components/modal.js.map +0 -1
  71. package/lib/components/multiselect.d.ts +0 -389
  72. package/lib/components/multiselect.d.ts.map +0 -1
  73. package/lib/components/multiselect.js +0 -1301
  74. package/lib/components/multiselect.js.map +0 -1
  75. package/lib/components/option.d.ts.map +0 -1
  76. package/lib/components/option.js.map +0 -1
  77. package/lib/components/popup.d.ts.map +0 -1
  78. package/lib/components/popup.js.map +0 -1
  79. package/lib/components/resize-observer.d.ts.map +0 -1
  80. package/lib/components/resize-observer.js.map +0 -1
  81. package/lib/components/scroll-container.d.ts.map +0 -1
  82. package/lib/components/scroll-container.js.map +0 -1
  83. package/lib/components/step.d.ts.map +0 -1
  84. package/lib/components/step.js.map +0 -1
  85. package/lib/components/tab.d.ts.map +0 -1
  86. package/lib/components/tab.js.map +0 -1
  87. package/lib/components/tabs.d.ts.map +0 -1
  88. package/lib/components/tabs.js.map +0 -1
  89. package/lib/components/tag.d.ts.map +0 -1
  90. package/lib/components/tag.js.map +0 -1
  91. package/lib/components/textarea.d.ts.map +0 -1
  92. package/lib/components/textarea.js.map +0 -1
  93. package/lib/components/tooltip.d.ts.map +0 -1
  94. package/lib/components/tooltip.js.map +0 -1
  95. package/lib/components/wizard.d.ts.map +0 -1
  96. package/lib/components/wizard.js.map +0 -1
  97. package/lib/icons/fontawesome/brands.d.ts.map +0 -1
  98. package/lib/icons/fontawesome/brands.js.map +0 -1
  99. package/lib/icons/fontawesome/regular.d.ts.map +0 -1
  100. package/lib/icons/fontawesome/regular.js.map +0 -1
  101. package/lib/icons/fontawesome/solid.d.ts.map +0 -1
  102. package/lib/icons/fontawesome/solid.js.map +0 -1
  103. package/lib/icons/heroicons/micro.d.ts.map +0 -1
  104. package/lib/icons/heroicons/micro.js.map +0 -1
  105. package/lib/icons/heroicons/mini.d.ts.map +0 -1
  106. package/lib/icons/heroicons/mini.js.map +0 -1
  107. package/lib/icons/heroicons/outline.d.ts.map +0 -1
  108. package/lib/icons/heroicons/outline.js.map +0 -1
  109. package/lib/icons/heroicons/solid.d.ts.map +0 -1
  110. package/lib/icons/heroicons/solid.js.map +0 -1
  111. package/lib/icons/lucide.d.ts.map +0 -1
  112. package/lib/icons/lucide.js.map +0 -1
  113. package/lib/icons/material/filled.d.ts.map +0 -1
  114. package/lib/icons/material/filled.js.map +0 -1
  115. package/lib/icons/material/outlined.d.ts.map +0 -1
  116. package/lib/icons/material/outlined.js.map +0 -1
  117. package/lib/icons/material/round.d.ts.map +0 -1
  118. package/lib/icons/material/round.js.map +0 -1
  119. package/lib/icons/material/sharp.d.ts.map +0 -1
  120. package/lib/icons/material/sharp.js.map +0 -1
  121. package/lib/icons/material/two-tone.d.ts.map +0 -1
  122. package/lib/icons/material/two-tone.js.map +0 -1
  123. package/lib/index.d.ts.map +0 -1
  124. package/lib/index.js.map +0 -1
  125. package/lib/styles/button.d.ts.map +0 -1
  126. package/lib/styles/button.js.map +0 -1
  127. package/lib/styles/calendar-month.d.ts.map +0 -1
  128. package/lib/styles/calendar-month.js.map +0 -1
  129. package/lib/styles/calendar-navigation.d.ts.map +0 -1
  130. package/lib/styles/calendar-navigation.js.map +0 -1
  131. package/lib/styles/calendar.d.ts.map +0 -1
  132. package/lib/styles/calendar.js.map +0 -1
  133. package/lib/styles/copy.d.ts.map +0 -1
  134. package/lib/styles/copy.js.map +0 -1
  135. package/lib/styles/date-picker.d.ts.map +0 -1
  136. package/lib/styles/date-picker.js.map +0 -1
  137. package/lib/styles/dropdown.d.ts +0 -12
  138. package/lib/styles/dropdown.d.ts.map +0 -1
  139. package/lib/styles/dropdown.js +0 -725
  140. package/lib/styles/dropdown.js.map +0 -1
  141. package/lib/styles/icon.d.ts.map +0 -1
  142. package/lib/styles/icon.js.map +0 -1
  143. package/lib/styles/input.d.ts.map +0 -1
  144. package/lib/styles/input.js.map +0 -1
  145. package/lib/styles/modal.d.ts.map +0 -1
  146. package/lib/styles/modal.js.map +0 -1
  147. package/lib/styles/multiselect.d.ts +0 -6
  148. package/lib/styles/multiselect.d.ts.map +0 -1
  149. package/lib/styles/multiselect.js +0 -568
  150. package/lib/styles/multiselect.js.map +0 -1
  151. package/lib/styles/option.d.ts.map +0 -1
  152. package/lib/styles/option.js.map +0 -1
  153. package/lib/styles/popup.d.ts.map +0 -1
  154. package/lib/styles/popup.js.map +0 -1
  155. package/lib/styles/resize-observer.d.ts.map +0 -1
  156. package/lib/styles/resize-observer.js.map +0 -1
  157. package/lib/styles/scroll-container.d.ts.map +0 -1
  158. package/lib/styles/scroll-container.js.map +0 -1
  159. package/lib/styles/step.d.ts.map +0 -1
  160. package/lib/styles/step.js.map +0 -1
  161. package/lib/styles/tab.d.ts.map +0 -1
  162. package/lib/styles/tab.js.map +0 -1
  163. package/lib/styles/tabs.d.ts.map +0 -1
  164. package/lib/styles/tabs.js.map +0 -1
  165. package/lib/styles/tag.d.ts.map +0 -1
  166. package/lib/styles/tag.js.map +0 -1
  167. package/lib/styles/textarea.d.ts.map +0 -1
  168. package/lib/styles/textarea.js.map +0 -1
  169. package/lib/styles/tooltip.d.ts.map +0 -1
  170. package/lib/styles/tooltip.js.map +0 -1
  171. package/lib/styles/wizard.d.ts.map +0 -1
  172. package/lib/styles/wizard.js.map +0 -1
  173. package/lib/types/common.d.ts.map +0 -1
  174. package/lib/types/common.js.map +0 -1
  175. package/lib/utils/calendar-utils.d.ts.map +0 -1
  176. package/lib/utils/calendar-utils.js.map +0 -1
  177. package/lib/utils/icon-registry.d.ts.map +0 -1
  178. package/lib/utils/icon-registry.js.map +0 -1
  179. package/lib/utils/locale.d.ts.map +0 -1
  180. package/lib/utils/locale.js.map +0 -1
  181. package/lib/utils/number-format.d.ts.map +0 -1
  182. package/lib/utils/number-format.js.map +0 -1
  183. package/lib/utils/parse-boolean.d.ts +0 -39
  184. package/lib/utils/parse-boolean.d.ts.map +0 -1
  185. package/lib/utils/parse-boolean.js +0 -23
  186. package/lib/utils/parse-boolean.js.map +0 -1
  187. package/lib/utils/positioning.d.ts.map +0 -1
  188. package/lib/utils/positioning.js.map +0 -1
  189. package/lib/utils/property-capture.d.ts.map +0 -1
  190. package/lib/utils/property-capture.js.map +0 -1
  191. package/lib/utils/property-manager.d.ts.map +0 -1
  192. package/lib/utils/property-manager.js.map +0 -1
  193. package/lib/utils/resize-observer.d.ts.map +0 -1
  194. package/lib/utils/resize-observer.js.map +0 -1
  195. package/lib/utils/scroll-lock.d.ts.map +0 -1
  196. package/lib/utils/scroll-lock.js.map +0 -1
  197. package/lib/utils/styles.d.ts.map +0 -1
  198. package/lib/utils/styles.js.map +0 -1
  199. package/lib/version.d.ts.map +0 -1
  200. package/lib/version.js.map +0 -1
@@ -1,5 +1,6 @@
1
1
  import { ensureStyles } from '../utils/styles.js';
2
2
  import { syncCustomFlavorSheet } from '../utils/flavor-sheet.js';
3
+ import { ERROR_FLAVOR } from '../utils/error-flavor.js';
3
4
  import { datePickerStyles, datePickerCustomFlavorCss } from '../styles/date-picker.js';
4
5
  import { lockScroll, unlockScroll } from '../utils/scroll-lock.js';
5
6
  import { computeAnchoredPosition, placementToAnchored } from '../utils/positioning.js';
@@ -7,6 +8,7 @@ import { getEffectiveLocale, observeLocaleChanges } from '../utils/locale.js';
7
8
  import { isMobileTouch } from '../utils/mobile.js';
8
9
  import { TyComponent } from '../base/ty-component.js';
9
10
  import { DEFAULT_SIZE } from '../types/common.js';
11
+ import { nativeMessage } from '../utils/validity.js';
10
12
  const CALENDAR_ICON_SVG = `<svg stroke='currentColor' fill='none' stroke-width='2' viewBox='0 0 24 24' width='16' height='16' xmlns='http://www.w3.org/2000/svg'><rect x='3' y='4' width='18' height='18' rx='2' ry='2'></rect><line x1='16' y1='2' x2='16' y2='6'></line><line x1='8' y1='2' x2='8' y2='6'></line><line x1='3' y1='10' x2='21' y2='10'></line></svg>`;
11
13
  const CLEAR_ICON_SVG = `<svg stroke='currentColor' fill='none' stroke-width='2' viewBox='0 0 24 24' width='14' height='14' xmlns='http://www.w3.org/2000/svg'><line x1='18' y1='6' x2='6' y2='18'></line><line x1='6' y1='6' x2='18' y2='18'></line></svg>`;
12
14
  const SCHEDULE_ICON_SVG = `<svg stroke='currentColor' fill='none' stroke-width='2' viewBox='0 0 24 24' width='16' height='16' xmlns='http://www.w3.org/2000/svg'><circle cx='12' cy='12' r='10'></circle><polyline points='12,6 12,12 16,14'></polyline></svg>`;
@@ -20,48 +22,63 @@ elementIds.set(element, id);
20
22
  }
21
23
  return id;
22
24
  }
23
- function parseValue(value, withTime) {
24
- if (!value)
25
+ const LOCAL_VALUE = /^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.\d+)?)?)?$/;
26
+ function wallClockDate(c) {
27
+ const date = new Date(0);
28
+ date.setUTCFullYear(c.year, c.month - 1, c.day);
29
+ date.setUTCHours(c.hour || 0, c.minute || 0, c.second || 0);
30
+ return date;
31
+ }
32
+ function parseValue(value) {
33
+ if (value === null || value === undefined || value === '')
25
34
  return null;
26
- let dateObj = null;
27
35
  if (typeof value === 'string') {
28
- if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
29
- const [yearStr, monthStr, dayStr] = value.split('-');
30
- const year = parseInt(yearStr, 10);
31
- const month = parseInt(monthStr, 10) - 1;
32
- const day = parseInt(dayStr, 10);
33
- dateObj = new Date(year, month, day, 0, 0, 0, 0);
34
- }
35
- else {
36
- dateObj = new Date(value);
37
- }
36
+ const local = LOCAL_VALUE.exec(value);
37
+ if (local) {
38
+ const [year, month, day, hour, minute, second] = local.slice(1, 7).map((g) => Number(g ?? 0));
39
+ const components = { year, month, day, hour, minute, second };
40
+ const check = wallClockDate(components);
41
+ const real = check.getUTCMonth() === month - 1 && check.getUTCDate() === day &&
42
+ hour < 24 && minute < 60 && second < 60;
43
+ return real ? components : null;
38
44
  }
39
- else if (typeof value === 'number') {
40
- dateObj = new Date(value);
41
45
  }
46
+ const dateObj = typeof value === 'number' || typeof value === 'string' ? new Date(value) : null;
42
47
  if (!dateObj || isNaN(dateObj.getTime())) {
43
48
  return null;
44
49
  }
45
- const components = {
50
+ return {
46
51
  year: dateObj.getFullYear(),
47
52
  month: dateObj.getMonth() + 1,
48
53
  day: dateObj.getDate(),
49
- hour: withTime ? dateObj.getHours() : 0,
50
- minute: withTime ? dateObj.getMinutes() : 0,
54
+ hour: dateObj.getHours(),
55
+ minute: dateObj.getMinutes(),
56
+ second: dateObj.getSeconds(),
57
+ epoch: dateObj.getTime(),
51
58
  };
52
- return components;
53
59
  }
54
60
  function componentsToDateObject(components) {
55
- const { year, month, day, hour, minute } = components;
61
+ const { year, month, day, hour, minute, second } = components;
56
62
  if (!year || !month || !day)
57
63
  return null;
58
- return new Date(year, month - 1, day, hour || 0, minute || 0, 0, 0);
64
+ const date = new Date(2000, 0, 1, 12);
65
+ date.setFullYear(year, month - 1, day);
66
+ date.setHours(hour || 0, minute || 0, second || 0, 0);
67
+ return date;
68
+ }
69
+ function effectiveComponents(components, withTime) {
70
+ return withTime ? components : { ...components, hour: 0, minute: 0, second: 0, epoch: undefined };
59
71
  }
60
- function componentsToOutputValue(components) {
72
+ function componentsToOutputValue(components, withTime, native) {
61
73
  if (!components.year || !components.month || !components.day) {
62
74
  return null;
63
75
  }
64
- const dateObj = componentsToDateObject(components);
76
+ const c = effectiveComponents(components, withTime);
77
+ if (native)
78
+ return componentsToLocalValue(c, withTime);
79
+ if (c.epoch !== undefined)
80
+ return new Date(c.epoch).toISOString();
81
+ const dateObj = componentsToDateObject(c);
65
82
  return dateObj ? dateObj.toISOString() : null;
66
83
  }
67
84
  function componentsToLocalValue(components, withTime) {
@@ -74,27 +91,34 @@ const day = components.day.toString().padStart(2, '0');
74
91
  if (withTime) {
75
92
  const hour = (components.hour || 0).toString().padStart(2, '0');
76
93
  const minute = (components.minute || 0).toString().padStart(2, '0');
77
- return `${year}-${month}-${day}T${hour}:${minute}`;
94
+ const second = components.second ? `:${components.second.toString().padStart(2, '0')}` : '';
95
+ return `${year}-${month}-${day}T${hour}:${minute}${second}`;
78
96
  }
79
97
  else {
80
98
  return `${year}-${month}-${day}`;
81
99
  }
82
100
  }
83
- function formatDisplayValue(components, formatType, locale, withTime) {
101
+ function displayDate(components, native) {
102
+ return native
103
+ ? { date: wallClockDate(components), timeZone: 'UTC' }
104
+ : { date: componentsToDateObject(components) };
105
+ }
106
+ function formatDisplayValue(components, formatType, locale, withTime, native) {
84
107
  if (!components.year || !components.month || !components.day) {
85
108
  return null;
86
109
  }
87
- const dateObj = componentsToDateObject(components);
88
- if (!dateObj)
110
+ const { date, timeZone } = displayDate(components, native);
111
+ if (!date)
89
112
  return null;
90
113
  const options = {
91
114
  dateStyle: formatType || 'long',
115
+ timeZone,
92
116
  };
93
117
  if (withTime) {
94
118
  options.timeStyle = 'short';
95
119
  }
96
120
  const formatter = new Intl.DateTimeFormat(locale, options);
97
- return formatter.format(dateObj);
121
+ return formatter.format(date);
98
122
  }
99
123
  class TimeFields {
100
124
  constructor(hourEl, minuteEl, onChange, hour = 0, minute = 0) {
@@ -213,12 +237,18 @@ withTime: false,
213
237
  open: false,
214
238
  };
215
239
  this._closeTimestamp = 0;
240
+ this._initialized = false;
241
+ this._syncing = false;
216
242
  this._customFlavorSheet = null;
217
243
  const shadow = this.shadowRoot;
218
244
  ensureStyles(shadow, { css: datePickerStyles, id: 'ty-date-picker' });
219
245
  }
220
246
  onConnect() {
247
+ if (this._initialized)
248
+ this._state.open = false;
249
+ else
221
250
  this.initializeState();
251
+ this.syncErrorFlavor();
222
252
  this.render();
223
253
  this.updateFormValue();
224
254
  this._localeObserver = observeLocaleChanges(this, () => {
@@ -241,6 +271,10 @@ onPropertiesChanged(changes) {
241
271
  for (const { name, newValue } of changes) {
242
272
  switch (name) {
243
273
  case 'flavor':
274
+ if (newValue !== 'danger') {
275
+ this.removeAttribute(ERROR_FLAVOR);
276
+ this.syncErrorFlavor();
277
+ }
244
278
  this._syncCustomFlavor();
245
279
  break;
246
280
  case 'size':
@@ -254,70 +288,59 @@ case 'noClear':
254
288
  case 'format':
255
289
  case 'locale':
256
290
  break;
291
+ case 'error':
292
+ this.syncErrorFlavor();
293
+ break;
257
294
  case 'value': {
258
- const newComponents = parseValue(newValue, this._state.withTime);
259
- if (newComponents === null) {
260
- const hasDate = this._state.year !== undefined ||
261
- this._state.month !== undefined ||
262
- this._state.day !== undefined;
263
- if (hasDate) {
295
+ if (!this._initialized || this._syncing)
296
+ break;
264
297
  this._state = {
265
298
  withTime: this._state.withTime,
266
299
  open: this._state.open,
300
+ ...parseValue(newValue),
267
301
  };
268
- }
269
302
  this.syncFormValue();
270
303
  break;
271
304
  }
272
- const currentComponents = {
273
- year: this._state.year,
274
- month: this._state.month,
275
- day: this._state.day,
276
- hour: this._state.hour,
277
- minute: this._state.minute,
278
- };
279
- const changed = newComponents?.year !== currentComponents.year ||
280
- newComponents?.month !== currentComponents.month ||
281
- newComponents?.day !== currentComponents.day ||
282
- newComponents?.hour !== currentComponents.hour ||
283
- newComponents?.minute !== currentComponents.minute;
284
- if (changed) {
285
- this._state = {
286
- ...this._state,
287
- ...newComponents,
288
- };
289
- this.syncFormValue();
290
- }
291
- break;
292
- }
293
305
  case 'with-time': {
306
+ if (!this._initialized)
307
+ break;
294
308
  const newWithTime = newValue;
295
- const oldWithTime = this._state.withTime;
296
- if (newWithTime !== oldWithTime) {
309
+ if (newWithTime !== this._state.withTime) {
297
310
  this._state.withTime = newWithTime;
298
- const hasDate = this._state.year !== undefined &&
299
- this._state.month !== undefined &&
300
- this._state.day !== undefined;
301
- if (hasDate) {
302
311
  this.syncFormValue();
303
- }
312
+ this.updateValidity();
304
313
  }
305
314
  break;
306
315
  }
316
+ case 'native':
317
+ if (this._initialized)
318
+ this.syncFormValue();
319
+ break;
307
320
  }
308
321
  }
309
322
  }
310
323
  getFormValue() {
311
- return componentsToOutputValue(this.getComponents());
324
+ return this.outputValue(this.getComponents());
325
+ }
326
+ outputValue(components) {
327
+ return componentsToOutputValue(components, this._state.withTime, !!this.getProperty('native'));
312
328
  }
313
329
  updateFormValue() {
314
330
  this._internals.setFormValue(this.getFormValue() ?? '');
315
331
  this.updateValidity();
316
332
  }
333
+ focusTarget() {
334
+ return this.shadowRoot?.querySelector('.native-date-input')
335
+ ?? this.shadowRoot?.querySelector('.date-picker-stub')
336
+ ?? null;
337
+ }
317
338
  focus(options) {
318
- const stub = this.shadowRoot?.querySelector('.date-picker-stub');
319
- if (stub)
320
- stub.focus(options);
339
+ if (this.getProperty('disabled'))
340
+ return;
341
+ const target = this.focusTarget();
342
+ if (target)
343
+ target.focus(options);
321
344
  else
322
345
  super.focus(options);
323
346
  }
@@ -326,8 +349,8 @@ if (this.getProperty('disabled') || !this.getProperty('required') || this.getFor
326
349
  this.applyValidity({});
327
350
  return;
328
351
  }
329
- const anchor = this.shadowRoot?.querySelector('.date-picker-stub, .native-date-input');
330
- this.applyValidity({ valueMissing: true }, 'Please select a date', anchor ?? undefined);
352
+ const type = this._state.withTime ? 'datetime-local' : 'date';
353
+ this.applyValidity({ valueMissing: true }, nativeMessage('input', { type, required: true }), this.focusTarget() ?? undefined);
331
354
  }
332
355
  getComponents() {
333
356
  return {
@@ -336,6 +359,8 @@ month: this._state.month,
336
359
  day: this._state.day,
337
360
  hour: this._state.hour,
338
361
  minute: this._state.minute,
362
+ second: this._state.second,
363
+ epoch: this._state.epoch,
339
364
  };
340
365
  }
341
366
  hasDate() {
@@ -345,25 +370,15 @@ getFormattedValue(components) {
345
370
  const c = components || this.getComponents();
346
371
  if (!c.year || !c.month || !c.day)
347
372
  return null;
348
- return formatDisplayValue(c, this.getProperty('format') || 'long', getEffectiveLocale(this, this.getProperty('locale')), this._state.withTime);
373
+ return formatDisplayValue(c, this.getProperty('format') || 'long', getEffectiveLocale(this, this.getProperty('locale')), this._state.withTime, !!this.getProperty('native'));
349
374
  }
350
375
  initializeState() {
351
- const valueAttr = this.getProperty('value');
352
- const withTime = this.getProperty('with-time');
353
- const components = parseValue(valueAttr, withTime);
354
- if (components === null) {
355
- this._state = {
356
- withTime,
357
- open: false,
358
- };
359
- }
360
- else {
361
376
  this._state = {
362
- ...components,
363
- withTime,
377
+ ...parseValue(this.getProperty('value')),
378
+ withTime: this.getProperty('with-time'),
364
379
  open: false,
365
380
  };
366
- }
381
+ this._initialized = true;
367
382
  this.syncFormValue();
368
383
  }
369
384
  updateState(updates, forceSync = false) {
@@ -374,24 +389,47 @@ this.syncFormValue();
374
389
  }
375
390
  }
376
391
  syncFormValue() {
377
- const utcValue = this.getFormValue();
392
+ const formValue = this.getFormValue();
378
393
  const currentValue = this.getProperty('value');
379
- if (utcValue !== currentValue) {
380
- if (utcValue) {
381
- this.setProperty('value', utcValue);
394
+ if (formValue !== currentValue) {
395
+ this._syncing = true;
396
+ try {
397
+ this.setProperty('value', formValue || null);
398
+ }
399
+ finally {
400
+ this._syncing = false;
382
401
  }
383
- else {
384
- this.setProperty('value', null);
385
402
  }
386
403
  }
404
+ syncErrorFlavor() {
405
+ if (!this._connected)
406
+ return;
407
+ const flavor = this.getProperty('flavor');
408
+ const replaced = this.getAttribute(ERROR_FLAVOR);
409
+ if (this.getProperty('error')) {
410
+ if (replaced === null && (flavor === 'default' || flavor === 'neutral')) {
411
+ this.setAttribute(ERROR_FLAVOR, this.getAttribute('flavor') ?? '');
412
+ this.setProperty('flavor', 'danger');
413
+ }
414
+ }
415
+ else if (replaced !== null) {
416
+ this.removeAttribute(ERROR_FLAVOR);
417
+ if (flavor !== 'danger')
418
+ return;
419
+ if (!replaced)
420
+ this.removeAttribute('flavor');
421
+ else
422
+ this.setProperty('flavor', replaced);
423
+ }
387
424
  }
388
425
  handleTimeInputChange(hour, minute) {
389
426
  if (!this.hasDate())
390
427
  return;
391
- const components = { ...this.getComponents(), hour, minute };
428
+ const components = { ...this.getComponents(), hour, minute, second: 0, epoch: undefined };
392
429
  this.updateState(components, true);
393
430
  this.emitChangeEvent(components, 'time-change');
394
431
  this.updateTimeSummary();
432
+ this.revealValidity();
395
433
  }
396
434
  updateTimeSummary(summary = this.shadowRoot?.querySelector('.time-summary')) {
397
435
  if (!summary)
@@ -405,19 +443,22 @@ iso.textContent = '';
405
443
  return;
406
444
  }
407
445
  const locale = getEffectiveLocale(this, this.getProperty('locale')) || undefined;
408
- const date = new Date(c.year, c.month - 1, c.day, c.hour ?? 0, c.minute ?? 0);
409
- const day = new Intl.DateTimeFormat(locale, { weekday: 'short', month: 'short', day: 'numeric' }).format(date);
410
- const time = new Intl.DateTimeFormat(locale, { hour: '2-digit', minute: '2-digit', hourCycle: 'h23' }).format(date);
446
+ const { date, timeZone } = displayDate(c, !!this.getProperty('native'));
447
+ const day = new Intl.DateTimeFormat(locale, { weekday: 'short', month: 'short', day: 'numeric', timeZone }).format(date);
448
+ const time = new Intl.DateTimeFormat(locale, { hour: '2-digit', minute: '2-digit', hourCycle: 'h23', timeZone }).format(date);
411
449
  text.textContent = `${day} · ${time}`;
412
450
  iso.textContent = componentsToLocalValue(c, true) ?? '';
413
451
  }
414
452
  emitChangeEvent(components, source) {
415
- const utcValue = components ? componentsToOutputValue(components) : null;
453
+ const value = components ? this.outputValue(components) : null;
416
454
  const localValue = components ? componentsToLocalValue(components, this._state.withTime) : null;
417
- const milliseconds = components ? componentsToDateObject(components)?.getTime() ?? null : null;
455
+ const effective = components && effectiveComponents(components, this._state.withTime);
456
+ const milliseconds = effective
457
+ ? effective.epoch ?? componentsToDateObject(effective)?.getTime() ?? null
458
+ : null;
418
459
  const formatted = components ? this.getFormattedValue(components) : null;
419
460
  const detail = {
420
- value: utcValue,
461
+ value,
421
462
  localValue,
422
463
  milliseconds,
423
464
  formatted,
@@ -435,7 +476,15 @@ set size(v) { this.setProperty('size', v); }
435
476
  get placement() { return this.getProperty('placement'); }
436
477
  set placement(v) { this.setProperty('placement', v); }
437
478
  get flavor() { return this.getProperty('flavor'); }
438
- set flavor(v) { this.setProperty('flavor', v); }
479
+ set flavor(v) {
480
+ this.removeAttribute(ERROR_FLAVOR);
481
+ this.setProperty('flavor', v);
482
+ }
483
+ attributeChangedCallback(name, oldValue, newValue) {
484
+ if (name === 'flavor' && newValue === 'danger' && oldValue === newValue)
485
+ this.removeAttribute(ERROR_FLAVOR);
486
+ super.attributeChangedCallback(name, oldValue, newValue);
487
+ }
439
488
  get label() { return this.getProperty('label'); }
440
489
  set label(v) { this.setProperty('label', v); }
441
490
  get placeholder() { return this.getProperty('placeholder'); }
@@ -458,16 +507,26 @@ get max() { return this.getProperty('max'); }
458
507
  set max(v) { this.setProperty('max', v ?? ''); }
459
508
  get withTime() { return this.getProperty('with-time'); }
460
509
  set withTime(v) { this.setProperty('with-time', v); }
510
+ get native() { return this.getProperty('native'); }
511
+ set native(v) { this.setProperty('native', v); }
512
+ get error() { return this.getProperty('error'); }
513
+ set error(v) { this.setProperty('error', v); }
461
514
  get value() {
515
+ if (this._initialized)
462
516
  return this.getProperty('value') || null;
517
+ const parsed = parseValue(this.getProperty('value'));
518
+ return parsed && componentsToOutputValue(parsed, !!this.getProperty('with-time'), !!this.getProperty('native'));
463
519
  }
464
520
  set value(val) {
465
521
  if (val === null || val === undefined || val === '') {
466
522
  this.setProperty('value', null);
467
523
  }
524
+ else if (typeof val === 'string') {
525
+ this.setProperty('value', val);
526
+ }
468
527
  else {
469
- const strValue = val instanceof Date ? val.toISOString() : val;
470
- this.setProperty('value', strValue);
528
+ const date = new Date(val);
529
+ this.setProperty('value', isNaN(date.getTime()) ? null : date.toISOString());
471
530
  }
472
531
  }
473
532
  clear() {
@@ -479,6 +538,8 @@ if (this.getProperty('disabled'))
479
538
  classes.push('disabled');
480
539
  if (this.getProperty('required'))
481
540
  classes.push('required');
541
+ if (this.getProperty('error'))
542
+ classes.push('error');
482
543
  if (this._state.open)
483
544
  classes.push('open');
484
545
  return classes.join(' ');
@@ -501,7 +562,7 @@ stub.setAttribute('aria-labelledby', `date-picker-label-${getElementHash(this)}`
501
562
  }
502
563
  stub.addEventListener('keydown', (e) => {
503
564
  const ke = e;
504
- if (this._state.open)
565
+ if (this._state.open || e.target !== stub)
505
566
  return;
506
567
  if (ke.key === 'Enter' || ke.key === ' ') {
507
568
  ke.preventDefault();
@@ -611,8 +672,8 @@ if (min)
611
672
  calendar.setAttribute('min', min);
612
673
  if (max)
613
674
  calendar.setAttribute('max', max);
614
- const flavor = this.getProperty('flavor');
615
- if (flavor && flavor !== 'default')
675
+ const flavor = this.calendarFlavor();
676
+ if (flavor)
616
677
  calendar.setAttribute('flavor', flavor);
617
678
  calendar.addEventListener('change', (e) => this.handleCalendarChange(e));
618
679
  contentWrapper.appendChild(calendar);
@@ -620,11 +681,17 @@ if (this._state.withTime) {
620
681
  contentWrapper.appendChild(this.renderTimeSection());
621
682
  }
622
683
  dialog.appendChild(contentWrapper);
623
- dialog.addEventListener('close', () => {
624
- this.updateState({ open: false });
625
- });
684
+ new MutationObserver(() => {
685
+ if (this._state.open && !dialog.open && this.isConnected)
686
+ this.closeDropdown();
687
+ }).observe(dialog, { attributes: true, attributeFilter: ['open'] });
626
688
  return dialog;
627
689
  }
690
+ calendarFlavor() {
691
+ const replaced = this.getAttribute(ERROR_FLAVOR);
692
+ const flavor = replaced ?? this.getProperty('flavor');
693
+ return flavor && flavor !== 'default' ? flavor : null;
694
+ }
628
695
  renderNativeInput() {
629
696
  const wrapper = document.createElement('div');
630
697
  wrapper.className = 'dropdown-wrapper';
@@ -636,21 +703,8 @@ stub.setAttribute('disabled', 'true');
636
703
  }
637
704
  const input = document.createElement('input');
638
705
  input.className = 'native-date-input';
639
- input.type = this._state.withTime ? 'datetime-local' : 'date';
640
- if (isDisabled)
641
- input.disabled = true;
642
- if (this.getProperty('required'))
643
- input.required = true;
644
- const minBound = this.getProperty('min');
645
- const maxBound = this.getProperty('max');
646
- if (minBound)
647
- input.min = this._state.withTime ? `${minBound}T00:00` : minBound;
648
- if (maxBound)
649
- input.max = this._state.withTime ? `${maxBound}T23:59` : maxBound;
650
- const localValue = componentsToLocalValue(this.getComponents(), this._state.withTime);
651
- if (localValue) {
652
- input.value = localValue;
653
- }
706
+ this.syncNativeInput(input);
707
+ const localValue = input.value;
654
708
  const placeholder = this.getProperty('placeholder') || 'Select date...';
655
709
  const formattedValue = this.getFormattedValue();
656
710
  const placeholderEl = document.createElement('span');
@@ -659,13 +713,17 @@ placeholderEl.textContent = formattedValue || placeholder;
659
713
  input.addEventListener('change', () => {
660
714
  if (!input.value) {
661
715
  this.clearValue();
662
- return;
663
716
  }
664
- const parsed = parseValue(input.value, this._state.withTime);
717
+ else {
718
+ const parsed = parseValue(input.value);
665
719
  if (parsed) {
666
- this.updateState(parsed, true);
667
- this.emitChangeEvent(parsed, 'selection');
720
+ const { hour, minute, second } = this._state.withTime ? parsed : this._state;
721
+ const components = { ...parsed, hour, minute, second, epoch: undefined };
722
+ this.updateState(components, true);
723
+ this.emitChangeEvent(components, 'selection');
668
724
  }
725
+ }
726
+ this.revealValidity();
669
727
  });
670
728
  const startSlot = document.createElement('slot');
671
729
  startSlot.name = 'start';
@@ -691,6 +749,23 @@ stub.appendChild(iconContainer);
691
749
  wrapper.appendChild(stub);
692
750
  return wrapper;
693
751
  }
752
+ syncNativeInput(input) {
753
+ const withTime = this._state.withTime;
754
+ input.type = withTime ? 'datetime-local' : 'date';
755
+ input.disabled = !!this.getProperty('disabled');
756
+ input.required = !!this.getProperty('required');
757
+ const min = this.getProperty('min');
758
+ const max = this.getProperty('max');
759
+ if (min)
760
+ input.min = withTime ? `${min}T00:00` : min;
761
+ else
762
+ input.removeAttribute('min');
763
+ if (max)
764
+ input.max = withTime ? `${max}T23:59` : max;
765
+ else
766
+ input.removeAttribute('max');
767
+ input.value = componentsToLocalValue(this.getComponents(), withTime) ?? '';
768
+ }
694
769
  calculateCalendarPosition() {
695
770
  if (!this.shadowRoot)
696
771
  return;
@@ -757,6 +832,8 @@ this.shadowRoot.innerHTML = '';
757
832
  const container = this.renderContainer();
758
833
  container.appendChild(this.renderNativeInput());
759
834
  this.shadowRoot.appendChild(container);
835
+ this.syncErrorLine();
836
+ this.updateValidity();
760
837
  return;
761
838
  }
762
839
  const existingDialog = this.shadowRoot.querySelector('.calendar-dialog');
@@ -766,6 +843,7 @@ this.updateDisplay();
766
843
  this.calculateCalendarPosition();
767
844
  return;
768
845
  }
846
+ const hadFocus = !!this.shadowRoot.activeElement;
769
847
  this.shadowRoot.innerHTML = '';
770
848
  const container = this.renderContainer();
771
849
  const wrapper = document.createElement('div');
@@ -774,7 +852,33 @@ wrapper.appendChild(this.renderStub());
774
852
  wrapper.appendChild(this.renderCalendarDropdown());
775
853
  container.appendChild(wrapper);
776
854
  this.shadowRoot.appendChild(container);
855
+ this.syncErrorLine();
777
856
  this.setupEventListeners();
857
+ this.updateValidity();
858
+ if (hadFocus)
859
+ this.focus({ preventScroll: true });
860
+ }
861
+ syncErrorLine() {
862
+ const container = this.shadowRoot?.querySelector('.dropdown-container');
863
+ if (!container)
864
+ return;
865
+ const error = this.getProperty('error');
866
+ const errorId = `date-picker-error-${getElementHash(this)}`;
867
+ let line = container.querySelector(':scope > .error-message');
868
+ if (error) {
869
+ if (!line) {
870
+ line = document.createElement('div');
871
+ line.className = 'error-message';
872
+ line.id = errorId;
873
+ container.appendChild(line);
874
+ }
875
+ line.textContent = error;
876
+ this.focusTarget()?.setAttribute('aria-describedby', errorId);
877
+ }
878
+ else {
879
+ line?.remove();
880
+ this.focusTarget()?.removeAttribute('aria-describedby');
881
+ }
778
882
  }
779
883
  updateDisplay() {
780
884
  if (!this.shadowRoot)
@@ -793,12 +897,15 @@ const clearButton = this.shadowRoot.querySelector('.stub-clear');
793
897
  if (clearButton) {
794
898
  clearButton.style.display = (clearable && hasValue && !isDisabled) ? '' : 'none';
795
899
  }
900
+ const stub = this.shadowRoot.querySelector('.date-picker-stub');
901
+ if (stub)
902
+ stub.className = this.buildStubClasses();
903
+ this.syncErrorLine();
796
904
  if (isMobileTouch()) {
797
905
  const nativeInput = this.shadowRoot.querySelector('.native-date-input');
798
- if (nativeInput) {
799
- const localValue = componentsToLocalValue(this.getComponents(), this._state.withTime);
800
- nativeInput.value = localValue || '';
801
- }
906
+ if (nativeInput)
907
+ this.syncNativeInput(nativeInput);
908
+ stub?.toggleAttribute('disabled', !!isDisabled);
802
909
  return;
803
910
  }
804
911
  const calendar = this.shadowRoot.querySelector('ty-calendar');
@@ -808,8 +915,8 @@ calendar.setAttribute('month', this._state.month.toString());
808
915
  calendar.setAttribute('day', this._state.day.toString());
809
916
  }
810
917
  if (calendar) {
811
- const flavor = this.getProperty('flavor');
812
- if (flavor && flavor !== 'default')
918
+ const flavor = this.calendarFlavor();
919
+ if (flavor)
813
920
  calendar.setAttribute('flavor', flavor);
814
921
  else
815
922
  calendar.removeAttribute('flavor');
@@ -851,7 +958,7 @@ event.preventDefault();
851
958
  const dialog = this.shadowRoot?.querySelector('.calendar-dialog');
852
959
  if (this._state.open && (!dialog || !dialog.open)) {
853
960
  this.updateState({ open: false }, true);
854
- unlockScroll(`date-picker-${this.id || this.toString()}`);
961
+ unlockScroll(`date-picker-${getElementHash(this)}`);
855
962
  }
856
963
  const disabled = this.getProperty('disabled');
857
964
  if (!disabled && Date.now() - this._closeTimestamp > 300) {
@@ -865,6 +972,8 @@ month: undefined,
865
972
  day: undefined,
866
973
  hour: undefined,
867
974
  minute: undefined,
975
+ second: undefined,
976
+ epoch: undefined,
868
977
  }, true);
869
978
  this.value = null;
870
979
  this.emitChangeEvent(null, 'clear');
@@ -874,6 +983,11 @@ handleClearClick(event) {
874
983
  event.preventDefault();
875
984
  event.stopPropagation();
876
985
  this.clearValue();
986
+ const active = document.activeElement;
987
+ if (this.matches(':focus-within') || !active || active === document.body) {
988
+ this.shadowRoot?.querySelector('.native-date-input')?.focus({ preventScroll: true });
989
+ }
990
+ this.revealValidity();
877
991
  }
878
992
  handleCalendarChange(event) {
879
993
  event.preventDefault();
@@ -889,9 +1003,12 @@ month: dayContext.month,
889
1003
  day: dayContext.dayInMonth,
890
1004
  hour: this._state.hour,
891
1005
  minute: this._state.minute,
1006
+ second: this._state.second,
1007
+ epoch: undefined,
892
1008
  };
893
1009
  this.updateState(newComponents, true);
894
1010
  this.emitChangeEvent(newComponents, 'selection');
1011
+ this.revealValidity();
895
1012
  if (!this._state.withTime) {
896
1013
  this.closeDropdown();
897
1014
  }
@@ -945,9 +1062,15 @@ this.render();
945
1062
  requestAnimationFrame(() => {
946
1063
  if (!this.shadowRoot)
947
1064
  return;
1065
+ if (!this._state.open || !this.isConnected)
1066
+ return;
1067
+ if (this.shadowRoot.querySelector('.date-picker-stub')?.checkVisibility() === false) {
1068
+ this.closeDropdown();
1069
+ return;
1070
+ }
948
1071
  const dialog = this.shadowRoot.querySelector('.calendar-dialog');
949
1072
  if (dialog) {
950
- const pickerId = `date-picker-${this.id || this.toString()}`;
1073
+ const pickerId = `date-picker-${getElementHash(this)}`;
951
1074
  lockScroll(pickerId);
952
1075
  dialog.showModal();
953
1076
  this.calculateCalendarPosition();
@@ -966,7 +1089,7 @@ closeDropdown() {
966
1089
  if (!this._state.open)
967
1090
  return;
968
1091
  this._closeTimestamp = Date.now();
969
- const pickerId = `date-picker-${this.id || this.toString()}`;
1092
+ const pickerId = `date-picker-${getElementHash(this)}`;
970
1093
  this.updateState({ open: false }, true);
971
1094
  unlockScroll(pickerId);
972
1095
  if (this.shadowRoot) {
@@ -981,6 +1104,7 @@ stubEl.setAttribute('aria-expanded', 'false');
981
1104
  }
982
1105
  this.dispatchEvent(new CustomEvent('close'));
983
1106
  this.render();
1107
+ this.focus();
984
1108
  }
985
1109
  cleanup() {
986
1110
  if (this._clickListener) {
@@ -996,7 +1120,7 @@ dialog.removeEventListener('click', this._dialogClickListener);
996
1120
  }
997
1121
  }
998
1122
  if (this._state.open) {
999
- const pickerId = `date-picker-${this.id || this.toString()}`;
1123
+ const pickerId = `date-picker-${getElementHash(this)}`;
1000
1124
  unlockScroll(pickerId);
1001
1125
  }
1002
1126
  this._clickListener = undefined;
@@ -1069,6 +1193,11 @@ type: 'boolean',
1069
1193
  visual: true,
1070
1194
  default: false
1071
1195
  },
1196
+ error: {
1197
+ type: 'string',
1198
+ visual: true,
1199
+ default: ''
1200
+ },
1072
1201
  format: {
1073
1202
  type: 'string',
1074
1203
  visual: true,
@@ -1099,6 +1228,11 @@ default: null
1099
1228
  type: 'boolean',
1100
1229
  visual: true,
1101
1230
  default: false
1231
+ },
1232
+ native: {
1233
+ type: 'boolean',
1234
+ visual: true,
1235
+ default: false
1102
1236
  }
1103
1237
  };
1104
1238
  TyDatePicker.formAssociated = true;