tyrell-components 1.0.0-TC56 → 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 (212) hide show
  1. package/css/tyrell.css +3 -3
  2. package/dist/tyrell.js +1 -1
  3. package/dist/tyrell.js.map +1 -0
  4. package/lib/base/ty-component.d.ts +15 -3
  5. package/lib/base/ty-component.js +26 -18
  6. package/lib/components/button.d.ts +4 -0
  7. package/lib/components/button.js +33 -6
  8. package/lib/components/calendar-month.js +3 -10
  9. package/lib/components/calendar.d.ts +5 -1
  10. package/lib/components/calendar.js +28 -34
  11. package/lib/components/checkbox.d.ts +3 -1
  12. package/lib/components/checkbox.js +9 -1
  13. package/lib/components/date-picker.d.ts +61 -7
  14. package/lib/components/date-picker.js +274 -133
  15. package/lib/components/file-upload.d.ts +4 -1
  16. package/lib/components/file-upload.js +17 -3
  17. package/lib/components/input.d.ts +82 -3
  18. package/lib/components/input.js +178 -20
  19. package/lib/components/modal.d.ts +2 -0
  20. package/lib/components/modal.js +49 -10
  21. package/lib/components/option.js +7 -5
  22. package/lib/components/popup.js +59 -10
  23. package/lib/components/radio.d.ts +4 -1
  24. package/lib/components/radio.js +10 -1
  25. package/lib/components/resize-observer.js +2 -0
  26. package/lib/components/scroll-container.js +2 -0
  27. package/lib/components/select.d.ts +34 -3
  28. package/lib/components/select.js +135 -11
  29. package/lib/components/selected-tags.d.ts +7 -1
  30. package/lib/components/selected-tags.js +10 -1
  31. package/lib/components/switch.d.ts +5 -1
  32. package/lib/components/switch.js +9 -1
  33. package/lib/components/textarea.d.ts +46 -2
  34. package/lib/components/textarea.js +60 -9
  35. package/lib/styles/button.js +20 -19
  36. package/lib/styles/date-picker.js +18 -1
  37. package/lib/styles/file-upload.d.ts +1 -1
  38. package/lib/styles/file-upload.js +3 -3
  39. package/lib/styles/icon.d.ts +1 -1
  40. package/lib/styles/icon.js +2 -2
  41. package/lib/styles/input.js +1 -1
  42. package/lib/styles/radio.js +2 -2
  43. package/lib/styles/select-base.js +26 -0
  44. package/lib/styles/textarea.d.ts +1 -1
  45. package/lib/styles/textarea.js +7 -0
  46. package/lib/utils/error-flavor.d.ts +8 -0
  47. package/lib/utils/error-flavor.js +1 -0
  48. package/lib/utils/property-manager.d.ts +6 -0
  49. package/lib/utils/property-manager.js +15 -5
  50. package/lib/utils/upgrade-properties.d.ts +21 -0
  51. package/lib/utils/upgrade-properties.js +47 -0
  52. package/lib/utils/validity.d.ts +72 -2
  53. package/lib/utils/validity.js +192 -8
  54. package/lib/version.d.ts +1 -1
  55. package/lib/version.js +1 -1
  56. package/package.json +3 -2
  57. package/lib/base/ty-component.d.ts.map +0 -1
  58. package/lib/base/ty-component.js.map +0 -1
  59. package/lib/components/button.d.ts.map +0 -1
  60. package/lib/components/button.js.map +0 -1
  61. package/lib/components/calendar-month.d.ts.map +0 -1
  62. package/lib/components/calendar-month.js.map +0 -1
  63. package/lib/components/calendar-navigation.d.ts.map +0 -1
  64. package/lib/components/calendar-navigation.js.map +0 -1
  65. package/lib/components/calendar.d.ts.map +0 -1
  66. package/lib/components/calendar.js.map +0 -1
  67. package/lib/components/checkbox.d.ts.map +0 -1
  68. package/lib/components/checkbox.js.map +0 -1
  69. package/lib/components/copy.d.ts.map +0 -1
  70. package/lib/components/copy.js.map +0 -1
  71. package/lib/components/date-picker.d.ts.map +0 -1
  72. package/lib/components/date-picker.js.map +0 -1
  73. package/lib/components/dropdown.d.ts +0 -403
  74. package/lib/components/dropdown.d.ts.map +0 -1
  75. package/lib/components/dropdown.js +0 -1315
  76. package/lib/components/dropdown.js.map +0 -1
  77. package/lib/components/icon.d.ts.map +0 -1
  78. package/lib/components/icon.js.map +0 -1
  79. package/lib/components/input.d.ts.map +0 -1
  80. package/lib/components/input.js.map +0 -1
  81. package/lib/components/modal.d.ts.map +0 -1
  82. package/lib/components/modal.js.map +0 -1
  83. package/lib/components/multiselect.d.ts +0 -389
  84. package/lib/components/multiselect.d.ts.map +0 -1
  85. package/lib/components/multiselect.js +0 -1301
  86. package/lib/components/multiselect.js.map +0 -1
  87. package/lib/components/option.d.ts.map +0 -1
  88. package/lib/components/option.js.map +0 -1
  89. package/lib/components/popup.d.ts.map +0 -1
  90. package/lib/components/popup.js.map +0 -1
  91. package/lib/components/resize-observer.d.ts.map +0 -1
  92. package/lib/components/resize-observer.js.map +0 -1
  93. package/lib/components/scroll-container.d.ts.map +0 -1
  94. package/lib/components/scroll-container.js.map +0 -1
  95. package/lib/components/step.d.ts.map +0 -1
  96. package/lib/components/step.js.map +0 -1
  97. package/lib/components/tab.d.ts.map +0 -1
  98. package/lib/components/tab.js.map +0 -1
  99. package/lib/components/tabs.d.ts.map +0 -1
  100. package/lib/components/tabs.js.map +0 -1
  101. package/lib/components/tag.d.ts.map +0 -1
  102. package/lib/components/tag.js.map +0 -1
  103. package/lib/components/textarea.d.ts.map +0 -1
  104. package/lib/components/textarea.js.map +0 -1
  105. package/lib/components/tooltip.d.ts.map +0 -1
  106. package/lib/components/tooltip.js.map +0 -1
  107. package/lib/components/wizard.d.ts.map +0 -1
  108. package/lib/components/wizard.js.map +0 -1
  109. package/lib/icons/fontawesome/brands.d.ts.map +0 -1
  110. package/lib/icons/fontawesome/brands.js.map +0 -1
  111. package/lib/icons/fontawesome/regular.d.ts.map +0 -1
  112. package/lib/icons/fontawesome/regular.js.map +0 -1
  113. package/lib/icons/fontawesome/solid.d.ts.map +0 -1
  114. package/lib/icons/fontawesome/solid.js.map +0 -1
  115. package/lib/icons/heroicons/micro.d.ts.map +0 -1
  116. package/lib/icons/heroicons/micro.js.map +0 -1
  117. package/lib/icons/heroicons/mini.d.ts.map +0 -1
  118. package/lib/icons/heroicons/mini.js.map +0 -1
  119. package/lib/icons/heroicons/outline.d.ts.map +0 -1
  120. package/lib/icons/heroicons/outline.js.map +0 -1
  121. package/lib/icons/heroicons/solid.d.ts.map +0 -1
  122. package/lib/icons/heroicons/solid.js.map +0 -1
  123. package/lib/icons/lucide.d.ts.map +0 -1
  124. package/lib/icons/lucide.js.map +0 -1
  125. package/lib/icons/material/filled.d.ts.map +0 -1
  126. package/lib/icons/material/filled.js.map +0 -1
  127. package/lib/icons/material/outlined.d.ts.map +0 -1
  128. package/lib/icons/material/outlined.js.map +0 -1
  129. package/lib/icons/material/round.d.ts.map +0 -1
  130. package/lib/icons/material/round.js.map +0 -1
  131. package/lib/icons/material/sharp.d.ts.map +0 -1
  132. package/lib/icons/material/sharp.js.map +0 -1
  133. package/lib/icons/material/two-tone.d.ts.map +0 -1
  134. package/lib/icons/material/two-tone.js.map +0 -1
  135. package/lib/index.d.ts.map +0 -1
  136. package/lib/index.js.map +0 -1
  137. package/lib/styles/button.d.ts.map +0 -1
  138. package/lib/styles/button.js.map +0 -1
  139. package/lib/styles/calendar-month.d.ts.map +0 -1
  140. package/lib/styles/calendar-month.js.map +0 -1
  141. package/lib/styles/calendar-navigation.d.ts.map +0 -1
  142. package/lib/styles/calendar-navigation.js.map +0 -1
  143. package/lib/styles/calendar.d.ts.map +0 -1
  144. package/lib/styles/calendar.js.map +0 -1
  145. package/lib/styles/copy.d.ts.map +0 -1
  146. package/lib/styles/copy.js.map +0 -1
  147. package/lib/styles/date-picker.d.ts.map +0 -1
  148. package/lib/styles/date-picker.js.map +0 -1
  149. package/lib/styles/dropdown.d.ts +0 -12
  150. package/lib/styles/dropdown.d.ts.map +0 -1
  151. package/lib/styles/dropdown.js +0 -725
  152. package/lib/styles/dropdown.js.map +0 -1
  153. package/lib/styles/icon.d.ts.map +0 -1
  154. package/lib/styles/icon.js.map +0 -1
  155. package/lib/styles/input.d.ts.map +0 -1
  156. package/lib/styles/input.js.map +0 -1
  157. package/lib/styles/modal.d.ts.map +0 -1
  158. package/lib/styles/modal.js.map +0 -1
  159. package/lib/styles/multiselect.d.ts +0 -6
  160. package/lib/styles/multiselect.d.ts.map +0 -1
  161. package/lib/styles/multiselect.js +0 -568
  162. package/lib/styles/multiselect.js.map +0 -1
  163. package/lib/styles/option.d.ts.map +0 -1
  164. package/lib/styles/option.js.map +0 -1
  165. package/lib/styles/popup.d.ts.map +0 -1
  166. package/lib/styles/popup.js.map +0 -1
  167. package/lib/styles/resize-observer.d.ts.map +0 -1
  168. package/lib/styles/resize-observer.js.map +0 -1
  169. package/lib/styles/scroll-container.d.ts.map +0 -1
  170. package/lib/styles/scroll-container.js.map +0 -1
  171. package/lib/styles/step.d.ts.map +0 -1
  172. package/lib/styles/step.js.map +0 -1
  173. package/lib/styles/tab.d.ts.map +0 -1
  174. package/lib/styles/tab.js.map +0 -1
  175. package/lib/styles/tabs.d.ts.map +0 -1
  176. package/lib/styles/tabs.js.map +0 -1
  177. package/lib/styles/tag.d.ts.map +0 -1
  178. package/lib/styles/tag.js.map +0 -1
  179. package/lib/styles/textarea.d.ts.map +0 -1
  180. package/lib/styles/textarea.js.map +0 -1
  181. package/lib/styles/tooltip.d.ts.map +0 -1
  182. package/lib/styles/tooltip.js.map +0 -1
  183. package/lib/styles/wizard.d.ts.map +0 -1
  184. package/lib/styles/wizard.js.map +0 -1
  185. package/lib/types/common.d.ts.map +0 -1
  186. package/lib/types/common.js.map +0 -1
  187. package/lib/utils/calendar-utils.d.ts.map +0 -1
  188. package/lib/utils/calendar-utils.js.map +0 -1
  189. package/lib/utils/icon-registry.d.ts.map +0 -1
  190. package/lib/utils/icon-registry.js.map +0 -1
  191. package/lib/utils/locale.d.ts.map +0 -1
  192. package/lib/utils/locale.js.map +0 -1
  193. package/lib/utils/number-format.d.ts.map +0 -1
  194. package/lib/utils/number-format.js.map +0 -1
  195. package/lib/utils/parse-boolean.d.ts +0 -39
  196. package/lib/utils/parse-boolean.d.ts.map +0 -1
  197. package/lib/utils/parse-boolean.js +0 -23
  198. package/lib/utils/parse-boolean.js.map +0 -1
  199. package/lib/utils/positioning.d.ts.map +0 -1
  200. package/lib/utils/positioning.js.map +0 -1
  201. package/lib/utils/property-capture.d.ts.map +0 -1
  202. package/lib/utils/property-capture.js.map +0 -1
  203. package/lib/utils/property-manager.d.ts.map +0 -1
  204. package/lib/utils/property-manager.js.map +0 -1
  205. package/lib/utils/resize-observer.d.ts.map +0 -1
  206. package/lib/utils/resize-observer.js.map +0 -1
  207. package/lib/utils/scroll-lock.d.ts.map +0 -1
  208. package/lib/utils/scroll-lock.js.map +0 -1
  209. package/lib/utils/styles.d.ts.map +0 -1
  210. package/lib/utils/styles.js.map +0 -1
  211. package/lib/version.d.ts.map +0 -1
  212. 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,14 +237,20 @@ 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
