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
@@ -81,9 +81,12 @@ 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;
88
+ /** Focus the drop zone: the host itself isn't focusable. */
89
+ focus(options?: FocusOptions): void;
87
90
  /**
88
91
  * Match a file against the `accept` attribute the way native inputs do:
89
92
  * comma-separated list of `.ext`, `type/subtype` or `type/*` entries.
@@ -1,6 +1,7 @@
1
1
  import { TyComponent } from "../base/ty-component.js";
2
2
  import { ensureStyles } from "../utils/styles.js";
3
3
  import { fileUploadStyles } from "../styles/file-upload.js";
4
+ import { nativeMessage } from "../utils/validity.js";
4
5
  const UPLOAD_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>`;
5
6
  const FILE_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>`;
6
7
  const X_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
@@ -29,7 +30,9 @@ this._dropHandler = null;
29
30
  this._inputChangeHandler = null;
30
31
  ensureStyles(this.shadowRoot, { css: fileUploadStyles, id: "ty-file-upload" });
31
32
  }
32
- onConnect() { }
33
+ onConnect() {
34
+ this.updateFormValue();
35
+ }
33
36
  onDisconnect() {
34
37
  this.removeEventListeners();
35
38
  }
@@ -50,21 +53,31 @@ this.updateValidity();
50
53
  updateValidity() {
51
54
  if (this.required && !this.disabled && this._files.length === 0) {
52
55
  const anchor = this.shadowRoot?.querySelector(".drop-zone");
53
- this.applyValidity({ valueMissing: true }, "Please select a file", anchor ?? undefined);
56
+ this.applyValidity({ valueMissing: true }, nativeMessage("input", { type: "file", required: true, multiple: this.multiple }), anchor ?? undefined);
54
57
  }
55
58
  else {
56
59
  this.applyValidity({});
57
60
  }
58
61
  }
59
62
  getFormValue() {
60
- if (this._files.length === 0 || !this.name)
63
+ if (!this.name)
61
64
  return null;
62
65
  const fd = new FormData();
66
+ if (this._files.length === 0) {
67
+ fd.append(this.name, new File([], '', { type: 'application/octet-stream' }));
68
+ }
63
69
  for (const file of this._files) {
64
70
  fd.append(this.name, file);
65
71
  }
66
72
  return fd;
67
73
  }
74
+ focus(options) {
75
+ const zone = this.shadowRoot?.querySelector('.drop-zone');
76
+ if (zone)
77
+ zone.focus(options);
78
+ else
79
+ super.focus(options);
80
+ }
68
81
  matchesAccept(file) {
69
82
  const accept = this.accept?.trim();
70
83
  if (!accept)
@@ -146,6 +159,7 @@ names: this._files.map((f) => f.name),
146
159
  };
147
160
  setTimeout(() => {
148
161
  this.dispatchEvent(new CustomEvent("change", { detail, bubbles: true, composed: true }));
162
+ this.revealValidity();
149
163
  }, 0);
150
164
  }
151
165
  removeFile(index) {
@@ -59,6 +59,41 @@ export declare class TyInput extends TyComponent<InputState> implements TyInputE
59
59
  visual: boolean;
60
60
  default: boolean;
61
61
  };
62
+ pattern: {
63
+ type: "string";
64
+ visual: boolean;
65
+ default: string;
66
+ };
67
+ minlength: {
68
+ type: "string";
69
+ visual: boolean;
70
+ default: string;
71
+ };
72
+ maxlength: {
73
+ type: "string";
74
+ visual: boolean;
75
+ default: string;
76
+ };
77
+ min: {
78
+ type: "string";
79
+ visual: boolean;
80
+ default: string;
81
+ };
82
+ max: {
83
+ type: "string";
84
+ visual: boolean;
85
+ default: string;
86
+ };
87
+ tooShortMessage: {
88
+ type: "string";
89
+ visual: boolean;
90
+ default: string;
91
+ };
92
+ tooLongMessage: {
93
+ type: "string";
94
+ visual: boolean;
95
+ default: string;
96
+ };
62
97
  error: {
63
98
  type: "string";
64
99
  visual: boolean;
@@ -103,7 +138,6 @@ export declare class TyInput extends TyComponent<InputState> implements TyInputE
103
138
  private _shadowValue;
104
139
  private _isFocused;
105
140
  private _listenersSetup;
106
- private _errorAutoDanger;
107
141
  private _customFlavorSheet;
108
142
  private _inputHandler;
109
143
  private _changeHandler;
@@ -117,6 +151,14 @@ export declare class TyInput extends TyComponent<InputState> implements TyInputE
117
151
  protected onConnect(): void;
118
152
  /** Custom (non-built-in) flavors — see utils/flavor-sheet.ts. */
