tyrell-components 1.0.0-TC57 → 1.0.0-TC59

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 (226) hide show
  1. package/css/tyrell-colors-static.css +14 -0
  2. package/css/tyrell-theme.css +1 -0
  3. package/css/tyrell.css +8 -6
  4. package/dist/tyrell-colors-static.css +1 -1
  5. package/dist/tyrell-theme.css +1 -1
  6. package/dist/tyrell.js +1 -1
  7. package/lib/base/ty-component.d.ts +20 -15
  8. package/lib/base/ty-component.js +58 -25
  9. package/lib/components/button.d.ts +14 -0
  10. package/lib/components/button.js +55 -7
  11. package/lib/components/calendar-month.d.ts +7 -1
  12. package/lib/components/calendar-month.js +35 -28
  13. package/lib/components/calendar-navigation.js +19 -6
  14. package/lib/components/calendar.d.ts +25 -4
  15. package/lib/components/calendar.js +98 -67
  16. package/lib/components/checkbox.d.ts +6 -1
  17. package/lib/components/checkbox.js +20 -6
  18. package/lib/components/copy.d.ts +2 -0
  19. package/lib/components/copy.js +15 -31
  20. package/lib/components/date-picker.d.ts +52 -19
  21. package/lib/components/date-picker.js +211 -147
  22. package/lib/components/file-upload.d.ts +2 -1
  23. package/lib/components/file-upload.js +8 -3
  24. package/lib/components/input.d.ts +4 -6
  25. package/lib/components/input.js +37 -62
  26. package/lib/components/modal.js +86 -9
  27. package/lib/components/option.js +2 -5
  28. package/lib/components/popup.js +99 -11
  29. package/lib/components/radio.d.ts +8 -1
  30. package/lib/components/radio.js +14 -29
  31. package/lib/components/resize-observer.js +2 -0
  32. package/lib/components/scroll-container.js +2 -0
  33. package/lib/components/select.d.ts +53 -6
  34. package/lib/components/select.js +127 -29
  35. package/lib/components/switch.d.ts +7 -1
  36. package/lib/components/switch.js +20 -6
  37. package/lib/components/tabs.js +1 -1
  38. package/lib/components/textarea.d.ts +45 -4
  39. package/lib/components/textarea.js +75 -18
  40. package/lib/index.d.ts +1 -0
  41. package/lib/index.js +1 -0
  42. package/lib/styles/button.js +21 -19
  43. package/lib/styles/calendar-month.js +1 -1
  44. package/lib/styles/calendar.js +3 -0
  45. package/lib/styles/checkbox.js +11 -5
  46. package/lib/styles/date-picker.js +15 -3
  47. package/lib/styles/input.js +5 -9
  48. package/lib/styles/option.d.ts +1 -1
  49. package/lib/styles/option.js +4 -0
  50. package/lib/styles/popup.d.ts +1 -1
  51. package/lib/styles/popup.js +2 -0
  52. package/lib/styles/radio.js +10 -4
  53. package/lib/styles/select-base.js +24 -3
  54. package/lib/styles/select.d.ts +1 -1
  55. package/lib/styles/select.js +4 -0
  56. package/lib/styles/switch.js +10 -5
  57. package/lib/styles/tag.js +2 -1
  58. package/lib/styles/textarea.d.ts +1 -1
  59. package/lib/styles/textarea.js +7 -0
  60. package/lib/utils/calendar-utils.d.ts +49 -0
  61. package/lib/utils/calendar-utils.js +63 -6
  62. package/lib/utils/scroll-lock.d.ts +10 -4
  63. package/lib/utils/scroll-lock.js +102 -2
  64. package/lib/utils/upgrade-properties.d.ts +21 -0
  65. package/lib/utils/upgrade-properties.js +47 -0
  66. package/lib/utils/validity.d.ts +89 -5
  67. package/lib/utils/validity.js +198 -25
  68. package/lib/version.d.ts +1 -1
  69. package/lib/version.js +1 -1
  70. package/package.json +2 -1
  71. package/lib/base/ty-component.d.ts.map +0 -1
  72. package/lib/base/ty-component.js.map +0 -1
  73. package/lib/components/button.d.ts.map +0 -1
  74. package/lib/components/button.js.map +0 -1
  75. package/lib/components/calendar-month.d.ts.map +0 -1
  76. package/lib/components/calendar-month.js.map +0 -1
  77. package/lib/components/calendar-navigation.d.ts.map +0 -1
  78. package/lib/components/calendar-navigation.js.map +0 -1
  79. package/lib/components/calendar.d.ts.map +0 -1
  80. package/lib/components/calendar.js.map +0 -1
  81. package/lib/components/checkbox.d.ts.map +0 -1
  82. package/lib/components/checkbox.js.map +0 -1
  83. package/lib/components/copy.d.ts.map +0 -1
  84. package/lib/components/copy.js.map +0 -1
  85. package/lib/components/date-picker.d.ts.map +0 -1
  86. package/lib/components/date-picker.js.map +0 -1
  87. package/lib/components/dropdown.d.ts +0 -403
  88. package/lib/components/dropdown.d.ts.map +0 -1
  89. package/lib/components/dropdown.js +0 -1315
  90. package/lib/components/dropdown.js.map +0 -1
  91. package/lib/components/icon.d.ts.map +0 -1
  92. package/lib/components/icon.js.map +0 -1
  93. package/lib/components/input.d.ts.map +0 -1
  94. package/lib/components/input.js.map +0 -1
  95. package/lib/components/modal.d.ts.map +0 -1
  96. package/lib/components/modal.js.map +0 -1
  97. package/lib/components/multiselect.d.ts +0 -389
  98. package/lib/components/multiselect.d.ts.map +0 -1
  99. package/lib/components/multiselect.js +0 -1301
  100. package/lib/components/multiselect.js.map +0 -1
  101. package/lib/components/option.d.ts.map +0 -1
  102. package/lib/components/option.js.map +0 -1
  103. package/lib/components/popup.d.ts.map +0 -1
  104. package/lib/components/popup.js.map +0 -1
  105. package/lib/components/resize-observer.d.ts.map +0 -1
  106. package/lib/components/resize-observer.js.map +0 -1
  107. package/lib/components/scroll-container.d.ts.map +0 -1
  108. package/lib/components/scroll-container.js.map +0 -1
  109. package/lib/components/step.d.ts.map +0 -1
  110. package/lib/components/step.js.map +0 -1
  111. package/lib/components/tab.d.ts.map +0 -1
  112. package/lib/components/tab.js.map +0 -1
  113. package/lib/components/tabs.d.ts.map +0 -1
  114. package/lib/components/tabs.js.map +0 -1
  115. package/lib/components/tag.d.ts.map +0 -1
  116. package/lib/components/tag.js.map +0 -1
  117. package/lib/components/textarea.d.ts.map +0 -1
  118. package/lib/components/textarea.js.map +0 -1
  119. package/lib/components/tooltip.d.ts.map +0 -1
  120. package/lib/components/tooltip.js.map +0 -1
  121. package/lib/components/wizard.d.ts.map +0 -1
  122. package/lib/components/wizard.js.map +0 -1
  123. package/lib/icons/fontawesome/brands.d.ts.map +0 -1
  124. package/lib/icons/fontawesome/brands.js.map +0 -1
  125. package/lib/icons/fontawesome/regular.d.ts.map +0 -1
  126. package/lib/icons/fontawesome/regular.js.map +0 -1
  127. package/lib/icons/fontawesome/solid.d.ts.map +0 -1
  128. package/lib/icons/fontawesome/solid.js.map +0 -1
  129. package/lib/icons/heroicons/micro.d.ts.map +0 -1
  130. package/lib/icons/heroicons/micro.js.map +0 -1
  131. package/lib/icons/heroicons/mini.d.ts.map +0 -1
  132. package/lib/icons/heroicons/mini.js.map +0 -1
  133. package/lib/icons/heroicons/outline.d.ts.map +0 -1
  134. package/lib/icons/heroicons/outline.js.map +0 -1
  135. package/lib/icons/heroicons/solid.d.ts.map +0 -1
  136. package/lib/icons/heroicons/solid.js.map +0 -1
  137. package/lib/icons/lucide.d.ts.map +0 -1
  138. package/lib/icons/lucide.js.map +0 -1
  139. package/lib/icons/material/filled.d.ts.map +0 -1
  140. package/lib/icons/material/filled.js.map +0 -1
  141. package/lib/icons/material/outlined.d.ts.map +0 -1
  142. package/lib/icons/material/outlined.js.map +0 -1
  143. package/lib/icons/material/round.d.ts.map +0 -1
  144. package/lib/icons/material/round.js.map +0 -1
  145. package/lib/icons/material/sharp.d.ts.map +0 -1
  146. package/lib/icons/material/sharp.js.map +0 -1
  147. package/lib/icons/material/two-tone.d.ts.map +0 -1
  148. package/lib/icons/material/two-tone.js.map +0 -1
  149. package/lib/index.d.ts.map +0 -1
  150. package/lib/index.js.map +0 -1
  151. package/lib/styles/button.d.ts.map +0 -1
  152. package/lib/styles/button.js.map +0 -1
  153. package/lib/styles/calendar-month.d.ts.map +0 -1
  154. package/lib/styles/calendar-month.js.map +0 -1
  155. package/lib/styles/calendar-navigation.d.ts.map +0 -1
  156. package/lib/styles/calendar-navigation.js.map +0 -1
  157. package/lib/styles/calendar.d.ts.map +0 -1
  158. package/lib/styles/calendar.js.map +0 -1
  159. package/lib/styles/copy.d.ts.map +0 -1
  160. package/lib/styles/copy.js.map +0 -1
  161. package/lib/styles/date-picker.d.ts.map +0 -1
  162. package/lib/styles/date-picker.js.map +0 -1
  163. package/lib/styles/dropdown.d.ts +0 -12
  164. package/lib/styles/dropdown.d.ts.map +0 -1
  165. package/lib/styles/dropdown.js +0 -725
  166. package/lib/styles/dropdown.js.map +0 -1
  167. package/lib/styles/icon.d.ts.map +0 -1
  168. package/lib/styles/icon.js.map +0 -1
  169. package/lib/styles/input.d.ts.map +0 -1
  170. package/lib/styles/input.js.map +0 -1
  171. package/lib/styles/modal.d.ts.map +0 -1
  172. package/lib/styles/modal.js.map +0 -1
  173. package/lib/styles/multiselect.d.ts +0 -6
  174. package/lib/styles/multiselect.d.ts.map +0 -1
  175. package/lib/styles/multiselect.js +0 -568
  176. package/lib/styles/multiselect.js.map +0 -1
  177. package/lib/styles/option.d.ts.map +0 -1
  178. package/lib/styles/option.js.map +0 -1
  179. package/lib/styles/popup.d.ts.map +0 -1
  180. package/lib/styles/popup.js.map +0 -1
  181. package/lib/styles/resize-observer.d.ts.map +0 -1
  182. package/lib/styles/resize-observer.js.map +0 -1
  183. package/lib/styles/scroll-container.d.ts.map +0 -1
  184. package/lib/styles/scroll-container.js.map +0 -1
  185. package/lib/styles/step.d.ts.map +0 -1
  186. package/lib/styles/step.js.map +0 -1
  187. package/lib/styles/tab.d.ts.map +0 -1
  188. package/lib/styles/tab.js.map +0 -1
  189. package/lib/styles/tabs.d.ts.map +0 -1
  190. package/lib/styles/tabs.js.map +0 -1
  191. package/lib/styles/tag.d.ts.map +0 -1
  192. package/lib/styles/tag.js.map +0 -1
  193. package/lib/styles/textarea.d.ts.map +0 -1
  194. package/lib/styles/textarea.js.map +0 -1
  195. package/lib/styles/tooltip.d.ts.map +0 -1
  196. package/lib/styles/tooltip.js.map +0 -1
  197. package/lib/styles/wizard.d.ts.map +0 -1
  198. package/lib/styles/wizard.js.map +0 -1
  199. package/lib/types/common.d.ts.map +0 -1
  200. package/lib/types/common.js.map +0 -1
  201. package/lib/utils/calendar-utils.d.ts.map +0 -1
  202. package/lib/utils/calendar-utils.js.map +0 -1
  203. package/lib/utils/icon-registry.d.ts.map +0 -1
  204. package/lib/utils/icon-registry.js.map +0 -1
  205. package/lib/utils/locale.d.ts.map +0 -1
  206. package/lib/utils/locale.js.map +0 -1
  207. package/lib/utils/number-format.d.ts.map +0 -1
  208. package/lib/utils/number-format.js.map +0 -1
  209. package/lib/utils/parse-boolean.d.ts +0 -39
  210. package/lib/utils/parse-boolean.d.ts.map +0 -1
  211. package/lib/utils/parse-boolean.js +0 -23
  212. package/lib/utils/parse-boolean.js.map +0 -1
  213. package/lib/utils/positioning.d.ts.map +0 -1
  214. package/lib/utils/positioning.js.map +0 -1
  215. package/lib/utils/property-capture.d.ts.map +0 -1
  216. package/lib/utils/property-capture.js.map +0 -1
  217. package/lib/utils/property-manager.d.ts.map +0 -1
  218. package/lib/utils/property-manager.js.map +0 -1
  219. package/lib/utils/resize-observer.d.ts.map +0 -1
  220. package/lib/utils/resize-observer.js.map +0 -1
  221. package/lib/utils/scroll-lock.d.ts.map +0 -1
  222. package/lib/utils/scroll-lock.js.map +0 -1
  223. package/lib/utils/styles.d.ts.map +0 -1
  224. package/lib/utils/styles.js.map +0 -1
  225. package/lib/version.d.ts.map +0 -1
  226. package/lib/version.js.map +0 -1