- this.updateValidity();
253
+ this.updateFormValue();
224
254
  this._localeObserver = observeLocaleChanges(this, () => {
225
255
  this.render();
226
256
  });
@@ -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,73 +288,69 @@ 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
- super.updateFormValue();
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
+ }
338
+ focus(options) {
339
+ if (this.getProperty('disabled'))
340
+ return;
341
+ const target = this.focusTarget();
342
+ if (target)
343
+ target.focus(options);
344
+ else
345
+ super.focus(options);
346
+ }
317
347
  updateValidity() {
318
348
  if (this.getProperty('disabled') || !this.getProperty('required') || this.getFormValue()) {
319
349
  this.applyValidity({});
320
350
  return;
321
351
  }
322
- const anchor = this.shadowRoot?.querySelector('.date-picker-stub, .native-date-input');
323
- 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);
324
354
  }
325
355
  getComponents() {
326
356
  return {
@@ -329,6 +359,8 @@ month: this._state.month,
329
359
  day: this._state.day,
330
360
  hour: this._state.hour,
331
361
  minute: this._state.minute,
362
+ second: this._state.second,
363
+ epoch: this._state.epoch,
332
364
  };
333
365
  }
334
366
  hasDate() {
@@ -338,25 +370,15 @@ getFormattedValue(components) {
338
370
  const c = components || this.getComponents();
339
371
  if (!c.year || !c.month || !c.day)
340
372
  return null;
341
- 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'));
342
374
  }
343
375
  initializeState() {
344
- const valueAttr = this.getProperty('value');
345
- const withTime = this.getProperty('with-time');
346
- const components = parseValue(valueAttr, withTime);
347
- if (components === null) {
348
- this._state = {
349
- withTime,
350
- open: false,
351
- };
352
- }
353
- else {
354
376
  this._state = {
355
- ...components,
356
- withTime,
377
+ ...parseValue(this.getProperty('value')),
378
+ withTime: this.getProperty('with-time'),
357
379
  open: false,
358
380
  };
359
- }
381
+ this._initialized = true;
360
382
  this.syncFormValue();
361
383
  }
362
384
  updateState(updates, forceSync = false) {
@@ -367,24 +389,47 @@ this.syncFormValue();
367
389
  }
368
390
  }
