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
@@ -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
@@ -119,6 +125,9 @@ export declare abstract class TyComponent<T = any> extends HTMLElement {
119
125
  /** Subclasses report their own validity here, not via _internals directly,
120
126
  * so a user's setCustomValidity() survives re-validation. */
121
127
  protected applyValidity(flags: ValidityStateFlags, message?: string, anchor?: HTMLElement): void;
128
+ /** Show the field's current validation message in its error display, if
129
+ * it's invalid. Called on blur after the user edited the field. */
130
+ protected revealValidity(): void;
122
131
  get validity(): ValidityState | undefined;
123
132
  get validationMessage(): string;
124
133
  get willValidate(): boolean;
@@ -154,6 +163,9 @@ export declare abstract class TyComponent<T = any> extends HTMLElement {
154
163
  * semantics). Non-form state (e.g. a checkbox's submission `value`, a
155
164
  * ty-radio's option `value`) is deliberately left alone — the old
156
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.
157
169
  */
158
170
  formResetCallback(): void;
159
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;
@@ -89,13 +91,11 @@ for (const [name, config] of Object.entries(ctor.properties)) {
89
91
  if (!config.formValue)
90
92
  continue;
91
93
  const attr = this.getAttribute(this._toKebabCase(name));
92
- this._resetDefaults.set(name, attr ?? config.default ?? null);
93
- }
94
+ this._resetDefaults.set(name, attr ?? (typeof config.default === 'function' ? config.default() : config.default) ?? null);
94
95
  }
95
- const preSetProps = this._capturePreSetProperties();
96
- if (preSetProps.length > 0) {
97
- this._handlePropertyChanges(preSetProps);
98
96
  }
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)
@@ -171,6 +172,10 @@ return this.constructor.formAssociated === true;
171
172
  applyValidity(flags, message, anchor) {
172
173
  this._validity.set(flags, message, anchor);
173
174
  }
175
+ revealValidity() {
176
+ if (this._validates)
177
+ this._validity.reveal();
178
+ }
174
179
  get validity() {
175
180
  return this._validates ? this._internals.validity : undefined;
176
181
  }
@@ -216,6 +221,9 @@ queueMicrotask(() => { this._handlingActivationClick = false; });
216
221
  return false;
217
222
  }
218
223
  formResetCallback() {
224
+ this._userEdited = false;
225
+ if (this._validates)
226
+ this._validity.reset();
219
227
  for (const [name, def] of this._resetDefaults ?? []) {
220
228
  this.setProperty(name, def);
221
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;
@@ -128,5 +130,7 @@ export declare class TyCalendar extends HTMLElement {
128
130
  private pickMonth;
129
131
  private handleKeydown;
130
132
  private focusNavTitle;
133
+ /** Focus the grid's tab stop (the focused day, or month in the month view). */
134
+ focus(options?: FocusOptions): void;
131
135
  private focusDayGrid;
132
136
  }
@@ -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
  }
@@ -792,6 +777,15 @@ requestAnimationFrame(() => {
792
777
  this._navigation?.shadowRoot?.querySelector('.nav-title')?.focus();
793
778
  });
794
779
  }