@@ -7,6 +7,8 @@ import { getEffectiveLocale, observeLocaleChanges } from '../utils/locale.js';
7
7
  import { isMobileTouch } from '../utils/mobile.js';
8
8
  import { TyComponent } from '../base/ty-component.js';
9
9
  import { DEFAULT_SIZE } from '../types/common.js';
10
+ import { nativeMessage } from '../utils/validity.js';
11
+ import { parseDateTime, toISODay, wallClockDate } from '../utils/calendar-utils.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,28 @@ elementIds.set(element, id);
20
22
  }
21
23
  return id;
22
24
  }
23
- function parseValue(value, withTime) {
24
- if (!value)
25
- return null;
26
- let dateObj = null;
27
- 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
- }
38
- }
39
- else if (typeof value === 'number') {
40
- dateObj = new Date(value);
41
- }
42
- if (!dateObj || isNaN(dateObj.getTime())) {
43
- return null;
44
- }
45
- const components = {
46
- year: dateObj.getFullYear(),
47
- month: dateObj.getMonth() + 1,
48
- day: dateObj.getDate(),
49
- hour: withTime ? dateObj.getHours() : 0,
50
- minute: withTime ? dateObj.getMinutes() : 0,
51
- };
52
- return components;
53
- }
54
25
  function componentsToDateObject(components) {
55
- const { year, month, day, hour, minute } = components;
26
+ const { year, month, day, hour, minute, second } = components;
56
27
  if (!year || !month || !day)
57
28
  return null;
58
- return new Date(year, month - 1, day, hour || 0, minute || 0, 0, 0);
29
+ const date = new Date(2000, 0, 1, 12);
30
+ date.setFullYear(year, month - 1, day);
31
+ date.setHours(hour || 0, minute || 0, second || 0, 0);
32
+ return date;
33
+ }
34
+ function effectiveComponents(components, withTime) {
35
+ return withTime ? components : { ...components, hour: 0, minute: 0, second: 0, epoch: undefined };
59
36
  }