369
391
  syncFormValue() {
370
- const utcValue = this.getFormValue();
392
+ const formValue = this.getFormValue();
371
393
  const currentValue = this.getProperty('value');
372
- if (utcValue !== currentValue) {
373
- if (utcValue) {
374
- this.setProperty('value', utcValue);
394
+ if (formValue !== currentValue) {
395
+ this._syncing = true;
396
+ try {
397
+ this.setProperty('value', formValue || null);
375
398
  }
376
- else {
377
- this.setProperty('value', null);
399
+ finally {
400
+ this._syncing = false;
401
+ }
402
+ }
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');
378
413
  }
379
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
+ }
380
424
  }
381
425
  handleTimeInputChange(hour, minute) {
382
426
  if (!this.hasDate())
383
427
  return;
384
- const components = { ...this.getComponents(), hour, minute };
428
+ const components = { ...this.getComponents(), hour, minute, second: 0, epoch: undefined };
385
429
  this.updateState(components, true);
386
430
  this.emitChangeEvent(components, 'time-change');
387
431
  this.updateTimeSummary();
432
+ this.revealValidity();
388
433
  }
389
434
  updateTimeSummary(summary = this.shadowRoot?.querySelector('.time-summary')) {
390
435
  if (!summary)
@@ -398,19 +443,22 @@ iso.textContent = '';
398
443
  return;
399
444
  }
400
445
  const locale = getEffectiveLocale(this, this.getProperty('locale')) || undefined;
401
- const date = new Date(c.year, c.month - 1, c.day, c.hour ?? 0, c.minute ?? 0);
402
- const day = new Intl.DateTimeFormat(locale, { weekday: 'short', month: 'short', day: 'numeric' }).format(date);
403
- 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);
404
449
  text.textContent = `${day} · ${time}`;
