tyrell-components 1.0.0-TC57 → 1.0.0-TC59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/css/tyrell-colors-static.css +14 -0
  2. package/css/tyrell-theme.css +1 -0
  3. package/css/tyrell.css +8 -6
  4. package/dist/tyrell-colors-static.css +1 -1
  5. package/dist/tyrell-theme.css +1 -1
  6. package/dist/tyrell.js +1 -1
  7. package/lib/base/ty-component.d.ts +20 -15
  8. package/lib/base/ty-component.js +58 -25
  9. package/lib/components/button.d.ts +14 -0
  10. package/lib/components/button.js +55 -7
  11. package/lib/components/calendar-month.d.ts +7 -1
  12. package/lib/components/calendar-month.js +35 -28
  13. package/lib/components/calendar-navigation.js +19 -6
  14. package/lib/components/calendar.d.ts +25 -4
  15. package/lib/components/calendar.js +98 -67
  16. package/lib/components/checkbox.d.ts +6 -1
  17. package/lib/components/checkbox.js +20 -6
  18. package/lib/components/copy.d.ts +2 -0
  19. package/lib/components/copy.js +15 -31
  20. package/lib/components/date-picker.d.ts +52 -19
  21. package/lib/components/date-picker.js +211 -147
  22. package/lib/components/file-upload.d.ts +2 -1
  23. package/lib/components/file-upload.js +8 -3
  24. package/lib/components/input.d.ts +4 -6
  25. package/lib/components/input.js +37 -62
  26. package/lib/components/modal.js +86 -9
  27. package/lib/components/option.js +2 -5
  28. package/lib/components/popup.js +99 -11
  29. package/lib/components/radio.d.ts +8 -1
  30. package/lib/components/radio.js +14 -29
  31. package/lib/components/resize-observer.js +2 -0
  32. package/lib/components/scroll-container.js +2 -0
  33. package/lib/components/select.d.ts +53 -6
  34. package/lib/components/select.js +127 -29
  35. package/lib/components/switch.d.ts +7 -1
  36. package/lib/components/switch.js +20 -6
  37. package/lib/components/tabs.js +1 -1
  38. package/lib/components/textarea.d.ts +45 -4
  39. package/lib/components/textarea.js +75 -18
  40. package/lib/index.d.ts +1 -0
  41. package/lib/index.js +1 -0
  42. package/lib/styles/button.js +21 -19
  43. package/lib/styles/calendar-month.js +1 -1
  44. package/lib/styles/calendar.js +3 -0
  45. package/lib/styles/checkbox.js +11 -5
  46. package/lib/styles/date-picker.js +15 -3
  47. package/lib/styles/input.js +5 -9
  48. package/lib/styles/option.d.ts +1 -1
  49. package/lib/styles/option.js +4 -0
  50. package/lib/styles/popup.d.ts +1 -1
  51. package/lib/styles/popup.js +2 -0
  52. package/lib/styles/radio.js +10 -4
  53. package/lib/styles/select-base.js +24 -3
  54. package/lib/styles/select.d.ts +1 -1
  55. package/lib/styles/select.js +4 -0
  56. package/lib/styles/switch.js +10 -5
  57. package/lib/styles/tag.js +2 -1
  58. package/lib/styles/textarea.d.ts +1 -1
  59. package/lib/styles/textarea.js +7 -0
  60. package/lib/utils/calendar-utils.d.ts +49 -0
  61. package/lib/utils/calendar-utils.js +63 -6
  62. package/lib/utils/scroll-lock.d.ts +10 -4
  63. package/lib/utils/scroll-lock.js +102 -2
  64. package/lib/utils/upgrade-properties.d.ts +21 -0
  65. package/lib/utils/upgrade-properties.js +47 -0
  66. package/lib/utils/validity.d.ts +89 -5
  67. package/lib/utils/validity.js +198 -25
  68. package/lib/version.d.ts +1 -1
  69. package/lib/version.js +1 -1
  70. package/package.json +2 -1
  71. package/lib/base/ty-component.d.ts.map +0 -1
  72. package/lib/base/ty-component.js.map +0 -1
  73. package/lib/components/button.d.ts.map +0 -1
  74. package/lib/components/button.js.map +0 -1
  75. package/lib/components/calendar-month.d.ts.map +0 -1
  76. package/lib/components/calendar-month.js.map +0 -1
  77. package/lib/components/calendar-navigation.d.ts.map +0 -1
  78. package/lib/components/calendar-navigation.js.map +0 -1
  79. package/lib/components/calendar.d.ts.map +0 -1
  80. package/lib/components/calendar.js.map +0 -1
  81. package/lib/components/checkbox.d.ts.map +0 -1
  82. package/lib/components/checkbox.js.map +0 -1
  83. package/lib/components/copy.d.ts.map +0 -1
  84. package/lib/components/copy.js.map +0 -1
  85. package/lib/components/date-picker.d.ts.map +0 -1
  86. package/lib/components/date-picker.js.map +0 -1
  87. package/lib/components/dropdown.d.ts +0 -403
  88. package/lib/components/dropdown.d.ts.map +0 -1
  89. package/lib/components/dropdown.js +0 -1315
  90. package/lib/components/dropdown.js.map +0 -1
  91. package/lib/components/icon.d.ts.map +0 -1
  92. package/lib/components/icon.js.map +0 -1
  93. package/lib/components/input.d.ts.map +0 -1
  94. package/lib/components/input.js.map +0 -1
  95. package/lib/components/modal.d.ts.map +0 -1
  96. package/lib/components/modal.js.map +0 -1
  97. package/lib/components/multiselect.d.ts +0 -389
  98. package/lib/components/multiselect.d.ts.map +0 -1
  99. package/lib/components/multiselect.js +0 -1301
  100. package/lib/components/multiselect.js.map +0 -1
  101. package/lib/components/option.d.ts.map +0 -1
  102. package/lib/components/option.js.map +0 -1
  103. package/lib/components/popup.d.ts.map +0 -1
  104. package/lib/components/popup.js.map +0 -1
  105. package/lib/components/resize-observer.d.ts.map +0 -1
  106. package/lib/components/resize-observer.js.map +0 -1
  107. package/lib/components/scroll-container.d.ts.map +0 -1
  108. package/lib/components/scroll-container.js.map +0 -1
  109. package/lib/components/step.d.ts.map +0 -1
  110. package/lib/components/step.js.map +0 -1
  111. package/lib/components/tab.d.ts.map +0 -1
  112. package/lib/components/tab.js.map +0 -1
  113. package/lib/components/tabs.d.ts.map +0 -1
  114. package/lib/components/tabs.js.map +0 -1
  115. package/lib/components/tag.d.ts.map +0 -1
  116. package/lib/components/tag.js.map +0 -1
  117. package/lib/components/textarea.d.ts.map +0 -1
  118. package/lib/components/textarea.js.map +0 -1
  119. package/lib/components/tooltip.d.ts.map +0 -1
  120. package/lib/components/tooltip.js.map +0 -1
  121. package/lib/components/wizard.d.ts.map +0 -1
  122. package/lib/components/wizard.js.map +0 -1
  123. package/lib/icons/fontawesome/brands.d.ts.map +0 -1
  124. package/lib/icons/fontawesome/brands.js.map +0 -1
  125. package/lib/icons/fontawesome/regular.d.ts.map +0 -1
  126. package/lib/icons/fontawesome/regular.js.map +0 -1
  127. package/lib/icons/fontawesome/solid.d.ts.map +0 -1
  128. package/lib/icons/fontawesome/solid.js.map +0 -1
  129. package/lib/icons/heroicons/micro.d.ts.map +0 -1
  130. package/lib/icons/heroicons/micro.js.map +0 -1
  131. package/lib/icons/heroicons/mini.d.ts.map +0 -1
  132. package/lib/icons/heroicons/mini.js.map +0 -1
  133. package/lib/icons/heroicons/outline.d.ts.map +0 -1
  134. package/lib/icons/heroicons/outline.js.map +0 -1
  135. package/lib/icons/heroicons/solid.d.ts.map +0 -1
  136. package/lib/icons/heroicons/solid.js.map +0 -1
  137. package/lib/icons/lucide.d.ts.map +0 -1
  138. package/lib/icons/lucide.js.map +0 -1
  139. package/lib/icons/material/filled.d.ts.map +0 -1
  140. package/lib/icons/material/filled.js.map +0 -1
  141. package/lib/icons/material/outlined.d.ts.map +0 -1
  142. package/lib/icons/material/outlined.js.map +0 -1
  143. package/lib/icons/material/round.d.ts.map +0 -1
  144. package/lib/icons/material/round.js.map +0 -1
  145. package/lib/icons/material/sharp.d.ts.map +0 -1
  146. package/lib/icons/material/sharp.js.map +0 -1
  147. package/lib/icons/material/two-tone.d.ts.map +0 -1
  148. package/lib/icons/material/two-tone.js.map +0 -1
  149. package/lib/index.d.ts.map +0 -1
  150. package/lib/index.js.map +0 -1
  151. package/lib/styles/button.d.ts.map +0 -1
  152. package/lib/styles/button.js.map +0 -1
  153. package/lib/styles/calendar-month.d.ts.map +0 -1
  154. package/lib/styles/calendar-month.js.map +0 -1
  155. package/lib/styles/calendar-navigation.d.ts.map +0 -1
  156. package/lib/styles/calendar-navigation.js.map +0 -1
  157. package/lib/styles/calendar.d.ts.map +0 -1
  158. package/lib/styles/calendar.js.map +0 -1
  159. package/lib/styles/copy.d.ts.map +0 -1
  160. package/lib/styles/copy.js.map +0 -1
  161. package/lib/styles/date-picker.d.ts.map +0 -1
  162. package/lib/styles/date-picker.js.map +0 -1
  163. package/lib/styles/dropdown.d.ts +0 -12
  164. package/lib/styles/dropdown.d.ts.map +0 -1
  165. package/lib/styles/dropdown.js +0 -725
  166. package/lib/styles/dropdown.js.map +0 -1
  167. package/lib/styles/icon.d.ts.map +0 -1
  168. package/lib/styles/icon.js.map +0 -1
  169. package/lib/styles/input.d.ts.map +0 -1
  170. package/lib/styles/input.js.map +0 -1
  171. package/lib/styles/modal.d.ts.map +0 -1
  172. package/lib/styles/modal.js.map +0 -1
  173. package/lib/styles/multiselect.d.ts +0 -6
  174. package/lib/styles/multiselect.d.ts.map +0 -1
  175. package/lib/styles/multiselect.js +0 -568
  176. package/lib/styles/multiselect.js.map +0 -1
  177. package/lib/styles/option.d.ts.map +0 -1
  178. package/lib/styles/option.js.map +0 -1
  179. package/lib/styles/popup.d.ts.map +0 -1
  180. package/lib/styles/popup.js.map +0 -1
  181. package/lib/styles/resize-observer.d.ts.map +0 -1
  182. package/lib/styles/resize-observer.js.map +0 -1
  183. package/lib/styles/scroll-container.d.ts.map +0 -1
  184. package/lib/styles/scroll-container.js.map +0 -1
  185. package/lib/styles/step.d.ts.map +0 -1
  186. package/lib/styles/step.js.map +0 -1
  187. package/lib/styles/tab.d.ts.map +0 -1
  188. package/lib/styles/tab.js.map +0 -1
  189. package/lib/styles/tabs.d.ts.map +0 -1
  190. package/lib/styles/tabs.js.map +0 -1
  191. package/lib/styles/tag.d.ts.map +0 -1
  192. package/lib/styles/tag.js.map +0 -1
  193. package/lib/styles/textarea.d.ts.map +0 -1
  194. package/lib/styles/textarea.js.map +0 -1
  195. package/lib/styles/tooltip.d.ts.map +0 -1
  196. package/lib/styles/tooltip.js.map +0 -1
  197. package/lib/styles/wizard.d.ts.map +0 -1
  198. package/lib/styles/wizard.js.map +0 -1
  199. package/lib/types/common.d.ts.map +0 -1
  200. package/lib/types/common.js.map +0 -1
  201. package/lib/utils/calendar-utils.d.ts.map +0 -1
  202. package/lib/utils/calendar-utils.js.map +0 -1
  203. package/lib/utils/icon-registry.d.ts.map +0 -1
  204. package/lib/utils/icon-registry.js.map +0 -1
  205. package/lib/utils/locale.d.ts.map +0 -1
  206. package/lib/utils/locale.js.map +0 -1
  207. package/lib/utils/number-format.d.ts.map +0 -1
  208. package/lib/utils/number-format.js.map +0 -1
  209. package/lib/utils/parse-boolean.d.ts +0 -39
  210. package/lib/utils/parse-boolean.d.ts.map +0 -1
  211. package/lib/utils/parse-boolean.js +0 -23
  212. package/lib/utils/parse-boolean.js.map +0 -1
  213. package/lib/utils/positioning.d.ts.map +0 -1
  214. package/lib/utils/positioning.js.map +0 -1
  215. package/lib/utils/property-capture.d.ts.map +0 -1
  216. package/lib/utils/property-capture.js.map +0 -1
  217. package/lib/utils/property-manager.d.ts.map +0 -1
  218. package/lib/utils/property-manager.js.map +0 -1
  219. package/lib/utils/resize-observer.d.ts.map +0 -1
  220. package/lib/utils/resize-observer.js.map +0 -1
  221. package/lib/utils/scroll-lock.d.ts.map +0 -1
  222. package/lib/utils/scroll-lock.js.map +0 -1
  223. package/lib/utils/styles.d.ts.map +0 -1
  224. package/lib/utils/styles.js.map +0 -1
  225. package/lib/version.d.ts.map +0 -1
  226. package/lib/version.js.map +0 -1