119
153
  private _syncCustomFlavor;
154
+ /**
155
+ * `error` turns a neutral field danger; clearing it (the author, or
156
+ * validation dropping the message it showed) puts back exactly the flavor
157
+ * attribute the author had, or none, kept meanwhile in ERROR_FLAVOR (same
158
+ * as ty-select and ty-date-picker). Runs once connected: while the element
159
+ * upgrades, `error` may come before the `flavor` written next to it.
160
+ */
161
+ private syncErrorFlavor;
120
162
  protected onDisconnect(): void;
121
163
  /** Handle property changes — called BEFORE render. */
122
164
  protected onPropertiesChanged(changes: PropertyChange[]): void;
@@ -143,14 +185,31 @@ export declare class TyInput extends TyComponent<InputState> implements TyInputE
143
185
  set label(v: string);
144
186
  get disabled(): boolean;
145
187
  set disabled(v: boolean);
188
+ /** Focus the inner <input>: the host itself isn't focusable. */
189
+ focus(options?: FocusOptions): void;
146
190
  get required(): boolean;
147
191
  set required(v: boolean);
192
+ get pattern(): string;
193
+ set pattern(v: string);
194
+ get minLength(): number;
195
+ set minLength(v: number);
196
+ get maxLength(): number;
197
+ set maxLength(v: number);
198
+ get min(): string;
199
+ set min(v: string);
200
+ get max(): string;
201
+ set max(v: string);
202
+ get tooShortMessage(): string;
203
+ set tooShortMessage(v: string);
204
+ get tooLongMessage(): string;
205
+ set tooLongMessage(v: string);
148
206
  get error(): string;
149
207
  set error(v: string);
150
208
  get size(): Size;
151
209
  set size(v: Size);
152
210
  get flavor(): Flavor;
153
211
  set flavor(v: Flavor);
212
+ attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
154
213
  get currency(): string;
155
214
  set currency(v: string);
156
215
  get locale(): string;
@@ -165,6 +224,13 @@ export declare class TyInput extends TyComponent<InputState> implements TyInputE
165
224
  private dispatchChangeEvent;
166
225
  private removeEventListeners;
167
226
  /** IMPORTANT: Only called ONCE, not on every render. */
227
+ /**
228
+ * Enter submits the owning form, like a native input (HTML implicit
229
+ * submission): through the form's default button when it has one (nothing
230
+ * if that button is disabled), otherwise only when this is the form's only
231
+ * text field.
232
+ */
233
+ private _implicitSubmit;
168
234
  private setupEventListeners;
169
235
  /** Render the input component with wrapper and slots. */
170
236
  render(): void;
@@ -174,10 +240,23 @@ export declare class TyInput extends TyComponent<InputState> implements TyInputE
174
240
  * `type` property stays "password" (form value and API unaffected).
175
241
  */
176
242
  private setupPasswordToggle;
243
+ /**
244
+ * pattern, minlength and maxlength go on the inner <input>: pattern for the
245
+ * browser's own patternMismatch check, minlength for its tooShort message
246
+ * after the user types, maxlength so typing stops at the limit like a
247
+ * native field. Formatted (numeric) types show a display string, so they
248
+ * get none.
249
+ */
250
+ private syncConstraintAttributes;
177
251
  /**
178
252
  * Reflect constraint validation to the owning <form> via ElementInternals.
179
- * Without this `required` is only cosmetic — the inner shadow <input> can't
180
- * block the outer form. Empty + required => valueMissing; otherwise valid.
253
+ * Without this the inner shadow <input> can't block the outer form.
254
+ * Rules, first failing one supplies the message:
255
+ * - required: empty => valueMissing
256
+ * - email/url: the inner input's own typeMismatch
257
+ * - text types: pattern, minlength, maxlength
258
+ * - number/currency/percent/compact: min, max against the numeric value
259
+ * Empty values only fail `required`, like native inputs.
181
260
  * Disabled fields are barred from constraint validation.
182
261
  */
183
262
  private updateValidity;
@@ -2,10 +2,12 @@ import { DEFAULT_SIZE } from '../types/common.js';
2
2
  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
+ import { ERROR_FLAVOR } from '../utils/error-flavor.js';
5
6
  import { inputStyles, inputCustomFlavorCss } from '../styles/input.js';