405
450
  iso.textContent = componentsToLocalValue(c, true) ?? '';
406
451
  }
407
452
  emitChangeEvent(components, source) {
408
- const utcValue = components ? componentsToOutputValue(components) : null;
453
+ const value = components ? this.outputValue(components) : null;
409
454
  const localValue = components ? componentsToLocalValue(components, this._state.withTime) : null;
410
- 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;
411
459
  const formatted = components ? this.getFormattedValue(components) : null;
412
460
  const detail = {
413
- value: utcValue,
461
+ value,
414
462
  localValue,
415
463
  milliseconds,
416
464
  formatted,
@@ -428,7 +476,15 @@ set size(v) { this.setProperty('size', v); }
428
476
  get placement() { return this.getProperty('placement'); }
429
477
  set placement(v) { this.setProperty('placement', v); }
430
478
  get flavor() { return this.getProperty('flavor'); }
431
- 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
+ }
432
488
  get label() { return this.getProperty('label'); }
433
489
  set label(v) { this.setProperty('label', v); }
434
490
  get placeholder() { return this.getProperty('placeholder'); }
@@ -451,16 +507,26 @@ get max() { return this.getProperty('max'); }
451
507
  set max(v) { this.setProperty('max', v ?? ''); }
452
508
  get withTime() { return this.getProperty('with-time'); }
453
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); }
454
514
  get value() {
515
+ if (this._initialized)
455
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'));
456
519
  }