@@ -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 { switchStyles, switchCustomFlavorCss } from "../styles/switch.js";
6
+ import { nativeMessage } from "../utils/validity.js";
6
7
  export class TySwitch extends TyComponent {
7
8
  constructor() {
8
9
  super();
@@ -18,6 +19,9 @@ ensureStyles(shadow, { css: switchStyles, id: "ty-switch" });
18
19
  onConnect() {
19
20
  this.setupEventListeners();
20
21
  this._syncCustomFlavor();
22
+ const el = this.shadowRoot.querySelector(".switch-container");
23
+ if (el && this.shadowRoot.activeElement !== el)
24
+ el.classList.remove("focused");
21
25
  }
22
26
  onDisconnect() {
23
27
  this.removeEventListeners();
@@ -44,8 +48,7 @@ return classes.join(" ");
44
48
  handleSwitchClick(e) {
45
49
  if (this.disabled)
46
50
  return;
47
- if (this.isDuplicateActivationClick())
48
- return;
51
+ this.showFocusRing(e instanceof KeyboardEvent);
49
52
  const newValue = !this.checked;
50
53
  this.checked = newValue;
51
54
  setTimeout(() => {
@@ -59,6 +62,9 @@ this.dispatchEvent(new CustomEvent("input", { detail: eventDetail, bubbles: true
59
62
  this.dispatchEvent(new CustomEvent("change", { detail: eventDetail, bubbles: true, composed: true }));
60
63
  }, 0);
61
64
  }
65
+ showFocusRing(on) {
66
+ this.shadowRoot?.querySelector(".switch-container")?.classList.toggle("focused", on);
67
+ }
62
68
  handleSwitchKeydown(e) {
63
69
  if (this.disabled)
64
70
  return;
@@ -106,7 +112,7 @@ if (!el)
106
112
  return;
107
113
  this._clickHandler = (e) => this.handleSwitchClick(e);
108
114
  this._keydownHandler = (e) => this.handleSwitchKeydown(e);
109
- this._focusHandler = () => el.classList.add("focused");
115
+ this._focusHandler = () => el.classList.toggle("focused", el.matches(":focus-visible"));
110
116
  this._blurHandler = () => el.classList.remove("focused");
111
117
  this.addEventListener("click", this._clickHandler);
112
118
  el.addEventListener("keydown", this._keydownHandler);
@@ -121,7 +127,8 @@ const classes = this.buildClassList();
121
127
  if (!switchEl) {
122
128
  switchEl = document.createElement("div");
123
129
  switchEl.className = "switch-container " + classes;
124
- switchEl.tabIndex = this.disabled ? -1 : 0;
130
+ if (!this.disabled)
131
+ switchEl.tabIndex = 0;
125
132
  switchEl.setAttribute("role", "switch");
126
133
  switchEl.setAttribute("aria-checked", this.checked ? "true" : "false");
127
134
  switchEl.setAttribute("aria-disabled", this.disabled ? "true" : "false");
@@ -135,8 +142,13 @@ switchEl.appendChild(track);
135
142
  shadow.appendChild(switchEl);
136
143
  }
137
144
  else {
145
+ const focused = switchEl.classList.contains("focused") && shadow.activeElement === switchEl;
138
146
  switchEl.className = "switch-container " + classes;
139
- switchEl.tabIndex = this.disabled ? -1 : 0;
147
+ switchEl.classList.toggle("focused", focused && !this.disabled);
148
+ if (this.disabled)
149
+ switchEl.removeAttribute("tabindex");
150
+ else
151
+ switchEl.tabIndex = 0;
140
152
  switchEl.setAttribute("aria-checked", this.checked ? "true" : "false");
141
153
  switchEl.setAttribute("aria-disabled", this.disabled ? "true" : "false");
142
154
  switchEl.setAttribute("aria-required", this.required ? "true" : "false");
@@ -159,7 +171,7 @@ this.applyValidity({});
159
171
  return;
160
172
  }
161
173
  if (this.required && !this.checked) {
162
- this.applyValidity({ valueMissing: true }, "Please turn this on", anchor ?? undefined);
174
+ this.applyValidity({ valueMissing: true }, nativeMessage("input", { type: "checkbox", switch: true, required: true }), anchor ?? undefined);
163
175
  }
164
176
  else {
165
177
  this.applyValidity({});
@@ -182,6 +194,8 @@ set flavor(value) { this.setProperty("flavor", value); }
182
194
  get form() { return this._internals.form; }
183
195
  }
184
196
  TySwitch.formAssociated = true;
197
+ TySwitch.delegatesFocus = true;
198
+ TySwitch.labelClicks = true;
185
199
  TySwitch.properties = {
186
200
  checked: {
187
201
  type: "boolean",
@@ -251,7 +251,7 @@ marker.style.top = `${position.top}px`;
251
251
  marker.style.width = `${position.width}px`;
252
252
  marker.style.height = `${position.height}px`;
253
253
  if (snap) {
254
- marker.offsetWidth;
254
+ marker.getBoundingClientRect();
255
255
  marker.style.transition = '';
256
256
  }
257
257
  }
@@ -21,6 +21,10 @@ export interface TyTextareaElement extends HTMLElement {
21
21
  label?: string;
22
22
  disabled?: boolean;
23
23
  required?: boolean;
24
+ minLength?: number;
25
+ maxLength?: number;
26
+ tooShortMessage?: string;
27
+ tooLongMessage?: string;
24
28
  error?: string;
25
29
  size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
26
30
  rows?: string | number;
@@ -63,6 +67,26 @@ export declare class TyTextarea extends TyComponent<TextareaState> implements Ty
63
67
  visual: boolean;
64
68
  default: boolean;
65
69
  };
70
+ minlength: {
71
+ type: "string";
72
+ visual: boolean;
73
+ default: string;
74
+ };
75
+ maxlength: {
76
+ type: "string";
77
+ visual: boolean;
78
+ default: string;
79
+ };
80
+ tooShortMessage: {
81
+ type: "string";
82
+ visual: boolean;
83
+ default: string;
84
+ };
85
+ tooLongMessage: {
86
+ type: "string";
87
+ visual: boolean;
88
+ default: string;
89
+ };
66
90
  error: {
67
91
  type: "string";
68
92
  visual: boolean;
@@ -129,6 +153,14 @@ export declare class TyTextarea extends TyComponent<TextareaState> implements Ty
129
153
  focus(options?: FocusOptions): void;
130
154
  get required(): boolean;
131
155
  set required(v: boolean);
156
+ get minLength(): number;
157
+ set minLength(v: number);
158
+ get maxLength(): number;
159
+ set maxLength(v: number);
160
+ get tooShortMessage(): string;
161
+ set tooShortMessage(v: string);
162
+ get tooLongMessage(): string;
163
+ set tooLongMessage(v: string);
132
164
  get error(): string;
133
165
  set error(v: string);
134
166
  get size(): 'xs' | 'sm' | 'md' | 'lg' | 'xl';
@@ -153,17 +185,26 @@ export declare class TyTextarea extends TyComponent<TextareaState> implements Ty
153
185
  private _destroyScrollbar;
154
186
  private emitValueEvent;
155
187
  private handleInputEvent;
156
- /** The user typed since the field was created: blur then shows its validation message. */
157
- private _userEdited;
158
188
  private _valueAtFocus;
159
189
  private handleFocusEvent;
160
190
  private handleBlurEvent;
161
191
  private buildClassList;
162
192
  private applyHeightConstraints;
163
193
  private setupTextareaEvents;
194
+ private removeTextareaEvents;
164
195
  protected render(): void;
165
- /** required + empty => valueMissing; otherwise valid. Disabled fields are
166
- * barred from constraint validation. */
196
+ /**
197
+ * minlength goes on the inner <textarea> for the browser's own too-short
198
+ * message after the user types, maxlength so typing stops at the limit
199
+ * (like ty-input).
200
+ */
201
+ private syncConstraintAttributes;
202
+ /**
203
+ * required + empty => valueMissing (the browser's own message); otherwise
204
+ * minlength / maxlength, worded like ty-input's (lengthValidity). Empty
205
+ * values only fail `required`, like native. Disabled fields are barred
206
+ * from constraint validation.
207
+ */
167
208
  private updateValidity;
168
209
  }
169
210
  export {};
@@ -4,7 +4,8 @@ import { ensureStyles } from '../utils/styles.js';
4
4
  import { textareaStyles } from '../styles/textarea.js';
5
5
  import { CustomScrollbar, isCustomScrollbarEnabled } from '../utils/custom-scrollbar.js';
6
6
  import { customScrollbarStyles } from '../styles/custom-scrollbar.js';
7
- const REQUIRED_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>`;
7
+ import { nativeMessage, lengthValidity, toLimit } from '../utils/validity.js';
8
+ import { REQUIRED_ICON_SVG } from '../utils/icons.js';
8
9
  export class TyTextarea extends TyComponent {
9
10
  constructor() {
10
11
  super();
@@ -14,6 +15,8 @@ this._scrollbar = null;
14
15
  this._isFocused = false;
15
16
  this.handleInputEvent = (e) => {
16
17
  this._userEdited = true;
18
+ e.stopPropagation();
19
+ e.stopImmediatePropagation();
17
20
  const target = e.target;
18
21
  const newValue = target.value;
19
22
  this.value = newValue;
@@ -24,7 +27,6 @@ this._internals.setFormValue(this.value || '');
24
27
  this.updateValidity();
25
28
  this.emitValueEvent('input', e);
26
29
  };
27
- this._userEdited = false;
28
30
  this._valueAtFocus = null;
29
31
  this.handleFocusEvent = () => {
30
32
  this._isFocused = true;
@@ -46,20 +48,22 @@ ensureStyles(shadow, { css: customScrollbarStyles, id: 'ty-custom-scrollbar' });
46
48
  }
47
49
  }
48
50
  onConnect() {
51
+ this.updateFormValue();
52
+ if (this._rendered) {
53
+ this.render();
54
+ if (this._textareaEl)
55
+ this.setupTextareaEvents(this._textareaEl);
56
+ }
49
57
  }
50
58
  onDisconnect() {
51
59
  this._destroyScrollbar();
52
- if (this._textareaEl) {
53
- const newTextarea = this._textareaEl.cloneNode(true);
54
- this._textareaEl.parentNode?.replaceChild(newTextarea, this._textareaEl);
55
- this._textareaEl = null;
56
- }
57
- this._dummyEl = null;
60
+ if (this._textareaEl)
61
+ this.removeTextareaEvents(this._textareaEl);
58
62
  }
59
63
  onPropertiesChanged(changes) {
60
64
  }
61
65
  getFormValue() {
62
- return this.getProperty('value') || null;
66
+ return this.getProperty('value') || '';
63
67
  }
64
68
  get value() { return this.getProperty('value'); }
65
69
  set value(v) { this.setProperty('value', v); }
@@ -76,6 +80,14 @@ this.shadowRoot?.querySelector('textarea')?.focus(options);
76
80
  }
77
81
  get required() { return this.getProperty('required'); }
78
82
  set required(v) { this.setProperty('required', v); }
83
+ get minLength() { return toLimit(this.getProperty('minlength')) ?? -1; }
84
+ set minLength(v) { this.setProperty('minlength', v == null || v < 0 ? '' : String(v)); }
85
+ get maxLength() { return toLimit(this.getProperty('maxlength')) ?? -1; }
86
+ set maxLength(v) { this.setProperty('maxlength', v == null || v < 0 ? '' : String(v)); }
87
+ get tooShortMessage() { return this.getProperty('tooShortMessage'); }
88
+ set tooShortMessage(v) { this.setProperty('tooShortMessage', v ?? ''); }
89
+ get tooLongMessage() { return this.getProperty('tooLongMessage'); }
90
+ set tooLongMessage(v) { this.setProperty('tooLongMessage', v ?? ''); }
79
91
  get error() { return this.getProperty('error'); }
80
92
  set error(v) { this.setProperty('error', v); }
81
93
  get size() {
@@ -182,6 +194,10 @@ textareaEl.style.overflowY = 'hidden';
182
194
  this._destroyScrollbar();
183
195
  }
184
196
  }
197
+ else if (maxHeight !== null && measuredHeight >= maxHeight && !this._scrollbar) {
198
+ textareaEl.style.overflowY = 'auto';
199
+ this._setupScrollbar(textareaEl);
200
+ }
185
201
  this._scrollbar?.update();
186
202
  }
187
203
  _setupScrollbar(textareaEl) {
@@ -218,7 +234,7 @@ value: this.value,
218
234
  originalEvent
219
235
  }
220
236
  };
221
- setTimeout(() => this.dispatchEvent(new CustomEvent(type, data)), 0);
237
+ this.dispatchEvent(new CustomEvent(type, data));
222
238
  }
223
239
  buildClassList() {
224
240
  const classes = [this.size];
@@ -248,6 +264,11 @@ textareaEl.addEventListener('input', this.handleInputEvent);
248
264
  textareaEl.addEventListener('focus', this.handleFocusEvent);
249
265
  textareaEl.addEventListener('blur', this.handleBlurEvent);
250
266
  }
267
+ removeTextareaEvents(textareaEl) {
268
+ textareaEl.removeEventListener('input', this.handleInputEvent);
269
+ textareaEl.removeEventListener('focus', this.handleFocusEvent);
270
+ textareaEl.removeEventListener('blur', this.handleBlurEvent);
271
+ }
251
272
  render() {
252
273
  const root = this.shadowRoot;
253
274
  const existingContainer = root.querySelector('.textarea-container');
@@ -257,18 +278,19 @@ const existingError = root.querySelector('.error-message');
257
278
  if (existingContainer && existingTextarea) {
258
279
  if (this.label) {
259
280
  if (existingLabel) {
260
- existingLabel.innerHTML = this.label +
261
- (this.required ? ` <span class="required-icon">${REQUIRED_ICON}</span>` : '');
262
- existingLabel.style.display = 'block';
281
+ existingLabel.innerHTML = `${this.label}${this.required ? `<span class="required-icon">${REQUIRED_ICON_SVG}</span>` : ''}`;
282
+ existingLabel.style.display = 'flex';
263
283
  }
264
284
  }
265
285
  else if (existingLabel) {
266
286
  existingLabel.style.display = 'none';
267
287
  }
288
+ if (existingTextarea.value !== (this.value || ''))
268
289
  existingTextarea.value = this.value || '';
269
290
  existingTextarea.placeholder = this.placeholder || '';
270
291
  existingTextarea.disabled = this.disabled;
271
292
  existingTextarea.required = this.required;
293
+ this.syncConstraintAttributes(existingTextarea);
272
294
  existingTextarea.className = this.buildClassList();
273
295
  if (this.label)
274
296
  existingTextarea.setAttribute('aria-label', this.label);
@@ -308,9 +330,8 @@ container.className = 'textarea-container';
308
330
  const labelEl = document.createElement('label');
309
331
  labelEl.className = 'ty-field-label';
310
332
  if (this.label) {
311
- labelEl.innerHTML = this.label +
312
- (this.required ? ` <span class="required-icon">${REQUIRED_ICON}</span>` : '');
313
- labelEl.style.display = 'block';
333
+ labelEl.innerHTML = `${this.label}${this.required ? `<span class="required-icon">${REQUIRED_ICON_SVG}</span>` : ''}`;
334
+ labelEl.style.display = 'flex';
314
335
  }
315
336
  else {
316
337
  labelEl.style.display = 'none';
@@ -320,6 +341,7 @@ textareaEl.value = this.value || '';
320
341
  textareaEl.placeholder = this.placeholder || '';
321
342
  textareaEl.disabled = this.disabled;
322
343
  textareaEl.required = this.required;
344
+ this.syncConstraintAttributes(textareaEl);
323
345
  textareaEl.className = this.buildClassList();
324
346
  if (this.label)
325
347
  textareaEl.setAttribute('aria-label', this.label);
@@ -371,6 +393,11 @@ this._textareaEl = textareaEl;
371
393
  }
372
394
  this.updateValidity();
373
395
  }
396
+ syncConstraintAttributes(ta) {
397
+ const set = (name, limit) => limit !== null ? ta.setAttribute(name, String(limit)) : ta.removeAttribute(name);
398
+ set('minlength', toLimit(this.getProperty('minlength')));
399
+ set('maxlength', toLimit(this.getProperty('maxlength')));
400
+ }
374
401
  updateValidity() {
375
402
  const ta = this.shadowRoot?.querySelector('textarea');
376
403
  if (this.disabled) {
@@ -378,12 +405,22 @@ this.applyValidity({});
378
405
  return;
379
406
  }
380
407
  const current = ta ? ta.value : String(this.value ?? '');
381
- if (this.required && current.trim() === '') {
382
- this.applyValidity({ valueMissing: true }, 'Please fill out this field', ta ?? undefined);
408
+ if (current.trim() === '') {
409
+ if (this.required) {
410
+ this.applyValidity({ valueMissing: true }, nativeMessage('textarea', { required: true }), ta ?? undefined);
383
411
  }
384
412
  else {
385
413
  this.applyValidity({});
386
414
  }
415
+ return;
416
+ }
417
+ const { tooShort, tooLong } = lengthValidity(current, {
418
+ minlength: this.getProperty('minlength'),
419
+ maxlength: this.getProperty('maxlength'),
420
+ tooShortMessage: this.tooShortMessage,
421
+ tooLongMessage: this.tooLongMessage,
422
+ }, ta);
423
+ this.applyValidity({ tooShort: !!tooShort, tooLong: !!tooLong }, tooShort ?? tooLong, ta ?? undefined);
387
424
  }
388
425
  }
389
426
  TyTextarea.formAssociated = true;
@@ -419,6 +456,26 @@ type: 'boolean',
419
456
  visual: true,
420
457
  default: false
421
458
  },
459
+ minlength: {
460
+ type: 'string',
461
+ visual: true,
462
+ default: ''
463
+ },
464
+ maxlength: {
465
+ type: 'string',
466
+ visual: true,
467
+ default: ''
468
+ },
469
+ tooShortMessage: {
470
+ type: 'string',
471
+ visual: true,
472
+ default: ''
473
+ },
474
+ tooLongMessage: {
475
+ type: 'string',
476
+ visual: true,
477
+ default: ''
478
+ },
422
479
  error: {
423
480
  type: 'string',
424
481
  visual: true,
package/lib/index.d.ts CHANGED
@@ -75,6 +75,7 @@ declare global {
75
75
  tyResizeObserver: {
76
76
  getSize: (id: string) => ResizeSize | undefined;
77
77
  onResize: (id: string, callback: ResizeCallback) => () => void;
78
+ getAllSizes: () => Record<string, ResizeSize>;
78
79
  sizes: Record<string, ResizeSize>;
79
80
  };
80
81
  tyLoader: {
package/lib/index.js CHANGED
@@ -54,6 +54,7 @@ clearCache: () => clearIcons()
54
54
  window.tyResizeObserver = {
55
55
  getSize: (id) => getResizeSize(id),
56
56
  onResize: (id, callback) => subscribeResize(id, callback),
57
+ getAllSizes: () => getAllSizes(),
57
58
  get sizes() { return getAllSizes(); }
58
59
  };
59
60
  }
@@ -69,11 +69,26 @@ color: var(--ty-button-color, ${ink('-strong')});
69
69
  export const buttonCustomFlavorCss = (base) => solidFlavor(base, 'neutral') + outlinedFlavor(base, 'neutral') + ghostFlavor(base, 'neutral');
70
70
  export const buttonStyles = `
71
71
  :host {
72
- display: inline-block;
72
+ --_rung: var(--ty-button-height);
73
+ display: inline-grid;
74
+ grid-template: minmax(var(--_rung), 1fr) / minmax(min-content, 1fr);
75
+ height: fit-content;
73
76
  font-family: inherit;
74
77
  }
78
+ :host([action]) {
79
+ --_rung: var(--ty-button-action-size);
80
+ }
81
+ :host([action]:not([wide])) {
82
+ width: fit-content;
83
+ aspect-ratio: 1;
84
+ }
75
85
  :host([wide]) {
76
- display: flex;
86
+ display: grid;
87
+ flex-grow: 1;
88
+ max-height: var(--_rung);
89
+ grid-template-rows: minmax(var(--_rung), max-content);
90
+ }
91
+ :host([wide]) button {
77
92
  flex-grow: 1;
78
93
  }
79
94
  :host {
@@ -140,6 +155,7 @@ align-items: center;
140
155
  justify-content: center;
141
156
  gap: var(--ty-button-gap);
142
157
  height: var(--ty-button-height);
158
+ min-height: 100%;
143
159
  padding: var(--ty-button-pad-y) var(--ty-button-pad-x);
144
160
  font-size: var(--ty-button-font);
145
161
  line-height: var(--ty-button-leading);
@@ -214,13 +230,11 @@ justify-content: center;
214
230
  ::slotted(ty-icon) {
215
231
  flex-shrink: 0;
216
232
  }
217
- :host([wide]) button {
218
- flex-grow: 1;
219
- }
220
233
  button.action {
221
234
  gap: 0px !important;
222
- height: var(--ty-button-action-size);
223
- width: var(--ty-button-action-size);
235
+ height: auto;
236
+ min-width: var(--ty-button-action-size);
237
+ min-height: var(--ty-button-action-size);
224
238
  padding: 0px !important;
225
239
  }
226
240
  button.action ::slotted(ty-icon) {
@@ -272,18 +286,6 @@ button.pill.xs { padding-left: 1em; padding-right: 1em; }
272
286
  button.pill.sm { padding-left: 1.125em; padding-right: 1.125em; }
273
287
  button.pill.lg { padding-left: 1.5em; padding-right: 1.5em; }
274
288
  button.pill.xl { padding-left: 1.75em; padding-right: 1.75em; }
275
- button.pill:has(ty-icon:only-child),
276
- button.pill:has(slot[name="start"]:only-child),
277
- button.pill:has(slot[name="end"]:only-child) {
278
- padding: 0;
279
- aspect-ratio: 1;
280
- min-width: var(--ty-size-md);
281
- }
282
- button.pill.xs:has(ty-icon:only-child) { min-width: 1.375rem; min-height: 1.375rem; }
283
- button.pill.sm:has(ty-icon:only-child) { min-width: 1.5rem; min-height: 1.5rem; }
284
- button.pill.md:has(ty-icon:only-child) { min-width: 2rem; min-height: 2rem; }
285
- button.pill.lg:has(ty-icon:only-child) { min-width: 2.25rem; min-height: 2.25rem; }
286
- button.pill.xl:has(ty-icon:only-child) { min-width: 2.5rem; min-height: 2.5rem; }
287
289
  button.solid {
288
290
  --_ring: var(--ty-color-neutral);
289
291
  --_solid-bg: var(--ty-button-bg, var(--ty-solid-neutral));
@@ -124,7 +124,7 @@ color: var(--ty-calendar-weekend-color, var(--ty-calendar-day-color, var(--ty-te
124
124
  color: var(--ty-calendar-other-month-color, color-mix(in oklab, var(--ty-text-faint) 65%, transparent));
125
125
  }
126
126
  .calendar-day-cell.other-month:hover {
127
- color: var(--ty-calendar-day-hover-color, var(--ty-text-soft, var(--ty-text-)));
127
+ color: var(--ty-calendar-day-hover-color, var(--ty-text-soft));
128
128
  background-color: var(--ty-calendar-day-hover-bg, var(--ty-bg-neutral-soft));
129
129
  }
130
130
  .calendar-day-cell.disabled {
@@ -10,6 +10,9 @@ box-sizing: border-box;
10
10
  font-family: inherit;
11
11
  align-items: center;
12
12
  }
13
+ .calendar-container:focus {
14
+ outline: none;
15
+ }
13
16
  @media (max-width: 320px) {
14
17
  .calendar-container {
15
18
  gap: 0.375rem;
@@ -14,14 +14,21 @@ export const checkboxStyles = `
14
14
  :host {
15
15
  display: inline-flex;
16
16
  width: auto;
17
- vertical-align: middle;
17
+ height: 1.25em;
18
+ height: 1lh;
19
+ align-items: center;
20
+ vertical-align: baseline;
21
+ overflow: visible;
22
+ }
23
+ :host::before {
24
+ content: "\\200b";
25
+ width: 0;
18
26
  }
19
27
  .checkbox-container {
20
28
  display: inline-flex;
29
+ flex-shrink: 0;
21
30
  align-items: center;
22
31
  justify-content: center;
23
- min-height: 1.25em;
24
- min-height: 1lh;
25
32
  outline: none;
26
33
  transition: var(--ty-local-transition, all 0.15s ease-in-out);
27
34
  user-select: none;
@@ -42,8 +49,7 @@ color: var(--checkbox-color-off-hover, color-mix(in oklab, var(--ty-text) 50%, t
42
49
  .checkbox-container[aria-checked="true"] {
43
50
  color: var(--checkbox-color, var(--ty-text));
44
51
  }
45
- .checkbox-container.focused,
46
- .checkbox-container:focus-visible {
52
+ .checkbox-container.focused {
47
53
  box-shadow: 0 0 0 3px var(--checkbox-ring, color-mix(in oklab, var(--ty-color-primary) 25%, transparent));
48
54
  }
49
55
  .checkbox-icon {
@@ -1,6 +1,6 @@
1
1
  import { FLAVORS } from "../types/common.js";
2
2
  import { fieldSizeVars } from "./field-size.js";
3
- const datePickerFlavor = (f, fb) => {
3
+ const datePickerFlavor = (f, fb, also = "") => {
4
4
  const c = (s) => fb
5
5
  ? s
6
6
  ? `var(--ty-color-${f}${s}, var(--ty-color-${f}, var(--ty-color-${fb}${s})))`
@@ -9,7 +9,7 @@ const c = (s) => fb
9
9
  ? `var(--ty-color-${f}${s}, var(--ty-color-${f}))`
10
10
  : `var(--ty-color-${f})`;
11
11
  return `
12
- :host([flavor="${f}"]) {
12
+ :host([flavor="${f}"])${also} {
13
13
  --date-picker-accent: var(--ty-input-${f}-border, ${c("-soft")});
14
14
  --date-picker-accent-bold: ${c("")};
15
15
  --date-picker-ring: color-mix(in oklab, ${c("")} 15%, transparent);
@@ -26,6 +26,7 @@ return `
26
26
  }
27
27
  `;
28
28
  };
29
+ const errorLook = `,\n:host([error]:not([error=""]))`;
29
30
  export const datePickerCustomFlavorCss = (base) => datePickerFlavor(base, "neutral");
30
31
  export const datePickerStyles = `
31
32
  ${fieldSizeVars}
@@ -133,7 +134,18 @@ opacity: 0.6;
133
134
  border-color: var(--date-picker-accent-bold, var(--ty-date-picker-border-focus));
134
135
  box-shadow: 0 0 0 3px var(--date-picker-ring, var(--ty-date-picker-shadow-focus));
135
136
  }
136
- ${FLAVORS.filter((f) => f !== "neutral").map((f) => datePickerFlavor(f)).join("")}
137
+ ${FLAVORS.filter((f) => f !== "neutral").map((f) => datePickerFlavor(f, undefined, f === "danger" ? errorLook : "")).join("")}
138
+ .error-message {
139
+ font-size: var(--ty-font-xs);
140
+ line-height: var(--ty-leading-xs);
141
+ letter-spacing: var(--ty-tracking-xs);
142
+ color: var(--ty-color-danger);
143
+ margin-top: 4px;
144
+ padding-left: var(--ty-field-outer-pad-x);
145
+ }
146
+ .date-picker-stub.error:not([disabled]) {
147
+ background: var(--ty-bg-danger-soft);
148
+ }
137
149
  .stub-text {
138
150
  flex: 1;
139
151
  white-space: nowrap;
@@ -1,6 +1,6 @@
1
1
  import { FLAVORS } from "../types/common.js";
2
2
  import { fieldSizeVars } from "./field-size.js";
3
- const inputFlavor = (f, fb) => {
3
+ const inputFlavor = (f, fb, also = "") => {
4
4
  const c = (s) => fb
5
5
  ? s
6
6
  ? `var(--ty-color-${f}${s}, var(--ty-color-${f}, var(--ty-color-${fb}${s})))`
@@ -9,7 +9,7 @@ const c = (s) => fb
9
9
  ? `var(--ty-color-${f}${s}, var(--ty-color-${f}))`
10
10
  : `var(--ty-color-${f})`;
11
11
  return `
12
- :host([flavor="${f}"]) {
12
+ :host([flavor="${f}"])${also} {
13
13
  --input-accent: var(--ty-input-${f}-border, ${c("-soft")});
14
14
  --input-accent-bold: ${c("")};
15
15
  --input-ring: color-mix(in oklab, ${c("")} 15%, transparent);
@@ -26,6 +26,7 @@ return `
26
26
  }
27
27
  `;
28
28
  };
29
+ const errorLook = `,\n:host([error]:not([error=""]))`;
29
30
  export const inputCustomFlavorCss = (base) => inputFlavor(base, "neutral");
30
31
  export const inputStyles = `
31
32
  ${fieldSizeVars}
@@ -137,14 +138,9 @@ color: var(--ty-color-danger);
137
138
  margin-top: 4px;
138
139
  padding-left: var(--ty-field-outer-pad-x);
139
140
  }
140
- .input-wrapper.error {
141
- border-color: var(--ty-color-danger);
141
+ .input-wrapper.error:not(.disabled) {
142
142
  background: var(--ty-bg-danger-soft);
143
143
  }
144
- .input-wrapper.error.focused {
145
- border-color: var(--ty-color-danger-bold);
146
- box-shadow: 0 0 0 3px var(--input-shadow-focus, var(--ty-input-shadow-focus));
147
- }
148
144
  input {
149
145
  flex: 1;
150
146
  min-width: 0;
@@ -177,7 +173,7 @@ input::placeholder {
177
173
  color: var(--input-placeholder, var(--ty-input-placeholder));
178
174
  font-weight: 400;
179
175
  }
180
- ${FLAVORS.filter((f) => f !== "neutral").map((f) => inputFlavor(f)).join("")}
176
+ ${FLAVORS.filter((f) => f !== "neutral").map((f) => inputFlavor(f, undefined, f === "danger" ? errorLook : "")).join("")}
181
177
  input:focus-visible {
182
178
  outline: none;
183
179
  }
@@ -1,2 +1,2 @@
1
1
  /** Option Component Styles */
2
- export declare const optionStyles = "\n.option-content {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--ty-spacing-2);\n box-sizing: border-box;\n width: 100%;\n\n cursor: pointer;\n user-select: none;\n\n transition: var(--ty-transition-all);\n\n /* Linear-paired typography */\n color: var(--ty-text);\n font-family: inherit;\n font-size: var(--ty-font-sm);\n line-height: var(--ty-leading-sm);\n letter-spacing: var(--ty-tracking-sm);\n padding: var(--ty-spacing-2) var(--ty-spacing-3);\n border-radius: var(--ty-radius-md);\n}\n\n/* Cloned copy (projected into the select's trigger field): host defaults to\n inline like any unstyled custom element, which ignores width/overflow \u2014\n force a block box so the parent's flex-basis:0 truncation (select.ts\n .select-stub.has-clone slot[name=\"selected\"]) actually clips it, and\n collapse the content to a single ellipsized line instead of wrapping. */\n:host([cloned]) {\n display: block;\n min-width: 0;\n overflow: hidden;\n}\n\n/* The clone sits in the select's TRIGGER, so its type follows the field size\n ladder (styles/field-size.ts) rather than the dropdown list's own scale \u2014\n the --ty-field-* vars inherit from ty-select through the light DOM. The\n fallbacks cover a ty-option rendered outside a select. */\n:host([cloned]) .option-content {\n display: block;\n padding: 0px;\n background: transparent;\n min-width: 0;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n font-size: var(--ty-field-font, var(--ty-font-sm));\n line-height: var(--ty-field-leading, var(--ty-leading-sm));\n letter-spacing: var(--ty-field-tracking, var(--ty-tracking-sm));\n}\n\n.option-content:hover {\n background: var(--ty-bg-neutral-soft);\n}\n\n.option-content[highlighted] {\n background: var(--ty-bg-neutral-soft);\n color: var(--ty-text-strong);\n}\n\n/* Selected: quiet \u2014 bolder text color only, no background slab, no weight change */\n.option-content[selected] {\n background: transparent;\n color: var(--ty-text-bold);\n}\n\n/* Lucide check \u2014 right-aligned. Its box is always reserved so selecting\n (or highlighting) never changes the row's width and rewraps the text. */\n.option-check {\n display: block;\n visibility: hidden;\n flex-shrink: 0;\n width: 14px;\n height: 14px;\n margin-left: auto;\n color: var(--ty-color-primary);\n}\n\n.option-content[selected] .option-check {\n visibility: visible;\n}\n\n/* Never show the check on the copy cloned into the select's trigger field */\n:host([cloned]) .option-check {\n display: none;\n}\n\n.option-content[selected]:hover {\n background: var(--ty-bg-neutral-soft);\n}\n\n.option-content[disabled] {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.option-content[hidden] {}\n\n.option-content * {\n pointer-events: none;\n}\n";
2
+ export declare const optionStyles = "\n/* Options the author hid and options a ty-select search left out, at any\n depth in the select (a framework component's element around each option\n too). !important beats the select's mobile list display:block. */\n:host([hidden]),\n:host([filtered-out]) {\n display: none !important;\n}\n\n.option-content {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--ty-spacing-2);\n box-sizing: border-box;\n width: 100%;\n\n cursor: pointer;\n user-select: none;\n\n transition: var(--ty-transition-all);\n\n /* Linear-paired typography */\n color: var(--ty-text);\n font-family: inherit;\n font-size: var(--ty-font-sm);\n line-height: var(--ty-leading-sm);\n letter-spacing: var(--ty-tracking-sm);\n padding: var(--ty-spacing-2) var(--ty-spacing-3);\n border-radius: var(--ty-radius-md);\n}\n\n/* Cloned copy (projected into the select's trigger field): host defaults to\n inline like any unstyled custom element, which ignores width/overflow \u2014\n force a block box so the parent's flex-basis:0 truncation (select.ts\n .select-stub.has-clone slot[name=\"selected\"]) actually clips it, and\n collapse the content to a single ellipsized line instead of wrapping. */\n:host([cloned]) {\n display: block;\n min-width: 0;\n overflow: hidden;\n}\n\n/* The clone sits in the select's TRIGGER, so its type follows the field size\n ladder (styles/field-size.ts) rather than the dropdown list's own scale \u2014\n the --ty-field-* vars inherit from ty-select through the light DOM. The\n fallbacks cover a ty-option rendered outside a select. */\n:host([cloned]) .option-content {\n display: block;\n padding: 0px;\n background: transparent;\n min-width: 0;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n font-size: var(--ty-field-font, var(--ty-font-sm));\n line-height: var(--ty-field-leading, var(--ty-leading-sm));\n letter-spacing: var(--ty-field-tracking, var(--ty-tracking-sm));\n}\n\n.option-content:hover {\n background: var(--ty-bg-neutral-soft);\n}\n\n.option-content[highlighted] {\n background: var(--ty-bg-neutral-soft);\n color: var(--ty-text-strong);\n}\n\n/* Selected: quiet \u2014 bolder text color only, no background slab, no weight change */\n.option-content[selected] {\n background: transparent;\n color: var(--ty-text-bold);\n}\n\n/* Lucide check \u2014 right-aligned. Its box is always reserved so selecting\n (or highlighting) never changes the row's width and rewraps the text. */\n.option-check {\n display: block;\n visibility: hidden;\n flex-shrink: 0;\n width: 14px;\n height: 14px;\n margin-left: auto;\n color: var(--ty-color-primary);\n}\n\n.option-content[selected] .option-check {\n visibility: visible;\n}\n\n/* Never show the check on the copy cloned into the select's trigger field */\n:host([cloned]) .option-check {\n display: none;\n}\n\n.option-content[selected]:hover {\n background: var(--ty-bg-neutral-soft);\n}\n\n.option-content[disabled] {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.option-content[hidden] {}\n\n.option-content * {\n pointer-events: none;\n}\n";
@@ -1,4 +1,8 @@
1
1
  export const optionStyles = `
2
+ :host([hidden]),
3
+ :host([filtered-out]) {
4
+ display: none !important;
5
+ }
2
6
  .option-content {
3
7
  display: flex;
4
8
  align-items: center;