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
@@ -9,6 +9,8 @@
9
9
  * - Property change events via 'prop:change' (separate from user 'change' events)
10
10
  */
11
11
  import { PropertyManager, PropertyConfig, PropertyChange } from '../utils/property-manager.js';
12
+ import { upgradeProperties } from '../utils/upgrade-properties.js';
13
+ export { upgradeProperties };
12
14
  /**
13
15
  * Base class for all Ty components with unified property lifecycle
14
16
  */
@@ -20,6 +22,9 @@ export declare abstract class TyComponent<T = any> extends HTMLElement {
20
22
  private _validity;
21
23
  protected _connected: boolean;
22
24
  protected _rendered: boolean;
25
+ /** The user edited the field since it was created or its form reset:
26
+ * blur then shows its validation message (like native :user-invalid). */
27
+ protected _userEdited: boolean;
23
28
  constructor();
24
29
  /**
25
30
  * Define which attributes to observe
@@ -56,10 +61,11 @@ export declare abstract class TyComponent<T = any> extends HTMLElement {
56
61
  */
57
62
  disconnectedCallback(): void;
58
63
  /**
59
- * Capture properties that were set before connectedCallback
60
- * (React, Vue, Reagent set properties before element is connected)
64
+ * upgradeProperties(), plus declared properties without an accessor of
65
+ * their own name (e.g. ty-input's `minlength`): those go straight to
66
+ * setProperty().
61
67
  */
62
- private _capturePreSetProperties;
68
+ private _upgradeProperties;
63
69
  /**
64
70
  * THE CORE LIFECYCLE METHOD
65
71
  * All property changes flow through here
@@ -157,6 +163,9 @@ export declare abstract class TyComponent<T = any> extends HTMLElement {
157
163
  * semantics). Non-form state (e.g. a checkbox's submission `value`, a
158
164
  * ty-radio's option `value`) is deliberately left alone — the old
159
165
  * blanket 'value' reset corrupted radio groups by wiping child values.
166
+ * Like a native control the field also looks untouched again: the
167
+ * validation message it showed is gone (an `error` the author set stays)
168
+ * and a blur shows nothing until the user edits it.
160
169
  */
161
170
  formResetCallback(): void;
162
171
  /**
@@ -1,10 +1,13 @@
1
1
  import { PropertyManager } from '../utils/property-manager.js';
2
2
  import { ValidityController } from '../utils/validity.js';
3
+ import { upgradeProperties } from '../utils/upgrade-properties.js';
4
+ export { upgradeProperties };
3
5
  export class TyComponent extends HTMLElement {
4
6
  constructor() {
5
7
  super();
6
8
  this._connected = false;
7
9
  this._rendered = false;
10
+ this._userEdited = false;
8
11
  this._handlingActivationClick = false;
9
12
  this._resetDefaults = null;
10
13
  const ctor = this.constructor;
@@ -81,7 +84,6 @@ this._handlePropertyChanges([change]);
81
84
  }
82
85
  }
83
86
  connectedCallback() {
84
- this._connected = true;
85
87
  if (!this._resetDefaults) {
86
88
  this._resetDefaults = new Map();
87
89
  const ctor = this.constructor;
@@ -92,10 +94,8 @@ const attr = this.getAttribute(this._toKebabCase(name));
92
94
  this._resetDefaults.set(name, attr ?? (typeof config.default === 'function' ? config.default() : config.default) ?? null);
93
95
  }
94
96
  }
95
- const preSetProps = this._capturePreSetProperties();
96
- if (preSetProps.length > 0) {
97
- this._handlePropertyChanges(preSetProps);
98
- }
97
+ this._upgradeProperties();
98
+ this._connected = true;
99
99
  this.onConnect();
100
100
  if (!this._rendered) {
101
101
  this.render();
@@ -106,19 +106,20 @@ disconnectedCallback() {
106
106
  this._connected = false;
107
107
  this.onDisconnect();
108
108
  }
109
- _capturePreSetProperties() {
110
- const changes = [];
111
- const ctor = this.constructor;
112
- for (const propName of Object.keys(ctor.properties)) {
113
- const descriptor = Object.getOwnPropertyDescriptor(this, propName);
114
- if (descriptor && descriptor.value !== undefined) {
115
- const change = this.props.updateProperty(propName, descriptor.value, 'property');
116
- if (change) {
117
- changes.push(change);
118
- }
119
- }
109
+ _upgradeProperties() {
110
+ for (const name of Object.keys(this.constructor.properties)) {
111
+ if (!Object.prototype.hasOwnProperty.call(this, name))
112
+ continue;
113
+ const value = this[name];
114
+ delete this[name];
115
+ if (value === undefined)
116
+ continue;
117
+ if (name in this)
118
+ this[name] = value;
119
+ else
120
+ this.setProperty(name, value);
120
121
  }
121
- return changes;
122
+ upgradeProperties(this);
122
123
  }
123
124
  _handlePropertyChanges(changes) {
124
125
  if (changes.length === 0)
@@ -220,6 +221,9 @@ queueMicrotask(() => { this._handlingActivationClick = false; });
220
221
  return false;
221
222
  }
222
223
  formResetCallback() {
224
+ this._userEdited = false;
225
+ if (this._validates)
226
+ this._validity.reset();
223
227
  for (const [name, def] of this._resetDefaults ?? []) {
224
228
  this.setProperty(name, def);
225
229
  }
@@ -89,11 +89,15 @@ export declare class TyButton extends TyComponent<ButtonState> implements TyButt
89
89
  };
90
90
  };
91
91
  private _structureInitialized;
92
+ private _formDisabled;
92
93
  private _customFlavorSheet;
94
+ private _ownClick;
93
95
  constructor();
94
96
  protected onConnect(): void;
95
97
  protected onDisconnect(): void;
96
98
  protected onPropertiesChanged(changes: PropertyChange[]): void;
99
+ formDisabledCallback(disabled: boolean): void;
100
+ private get _isDisabled();
97
101
  /**
98
102
  * Custom flavors: adopt generated per-flavor rules pointed at the user's
99
103
  * design tokens (--ty-solid-X / --ty-color-X / --ty-bg-X), falling back to
@@ -8,8 +8,19 @@ export class TyButton extends TyComponent {
8
8
  constructor() {
9
9
  super();
10
10
  this._structureInitialized = false;
11
+ this._formDisabled = false;
11
12
  this._customFlavorSheet = null;
13
+ this._ownClick = null;
12
14
  ensureStyles(this.shadowRoot, { css: buttonStyles, id: "ty-button" });
15
+ this.addEventListener("click", (e) => {
16
+ const button = this.shadowRoot.querySelector("button");
17
+ if (!button || e === this._ownClick)
18
+ return;
19
+ if (e.composedPath().includes(button))
20
+ return;
21
+ e.stopImmediatePropagation();
22
+ button.click();
23
+ }, true);
13
24
  }
14
25
  onConnect() {
15
26
  this._syncCustomFlavor();
@@ -20,6 +31,14 @@ if (changes.some((c) => c.name === "flavor")) {
20
31
  this._syncCustomFlavor();
21
32
  }
22
33
  }
34
+ formDisabledCallback(disabled) {
35
+ this._formDisabled = disabled;
36
+ if (this._structureInitialized)
37
+ this.render();
38
+ }
39
+ get _isDisabled() {
40
+ return this.disabled || this._formDisabled;
41
+ }
23
42
  _syncCustomFlavor() {
24
43
  const { base } = this.parseFlavor();
25
44
  this._customFlavorSheet = syncCustomFlavorSheet(this.shadowRoot, this._customFlavorSheet, base, () => buttonCustomFlavorCss(base));
@@ -136,7 +155,7 @@ initializeButtonStructure() {
136
155
  const shadow = this.shadowRoot;
137
156
  const classes = this.buildClasses();
138
157
  const button = document.createElement("button");
139
- button.disabled = this.disabled;
158
+ button.disabled = this._isDisabled;
140
159
  button.className = classes;
141
160
  button.setAttribute("part", "button");
142
161
  const loader = document.createElement("span");
@@ -155,15 +174,21 @@ button.appendChild(startSlot);
155
174
  button.appendChild(defaultSlot);
156
175
  button.appendChild(endSlot);
157
176
  button.addEventListener("click", (e) => {
158
- if (this.disabled || this.loading)
177
+ const path = e.composedPath();
178
+ if (path.slice(0, path.indexOf(button)).some((n) => n instanceof Element && n.localName === "ty-popup"))
159
179
  return;
160
180
  e.stopPropagation();
161
- this.handleFormAction();
162
- this.dispatchEvent(new CustomEvent("click", {
181
+ if (this._isDisabled || this.loading)
182
+ return;
183
+ const click = new CustomEvent("click", {
163
184
  bubbles: true,
164
185
  composed: true,
186
+ cancelable: true,
165
187
  detail: { originalEvent: e },
166
- }));
188
+ });
189
+ this._ownClick = click;
190
+ if (this.dispatchEvent(click))
191
+ this.handleFormAction();
167
192
  });
168
193
  this.applyLoadingState(button);
169
194
  shadow.appendChild(button);
@@ -174,12 +199,14 @@ const isLoading = this.loading;
174
199
  button.classList.toggle("loading", isLoading);
175
200
  if (isLoading) {
176
201
  button.setAttribute("aria-busy", "true");
202
+ button.setAttribute("aria-disabled", "true");
177
203
  const loader = button.querySelector(".loader-icon");
178
204
  if (loader)
179
205
  loader.innerHTML = getLoaderSvg();
180
206
  }
181
207
  else {
182
208
  button.removeAttribute("aria-busy");
209
+ button.removeAttribute("aria-disabled");
183
210
  }
184
211
  }
185
212
  render() {
@@ -191,7 +218,7 @@ return;
191
218
  const classes = this.buildClasses();
192
219
  const button = shadow.querySelector("button");
193
220
  if (button) {
194
- button.disabled = this.disabled;
221
+ button.disabled = this._isDisabled;
195
222
  button.className = classes;
196
223
  this.applyLoadingState(button);
197
224
  }
@@ -1,4 +1,5 @@
1
1
  import { ensureStyles } from '../utils/styles.js';
2
+ import { liftProperties, upgradeProperties } from '../utils/upgrade-properties.js';
2
3
  import { syncCustomFlavorSheet } from '../utils/flavor-sheet.js';
3
4
  import { calendarMonthStyles, calendarMonthCustomFlavorCss } from '../styles/calendar-month.js';
4
5
  import { getCalendarMonthDays, getLocalizedWeekdays, parseISODate } from '../utils/calendar-utils.js';
@@ -53,6 +54,7 @@ const today = new Date();
53
54
  this._displayYear = today.getFullYear();
54
55
  this._displayMonth = today.getMonth() + 1;
55
56
  this.attachShadow({ mode: 'open' });
57
+ liftProperties(this);
56
58
  }
57
59
  connectedCallback() {
58
60
  if (!this._displayMonth) {
@@ -68,16 +70,7 @@ const sizeAttr = this.getAttribute('size');
68
70
  if (sizeAttr && (sizeAttr === 'sm' || sizeAttr === 'md' || sizeAttr === 'lg')) {
69
71
  this._size = sizeAttr;
70
72
  }
71
- const plainDayContentFn = this.dayContentFn;
72
- if (plainDayContentFn && !this._dayContentFn) {
73
- this._dayContentFn = plainDayContentFn;
74
- delete this.dayContentFn;
75
- }
76
- const plainCustomCSS = this.customCSS;
77
- if (plainCustomCSS && !this._customCSS) {
78
- this._customCSS = plainCustomCSS;
79
- delete this.customCSS;
80
- }
73
+ upgradeProperties(this);
81
74
  this._localeObserver = observeLocaleChanges(this, () => {
82
75
  this.render();
83
76
  });
@@ -50,6 +50,7 @@ export declare class TyCalendar extends HTMLElement {
50
50
  private _internals?;
51
51
  private _validity?;
52
52
  private _initialValue;
53
+ private _valueSets;
53
54
  private _localeObserver?;
54
55
  static formAssociated: boolean;
55
56
  static get observedAttributes(): string[];
@@ -94,7 +95,8 @@ export declare class TyCalendar extends HTMLElement {
94
95
  private syncChildComponents;
95
96
  private updateFormValue;
96
97
  /**
97
- * Selection outside [min, max] => rangeUnderflow/rangeOverflow.
98
+ * Selection outside [min, max] => rangeUnderflow/rangeOverflow, with a
99
+ * native date input's message.
98
100
  * UI can't produce this (days are disabled), but programmatic values can.
99
101
  */
100
102
  private updateValidity;
@@ -1,10 +1,11 @@
1
1
  import { ensureStyles } from '../utils/styles.js';
2
+ import { liftProperties, upgradeProperties } from '../utils/upgrade-properties.js';
2
3
  import { calendarStyles } from '../styles/calendar.js';
3
4
  import './calendar-month.js';
4
5
  import './calendar-navigation.js';
5
6
  import { parseISODate, getMonthName } from '../utils/calendar-utils.js';
6
7
  import { getEffectiveLocale, observeLocaleChanges } from '../utils/locale.js';
7
- import { ValidityController } from '../utils/validity.js';
8
+ import { ValidityController, nativeMessage } from '../utils/validity.js';
8
9
  function getCurrentDate() {
9
10
  const now = new Date();
10
11
  return {
@@ -49,12 +50,14 @@ this._flavor = 'primary';
49
50
  this._view = 'days';
50
51
  this._dayGridHeight = 0;
51
52
  this._initialValue = '';
53
+ this._valueSets = 0;
52
54
  const current = getCurrentDate();
53
55
  this._state = {
54
56
  displayYear: current.year,
55
57
  displayMonth: current.month,
56
58
  };
57
59
  this.attachShadow({ mode: 'open' });
60
+ liftProperties(this);
58
61
  if ('attachInternals' in this) {
59
62
  this._internals = this.attachInternals();
60
63
  this._validity = new ValidityController(this, this._internals);
@@ -62,36 +65,7 @@ this._validity = new ValidityController(this, this._internals);
62
65
  }
63
66
  connectedCallback() {
64
67
  this.initializeFromAttributes();
65
- const plainDayContentFn = this.dayContentFn;
66
- if (plainDayContentFn && !this._dayContentFn) {
67
- this._dayContentFn = plainDayContentFn;
68
- delete this.dayContentFn;
69
- }
70
- const plainCustomCSS = this.customCSS;
71
- if (plainCustomCSS && !this._customCSS) {
72
- this._customCSS = plainCustomCSS;
73
- delete this.customCSS;
74
- }
75
- const plainValue = this.value;
76
- if (plainValue && typeof plainValue === 'string' && plainValue !== this.value) {
77
- const match = plainValue.match(/^(\d{4})-(\d{2})-(\d{2})$/);
78
- if (match) {
79
- const year = parseInt(match[1], 10);
80
- const month = parseInt(match[2], 10);
81
- const day = parseInt(match[3], 10);
82
- this._state.selectedYear = year;
83
- this._state.selectedMonth = month;
84
- this._state.selectedDay = day;
85
- this._state.displayYear = year;
86
- this._state.displayMonth = month;
87
- if (!this._stateless) {
88
- this.setAttribute('year', year.toString());
89
- this.setAttribute('month', month.toString());
90
- this.setAttribute('day', day.toString());
91
- }
92
- }
93
- delete this.value;
94
- }
68
+ upgradeProperties(this);
95
69
  this.render();
96
70
  this.updateFormValue();
97
71
  this._localeObserver = observeLocaleChanges(this, () => {
@@ -337,18 +311,24 @@ return formatDateISO(selectedYear, selectedMonth, selectedDay);
337
311
  return '';
338
312
  }
339
313
  set value(isoDate) {
314
+ const set = ++this._valueSets;
315
+ const superseded = () => set !== this._valueSets;
340
316
  if (!isoDate) {
341
317
  delete this._state.selectedYear;
342
318
  delete this._state.selectedMonth;
343
319
  delete this._state.selectedDay;
344
320
  if (!this._stateless) {
345
321
  this.removeAttribute('year');
322
+ if (superseded())
323
+ return;
346
324
  this.removeAttribute('month');
325
+ if (superseded())
326
+ return;
347
327
  this.removeAttribute('day');
348
328
  }
349
329
  }
350
330
  else {
351
- const match = isoDate.match(/^(\d{4})-(\d{2})-(\d{2})$/);
331
+ const match = typeof isoDate === 'string' && isoDate.match(/^(\d{4})-(\d{2})-(\d{2})$/);
352
332
  if (match) {
353
333
  const year = parseInt(match[1], 10);
354
334
  const month = parseInt(match[2], 10);
@@ -360,7 +340,11 @@ this._state.displayYear = year;
360
340
  this._state.displayMonth = month;
361
341
  if (!this._stateless) {
362
342
  this.setAttribute('year', year.toString());
343
+ if (superseded())
344
+ return;
363
345
  this.setAttribute('month', month.toString());
346
+ if (superseded())
347
+ return;
364
348
  this.setAttribute('day', day.toString());
365
349
  }
366
350
  }
@@ -503,12 +487,13 @@ if (selectedYear && selectedMonth && selectedDay) {
503
487
  const sel = Date.UTC(selectedYear, selectedMonth - 1, selectedDay);
504
488
  const min = this._min ? parseISODate(this._min) : null;
505
489
  const max = this._max ? parseISODate(this._max) : null;
490
+ const message = () => nativeMessage('input', { type: 'date', min: this._min, max: this._max }, formatDateISO(selectedYear, selectedMonth, selectedDay));
506
491
  if (min && sel < Date.UTC(min.year, min.month - 1, min.day)) {
507
- this._validity.set({ rangeUnderflow: true }, `Date must be ${this._min} or later`);
492
+ this._validity.set({ rangeUnderflow: true }, message());
508
493
  return;
509
494
  }
510
495
  if (max && sel > Date.UTC(max.year, max.month - 1, max.day)) {
511
- this._validity.set({ rangeOverflow: true }, `Date must be ${this._max} or earlier`);
496
+ this._validity.set({ rangeOverflow: true }, message());
512
497
  return;
513
498
  }
514
499
  }
@@ -112,7 +112,7 @@ export declare class TyCheckbox extends TyComponent<CheckboxState> implements Ty
112
112
  protected render(): void;
113
113
  /** Focus the checkbox itself (the host is not focusable; its role element is). */
114
114
  focus(options?: FocusOptions): void;
115
- /** required + unchecked => valueMissing; otherwise valid. */
115
+ /** required + unchecked => valueMissing (the browser's own message); otherwise valid. */
116
116
  private updateValidity;
117
117
  get checked(): boolean;
118
118
  set checked(value: boolean);
@@ -3,6 +3,7 @@ import { TyComponent } from "../base/ty-component.js";
3
3
  import { ensureStyles } from "../utils/styles.js";
4
4
  import { syncCustomFlavorSheet } from "../utils/flavor-sheet.js";
5
5
  import { checkboxStyles, checkboxCustomFlavorCss } from "../styles/checkbox.js";
6
+ import { nativeMessage } from "../utils/validity.js";
6
7
  const CHECK_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>`;
7
8
  const DASH_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/></svg>`;
8
9
  export class TyCheckbox extends TyComponent {
@@ -179,7 +180,7 @@ this.applyValidity({});
179
180
  return;
180
181
  }
181
182
  if (this.required && !this.checked) {
182
- this.applyValidity({ valueMissing: true }, "Please check this box", anchor ?? undefined);
183
+ this.applyValidity({ valueMissing: true }, nativeMessage("input", { type: "checkbox", required: true }), anchor ?? undefined);
183
184
  }
184
185
  else {
185
186
  this.applyValidity({});
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * TyDatePicker Web Component — read-only input stub + calendar dropdown
3
3
  * (modal <dialog>), optional time input, form participation via
4
- * ElementInternals. Value is UTC; display is local.
4
+ * ElementInternals. Value is a UTC instant (wall-clock with `native`);
5
+ * display is local.
5
6
  */
6
7
  import { type Placement } from '../utils/positioning.js';
7
8
  import { TyComponent } from '../base/ty-component.js';
@@ -16,6 +17,8 @@ interface DateComponents {
16
17
  day?: number;
17
18
  hour?: number;
18
19
  minute?: number;
20
+ second?: number;
21
+ epoch?: number;
19
22
  }
20
23
  /**
21
24
  * Internal date picker state
@@ -99,6 +102,11 @@ export declare class TyDatePicker extends TyComponent<DatePickerState> {
99
102
  visual: boolean;
100
103
  default: boolean;
101
104
  };
105
+ error: {
106
+ type: "string";
107
+ visual: boolean;
108
+ default: string;
109
+ };
102
110
  format: {
103
111
  type: "string";
104
112
  visual: boolean;
@@ -123,12 +131,19 @@ export declare class TyDatePicker extends TyComponent<DatePickerState> {
123
131
  visual: boolean;
124
132
  default: boolean;
125
133
  };
134
+ native: {
135
+ type: "boolean";
136
+ visual: boolean;
137
+ default: boolean;
138
+ };
126
139
  };
127
140
  private _state;
128
141
  private _clickListener?;
129
142
  private _keydownListener?;
130
143
  private _dialogClickListener?;
131
144
  private _closeTimestamp;
145
+ private _initialized;
146
+ private _syncing;
132
147
  private _timeInput?;
133
148
  private _localeObserver?;
134
149
  private _customFlavorSheet;
@@ -142,16 +157,21 @@ export declare class TyDatePicker extends TyComponent<DatePickerState> {
142
157
  /** Handle state updates BEFORE render. */
143
158
  protected onPropertiesChanged(changes: PropertyChange[]): void;
144
159
  /**
145
- * Get form value - returns UTC string from current state
146
- * TyComponent calls this automatically when value property changes
160
+ * Get form value - the submitted string from current state (see
161
+ * componentsToOutputValue). TyComponent calls this automatically when the
162
+ * value property changes.
147
163
  */
148
164
  protected getFormValue(): FormDataEntryValue | null;
165
+ private outputValue;
149
166
  protected updateFormValue(): void;
150
- /** Focus the field: the host itself isn't focusable. */
167
+ /** The field's focusable element: the stub, or the native input on touch devices. */
168
+ private focusTarget;
169
+ /** Focus the field: the host itself isn't focusable. Disabled: nothing, like a native input. */
151
170
  focus(options?: FocusOptions): void;
152
171
  /**
153
172
  * `required` used to be cosmetic: nothing reported valueMissing, so an empty
154
173
  * required picker never blocked its form. Disabled pickers don't validate.
174
+ * The message is a native date (datetime-local with time) input's.
155
175
  */
156
176
  private updateValidity;
157
177
  private getComponents;
@@ -173,6 +193,14 @@ export declare class TyDatePicker extends TyComponent<DatePickerState> {
173
193
  * Compares before setting to prevent circular triggers
174
194
  */
175
195
  private syncFormValue;
196
+ /**
197
+ * `error` turns an unflavored field ('default'/'neutral') danger, like
198
+ * ty-input; clearing it puts back exactly the flavor attribute the author
199
+ * had, or none, kept meanwhile in ERROR_FLAVOR. Runs once connected: while
200
+ * the element upgrades, `error` may come before the `flavor` written next
201
+ * to it.
202
+ */
203
+ private syncErrorFlavor;
176
204
  /**
177
205
  * Handle time changes from the footer's hour/minute fields
178
206
  */
@@ -186,6 +214,7 @@ export declare class TyDatePicker extends TyComponent<DatePickerState> {
186
214
  set placement(v: Placement | '');
187
215
  get flavor(): DatePickerFlavor;
188
216
  set flavor(v: DatePickerFlavor);
217
+ attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
189
218
  get label(): string;
190
219
  set label(v: string);
191
220
  get placeholder(): string;
@@ -208,17 +237,23 @@ export declare class TyDatePicker extends TyComponent<DatePickerState> {
208
237
  set max(v: string);
209
238
  get withTime(): boolean;
210
239
  set withTime(v: boolean);
240
+ get native(): boolean;
241
+ set native(v: boolean);
242
+ get error(): string;
243
+ set error(v: string);
211
244
  /**
212
- * Get current value (UTC ISO string)
245
+ * Get current value — the string the form submits: a UTC ISO instant, or
246
+ * wall-clock ('YYYY-MM-DD' / 'YYYY-MM-DDTHH:mm') with `native`. null when
247
+ * empty (the form then submits name=, like a native date input).
213
248
  */
214
249
  get value(): string | null;
215
250
  /**
216
- * Set value (UTC ISO string, Date object, or null)
251
+ * Set value: any format parseValue() reads, a Date, epoch milliseconds,
252
+ * or null. It's normalized to the submitted form.
217
253
  *
218
254
  * When set to null/undefined/empty string, the attribute is removed.
219
- * When set to a valid date, the attribute is set to ISO UTC string.
220
255
  */
221
- set value(val: string | Date | null);
256
+ set value(val: string | number | Date | null);
222
257
  /**
223
258
  * Clear the date value programmatically WITH a change event (source:
224
259
  * 'clear'). Works regardless of `no-clear` — lets any
@@ -232,15 +267,32 @@ export declare class TyDatePicker extends TyComponent<DatePickerState> {
232
267
  /** Footer: clock + "Time" on the left, HH : MM on the right, summary below. */
233
268
  private renderTimeSection;
234
269
  private renderCalendarDropdown;
270
+ /**
271
+ * Match the popup calendar to the stub's flavor — the one the user set, not
272
+ * the danger an error swapped in. 'default' is the stub's own unstyled
273
+ * state, not a real flavor: the calendar stays on its own default.
274
+ */
275
+ private calendarFlavor;
235
276
  /**
236
277
  * Render native date input for mobile touch devices.
237
278
  * Uses <input type="date"> or <input type="datetime-local"> when with-time.
238
279
  * Reuses .date-picker-stub styling and existing event/state infrastructure.
239
280
  */
240
281
  private renderNativeInput;
282
+ /**
283
+ * The touch devices' native input follows the picker: type (date, or
284
+ * datetime-local with time), disabled, required, min/max (datetime-local
285
+ * wants a time suffix) and value — also when they change at runtime.
286
+ */
287
+ private syncNativeInput;
241
288
  private calculateCalendarPosition;
242
289
  private renderContainer;
243
290
  protected render(): void;
291
+ /**
292
+ * The `error` line under the field — same markup as ty-input's; the field
293
+ * (stub, or the native input on touch devices) is described by it.
294
+ */
295
+ private syncErrorLine;
244
296
  /**
245
297
  * Update display without destroying DOM (for open dialog)
246
298
  */