60
- function componentsToOutputValue(components) {
37
+ function componentsToOutputValue(components, withTime, native) {
61
38
  if (!components.year || !components.month || !components.day) {
62
39
  return null;
63
40
  }
64
- const dateObj = componentsToDateObject(components);
41
+ const c = effectiveComponents(components, withTime);
42
+ if (native)
43
+ return componentsToLocalValue(c, withTime);
44
+ if (c.epoch !== undefined)
45
+ return new Date(c.epoch).toISOString();
46
+ const dateObj = componentsToDateObject(c);
65
47
  return dateObj ? dateObj.toISOString() : null;
66
48
  }
67
49
  function componentsToLocalValue(components, withTime) {
@@ -74,27 +56,34 @@ const day = components.day.toString().padStart(2, '0');
74
56
  if (withTime) {
75
57
  const hour = (components.hour || 0).toString().padStart(2, '0');
76
58
  const minute = (components.minute || 0).toString().padStart(2, '0');
77
- return `${year}-${month}-${day}T${hour}:${minute}`;
59
+ const second = components.second ? `:${components.second.toString().padStart(2, '0')}` : '';
60
+ return `${year}-${month}-${day}T${hour}:${minute}${second}`;
78
61
  }
79
62
  else {
80
63
  return `${year}-${month}-${day}`;
81
64
  }
82
65
  }
83
- function formatDisplayValue(components, formatType, locale, withTime) {
66
+ function displayDate(components, native) {
67
+ return native
68
+ ? { date: wallClockDate(components), timeZone: 'UTC' }
69
+ : { date: componentsToDateObject(components) };
70
+ }
71
+ function formatDisplayValue(components, formatType, locale, withTime, native) {
84
72
  if (!components.year || !components.month || !components.day) {
85
73
  return null;
86
74
  }
87
- const dateObj = componentsToDateObject(components);
88
- if (!dateObj)
75
+ const { date, timeZone } = displayDate(components, native);
76
+ if (!date)
89
77
  return null;
90
78
  const options = {
91
79
  dateStyle: formatType || 'long',
80
+ timeZone,
92
81
  };
93
82
  if (withTime) {
94
83
  options.timeStyle = 'short';
95
84
  }
96
85
  const formatter = new Intl.DateTimeFormat(locale, options);
97
- return formatter.format(dateObj);
86
+ return formatter.format(date);
98
87
  }
99
88
  class TimeFields {
100
89
  constructor(hourEl, minuteEl, onChange, hour = 0, minute = 0) {
@@ -213,11 +202,16 @@ withTime: false,
213
202
  open: false,
214
203
  };
215
204
  this._closeTimestamp = 0;
205
+ this._initialized = false;
206
+ this._syncing = false;
216
207
  this._customFlavorSheet = null;
217
208
  const shadow = this.shadowRoot;
218
209
  ensureStyles(shadow, { css: datePickerStyles, id: 'ty-date-picker' });
219
210
  }
220
211
  onConnect() {
212
+ if (this._initialized)
213
+ this._state.open = false;
214
+ else
221
215
  this.initializeState();
222
216
  this.render();
223
217
  this.updateFormValue();
@@ -253,71 +247,58 @@ break;
253
247
  case 'noClear':
254
248
  case 'format':
255
249
  case 'locale':
250
+ case 'error':
256
251
  break;
257
252
  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) {
253
+ if (!this._initialized || this._syncing)
254
+ break;
264
255
  this._state = {
265
256
  withTime: this._state.withTime,
266
257
  open: this._state.open,
258
+ ...parseDateTime(newValue),
267
259
  };
268
- }
269
260
  this.syncFormValue();
270
261
  break;
271
262
  }
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
263
  case 'with-time': {
264
+ if (!this._initialized)
265
+ break;
294
266
  const newWithTime = newValue;
295
- const oldWithTime = this._state.withTime;
296
- if (newWithTime !== oldWithTime) {
267
+ if (newWithTime !== this._state.withTime) {
297
268
  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
269
  this.syncFormValue();
303
- }
270
+ this.updateValidity();
304
271
  }
305
272
  break;
306
273
  }
274
+ case 'native':
275
+ if (this._initialized)
276
+ this.syncFormValue();
277
+ break;
307
278
  }