457
520
  set value(val) {
458
521
  if (val === null || val === undefined || val === '') {
459
522
  this.setProperty('value', null);
460
523
  }
524
+ else if (typeof val === 'string') {
525
+ this.setProperty('value', val);
526
+ }
461
527
  else {
462
- const strValue = val instanceof Date ? val.toISOString() : val;
463
- this.setProperty('value', strValue);
528
+ const date = new Date(val);
529
+ this.setProperty('value', isNaN(date.getTime()) ? null : date.toISOString());
464
530
  }
465
531
  }
466
532
  clear() {
@@ -472,6 +538,8 @@ if (this.getProperty('disabled'))
472
538
  classes.push('disabled');
473
539
  if (this.getProperty('required'))
474
540
  classes.push('required');
541
+ if (this.getProperty('error'))
542
+ classes.push('error');
475
543
  if (this._state.open)
476
544
  classes.push('open');
477
545
  return classes.join(' ');
@@ -494,7 +562,7 @@ stub.setAttribute('aria-labelledby', `date-picker-label-${getElementHash(this)}`
494
562
  }
495
563
  stub.addEventListener('keydown', (e) => {
496
564
  const ke = e;
497
- if (this._state.open)
565
+ if (this._state.open || e.target !== stub)
498
566
  return;
499
567
  if (ke.key === 'Enter' || ke.key === ' ') {
500
568
  ke.preventDefault();
@@ -604,8 +672,8 @@ if (min)
604
672
  calendar.setAttribute('min', min);
605
673
  if (max)
606
674
  calendar.setAttribute('max', max);
607
- const flavor = this.getProperty('flavor');
608
- if (flavor && flavor !== 'default')
675
+ const flavor = this.calendarFlavor();
676
+ if (flavor)
609
677
  calendar.setAttribute('flavor', flavor);
610
678
  calendar.addEventListener('change', (e) => this.handleCalendarChange(e));
611
679
  contentWrapper.appendChild(calendar);
@@ -613,11 +681,17 @@ if (this._state.withTime) {
613
681
  contentWrapper.appendChild(this.renderTimeSection());
614
682
  }
615
683
  dialog.appendChild(contentWrapper);
616
- dialog.addEventListener('close', () => {
617
- this.updateState({ open: false });
618
- });
684
+ new MutationObserver(() => {
685
+ if (this._state.open && !dialog.open && this.isConnected)
686
+ this.closeDropdown();
687
+ }).observe(dialog, { attributes: true, attributeFilter: ['open'] });
619
688
  return dialog;
620
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
+ }
621
695
  renderNativeInput() {
622
696
  const wrapper = document.createElement('div');
623
697
  wrapper.className = 'dropdown-wrapper';
@@ -629,21 +703,8 @@ stub.setAttribute('disabled', 'true');
629
703
  }
630
704
  const input = document.createElement('input');
631
705
  input.className = 'native-date-input';
632
- input.type = this._state.withTime ? 'datetime-local' : 'date';
633
- if (isDisabled)
634
- input.disabled = true;
635
- if (this.getProperty('required'))
636
- input.required = true;
637
- const minBound = this.getProperty('min');
638
- const maxBound = this.getProperty('max');
639
- if (minBound)
640
- input.min = this._state.withTime ? `${minBound}T00:00` : minBound;
641
- if (maxBound)
642
- input.max = this._state.withTime ? `${maxBound}T23:59` : maxBound;
643
- const localValue = componentsToLocalValue(this.getComponents(), this._state.withTime);
644
- if (localValue) {
645
- input.value = localValue;
646
- }
706
+ this.syncNativeInput(input);
707
+ const localValue = input.value;
647
708
  const placeholder = this.getProperty('placeholder') || 'Select date...';
648
709
  const formattedValue = this.getFormattedValue();
649
710
  const placeholderEl = document.createElement('span');
@@ -652,13 +713,17 @@ placeholderEl.textContent = formattedValue || placeholder;
652
713
  input.addEventListener('change', () => {
653
714
  if (!input.value) {
654
715
  this.clearValue();
655
- return;
656
716
  }
657
- const parsed = parseValue(input.value, this._state.withTime);
717
+ else {
718
+ const parsed = parseValue(input.value);
658
719
  if (parsed) {
659
- this.updateState(parsed, true);
660
- 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');
724
+ }
661
725
  }
726
+ this.revealValidity();
662
727
  });
663
728
  const startSlot = document.createElement('slot');
664
729
  startSlot.name = 'start';
@@ -684,6 +749,23 @@ stub.appendChild(iconContainer);
684
749
  wrapper.appendChild(stub);
685
750
  return wrapper;
686
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
+ }
687
769
  calculateCalendarPosition() {
688
770
  if (!this.shadowRoot)
689
771
  return;
@@ -750,6 +832,8 @@ this.shadowRoot.innerHTML = '';
750
832
  const container = this.renderContainer();
751
833
  container.appendChild(this.renderNativeInput());
752
834
  this.shadowRoot.appendChild(container);
835
+ this.syncErrorLine();
836
+ this.updateValidity();
753
837
  return;
754
838
  }
755
839
  const existingDialog = this.shadowRoot.querySelector('.calendar-dialog');
@@ -759,6 +843,7 @@ this.updateDisplay();
759
843
  this.calculateCalendarPosition();
760
844
  return;
761
845
  }
846
+ const hadFocus = !!this.shadowRoot.activeElement;
762
847
  this.shadowRoot.innerHTML = '';
763
848
  const container = this.renderContainer();
764
849
  const wrapper = document.createElement('div');
@@ -767,7 +852,33 @@ wrapper.appendChild(this.renderStub());
767
852
  wrapper.appendChild(this.renderCalendarDropdown());
768
853
  container.appendChild(wrapper);
769
854
  this.shadowRoot.appendChild(container);
855
+ this.syncErrorLine();
770
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
+ }
771
882
  }