780
+ focus(options) {
781
+ const cell = this._view === 'months'
782
+ ? this._monthGrid?.querySelector('.month-cell[tabindex="0"]')
783
+ : this._monthDisplay?.shadowRoot?.querySelector('.calendar-day-cell[tabindex="0"]');
784
+ if (cell)
785
+ cell.focus(options);
786
+ else
787
+ super.focus(options);
788
+ }
795
789
  focusDayGrid() {
796
790
  requestAnimationFrame(() => {
797
791
  const cell = this._monthDisplay?.shadowRoot?.querySelector('.calendar-day-cell[tabindex="0"]');
@@ -110,7 +110,9 @@ export declare class TyCheckbox extends TyComponent<CheckboxState> implements Ty
110
110
  * Label, required asterisk, and error UI are the consumer's responsibility.
111
111
  */
112
112
  protected render(): void;
113
- /** required + unchecked => valueMissing; otherwise valid. */
113
+ /** Focus the checkbox itself (the host is not focusable; its role element is). */
114
+ focus(options?: FocusOptions): void;
115
+ /** required + unchecked => valueMissing (the browser's own message); otherwise valid. */
114
116
  private updateValidity;
115
117
  get checked(): boolean;
116
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 {
@@ -165,6 +166,13 @@ this.setupEventListeners();
165
166
  this.applyLabelName(checkboxEl);
166
167
  this.updateValidity();
167
168
  }
169
+ focus(options) {
170
+ const el = this.shadowRoot?.querySelector(".checkbox-container");
171
+ if (el)
172
+ el.focus(options);
173
+ else
174
+ super.focus(options);
175
+ }
168
176
  updateValidity() {
169
177
  const anchor = this.shadowRoot?.querySelector(".checkbox-container");
170
178
  if (this.disabled) {
@@ -172,7 +180,7 @@ this.applyValidity({});
172
180
  return;
173
181
  }
174
182
  if (this.required && !this.checked) {
175
- this.applyValidity({ valueMissing: true }, "Please check this box", anchor ?? undefined);
183
+ this.applyValidity({ valueMissing: true }, nativeMessage("input", { type: "checkbox", required: true }), anchor ?? undefined);
176
184
  }
177
185
  else {
178
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,14 +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;
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. */
170
+ focus(options?: FocusOptions): void;
150
171
  /**
151
172
  * `required` used to be cosmetic: nothing reported valueMissing, so an empty
152
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.
153
175
  */
154
176
  private updateValidity;
155
177
  private getComponents;
@@ -171,6 +193,14 @@ export declare class TyDatePicker extends TyComponent<DatePickerState> {
171
193
  * Compares before setting to prevent circular triggers
172
194
  */
173
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;
174
204
  /**
175
205
  * Handle time changes from the footer's hour/minute fields
176
206
  */
@@ -184,6 +214,7 @@ export declare class TyDatePicker extends TyComponent<DatePickerState> {
184
214
  set placement(v: Placement | '');
185
215
  get flavor(): DatePickerFlavor;
186
216
  set flavor(v: DatePickerFlavor);
217
+ attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
187
218
  get label(): string;
188
219
  set label(v: string);
189
220
  get placeholder(): string;
@@ -206,17 +237,23 @@ export declare class TyDatePicker extends TyComponent<DatePickerState> {
206
237
  set max(v: string);
207
238
  get withTime(): boolean;
208
239
  set withTime(v: boolean);
240
+ get native(): boolean;
241
+ set native(v: boolean);
242
+ get error(): string;
243
+ set error(v: string);
209
244
  /**
210
- * 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).
211
248
  */
212
249
  get value(): string | null;
213
250
  /**
214
- * 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.
215
253
  *
216
254
  * When set to null/undefined/empty string, the attribute is removed.
217
- * When set to a valid date, the attribute is set to ISO UTC string.
218
255
  */
219
- set value(val: string | Date | null);
256
+ set value(val: string | number | Date | null);
220
257
  /**
221
258
  * Clear the date value programmatically WITH a change event (source:
222
259
  * 'clear'). Works regardless of `no-clear` — lets any
@@ -230,15 +267,32 @@ export declare class TyDatePicker extends TyComponent<DatePickerState> {
230
267
  /** Footer: clock + "Time" on the left, HH : MM on the right, summary below. */
231
268
  private renderTimeSection;
232
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;
233
276
  /**
234
277
  * Render native date input for mobile touch devices.
235
278
  * Uses <input type="date"> or <input type="datetime-local"> when with-time.
236
279
  * Reuses .date-picker-stub styling and existing event/state infrastructure.
237
280
  */
238
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;
239
288
  private calculateCalendarPosition;
240
289
  private renderContainer;
241
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;
242
296
  /**
243
297
  * Update display without destroying DOM (for open dialog)
244
298
  */