308
279
  }
309
280
  }
310
281
  getFormValue() {
311
- return componentsToOutputValue(this.getComponents());
282
+ return this.outputValue(this.getComponents());
283
+ }
284
+ outputValue(components) {
285
+ return componentsToOutputValue(components, this._state.withTime, !!this.getProperty('native'));
312
286
  }
313
287
  updateFormValue() {
314
288
  this._internals.setFormValue(this.getFormValue() ?? '');
315
289
  this.updateValidity();
316
290
  }
291
+ focusTarget() {
292
+ return this.shadowRoot?.querySelector('.native-date-input')
293
+ ?? this.shadowRoot?.querySelector('.date-picker-stub')
294
+ ?? null;
295
+ }
317
296
  focus(options) {
318
- const stub = this.shadowRoot?.querySelector('.date-picker-stub');
319
- if (stub)
320
- stub.focus(options);
297
+ if (this.getProperty('disabled'))
298
+ return;
299
+ const target = this.focusTarget();
300
+ if (target)
301
+ target.focus(options);
321
302
  else
322
303
  super.focus(options);
323
304
  }
@@ -326,8 +307,8 @@ if (this.getProperty('disabled') || !this.getProperty('required') || this.getFor
326
307
  this.applyValidity({});
327
308
  return;
328
309
  }