772
883
  updateDisplay() {
773
884
  if (!this.shadowRoot)
@@ -786,12 +897,15 @@ const clearButton = this.shadowRoot.querySelector('.stub-clear');
786
897
  if (clearButton) {
787
898
  clearButton.style.display = (clearable && hasValue && !isDisabled) ? '' : 'none';
788
899
  }
900
+ const stub = this.shadowRoot.querySelector('.date-picker-stub');
901
+ if (stub)
902
+ stub.className = this.buildStubClasses();
903
+ this.syncErrorLine();
789
904
  if (isMobileTouch()) {
790
905
  const nativeInput = this.shadowRoot.querySelector('.native-date-input');
791
- if (nativeInput) {
792
- const localValue = componentsToLocalValue(this.getComponents(), this._state.withTime);
793
- nativeInput.value = localValue || '';
794
- }
906
+ if (nativeInput)
907
+ this.syncNativeInput(nativeInput);
908
+ stub?.toggleAttribute('disabled', !!isDisabled);
795
909
  return;
796
910
  }
797
911
  const calendar = this.shadowRoot.querySelector('ty-calendar');
@@ -801,8 +915,8 @@ calendar.setAttribute('month', this._state.month.toString());
801
915
  calendar.setAttribute('day', this._state.day.toString());
802
916
  }