6
7
  import { formatNumber, parseNumericValue, shouldFormat as shouldFormatType } from '../utils/number-format.js';
7
8
  import { getEffectiveLocale, observeLocaleChanges } from '../utils/locale.js';
8
9
  import { REQUIRED_ICON_SVG } from '../utils/icons.js';
10
+ import { nativeMessage, nativeValidity, lengthValidity, toLimit } from '../utils/validity.js';
9
11
  const EYE_ICON_SVG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"/><circle cx="12" cy="12" r="3"/></svg>`;
10
12
  const EYE_OFF_ICON_SVG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49"/><path d="M14.084 14.158a3 3 0 0 1-4.242-4.242"/><path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143"/><path d="m2 2 20 20"/></svg>`;
11
13
  export class TyInput extends TyComponent {
@@ -14,7 +16,6 @@ super();
14
16
  this._shadowValue = null;
15
17
  this._isFocused = false;
16
18
  this._listenersSetup = false;
17
- this._errorAutoDanger = false;
18
19
  this._customFlavorSheet = null;
19
20
  this._inputHandler = null;
20
21
  this._changeHandler = null;
@@ -22,6 +23,34 @@ this._focusHandler = null;
22
23
  this._blurHandler = null;
23
24
  this._inputDebounceTimer = null;
24
25
  this._changeDebounceTimer = null;
26
+ this._implicitSubmit = (e) => {
27
+ if (e.key !== 'Enter' || e.isComposing || e.defaultPrevented)
28
+ return;
29
+ const form = this._internals.form;
30
+ if (!form)
31
+ return;
32
+ const controls = Array.from(form.elements);
33
+ const isSubmitter = (el) => (el instanceof HTMLButtonElement && el.type === 'submit') ||
34
+ (el instanceof HTMLInputElement && (el.type === 'submit' || el.type === 'image')) ||
35
+ (el.tagName === 'TY-BUTTON' && !['button', 'reset'].includes(el.getAttribute('type') ?? ''));
36
+ const submitter = controls.find(isSubmitter);
37
+ if (submitter) {
38
+ if (submitter.disabled || submitter.hasAttribute('disabled'))
39
+ return;
40
+ e.preventDefault();
41
+ const target = submitter.tagName === 'TY-BUTTON'
42
+ ? submitter.shadowRoot?.querySelector('button') ?? submitter
43
+ : submitter;
44
+ target.click();
45
+ return;
46
+ }
47
+ const textFields = controls.filter((el) => el.tagName === 'TY-INPUT' ||
48
+ (el instanceof HTMLInputElement && !['button', 'checkbox', 'radio', 'file', 'hidden', 'image', 'reset', 'submit', 'range', 'color'].includes(el.type)));
49
+ if (textFields.length === 1) {
50
+ e.preventDefault();
51
+ form.requestSubmit();
52
+ }
53
+ };
25
54
  const shadow = this.shadowRoot;
26
55
  ensureStyles(shadow, { css: inputStyles, id: 'ty-input' });
27
56
  }
@@ -31,10 +60,32 @@ this._localeObserver = observeLocaleChanges(this, () => {
31
60
  this.render();
32
61
  });
33
62
  this._syncCustomFlavor();
63
+ this.syncErrorFlavor();
34
64
  }
35
65
  _syncCustomFlavor() {
36
66
  this._customFlavorSheet = syncCustomFlavorSheet(this.shadowRoot, this._customFlavorSheet, this.flavor, ({ base }) => inputCustomFlavorCss(base));
37
67
  }
68
+ syncErrorFlavor() {
69
+ if (!this._connected)
70
+ return;
71
+ const flavor = this.getProperty('flavor');
72
+ const replaced = this.getAttribute(ERROR_FLAVOR);
73
+ if (this.getProperty('error')) {
74
+ if (replaced === null && flavor === 'neutral') {
75
+ this.setAttribute(ERROR_FLAVOR, this.getAttribute('flavor') ?? '');
76
+ this.setProperty('flavor', 'danger');
77
+ }
78
+ }
79
+ else if (replaced !== null) {
80
+ this.removeAttribute(ERROR_FLAVOR);
81
+ if (flavor !== 'danger')
82
+ return;
83
+ if (!replaced)
84
+ this.removeAttribute('flavor');
85
+ else
86
+ this.setProperty('flavor', replaced);
87
+ }
88
+ }
38
89
  onDisconnect() {
39
90
  this.removeEventListeners();
40
91
  if (this._localeObserver) {
@@ -54,6 +105,10 @@ onPropertiesChanged(changes) {
54
105
  for (const { name, newValue } of changes) {
55
106
  switch (name) {
56
107
  case 'flavor':
108
+ if (newValue !== 'danger') {
109
+ this.removeAttribute(ERROR_FLAVOR);
110
+ this.syncErrorFlavor();
111
+ }
57
112
  this._syncCustomFlavor();
58
113
  break;
59
114
  case 'value':
@@ -64,18 +119,7 @@ const currentValue = this.getProperty('value') || '';
64
119
  this._shadowValue = this.parseShadowValue(currentValue);
65
120
  break;
66
121
  case 'error':
67
- if (newValue) {
68
- if (this.getProperty('flavor') === 'neutral') {
69
- this.setProperty('flavor', 'danger');
70
- this._errorAutoDanger = true;
71
- }
72
- }
73
- else if (this._errorAutoDanger) {
74
- if (this.getProperty('flavor') === 'danger') {
75
- this.setProperty('flavor', 'neutral');
76
- }
77
- this._errorAutoDanger = false;
78
- }
122
+ this.syncErrorFlavor();
79
123
  break;
80
124
  }
81
125
  }
@@ -95,14 +139,17 @@ this._changeDebounceTimer = null;
95
139
  this.render();
96
140
  }
97
141
  getFormValue() {
98
- const formValue = this._shadowValue !== null ? String(this._shadowValue) : null;
142
+ const formValue = this._shadowValue !== null ? String(this._shadowValue) : '';
99
143
  return formValue;
100
144
  }
101
145
  initializeShadowValue() {
102
146
  const currentValue = this.getProperty('value');
103
147
  if (currentValue) {
104
148
  this._shadowValue = this.parseShadowValue(currentValue);
105
- this._internals.setFormValue(this._shadowValue !== null ? String(this._shadowValue) : null);
149
+ this._internals.setFormValue(this._shadowValue !== null ? String(this._shadowValue) : '');
150
+ }
151
+ else {
152
+ this._internals.setFormValue('');
106
153
  }
107
154
  }
108
155
  parseShadowValue(value) {
@@ -155,14 +202,39 @@ get label() { return this.getProperty('label'); }
155
202
  set label(v) { this.setProperty('label', v); }
156
203
  get disabled() { return this.getProperty('disabled'); }
157
204
  set disabled(v) { this.setProperty('disabled', v); }
205
+ focus(options) {
206
+ this.shadowRoot?.querySelector('input')?.focus(options);
207
+ }
158
208
  get required() { return this.getProperty('required'); }
159
209
  set required(v) { this.setProperty('required', v); }
210
+ get pattern() { return this.getProperty('pattern'); }
211
+ set pattern(v) { this.setProperty('pattern', v ?? ''); }
212
+ get minLength() { return toLimit(this.getProperty('minlength')) ?? -1; }
213
+ set minLength(v) { this.setProperty('minlength', v == null || v < 0 ? '' : String(v)); }
214
+ get maxLength() { return toLimit(this.getProperty('maxlength')) ?? -1; }
215
+ set maxLength(v) { this.setProperty('maxlength', v == null || v < 0 ? '' : String(v)); }
216
+ get min() { return this.getProperty('min'); }
217
+ set min(v) { this.setProperty('min', v == null ? '' : String(v)); }
218
+ get max() { return this.getProperty('max'); }
219
+ set max(v) { this.setProperty('max', v == null ? '' : String(v)); }
220
+ get tooShortMessage() { return this.getProperty('tooShortMessage'); }
221
+ set tooShortMessage(v) { this.setProperty('tooShortMessage', v ?? ''); }
222
+ get tooLongMessage() { return this.getProperty('tooLongMessage'); }
223
+ set tooLongMessage(v) { this.setProperty('tooLongMessage', v ?? ''); }
160
224
  get error() { return this.getProperty('error'); }
161
225
  set error(v) { this.setProperty('error', v); }
162
226
  get size() { return this.getProperty('size'); }
163
227
  set size(v) { this.setProperty('size', v); }
164
228
  get flavor() { return this.getProperty('flavor'); }
165
- set flavor(v) { this.setProperty('flavor', v); }
229
+ set flavor(v) {
230
+ this.removeAttribute(ERROR_FLAVOR);
231
+ this.setProperty('flavor', v);
232
+ }
233
+ attributeChangedCallback(name, oldValue, newValue) {
234
+ if (name === 'flavor' && newValue === 'danger' && oldValue === newValue)
235
+ this.removeAttribute(ERROR_FLAVOR);
236
+ super.attributeChangedCallback(name, oldValue, newValue);
237
+ }
166
238
  get currency() { return this.getProperty('currency'); }
167
239
  set currency(v) { this.setProperty('currency', v); }
168
240
  get locale() {
@@ -235,6 +307,7 @@ if (this._blurHandler) {
235
307
  inputEl.removeEventListener('blur', this._blurHandler);
236
308
  this._blurHandler = null;
237
309
  }
310
+ inputEl.removeEventListener('keydown', this._implicitSubmit);
238
311
  this._listenersSetup = false;
239
312
  }
240
313
  setupEventListeners() {
@@ -249,12 +322,13 @@ console.warn('[TyInput.setupEventListeners] ⚠️ Input element not found!');
249
322
  return;
250
323
  }
251
324
  this._inputHandler = (e) => {
325
+ this._userEdited = true;
252
326
  e.stopPropagation();
253
327
  e.stopImmediatePropagation();
254
328
  const target = e.target;
255
329
  const rawValue = target.value;
256
330
  this._shadowValue = this.parseShadowValue(rawValue);
257
- this._internals.setFormValue(this._shadowValue !== null ? String(this._shadowValue) : null);
331
+ this._internals.setFormValue(this._shadowValue !== null ? String(this._shadowValue) : '');
258
332
  this.updateValidity();
259
333
  if (this._inputDebounceTimer !== null) {
260
334
  clearTimeout(this._inputDebounceTimer);
@@ -275,7 +349,7 @@ e.stopImmediatePropagation();
275
349
  const target = e.target;
276
350
  const rawValue = target.value;
277
351
  this._shadowValue = this.parseShadowValue(rawValue);
278
- this._internals.setFormValue(this._shadowValue !== null ? String(this._shadowValue) : null);
352
+ this._internals.setFormValue(this._shadowValue !== null ? String(this._shadowValue) : '');
279
353
  this.updateValidity();
280
354
  if (this._changeDebounceTimer !== null) {
281
355
  clearTimeout(this._changeDebounceTimer);
@@ -317,6 +391,8 @@ this.dispatchChangeEvent(rawValue, e);
317
391
  }
318
392
  this._isFocused = false;
319
393
  wrapperEl.classList.remove('focused');
394
+ if (this._userEdited)
395
+ this.revealValidity();
320
396
  if (this.shouldFormat()) {
321
397
  inputEl.value = this.getDisplayValue();
322
398
  }
@@ -330,6 +406,7 @@ inputEl.addEventListener('input', this._inputHandler);
330
406
  inputEl.addEventListener('change', this._changeHandler);
331
407
  inputEl.addEventListener('focus', this._focusHandler);
332
408
  inputEl.addEventListener('blur', this._blurHandler);
409
+ inputEl.addEventListener('keydown', this._implicitSubmit);
333
410
  this._listenersSetup = true;
334
411
  }
335
412
  render() {
@@ -350,6 +427,7 @@ const inputMode = shouldFormatType(this.type) ? 'decimal'
350
427
  const displayValue = this.getDisplayValue();
351
428
  if (existingInput && existingWrapper) {
352
429
  existingInput.type = inputType;
430
+ if (existingInput.value !== displayValue)
353
431
  existingInput.value = displayValue;
354
432
  existingInput.placeholder = this.placeholder;
355
433
  existingInput.name = this.name;
@@ -372,6 +450,7 @@ existingInput.setAttribute('required', '');
372
450
  else {
373
451
  existingInput.removeAttribute('required');
374
452
  }
453
+ this.syncConstraintAttributes(existingInput);
375
454
  if (this.label)
376
455
  existingInput.setAttribute('aria-label', this.label);
377
456
  else
@@ -445,6 +524,7 @@ ${errorHtml}
445
524
  const inputEl = shadow.querySelector('input');
446
525
  inputEl.disabled = this.disabled;
447
526
  inputEl.required = this.required;
527
+ this.syncConstraintAttributes(inputEl);
448
528
  if (this.label)
449
529
  inputEl.setAttribute('aria-label', this.label);
450
530
  this.setupPasswordToggle();
@@ -467,6 +547,13 @@ toggle.setAttribute('aria-label', reveal ? 'Hide password' : 'Show password');
467
547
  toggle.setAttribute('aria-pressed', String(reveal));
468
548
  });
469
549
  }
550
+ syncConstraintAttributes(inputEl) {
551
+ const text = !shouldFormatType(this.type);
552
+ const set = (name, v) => text && v !== '' ? inputEl.setAttribute(name, v) : inputEl.removeAttribute(name);
553
+ set('pattern', this.pattern);
554
+ set('minlength', toLimit(this.getProperty('minlength')) != null ? String(this.minLength) : '');
555
+ set('maxlength', toLimit(this.getProperty('maxlength')) != null ? String(this.maxLength) : '');
556
+ }
470
557
  updateValidity() {
471
558
  const inputEl = this.shadowRoot?.querySelector('input');
472
559
  if (this.disabled) {
@@ -474,12 +561,48 @@ this.applyValidity({});
474
561
  return;
475
562
  }
476
563
  const current = inputEl ? inputEl.value : String(this.getProperty('value') ?? '');
477
- if (this.required && current.trim() === '') {
478
- this.applyValidity({ valueMissing: true }, 'Please fill out this field', inputEl ?? undefined);
564
+ if (current.trim() === '') {
565
+ if (this.required) {
566
+ this.applyValidity({ valueMissing: true }, nativeMessage('input', { type: 'text', required: true }), inputEl ?? undefined);
479
567
  }
480
568
  else {
481
569
  this.applyValidity({});
482
570
  }
571
+ return;
572
+ }
573
+ const flags = {};
574
+ const messages = [];
575
+ const fail = (flag, message) => {
576
+ flags[flag] = true;
577
+ messages.push(message);
578
+ };
579
+ if (shouldFormatType(this.type)) {
580
+ const n = typeof this._shadowValue === 'number' ? this._shadowValue : null;
581
+ if (n !== null && (this.min || this.max)) {
582
+ const { validity, message } = nativeValidity('input', { type: 'number', step: 'any', min: this.min, max: this.max }, String(n));
583
+ if (validity.rangeUnderflow)
584
+ fail('rangeUnderflow', message);
585
+ if (validity.rangeOverflow)
586
+ fail('rangeOverflow', message);
587
+ }
588
+ }
589
+ else {
590
+ if (inputEl?.validity.typeMismatch)
591
+ fail('typeMismatch', inputEl.validationMessage);
592
+ if (inputEl?.validity.patternMismatch)
593
+ fail('patternMismatch', inputEl.validationMessage);
594
+ const { tooShort, tooLong } = lengthValidity(current, {
595
+ minlength: this.getProperty('minlength'),
596
+ maxlength: this.getProperty('maxlength'),
597
+ tooShortMessage: this.tooShortMessage,
598
+ tooLongMessage: this.tooLongMessage,
599
+ }, inputEl);
600
+ if (tooShort)
601
+ fail('tooShort', tooShort);
602
+ if (tooLong)
603
+ fail('tooLong', tooLong);
604
+ }
605
+ this.applyValidity(flags, messages[0], inputEl ?? undefined);
483
606
  }
484
607
  }
485
608
  TyInput.formAssociated = true;
@@ -538,6 +661,41 @@ type: 'boolean',
538
661
  visual: true,
539
662
  default: false
540
663
  },
664
+ pattern: {
665
+ type: 'string',
666
+ visual: true,
667
+ default: ''
668
+ },
669
+ minlength: {
670
+ type: 'string',
671
+ visual: true,
672
+ default: ''
673
+ },
674
+ maxlength: {
675
+ type: 'string',
676
+ visual: true,
677
+ default: ''
678
+ },
679
+ min: {
680
+ type: 'string',
681
+ visual: true,
682
+ default: ''
683
+ },
684
+ max: {
685
+ type: 'string',
686
+ visual: true,
687
+ default: ''
688
+ },
689
+ tooShortMessage: {
690
+ type: 'string',
691
+ visual: true,
692
+ default: ''
693
+ },
694
+ tooLongMessage: {
695
+ type: 'string',
696
+ visual: true,
697
+ default: ''
698
+ },
541
699
  error: {
542
700
  type: 'string',
543
701
  visual: true,
@@ -52,6 +52,8 @@ export declare class TyModal extends HTMLElement {
52
52
  set open(v: boolean);
53
53
  get noBackdrop(): boolean;
54
54
  set noBackdrop(v: boolean);
55
+ get noZoom(): boolean;
56
+ set noZoom(v: boolean);
55
57
  get preventOutsideClick(): boolean;
56
58
  set preventOutsideClick(v: boolean);
57
59
  get preventEscape(): boolean;