329
- const anchor = this.shadowRoot?.querySelector('.date-picker-stub, .native-date-input');
330
- this.applyValidity({ valueMissing: true }, 'Please select a date', anchor ?? undefined);
310
+ const type = this._state.withTime ? 'datetime-local' : 'date';
311
+ this.applyValidity({ valueMissing: true }, nativeMessage('input', { type, required: true }), this.focusTarget() ?? undefined);
331
312
  }
332
313
  getComponents() {
333
314
  return {
@@ -336,6 +317,8 @@ month: this._state.month,
336
317
  day: this._state.day,
337
318
  hour: this._state.hour,
338
319
  minute: this._state.minute,
320
+ second: this._state.second,
321
+ epoch: this._state.epoch,
339
322
  };
340
323
  }
341
324
  hasDate() {
@@ -345,25 +328,15 @@ getFormattedValue(components) {
345
328
  const c = components || this.getComponents();
346
329
  if (!c.year || !c.month || !c.day)
347
330
  return null;
348
- return formatDisplayValue(c, this.getProperty('format') || 'long', getEffectiveLocale(this, this.getProperty('locale')), this._state.withTime);
331
+ return formatDisplayValue(c, this.getProperty('format') || 'long', getEffectiveLocale(this, this.getProperty('locale')), this._state.withTime, !!this.getProperty('native'));
349
332
  }
350
333
  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
334
  this._state = {
356
- withTime,
335
+ ...parseDateTime(this.getProperty('value')),
336
+ withTime: this.getProperty('with-time'),
357
337
  open: false,
358
338
  };
359
- }
360
- else {
361
- this._state = {
362
- ...components,
363
- withTime,
364
- open: false,
365
- };
366
- }
339
+ this._initialized = true;
367
340
  this.syncFormValue();
368
341
  }
369
342
  updateState(updates, forceSync = false) {
@@ -374,24 +347,26 @@ this.syncFormValue();
374
347
  }
375
348
  }