803
917
  if (calendar) {
804
- const flavor = this.getProperty('flavor');
805
- if (flavor && flavor !== 'default')
918
+ const flavor = this.calendarFlavor();
919
+ if (flavor)
806
920
  calendar.setAttribute('flavor', flavor);
807
921
  else
808
922
  calendar.removeAttribute('flavor');
@@ -844,7 +958,7 @@ event.preventDefault();
844
958
  const dialog = this.shadowRoot?.querySelector('.calendar-dialog');
845
959
  if (this._state.open && (!dialog || !dialog.open)) {
846
960
  this.updateState({ open: false }, true);
847
- unlockScroll(`date-picker-${this.id || this.toString()}`);
961
+ unlockScroll(`date-picker-${getElementHash(this)}`);
848
962
  }
849
963
  const disabled = this.getProperty('disabled');
850
964
  if (!disabled && Date.now() - this._closeTimestamp > 300) {
@@ -858,6 +972,8 @@ month: undefined,
858
972
  day: undefined,
859
973
  hour: undefined,
860
974
  minute: undefined,
975
+ second: undefined,
976
+ epoch: undefined,
861
977
  }, true);
862
978
  this.value = null;
863
979
  this.emitChangeEvent(null, 'clear');
@@ -867,6 +983,11 @@ handleClearClick(event) {
867
983
  event.preventDefault();
868
984
  event.stopPropagation();
869
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();
870
991
  }
871
992
  handleCalendarChange(event) {
872
993
  event.preventDefault();
@@ -882,9 +1003,12 @@ month: dayContext.month,
882
1003
  day: dayContext.dayInMonth,
883
1004
  hour: this._state.hour,
884
1005
  minute: this._state.minute,
1006
+ second: this._state.second,
1007
+ epoch: undefined,
885
1008
  };
886
1009
  this.updateState(newComponents, true);
887
1010
  this.emitChangeEvent(newComponents, 'selection');
1011
+ this.revealValidity();
888
1012
  if (!this._state.withTime) {
889
1013
  this.closeDropdown();
890
1014
  }
@@ -938,9 +1062,15 @@ this.render();
938
1062
  requestAnimationFrame(() => {
939
1063
  if (!this.shadowRoot)
940
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
+ }
941
1071
  const dialog = this.shadowRoot.querySelector('.calendar-dialog');
942
1072
  if (dialog) {
943
- const pickerId = `date-picker-${this.id || this.toString()}`;
1073
+ const pickerId = `date-picker-${getElementHash(this)}`;
944
1074
  lockScroll(pickerId);
945
1075
  dialog.showModal();
946
1076
  this.calculateCalendarPosition();
@@ -959,7 +1089,7 @@ closeDropdown() {
959
1089
  if (!this._state.open)
960
1090
  return;
961
1091
  this._closeTimestamp = Date.now();
962
- const pickerId = `date-picker-${this.id || this.toString()}`;
1092
+ const pickerId = `date-picker-${getElementHash(this)}`;
963
1093
  this.updateState({ open: false }, true);
964
1094
  unlockScroll(pickerId);
965
1095
  if (this.shadowRoot) {
@@ -974,6 +1104,7 @@ stubEl.setAttribute('aria-expanded', 'false');
974
1104
  }
975
1105
  this.dispatchEvent(new CustomEvent('close'));
976
1106
  this.render();
1107
+ this.focus();
977
1108
  }
978
1109
  cleanup() {
979
1110
  if (this._clickListener) {
@@ -989,7 +1120,7 @@ dialog.removeEventListener('click', this._dialogClickListener);
989
1120
  }
990
1121
  }
991
1122
  if (this._state.open) {
992
- const pickerId = `date-picker-${this.id || this.toString()}`;
1123
+ const pickerId = `date-picker-${getElementHash(this)}`;
993
1124
  unlockScroll(pickerId);
994
1125
  }
995
1126
  this._clickListener = undefined;
@@ -1062,6 +1193,11 @@ type: 'boolean',
1062
1193
  visual: true,
1063
1194
  default: false
1064
1195
  },
1196
+ error: {
1197
+ type: 'string',
1198
+ visual: true,
1199
+ default: ''
1200
+ },
1065
1201
  format: {
1066
1202
  type: 'string',
1067
1203
  visual: true,
@@ -1092,6 +1228,11 @@ default: null
1092
1228
  type: 'boolean',
1093
1229
  visual: true,
1094
1230
  default: false
1231
+ },
1232
+ native: {
1233
+ type: 'boolean',
1234
+ visual: true,
1235
+ default: false
1095
1236
  }
1096
1237
  };
1097
1238
  TyDatePicker.formAssociated = true;