376
349
  syncFormValue() {
377
- const utcValue = this.getFormValue();
350
+ const formValue = this.getFormValue();
378
351
  const currentValue = this.getProperty('value');
379
- if (utcValue !== currentValue) {
380
- if (utcValue) {
381
- this.setProperty('value', utcValue);
352
+ if (formValue !== currentValue) {
353
+ this._syncing = true;
354
+ try {
355
+ this.setProperty('value', formValue || null);
382
356
  }
383
- else {
384
- this.setProperty('value', null);
357
+ finally {
358
+ this._syncing = false;
385
359
  }
386
360
  }
387
361
  }
388
362
  handleTimeInputChange(hour, minute) {
389
363
  if (!this.hasDate())
390
364
  return;
391
- const components = { ...this.getComponents(), hour, minute };
365
+ const components = { ...this.getComponents(), hour, minute, second: 0, epoch: undefined };
392
366
  this.updateState(components, true);
393
367
  this.emitChangeEvent(components, 'time-change');
394
368
  this.updateTimeSummary();
369
+ this.revealValidity();
395
370
  }
396
371
  updateTimeSummary(summary = this.shadowRoot?.querySelector('.time-summary')) {
397
372
  if (!summary)
@@ -405,19 +380,22 @@ iso.textContent = '';
405
380
  return;
406
381
  }
407
382
  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);
383
+ const { date, timeZone } = displayDate(c, !!this.getProperty('native'));
384
+ const day = new Intl.DateTimeFormat(locale, { weekday: 'short', month: 'short', day: 'numeric', timeZone }).format(date);
385
+ const time = new Intl.DateTimeFormat(locale, { hour: '2-digit', minute: '2-digit', hourCycle: 'h23', timeZone }).format(date);
411
386
  text.textContent = `${day} · ${time}`;
412
387
  iso.textContent = componentsToLocalValue(c, true) ?? '';
413
388
  }
414
389
  emitChangeEvent(components, source) {
415
- const utcValue = components ? componentsToOutputValue(components) : null;
390
+ const value = components ? this.outputValue(components) : null;
416
391
  const localValue = components ? componentsToLocalValue(components, this._state.withTime) : null;
417
- const milliseconds = components ? componentsToDateObject(components)?.getTime() ?? null : null;
392
+ const effective = components && effectiveComponents(components, this._state.withTime);
393
+ const milliseconds = effective
394
+ ? effective.epoch ?? componentsToDateObject(effective)?.getTime() ?? null
395
+ : null;
418
396
  const formatted = components ? this.getFormattedValue(components) : null;
419
397
  const detail = {
420
- value: utcValue,
398
+ value,
421
399
  localValue,
422
400
  milliseconds,
423
401
  formatted,
@@ -458,16 +436,26 @@ get max() { return this.getProperty('max'); }
458
436
  set max(v) { this.setProperty('max', v ?? ''); }
459
437
  get withTime() { return this.getProperty('with-time'); }
460
438
  set withTime(v) { this.setProperty('with-time', v); }
439
+ get native() { return this.getProperty('native'); }
440
+ set native(v) { this.setProperty('native', v); }
441
+ get error() { return this.getProperty('error'); }
442
+ set error(v) { this.setProperty('error', v); }
461
443
  get value() {
444
+ if (this._initialized)
462
445
  return this.getProperty('value') || null;
446
+ const parsed = parseDateTime(this.getProperty('value'));
447
+ return parsed && componentsToOutputValue(parsed, !!this.getProperty('with-time'), !!this.getProperty('native'));
463
448
  }
464
449
  set value(val) {
465
450
  if (val === null || val === undefined || val === '') {
466
451
  this.setProperty('value', null);
467
452
  }
453
+ else if (typeof val === 'string') {
454
+ this.setProperty('value', val);
455
+ }
468
456
  else {
469
- const strValue = val instanceof Date ? val.toISOString() : val;
470
- this.setProperty('value', strValue);
457
+ const date = new Date(val);
458
+ this.setProperty('value', isNaN(date.getTime()) ? null : date.toISOString());
471
459
  }
472
460
  }
473
461
  clear() {
@@ -479,6 +467,8 @@ if (this.getProperty('disabled'))
479
467
  classes.push('disabled');
480
468
  if (this.getProperty('required'))
481
469
  classes.push('required');
470
+ if (this.getProperty('error'))
471
+ classes.push('error');
482
472
  if (this._state.open)
483
473
  classes.push('open');
484
474
  return classes.join(' ');
@@ -501,7 +491,7 @@ stub.setAttribute('aria-labelledby', `date-picker-label-${getElementHash(this)}`
501
491
  }
502
492
  stub.addEventListener('keydown', (e) => {
503
493
  const ke = e;
504
- if (this._state.open)
494
+ if (this._state.open || e.target !== stub)
505
495
  return;
506
496
  if (ke.key === 'Enter' || ke.key === ' ') {
507
497
  ke.preventDefault();
@@ -597,7 +587,7 @@ const contentWrapper = document.createElement('div');
597
587
  contentWrapper.className = 'calendar-content';
598
588
  const calendar = document.createElement('ty-calendar');
599
589
  if (this._state.year && this._state.month && this._state.day) {
600
- calendar.setAttribute('year', this._state.year.toString());
590
+ calendar.setAttribute('year', String(this._state.year).padStart(4, '0'));
601
591
  calendar.setAttribute('month', this._state.month.toString());
602
592
  calendar.setAttribute('day', this._state.day.toString());
603
593
  }
@@ -611,8 +601,8 @@ if (min)
611
601
  calendar.setAttribute('min', min);
612
602
  if (max)
613
603
  calendar.setAttribute('max', max);
614
- const flavor = this.getProperty('flavor');
615
- if (flavor && flavor !== 'default')
604
+ const flavor = this.calendarFlavor();
605
+ if (flavor)
616
606
  calendar.setAttribute('flavor', flavor);
617
607
  calendar.addEventListener('change', (e) => this.handleCalendarChange(e));
618
608
  contentWrapper.appendChild(calendar);
@@ -620,11 +610,18 @@ if (this._state.withTime) {
620
610
  contentWrapper.appendChild(this.renderTimeSection());
621
611
  }
622
612
  dialog.appendChild(contentWrapper);
623
- dialog.addEventListener('close', () => {
624
- this.updateState({ open: false });
625
- });
613
+ new MutationObserver(() => {
614
+ if (this._state.open && !dialog.open && dialog.isConnected && this.isConnected)
615
+ this.closeDropdown();
616
+ }).observe(dialog, { attributes: true, attributeFilter: ['open'] });
626
617
  return dialog;
627
618
  }
619
+ calendarFlavor() {
620
+ if (this.getProperty('error'))
621
+ return 'danger';
622
+ const flavor = this.getProperty('flavor');
623
+ return flavor && flavor !== 'default' ? flavor : null;
624
+ }
628
625
  renderNativeInput() {
629
626
  const wrapper = document.createElement('div');
630
627
  wrapper.className = 'dropdown-wrapper';
@@ -636,21 +633,8 @@ stub.setAttribute('disabled', 'true');
636
633
  }
637
634
  const input = document.createElement('input');
638
635
  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
- }
636
+ this.syncNativeInput(input);
637
+ const localValue = input.value;
654
638
  const placeholder = this.getProperty('placeholder') || 'Select date...';
655
639
  const formattedValue = this.getFormattedValue();
656
640
  const placeholderEl = document.createElement('span');
@@ -659,13 +643,17 @@ placeholderEl.textContent = formattedValue || placeholder;
659
643
  input.addEventListener('change', () => {
660
644
  if (!input.value) {
661
645
  this.clearValue();
662
- return;
663
646
  }
664
- const parsed = parseValue(input.value, this._state.withTime);
647
+ else {
648
+ const parsed = parseDateTime(input.value);
665
649
  if (parsed) {
666
- this.updateState(parsed, true);
667
- this.emitChangeEvent(parsed, 'selection');
650
+ const { hour, minute, second } = this._state.withTime ? parsed : this._state;
651
+ const components = { ...parsed, hour, minute, second, epoch: undefined };
652
+ this.updateState(components, true);
653
+ this.emitChangeEvent(components, 'selection');
654
+ }
668
655
  }
656
+ this.revealValidity();
669
657
  });
670
658
  const startSlot = document.createElement('slot');
671
659
  startSlot.name = 'start';
@@ -691,6 +679,23 @@ stub.appendChild(iconContainer);
691
679
  wrapper.appendChild(stub);
692
680
  return wrapper;
693
681
  }
682
+ syncNativeInput(input) {
683
+ const withTime = this._state.withTime;
684
+ input.type = withTime ? 'datetime-local' : 'date';
685
+ input.disabled = !!this.getProperty('disabled');
686
+ input.required = !!this.getProperty('required');
687
+ const min = toISODay(this.getProperty('min'));
688
+ const max = toISODay(this.getProperty('max'));
689
+ if (min)
690
+ input.min = withTime ? `${min}T00:00` : min;
691
+ else
692
+ input.removeAttribute('min');
693
+ if (max)
694
+ input.max = withTime ? `${max}T23:59` : max;
695
+ else
696
+ input.removeAttribute('max');
697
+ input.value = componentsToLocalValue(this.getComponents(), withTime) ?? '';
698
+ }
694
699
  calculateCalendarPosition() {
695
700
  if (!this.shadowRoot)
696
701
  return;
@@ -757,6 +762,8 @@ this.shadowRoot.innerHTML = '';
757
762
  const container = this.renderContainer();
758
763
  container.appendChild(this.renderNativeInput());
759
764
  this.shadowRoot.appendChild(container);
765
+ this.syncErrorLine();
766
+ this.updateValidity();
760
767
  return;
761
768
  }
762
769
  const existingDialog = this.shadowRoot.querySelector('.calendar-dialog');
@@ -766,6 +773,7 @@ this.updateDisplay();
766
773
  this.calculateCalendarPosition();
767
774
  return;
768
775
  }
776
+ const hadFocus = !!this.shadowRoot.activeElement;
769
777
  this.shadowRoot.innerHTML = '';
770
778
  const container = this.renderContainer();
771
779
  const wrapper = document.createElement('div');
@@ -774,7 +782,33 @@ wrapper.appendChild(this.renderStub());
774
782
  wrapper.appendChild(this.renderCalendarDropdown());
775
783
  container.appendChild(wrapper);
776
784
  this.shadowRoot.appendChild(container);
785
+ this.syncErrorLine();
777
786
  this.setupEventListeners();
787
+ this.updateValidity();
788
+ if (hadFocus)
789
+ this.focus({ preventScroll: true });
790
+ }
791
+ syncErrorLine() {
792
+ const container = this.shadowRoot?.querySelector('.dropdown-container');
793
+ if (!container)
794
+ return;
795
+ const error = this.getProperty('error');
796
+ const errorId = `date-picker-error-${getElementHash(this)}`;
797
+ let line = container.querySelector(':scope > .error-message');
798
+ if (error) {
799
+ if (!line) {
800
+ line = document.createElement('div');
801
+ line.className = 'error-message';
802
+ line.id = errorId;
803
+ container.appendChild(line);
804
+ }
805
+ line.textContent = error;
806
+ this.focusTarget()?.setAttribute('aria-describedby', errorId);
807
+ }
808
+ else {
809
+ line?.remove();
810
+ this.focusTarget()?.removeAttribute('aria-describedby');
811
+ }
778
812
  }
779
813
  updateDisplay() {
780
814
  if (!this.shadowRoot)
@@ -793,23 +827,26 @@ const clearButton = this.shadowRoot.querySelector('.stub-clear');
793
827
  if (clearButton) {
794
828
  clearButton.style.display = (clearable && hasValue && !isDisabled) ? '' : 'none';
795
829
  }
830
+ const stub = this.shadowRoot.querySelector('.date-picker-stub');
831
+ if (stub)
832
+ stub.className = this.buildStubClasses();
833
+ this.syncErrorLine();
796
834
  if (isMobileTouch()) {
797
835
  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
- }
836
+ if (nativeInput)
837
+ this.syncNativeInput(nativeInput);
838
+ stub?.toggleAttribute('disabled', !!isDisabled);
802
839
  return;
803
840
  }
804
841
  const calendar = this.shadowRoot.querySelector('ty-calendar');
805
842
  if (calendar && hasValue) {
806
- calendar.setAttribute('year', this._state.year.toString());
843
+ calendar.setAttribute('year', String(this._state.year).padStart(4, '0'));
807
844
  calendar.setAttribute('month', this._state.month.toString());
808
845
  calendar.setAttribute('day', this._state.day.toString());
809
846
  }
810
847
  if (calendar) {
811
- const flavor = this.getProperty('flavor');
812
- if (flavor && flavor !== 'default')
848
+ const flavor = this.calendarFlavor();
849
+ if (flavor)
813
850
  calendar.setAttribute('flavor', flavor);
814
851
  else
815
852
  calendar.removeAttribute('flavor');
@@ -851,7 +888,7 @@ event.preventDefault();
851
888
  const dialog = this.shadowRoot?.querySelector('.calendar-dialog');
852
889
  if (this._state.open && (!dialog || !dialog.open)) {
853
890
  this.updateState({ open: false }, true);
854
- unlockScroll(`date-picker-${this.id || this.toString()}`);
891
+ unlockScroll(`date-picker-${getElementHash(this)}`);
855
892
  }
856
893
  const disabled = this.getProperty('disabled');
857
894
  if (!disabled && Date.now() - this._closeTimestamp > 300) {
@@ -865,6 +902,8 @@ month: undefined,
865
902
  day: undefined,
866
903
  hour: undefined,
867
904
  minute: undefined,
905
+ second: undefined,
906
+ epoch: undefined,
868
907
  }, true);
869
908
  this.value = null;
870
909
  this.emitChangeEvent(null, 'clear');
@@ -874,6 +913,11 @@ handleClearClick(event) {
874
913
  event.preventDefault();
875
914
  event.stopPropagation();
876
915
  this.clearValue();
916
+ const active = document.activeElement;
917
+ if (this.matches(':focus-within') || !active || active === document.body) {
918
+ this.shadowRoot?.querySelector('.native-date-input')?.focus({ preventScroll: true });
919
+ }
920
+ this.revealValidity();
877
921
  }
878
922
  handleCalendarChange(event) {
879
923
  event.preventDefault();
@@ -889,9 +933,12 @@ month: dayContext.month,
889
933
  day: dayContext.dayInMonth,
890
934
  hour: this._state.hour,
891
935
  minute: this._state.minute,
936
+ second: this._state.second,
937
+ epoch: undefined,
892
938
  };
893
939
  this.updateState(newComponents, true);
894
940
  this.emitChangeEvent(newComponents, 'selection');
941
+ this.revealValidity();
895
942
  if (!this._state.withTime) {
896
943
  this.closeDropdown();
897
944
  }
@@ -945,9 +992,15 @@ this.render();
945
992
  requestAnimationFrame(() => {
946
993
  if (!this.shadowRoot)
947
994
  return;
995
+ if (!this._state.open || !this.isConnected)
996
+ return;
997
+ if (this.shadowRoot.querySelector('.date-picker-stub')?.checkVisibility() === false) {
998
+ this.closeDropdown();
999
+ return;
1000
+ }
948
1001
  const dialog = this.shadowRoot.querySelector('.calendar-dialog');
949
1002
  if (dialog) {
950
- const pickerId = `date-picker-${this.id || this.toString()}`;
1003
+ const pickerId = `date-picker-${getElementHash(this)}`;
951
1004
  lockScroll(pickerId);
952
1005
  dialog.showModal();
953
1006
  this.calculateCalendarPosition();
@@ -966,7 +1019,7 @@ closeDropdown() {
966
1019
  if (!this._state.open)
967
1020
  return;
968
1021
  this._closeTimestamp = Date.now();
969
- const pickerId = `date-picker-${this.id || this.toString()}`;
1022
+ const pickerId = `date-picker-${getElementHash(this)}`;
970
1023
  this.updateState({ open: false }, true);
971
1024
  unlockScroll(pickerId);
972
1025
  if (this.shadowRoot) {
@@ -981,6 +1034,7 @@ stubEl.setAttribute('aria-expanded', 'false');
981
1034
  }
982
1035
  this.dispatchEvent(new CustomEvent('close'));
983
1036
  this.render();
1037
+ this.focus();
984
1038
  }
985
1039
  cleanup() {
986
1040
  if (this._clickListener) {
@@ -996,7 +1050,7 @@ dialog.removeEventListener('click', this._dialogClickListener);
996
1050
  }
997
1051
  }
998
1052
  if (this._state.open) {
999
- const pickerId = `date-picker-${this.id || this.toString()}`;
1053
+ const pickerId = `date-picker-${getElementHash(this)}`;
1000
1054
  unlockScroll(pickerId);
1001
1055
  }
1002
1056
  this._clickListener = undefined;
@@ -1069,6 +1123,11 @@ type: 'boolean',
1069
1123
  visual: true,
1070
1124
  default: false
1071
1125
  },
1126
+ error: {
1127
+ type: 'string',
1128
+ visual: true,
1129
+ default: ''
1130
+ },
1072
1131
  format: {
1073
1132
  type: 'string',
1074
1133
  visual: true,
@@ -1099,6 +1158,11 @@ default: null
1099
1158
  type: 'boolean',
1100
1159
  visual: true,
1101
1160
  default: false
1161
+ },
1162
+ native: {
1163
+ type: 'boolean',
1164
+ visual: true,
1165
+ default: false
1102
1166
  }
1103
1167
  };
1104
1168
  TyDatePicker.formAssociated = true;
@@ -81,7 +81,8 @@ export declare class TyFileUpload extends TyComponent<FileUploadState> implement
81
81
  protected onFormReset(): void;
82
82
  protected updateFormValue(): void;
83
83
  /** Report `required` to the owning form, like ty-input and ty-textarea
84
- do — without this the asterisk showed but submission went through. */
84
+ do — without this the asterisk showed but submission went through.
85
+ The message is a native file input's. */
85
86
  private updateValidity;
86
87
  protected getFormValue(): FormData | string | null;
87
88
  /** Focus the drop zone: the host itself isn't focusable. */