tyrell-components 1.0.0-RC11 → 1.0.0-RC13

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 (240) hide show
  1. package/README.md +1 -0
  2. package/css/tyrell-colors-static.css +316 -0
  3. package/css/tyrell-theme.css +1454 -0
  4. package/css/tyrell.css +112 -420
  5. package/dist/tyrell-colors-static.css +316 -0
  6. package/dist/tyrell-theme.css +1454 -0
  7. package/dist/tyrell.css +112 -420
  8. package/dist/tyrell.js +1 -1
  9. package/lib/base/ty-component.d.ts +7 -0
  10. package/lib/base/ty-component.d.ts.map +1 -1
  11. package/lib/base/ty-component.js +27 -0
  12. package/lib/base/ty-component.js.map +1 -1
  13. package/lib/components/button.d.ts +16 -1
  14. package/lib/components/button.d.ts.map +1 -1
  15. package/lib/components/button.js +34 -4
  16. package/lib/components/button.js.map +1 -1
  17. package/lib/components/calendar-month.d.ts +23 -0
  18. package/lib/components/calendar-month.d.ts.map +1 -1
  19. package/lib/components/calendar-month.js +168 -11
  20. package/lib/components/calendar-month.js.map +1 -1
  21. package/lib/components/calendar-navigation.d.ts +31 -5
  22. package/lib/components/calendar-navigation.d.ts.map +1 -1
  23. package/lib/components/calendar-navigation.js +71 -31
  24. package/lib/components/calendar-navigation.js.map +1 -1
  25. package/lib/components/calendar.d.ts +17 -2
  26. package/lib/components/calendar.d.ts.map +1 -1
  27. package/lib/components/calendar.js +83 -12
  28. package/lib/components/calendar.js.map +1 -1
  29. package/lib/components/checkbox.d.ts +14 -3
  30. package/lib/components/checkbox.d.ts.map +1 -1
  31. package/lib/components/checkbox.js +86 -57
  32. package/lib/components/checkbox.js.map +1 -1
  33. package/lib/components/copy.d.ts +14 -3
  34. package/lib/components/copy.d.ts.map +1 -1
  35. package/lib/components/copy.js +47 -25
  36. package/lib/components/copy.js.map +1 -1
  37. package/lib/components/date-picker.d.ts +26 -1
  38. package/lib/components/date-picker.d.ts.map +1 -1
  39. package/lib/components/date-picker.js +151 -32
  40. package/lib/components/date-picker.js.map +1 -1
  41. package/lib/components/input.d.ts +17 -2
  42. package/lib/components/input.d.ts.map +1 -1
  43. package/lib/components/input.js +104 -25
  44. package/lib/components/input.js.map +1 -1
  45. package/lib/components/modal.d.ts.map +1 -1
  46. package/lib/components/modal.js +28 -3
  47. package/lib/components/modal.js.map +1 -1
  48. package/lib/components/option.d.ts +0 -27
  49. package/lib/components/option.d.ts.map +1 -1
  50. package/lib/components/option.js +11 -102
  51. package/lib/components/option.js.map +1 -1
  52. package/lib/components/popup.d.ts.map +1 -1
  53. package/lib/components/popup.js +36 -2
  54. package/lib/components/popup.js.map +1 -1
  55. package/lib/components/radio.d.ts +6 -2
  56. package/lib/components/radio.d.ts.map +1 -1
  57. package/lib/components/radio.js +55 -13
  58. package/lib/components/radio.js.map +1 -1
  59. package/lib/components/scroll-container.d.ts +16 -0
  60. package/lib/components/scroll-container.d.ts.map +1 -1
  61. package/lib/components/scroll-container.js +105 -1
  62. package/lib/components/scroll-container.js.map +1 -1
  63. package/lib/components/select.d.ts +550 -0
  64. package/lib/components/select.d.ts.map +1 -0
  65. package/lib/components/select.js +2115 -0
  66. package/lib/components/select.js.map +1 -0
  67. package/lib/components/selected-tags.d.ts +93 -0
  68. package/lib/components/selected-tags.d.ts.map +1 -0
  69. package/lib/components/selected-tags.js +286 -0
  70. package/lib/components/selected-tags.js.map +1 -0
  71. package/lib/components/switch.d.ts +6 -3
  72. package/lib/components/switch.d.ts.map +1 -1
  73. package/lib/components/switch.js +54 -36
  74. package/lib/components/switch.js.map +1 -1
  75. package/lib/components/tabs.d.ts.map +1 -1
  76. package/lib/components/tabs.js +190 -25
  77. package/lib/components/tabs.js.map +1 -1
  78. package/lib/components/tag.d.ts +9 -1
  79. package/lib/components/tag.d.ts.map +1 -1
  80. package/lib/components/tag.js +41 -5
  81. package/lib/components/tag.js.map +1 -1
  82. package/lib/components/textarea.d.ts +3 -0
  83. package/lib/components/textarea.d.ts.map +1 -1
  84. package/lib/components/textarea.js +61 -6
  85. package/lib/components/textarea.js.map +1 -1
  86. package/lib/components/tooltip.d.ts +4 -2
  87. package/lib/components/tooltip.d.ts.map +1 -1
  88. package/lib/components/tooltip.js +109 -70
  89. package/lib/components/tooltip.js.map +1 -1
  90. package/lib/components/wizard.d.ts.map +1 -1
  91. package/lib/components/wizard.js +22 -3
  92. package/lib/components/wizard.js.map +1 -1
  93. package/lib/index.d.ts +3 -2
  94. package/lib/index.d.ts.map +1 -1
  95. package/lib/index.js +3 -2
  96. package/lib/index.js.map +1 -1
  97. package/lib/styles/button.d.ts +41 -6
  98. package/lib/styles/button.d.ts.map +1 -1
  99. package/lib/styles/button.js +294 -254
  100. package/lib/styles/button.js.map +1 -1
  101. package/lib/styles/calendar-month.d.ts +3 -1
  102. package/lib/styles/calendar-month.d.ts.map +1 -1
  103. package/lib/styles/calendar-month.js +100 -67
  104. package/lib/styles/calendar-month.js.map +1 -1
  105. package/lib/styles/calendar-navigation.d.ts +1 -1
  106. package/lib/styles/calendar-navigation.d.ts.map +1 -1
  107. package/lib/styles/calendar-navigation.js +20 -28
  108. package/lib/styles/calendar-navigation.js.map +1 -1
  109. package/lib/styles/checkbox.d.ts +7 -5
  110. package/lib/styles/checkbox.d.ts.map +1 -1
  111. package/lib/styles/checkbox.js +125 -8
  112. package/lib/styles/checkbox.js.map +1 -1
  113. package/lib/styles/copy.d.ts +3 -1
  114. package/lib/styles/copy.d.ts.map +1 -1
  115. package/lib/styles/copy.js +61 -6
  116. package/lib/styles/copy.js.map +1 -1
  117. package/lib/styles/custom-scrollbar.d.ts +1 -1
  118. package/lib/styles/custom-scrollbar.d.ts.map +1 -1
  119. package/lib/styles/custom-scrollbar.js +2 -2
  120. package/lib/styles/date-picker.d.ts +3 -1
  121. package/lib/styles/date-picker.d.ts.map +1 -1
  122. package/lib/styles/date-picker.js +65 -103
  123. package/lib/styles/date-picker.js.map +1 -1
  124. package/lib/styles/file-upload.d.ts +1 -1
  125. package/lib/styles/file-upload.d.ts.map +1 -1
  126. package/lib/styles/file-upload.js +3 -3
  127. package/lib/styles/input.d.ts +3 -1
  128. package/lib/styles/input.d.ts.map +1 -1
  129. package/lib/styles/input.js +93 -419
  130. package/lib/styles/input.js.map +1 -1
  131. package/lib/styles/multiselect.d.ts +3 -1
  132. package/lib/styles/multiselect.d.ts.map +1 -1
  133. package/lib/styles/multiselect.js +26 -2
  134. package/lib/styles/multiselect.js.map +1 -1
  135. package/lib/styles/option.d.ts +1 -1
  136. package/lib/styles/option.d.ts.map +1 -1
  137. package/lib/styles/option.js +20 -47
  138. package/lib/styles/option.js.map +1 -1
  139. package/lib/styles/radio.d.ts +3 -1
  140. package/lib/styles/radio.d.ts.map +1 -1
  141. package/lib/styles/radio.js +24 -21
  142. package/lib/styles/radio.js.map +1 -1
  143. package/lib/styles/scroll-container.d.ts +1 -1
  144. package/lib/styles/scroll-container.d.ts.map +1 -1
  145. package/lib/styles/scroll-container.js +6 -0
  146. package/lib/styles/scroll-container.js.map +1 -1
  147. package/lib/styles/select-base.d.ts +9 -0
  148. package/lib/styles/select-base.d.ts.map +1 -0
  149. package/lib/styles/select-base.js +973 -0
  150. package/lib/styles/select-base.js.map +1 -0
  151. package/lib/styles/select.d.ts +12 -0
  152. package/lib/styles/select.d.ts.map +1 -0
  153. package/lib/styles/select.js +308 -0
  154. package/lib/styles/select.js.map +1 -0
  155. package/lib/styles/step.d.ts +1 -1
  156. package/lib/styles/step.d.ts.map +1 -1
  157. package/lib/styles/switch.d.ts +3 -1
  158. package/lib/styles/switch.d.ts.map +1 -1
  159. package/lib/styles/switch.js +25 -10
  160. package/lib/styles/switch.js.map +1 -1
  161. package/lib/styles/tab.d.ts +1 -1
  162. package/lib/styles/tab.d.ts.map +1 -1
  163. package/lib/styles/tabs.d.ts +1 -1
  164. package/lib/styles/tabs.d.ts.map +1 -1
  165. package/lib/styles/tabs.js +71 -1
  166. package/lib/styles/tabs.js.map +1 -1
  167. package/lib/styles/tag.d.ts +1 -1
  168. package/lib/styles/tag.d.ts.map +1 -1
  169. package/lib/styles/tag.js +40 -141
  170. package/lib/styles/tag.js.map +1 -1
  171. package/lib/styles/textarea.d.ts +1 -1
  172. package/lib/styles/textarea.d.ts.map +1 -1
  173. package/lib/styles/textarea.js +71 -37
  174. package/lib/styles/textarea.js.map +1 -1
  175. package/lib/styles/tooltip.d.ts +4 -2
  176. package/lib/styles/tooltip.d.ts.map +1 -1
  177. package/lib/styles/tooltip.js +4 -119
  178. package/lib/styles/tooltip.js.map +1 -1
  179. package/lib/styles/wizard.d.ts +1 -1
  180. package/lib/styles/wizard.d.ts.map +1 -1
  181. package/lib/styles/wizard.js +18 -6
  182. package/lib/styles/wizard.js.map +1 -1
  183. package/lib/types/common.d.ts +2 -1
  184. package/lib/types/common.d.ts.map +1 -1
  185. package/lib/types/common.js +15 -1
  186. package/lib/types/common.js.map +1 -1
  187. package/lib/utils/calendar-utils.d.ts +0 -48
  188. package/lib/utils/calendar-utils.d.ts.map +1 -1
  189. package/lib/utils/calendar-utils.js +0 -69
  190. package/lib/utils/calendar-utils.js.map +1 -1
  191. package/lib/utils/flavor-sheet.d.ts +32 -0
  192. package/lib/utils/flavor-sheet.d.ts.map +1 -0
  193. package/lib/utils/flavor-sheet.js +40 -0
  194. package/lib/utils/flavor-sheet.js.map +1 -0
  195. package/lib/utils/icon-registry.d.ts +0 -13
  196. package/lib/utils/icon-registry.d.ts.map +1 -1
  197. package/lib/utils/icon-registry.js +0 -35
  198. package/lib/utils/icon-registry.js.map +1 -1
  199. package/lib/utils/icons.d.ts +10 -0
  200. package/lib/utils/icons.d.ts.map +1 -0
  201. package/lib/utils/icons.js +10 -0
  202. package/lib/utils/icons.js.map +1 -0
  203. package/lib/utils/loader-registry.d.ts +2 -2
  204. package/lib/utils/loader-registry.js +2 -2
  205. package/lib/utils/locale.d.ts +0 -57
  206. package/lib/utils/locale.d.ts.map +1 -1
  207. package/lib/utils/locale.js +0 -89
  208. package/lib/utils/locale.js.map +1 -1
  209. package/lib/utils/number-format.d.ts +0 -21
  210. package/lib/utils/number-format.d.ts.map +1 -1
  211. package/lib/utils/number-format.js +0 -27
  212. package/lib/utils/number-format.js.map +1 -1
  213. package/lib/utils/positioning.d.ts +29 -22
  214. package/lib/utils/positioning.d.ts.map +1 -1
  215. package/lib/utils/positioning.js +23 -87
  216. package/lib/utils/positioning.js.map +1 -1
  217. package/lib/utils/scroll-lock.d.ts.map +1 -1
  218. package/lib/utils/scroll-lock.js +0 -1
  219. package/lib/utils/scroll-lock.js.map +1 -1
  220. package/lib/utils/styles.d.ts +0 -5
  221. package/lib/utils/styles.d.ts.map +1 -1
  222. package/lib/utils/styles.js +0 -7
  223. package/lib/utils/styles.js.map +1 -1
  224. package/lib/version.d.ts +1 -1
  225. package/lib/version.js +1 -1
  226. package/package.json +22 -11
  227. package/css/tyrell-brand.css +0 -913
  228. package/dist/tyrell-brand.css +0 -913
  229. package/lib/components/dropdown.d.ts +0 -424
  230. package/lib/components/dropdown.d.ts.map +0 -1
  231. package/lib/components/dropdown.js +0 -1640
  232. package/lib/components/dropdown.js.map +0 -1
  233. package/lib/components/multiselect.d.ts +0 -397
  234. package/lib/components/multiselect.d.ts.map +0 -1
  235. package/lib/components/multiselect.js +0 -1595
  236. package/lib/components/multiselect.js.map +0 -1
  237. package/lib/styles/dropdown.d.ts +0 -12
  238. package/lib/styles/dropdown.d.ts.map +0 -1
  239. package/lib/styles/dropdown.js +0 -1081
  240. package/lib/styles/dropdown.js.map +0 -1
@@ -0,0 +1,973 @@
1
+ /**
2
+ * Select Field Styles — shared chrome for ty-select (both single and
3
+ * multiple modes). Formerly split across ty-dropdown / ty-multiselect;
4
+ * those were consolidated into ty-select and this file kept the name.
5
+ */
6
+ import { FLAVORS } from "../types/common.js";
7
+ import { customScrollbarStyles } from "./custom-scrollbar.js";
8
+ /* Flavor rules only set --select-accent (border), --select-accent-bold
9
+ (hover/open border), and --select-ring (open focus ring); the .select-stub
10
+ rules below consume them. Doubles as the per-instance override API:
11
+ `ty-select { --select-accent: … }`. Neutral is skipped — it IS the default
12
+ unstyled chrome (--ty-input-* tokens), same convention as
13
+ ty-input/ty-date-picker. Fallback flavor `fb` is used for custom flavors
14
+ so missing tokens degrade to neutral. */
15
+ const selectFlavor = (f, fb) => {
16
+ const c = (s) => fb
17
+ ? s
18
+ ? `var(--ty-color-${f}${s}, var(--ty-color-${f}, var(--ty-color-${fb}${s})))`
19
+ : `var(--ty-color-${f}, var(--ty-color-${fb}))`
20
+ : s
21
+ ? `var(--ty-color-${f}${s}, var(--ty-color-${f}))`
22
+ : `var(--ty-color-${f})`;
23
+ return `
24
+ :host([flavor="${f}"]) {
25
+ --select-accent: var(--ty-input-${f}-border, ${c("-soft")});
26
+ --select-accent-bold: ${c("")};
27
+ --select-ring: color-mix(in oklab, ${c("")} 15%, transparent);
28
+ }
29
+ :host([flavor="${f}+"]) {
30
+ --select-accent: ${c("")};
31
+ --select-accent-bold: ${c("-strong")};
32
+ --select-ring: color-mix(in oklab, ${c("-strong")} 15%, transparent);
33
+ }
34
+ :host([flavor="${f}-"]) {
35
+ --select-accent: ${c("-faint")};
36
+ --select-accent-bold: ${c("-soft")};
37
+ --select-ring: color-mix(in oklab, ${c("-soft")} 15%, transparent);
38
+ }
39
+ `;
40
+ };
41
+ /** Rules for one custom (non-built-in) flavor — see utils/flavor-sheet.ts. */
42
+ export const selectCustomFlavorCss = (base) => selectFlavor(base, "neutral");
43
+ export const selectBaseStyles = `
44
+ /* Select-field-specific styles extending dropdown base styles */
45
+
46
+ :host {
47
+ display: block;
48
+ width: 100%;
49
+ font-family: var(--ty-font-sans);
50
+ --mobile-border-color: var(--ty-border, #5858587d);
51
+ }
52
+
53
+ /* compact is a toolbar/filter-bar trigger — it must hug its own content so
54
+ it can sit inline next to other toolbar items, not claim a full row.
55
+ inline-block (not plain inline): a block box still ignores width and
56
+ collapses vertical margin, which is what we're fixing above. */
57
+ :host([compact]) {
58
+ display: inline-block;
59
+ width: auto;
60
+ }
61
+
62
+ /* ===== DIALOG POSITIONING SUPPORT ===== */
63
+
64
+ .dropdown-dialog {
65
+ position: fixed;
66
+ width: var(--dropdown-width, 200px);
67
+ max-width: 100vw;
68
+ margin: 0;
69
+ padding: 0;
70
+ border: none;
71
+ background: transparent;
72
+ box-sizing: border-box;
73
+ padding: var(--dropdown-padding, 20px);
74
+ /* Modal handles z-index automatically */
75
+
76
+ /* Hidden by default */
77
+ opacity: 0;
78
+ transition: opacity 400ms ease;
79
+
80
+ transform: translate(var(--dropdown-offset-x, 0px), var(--dropdown-offset-y, 0px));
81
+ top: -1000px;
82
+ left: -1000px;
83
+ }
84
+
85
+ /* When opened via showModal(), apply flex layout and direction */
86
+ .dropdown-dialog[open] {
87
+ display: flex;
88
+ flex-direction: column;
89
+ }
90
+
91
+ /* Direction-based positioning with CSS classes */
92
+ .dropdown-dialog.position-below {
93
+ left: var(--dropdown-x);
94
+ top: var(--dropdown-y);
95
+ }
96
+
97
+ .dropdown-dialog.position-above {
98
+ left: var(--dropdown-x);
99
+ bottom: var(--dropdown-y);
100
+ top: auto;
101
+ flex-direction: column-reverse;
102
+ }
103
+
104
+ .dropdown-dialog.position-above .dropdown-header {
105
+ margin-top: 4px;
106
+ }
107
+
108
+ .dropdown-dialog.position-below .dropdown-header {
109
+ margin-bottom: 4px;
110
+ }
111
+
112
+ .dropdown-dialog.position-below .dropdown-options {
113
+ /* Optional: Add upward-pointing shadow for below positioning */
114
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1), var(--ty-shadow-md);
115
+ }
116
+
117
+ /* Animate when .open class is added */
118
+ .dropdown-dialog.open {
119
+ opacity: 1;
120
+ }
121
+
122
+ .dropdown-dialog.open .dropdown-options {
123
+ opacity: 1;
124
+ transform: translateY(0) scale(1);
125
+ }
126
+
127
+ .dropdown-dialog::backdrop {
128
+ background: transparent;
129
+ }
130
+
131
+ /* ===== DIALOG HEADER ===== */
132
+
133
+ .dropdown-header {
134
+ display: flex;
135
+ align-items: center;
136
+ gap: var(--ty-spacing-2);
137
+ position: relative;
138
+ }
139
+
140
+ .dropdown-search-input {
141
+ width: 100%;
142
+ min-width: 0;
143
+ box-sizing: border-box;
144
+ background: var(--input-bg, var(--ty-input-bg));
145
+ color: var(--input-color, var(--ty-input-color));
146
+ border: 1px solid var(--input-border, var(--ty-input-border));
147
+ border-radius: var(--ty-radius-md);
148
+ font-family: var(--ty-font-sans);
149
+ font-size: var(--ty-font-sm);
150
+ line-height: var(--ty-leading-sm);
151
+ letter-spacing: var(--ty-tracking-sm);
152
+ font-weight: var(--ty-font-normal);
153
+ min-height: var(--ty-size-md);
154
+ padding: var(--ty-spacing-2) var(--ty-spacing-3);
155
+ padding-right: calc(var(--ty-spacing-3) + 1rem + var(--ty-spacing-2));
156
+ transition: var(--ty-transition-all);
157
+ outline: none;
158
+ }
159
+
160
+ .dropdown-search-input:focus {
161
+ border-color: var(--input-border-focus, var(--ty-input-border-focus));
162
+ box-shadow: 0 0 0 3px var(--input-shadow-focus, var(--ty-input-shadow-focus));
163
+ }
164
+
165
+ .dropdown-search-input:disabled {
166
+ background-color: var(--input-disabled-bg, var(--ty-input-disabled-bg));
167
+ color: var(--input-disabled-color, var(--ty-input-disabled-color));
168
+ cursor: not-allowed;
169
+ opacity: 0.6;
170
+ }
171
+
172
+ .dropdown-search-input::placeholder {
173
+ color: var(--input-placeholder, var(--ty-input-placeholder));
174
+ }
175
+
176
+ .dropdown-search-chevron {
177
+ position: absolute;
178
+ top: 50%;
179
+ right: var(--ty-spacing-3);
180
+ transform: translateY(-50%);
181
+ width: 1rem;
182
+ height: 1rem;
183
+ color: var(--input-placeholder, var(--ty-input-placeholder));
184
+ transition: var(--ty-transition-transform);
185
+ pointer-events: none;
186
+ }
187
+
188
+ .dropdown-search-chevron.open {
189
+ transform: translateY(-50%) rotate(180deg);
190
+ }
191
+
192
+ .dropdown-search-chevron svg {
193
+ width: 100%;
194
+ height: 100%;
195
+ }
196
+
197
+ /* ===== SELECT FIELD-SPECIFIC STYLES ===== */
198
+
199
+ /* Select stub modifications */
200
+ .select-stub {
201
+ min-height: var(--ty-size-md);
202
+ display: flex;
203
+ flex-wrap: wrap;
204
+ gap: 0.25rem;
205
+ align-items: center;
206
+ padding: 0 2.5rem 0 0.75rem;
207
+ /* Transitions - includes opacity for open state */
208
+ transition: var(--ty-transition-all), opacity 0.2s ease;
209
+ outline: none;
210
+ background: var(--input-bg, var(--ty-input-bg));
211
+ color: var(--input-color, var(--ty-input-color));
212
+ border: 1px solid var(--select-accent, var(--input-border, var(--ty-input-border)));
213
+ border-radius: var(--ty-radius-md);
214
+ font-family: var(--ty-font-sans);
215
+ font-size: var(--ty-font-sm);
216
+ line-height: var(--ty-leading-sm);
217
+ letter-spacing: var(--ty-tracking-sm);
218
+ cursor: pointer;
219
+ position: relative;
220
+ width: 100%;
221
+ box-sizing: border-box;
222
+ }
223
+
224
+ /* ===== SIZE MODIFIERS =====
225
+ buildStubClasses() always stamps exactly one size class on .select-stub.
226
+ min-height comes from the shared --ty-size-* tokens (same ones ty-input
227
+ and ty-date-picker consume) so a given size is the same height on every
228
+ field. Right padding stays 2.5rem for chevron room. Zero vertical
229
+ padding — same fix as ty-input: with align-items:center on a flex
230
+ container, ANY vertical padding adds on top of min-height instead of
231
+ being absorbed by it, so a padded stub silently overflows past the
232
+ shared field height (measured +3px at sm/md before this was zeroed).
233
+ min-height + centering alone gets the exact height. */
234
+ .select-stub.sm {
235
+ min-height: var(--ty-size-sm);
236
+ padding: 0 2.5rem 0 0.625rem;
237
+ font-size: var(--ty-font-sm);
238
+ line-height: var(--ty-leading-sm);
239
+ letter-spacing: var(--ty-tracking-sm);
240
+ }
241
+
242
+ /* md matches the base rule; listed for parity/overrides */
243
+ .select-stub.md {
244
+ min-height: var(--ty-size-md);
245
+ padding: 0 2.5rem 0 0.75rem;
246
+ }
247
+
248
+ .select-stub.lg {
249
+ min-height: var(--ty-size-lg);
250
+ padding: 0 2.5rem 0 0.875rem;
251
+ font-size: var(--ty-font-base);
252
+ line-height: var(--ty-leading-base);
253
+ letter-spacing: var(--ty-tracking-base);
254
+ }
255
+
256
+ .select-stub:hover {
257
+ border-color: var(--select-accent-bold, var(--input-border-hover, var(--ty-input-border-hover)));
258
+ }
259
+
260
+ .select-stub[disabled] {
261
+ background-color: var(--input-disabled-bg, var(--ty-input-disabled-bg));
262
+ color: var(--input-disabled-color, var(--ty-input-disabled-color));
263
+ cursor: not-allowed;
264
+ opacity: 0.6;
265
+ }
266
+
267
+ /* Hide stub when dropdown is open */
268
+ .dropdown-wrapper:has(.dropdown-chevron.open) .select-stub {
269
+ opacity: 0;
270
+ pointer-events: none;
271
+ }
272
+
273
+ /* Open state — same escalation as ty-input's .focused: bolder border + ring.
274
+ Applies whenever the stub stays visible while open (field skin without
275
+ the opacity-hide above kicking in — e.g. single-select with no tags).
276
+ :not(.custom-trigger) — that skin has its own unconditional box-shadow:
277
+ none reset below; excluding it here keeps the two rules from competing
278
+ on specificity (:has()/:not() here would otherwise outrank it).
279
+ :not(:has(search:focus)) — the search row auto-focuses on open, which
280
+ already carries its own real :focus ring (below); without this guard the
281
+ stub's "fake" open-ring stacks on top of it, and the field reads as two
282
+ simultaneously-focused controls. Real focus wins; the stub ring is only
283
+ for select skins that have nothing else to show it. */
284
+ .dropdown-wrapper:has(.dropdown-chevron.open):not(:has(.dropdown-search-input:focus)) .select-stub:not(.custom-trigger),
285
+ .select-stub:focus,
286
+ .select-stub:focus-visible {
287
+ border-color: var(--select-accent-bold, var(--input-border-focus, var(--ty-input-border-focus)));
288
+ box-shadow: 0 0 0 3px var(--select-ring, var(--input-shadow-focus, var(--ty-input-shadow-focus)));
289
+ }
290
+
291
+ /* Custom trigger (slot="trigger"): setupTriggerSlot() toggles this class when
292
+ the consumer assigns their own content — the slot's fallback (start/selected/
293
+ placeholder/end/chevron, i.e. the whole default field skin) is entirely
294
+ replaced at that point, so the wrapping .select-stub itself must go bare.
295
+ Without this, the stub's own border/background/padding and the open/focus
296
+ ring above still wrapped the custom content in a second, unwanted outline. */
297
+ .select-stub.custom-trigger {
298
+ min-height: 0;
299
+ padding: 0;
300
+ border: none;
301
+ background: transparent;
302
+ }
303
+ .select-stub.custom-trigger:hover,
304
+ .dropdown-wrapper:has(.dropdown-chevron.open) .select-stub.custom-trigger,
305
+ .select-stub.custom-trigger:focus,
306
+ .select-stub.custom-trigger:focus-visible {
307
+ border-color: transparent;
308
+ box-shadow: none;
309
+ }
310
+
311
+ /* Hide stub chips when mobile dialog is open (let modal show them) */
312
+ .dropdown-mode-mobile .dropdown-wrapper:has(.mobile-dialog[open]) .select-chips {
313
+ display: none;
314
+ }
315
+
316
+ /* When tags are present, reduce padding to make room */
317
+ .select-stub.has-tags {
318
+ padding: 0.25rem 2.5rem 0.25rem 0.5rem;
319
+ width: 100%;
320
+ }
321
+
322
+ .select-stub.has-tags slot[name="selected"] {
323
+ display: flex;
324
+ flex-wrap: wrap;
325
+ gap: 0.25rem;
326
+ }
327
+
328
+ /* ===== CHEVRON INDICATOR ===== */
329
+
330
+ .dropdown-chevron {
331
+ position: absolute;
332
+ top: 50%;
333
+ right: var(--ty-spacing-3);
334
+ transform: translateY(-50%);
335
+ width: 1rem;
336
+ height: 1rem;
337
+ color: var(--input-placeholder, var(--ty-input-placeholder));
338
+ transition: var(--ty-transition-transform);
339
+ pointer-events: none;
340
+ }
341
+
342
+ .dropdown-chevron.open {
343
+ transform: translateY(-50%) rotate(180deg);
344
+ }
345
+
346
+ .dropdown-chevron svg {
347
+ width: 100%;
348
+ height: 100%;
349
+ }
350
+
351
+
352
+ /* Tags container */
353
+ .select-chips {
354
+ display: flex;
355
+ flex-wrap: wrap;
356
+ gap: 0.5rem;
357
+ align-items: center;
358
+ min-width: 0;
359
+ }
360
+
361
+ .dropdown-placeholder {
362
+ flex-grow: 1;
363
+ color: var(--input-placeholder, var(--ty-input-placeholder, #9ca3af));
364
+ font-size: var(--ty-font-sm);
365
+ line-height: var(--ty-leading-sm);
366
+ letter-spacing: var(--ty-tracking-sm);
367
+ font-weight: var(--ty-font-light);
368
+ font-style: italic;
369
+ }
370
+
371
+ /* Placeholder styling when tags are present */
372
+ .dropdown-placeholder.hidden {
373
+ display: none;
374
+ }
375
+
376
+ /* Options area styling - Override for select */
377
+ .dropdown-options {
378
+ opacity: 0;
379
+ /* Floating surface/border, not --input-* — this is a popup panel over
380
+ arbitrary page content, not a form field. --ty-input-border resolves to
381
+ the deliberately-faint resting-state input border (barely visible by
382
+ design, escalates on hover/focus); reused here it made the popup's own
383
+ edge nearly invisible against the page behind it. --ty-floating-border
384
+ is the token tuned for exactly this: floating panels over content. */
385
+ background: var(--ty-surface-floating);
386
+ border: 1px solid var(--ty-floating-border);
387
+ border-radius: var(--ty-radius-lg);
388
+ max-height: 16rem;
389
+ width: 100%;
390
+ max-width: 100%;
391
+ overflow-x: hidden;
392
+ overflow-y: auto;
393
+ scroll-behavior: smooth;
394
+ box-sizing: border-box;
395
+ position: relative;
396
+ box-shadow:
397
+ 0 20px 25px -5px rgba(0, 0, 0, 0.1),
398
+ 0 10px 10px -5px rgba(0, 0, 0, 0.04);
399
+
400
+ /* Animation properties */
401
+ transform: translateY(-8px) scale(0.95);
402
+ transition:
403
+ opacity 200ms cubic-bezier(0.16, 1, 0.3, 1),
404
+ transform 200ms cubic-bezier(0.16, 1, 0.3, 1);
405
+
406
+ /* Select-specific: flex wrap for tags */
407
+ display: flex;
408
+ flex-wrap: wrap;
409
+ padding: 0.5rem;
410
+ gap: 0.5rem;
411
+
412
+ }
413
+
414
+ /* Hide native scrollbar only when custom scrollbar is active */
415
+ .dropdown-options.ty-custom-scroll {
416
+ scrollbar-width: none;
417
+ -ms-overflow-style: none;
418
+ }
419
+
420
+ .dropdown-options.ty-custom-scroll::-webkit-scrollbar {
421
+ display: none;
422
+ }
423
+
424
+ /* Options wrapper - positioned container for scrollbar track */
425
+ .dropdown-options-wrapper {
426
+ position: relative;
427
+ }
428
+
429
+ /* Show custom scrollbar on hover */
430
+ .dropdown-options-wrapper:hover .ty-scrollbar-track-y.has-overflow {
431
+ opacity: 1;
432
+ }
433
+
434
+ /* Make ty-tags in dropdown clickable with pointer cursor */
435
+ .dropdown-options ty-tag {
436
+ user-select: none;
437
+ transition: transform 0.1s ease;
438
+ }
439
+
440
+ .dropdown-options ty-tag:hover {
441
+ transform: scale(1.02);
442
+ }
443
+
444
+ .dropdown-options ty-tag:active {
445
+ transform: scale(0.98);
446
+ }
447
+
448
+ /* Visual feedback for selected tags in options */
449
+ .dropdown-options ty-tag[selected] {
450
+ opacity: 0.5;
451
+ }
452
+
453
+ /* Ensure ty-tag components in the select field have proper sizing */
454
+ .select-chips ty-tag {
455
+ max-width: 150px;
456
+ }
457
+
458
+ /* Responsive adjustments */
459
+ @media (max-width: 640px) {
460
+ .select-chips ty-tag {
461
+ max-width: 100px;
462
+ }
463
+ }
464
+
465
+
466
+ /* Ensure proper spacing in container layouts */
467
+ .select-container {
468
+ display: flex;
469
+ flex-direction: column;
470
+ /* No gap here — label→field spacing comes from .ty-field-label's
471
+ margin-bottom (6px), same as ty-input/ty-date-picker/ty-textarea.
472
+ A flex gap would STACK on that margin (flex margins don't collapse),
473
+ which is exactly the 4px misalignment this used to cause. */
474
+ }
475
+
476
+ /* ===== DROPDOWN WRAPPER & LABEL ===== */
477
+
478
+ .dropdown-wrapper {
479
+ position: relative;
480
+ display: block;
481
+ width: 100%;
482
+ }
483
+
484
+ .ty-field-label {
485
+ font-size: var(--ty-font-sm);
486
+ line-height: var(--ty-leading-sm);
487
+ letter-spacing: var(--ty-tracking-sm);
488
+ font-weight: var(--ty-font-medium);
489
+ color: var(--ty-label-color);
490
+ margin-bottom: 6px;
491
+ padding-left: 12px;
492
+ display: flex;
493
+ align-items: center;
494
+ }
495
+
496
+ /* Required indicator - using SVG icon */
497
+ .required-icon {
498
+ display: inline-flex;
499
+ align-items: center;
500
+ color: #ef4444;
501
+ width: 12px;
502
+ height: 12px;
503
+ vertical-align: middle;
504
+ margin-left: 4px;
505
+ }
506
+
507
+ .required-icon svg {
508
+ width: 100%;
509
+ height: 100%;
510
+ }
511
+
512
+ :host([disabled]) .select-container {
513
+ pointer-events: none;
514
+ }
515
+
516
+ /* ============================================================================
517
+ MOBILE MODAL STYLES
518
+ ============================================================================ */
519
+
520
+ .dropdown-mode-mobile .mobile-dialog {
521
+ position: fixed;
522
+ top: 0;
523
+ left: 0;
524
+ right: 0;
525
+ bottom: 0;
526
+ width: 100vw;
527
+ height: 100dvh; /* dvh, not vh — shrinks with the on-screen keyboard so the
528
+ dialog never sits partly behind it */
529
+ max-width: 100vw;
530
+ max-height: 100dvh;
531
+ margin: 0;
532
+ padding: 0;
533
+ padding-top: 6dvh; /* Sit higher than the old 10vh, but 4dvh left the
534
+ floating label with no headroom — it sits above
535
+ .mobile-search-header via position:absolute, so it
536
+ needs room inside this padding, not just inside the
537
+ content box, or it crowds the very top of the screen */
538
+ border: none;
539
+ background: transparent; /* Backdrop handles background */
540
+ /* Note: Don't set display - browser controls <dialog> visibility */
541
+ align-items: flex-start;
542
+ justify-content: center;
543
+ opacity: 0;
544
+ transition: opacity 300ms ease;
545
+ }
546
+
547
+ /* When opened via showModal(), add flex layout */
548
+ .dropdown-mode-mobile .mobile-dialog[open] {
549
+ display: flex;
550
+ }
551
+
552
+ .dropdown-mode-mobile .mobile-dialog.open {
553
+ opacity: 1;
554
+ }
555
+
556
+ /* Native dialog backdrop with blur */
557
+ .dropdown-mode-mobile .mobile-dialog::backdrop {
558
+ background: rgba(0, 0, 0, 0.65); /* was 0.5 — needed more depth to reliably
559
+ carry white header text/icons over a
560
+ light page blurred behind it */
561
+ backdrop-filter: blur(8px);
562
+ -webkit-backdrop-filter: blur(8px);
563
+ }
564
+
565
+ .dropdown-mode-mobile .mobile-dialog-content {
566
+ position: relative;
567
+ display: flex;
568
+ flex-direction: column;
569
+ width: calc(100% - 32px); /* Side margins */
570
+ max-width: 400px; /* Constrained width like dropdown */
571
+ min-height: 200px;
572
+ max-height: 90dvh; /* leaves room below the 6dvh padding-top, shrinks with keyboard */
573
+ opacity: 0;
574
+ transform: scale(0.95);
575
+ transition:
576
+ opacity 300ms cubic-bezier(0.16, 1, 0.3, 1),
577
+ transform 300ms cubic-bezier(0.16, 1, 0.3, 1);
578
+ }
579
+
580
+ .dropdown-mode-mobile .mobile-dialog.open .mobile-dialog-content {
581
+ opacity: 1;
582
+ transform: scale(1);
583
+ }
584
+
585
+ .dropdown-mode-mobile .mobile-search-input:focus {
586
+ border-color: var(--ty-border);
587
+ }
588
+
589
+ /* Mobile search header - label floats above, search + close below */
590
+ .dropdown-mode-mobile .mobile-search-header {
591
+ flex-shrink: 0;
592
+ display: flex;
593
+ flex-direction: column;
594
+ margin-bottom: 16px;
595
+ padding: 0;
596
+ background: transparent;
597
+ position: relative;
598
+ }
599
+
600
+ .dropdown-mode-mobile .mobile-header-content {
601
+ display: flex;
602
+ align-items: center;
603
+ gap: 12px;
604
+ width: 100%;
605
+ /* When search is disabled, updateSearchVisibility() hides the icon+input
606
+ (see select.ts) but leaves the close button as the row's only child —
607
+ without this it falls back to flex-start and strands the button on the
608
+ left. Harmless when search IS shown: the input's flex:1 already
609
+ consumes all remaining space, so justify-content never gets a say. */
610
+ justify-content: flex-end;
611
+ }
612
+
613
+ .dropdown-mode-mobile .mobile-header-label {
614
+ position: absolute;
615
+ bottom: 100%;
616
+ left: 2px;
617
+ margin-bottom: 8px; /* was 4px — too tight against the search row */
618
+ font-size: var(--ty-font-base); /* was --ty-font-lg — read as oversized/heavy paired with 700 */
619
+ line-height: var(--ty-leading-base);
620
+ letter-spacing: var(--ty-tracking-base);
621
+ font-weight: 600; /* was 700 — 700 at font-lg read as clunky */
622
+ /* This sits on the dialog's ::backdrop (rgba(0,0,0,.65) + blur) — a fixed
623
+ DARK overlay regardless of theme, not the theme's own surface color.
624
+ A theme-toggling token (light mode → black) fights that: black text on
625
+ a dark blurred backdrop is the wrong combination. Fixed white, same
626
+ reasoning as ty-tooltip's deliberately theme-independent default in
627
+ tyrell.css — this element's job is to pop against a dark backdrop in
628
+ both themes, not to track the page's light/dark state. */
629
+ color: #ffffff;
630
+ pointer-events: none;
631
+ }
632
+
633
+ /* Close button — borderless ghost icon button (was a bordered circle badge,
634
+ which read as a heavy, disconnected "chip" next to the plain search icon).
635
+ Matches ty-button's ghost treatment: transparent at rest, soft neutral
636
+ fill on hover/active, no border ever. */
637
+ .dropdown-mode-mobile .mobile-close-button {
638
+ flex-shrink: 0;
639
+ width: 32px;
640
+ height: 32px;
641
+ display: flex;
642
+ align-items: center;
643
+ justify-content: center;
644
+ background: transparent;
645
+ border: none;
646
+ border-radius: 50%;
647
+ /* Same reasoning as .mobile-header-label: fixed white, not a theme token —
648
+ this sits on the dark ::backdrop blur in both themes, never on a theme
649
+ surface. */
650
+ color: #ffffff;
651
+ cursor: pointer;
652
+ transition: var(--ty-transition-all);
653
+ padding: 0;
654
+ }
655
+
656
+ /* Hover/active fills also go fixed-white-tinted rather than theme neutral —
657
+ --ty-bg-neutral-soft is a LIGHT fill in light mode, which would swallow
658
+ the white icon it's meant to sit behind. */
659
+ .dropdown-mode-mobile .mobile-close-button:hover {
660
+ background: rgba(255, 255, 255, 0.15);
661
+ color: #ffffff;
662
+ }
663
+
664
+ .dropdown-mode-mobile .mobile-close-button:active {
665
+ transform: scale(0.9);
666
+ background: rgba(255, 255, 255, 0.25);
667
+ }
668
+
669
+ .dropdown-mode-mobile .mobile-close-button svg {
670
+ width: 18px;
671
+ height: 18px;
672
+ }
673
+
674
+ /* Mobile search input (matches dropdown.ts) */
675
+ .dropdown-mode-mobile .mobile-search-input {
676
+ flex: 1;
677
+ min-width: 0;
678
+ box-sizing: border-box;
679
+ background: var(--ty-surface-floating);
680
+ color: var(--ty-text);
681
+ border: 2px solid;
682
+ border-color: var(--mobile-border-color);
683
+ border-radius: var(--ty-radius-md);
684
+ font-family: var(--ty-font-sans);
685
+ font-size: var(--ty-font-sm);
686
+ line-height: var(--ty-leading-sm);
687
+ letter-spacing: var(--ty-tracking-sm);
688
+ font-weight: var(--ty-font-normal);
689
+ padding: var(--ty-spacing-2) var(--ty-spacing-3);
690
+ height: 40px;
691
+ transition: var(--ty-transition-all);
692
+ outline: none;
693
+ }
694
+
695
+ .dropdown-mode-mobile .mobile-search-input::placeholder {
696
+ color: var(--ty-text-faint);
697
+ }
698
+
699
+ .dropdown-mode-mobile .mobile-search-input:focus {
700
+ border-color: var(--ty-border);
701
+ }
702
+
703
+ /* ============================================================================
704
+ MOBILE BODY & SECTIONS - UPDATED STRUCTURE
705
+ ============================================================================ */
706
+
707
+ /* Mobile body - contains both sections */
708
+ .dropdown-mode-mobile .mobile-body {
709
+ position: relative;
710
+ display: flex;
711
+ flex-direction: column;
712
+ height: var(--body-height, 350px);
713
+ max-height: 350px;
714
+ overflow: hidden;
715
+ background: var(--ty-surface-floating);
716
+ border-radius: var(--ty-radius-lg);
717
+ box-shadow:
718
+ 0 20px 25px -5px rgba(0, 0, 0, 0.1),
719
+ 0 10px 10px -5px rgba(0, 0, 0, 0.04);
720
+ border: 2px solid;
721
+ border-color: var(--mobile-border-color);
722
+ }
723
+
724
+ /* ===== SECTION HEADERS - Labels, not buttons ===== */
725
+
726
+ .dropdown-mode-mobile .section-header {
727
+ display: flex;
728
+ align-items: center;
729
+ justify-content: space-between;
730
+ height: 36px;
731
+ flex-shrink: 0;
732
+ padding: 0 16px;
733
+ font-size: 11px;
734
+ font-weight: 600;
735
+ text-transform: uppercase;
736
+ letter-spacing: 0.06em;
737
+ color: var(--ty-text-bold);
738
+ background: transparent;
739
+ cursor: default;
740
+ user-select: none;
741
+ }
742
+
743
+ .dropdown-mode-mobile .section-header .section-title {
744
+ flex: 1;
745
+ }
746
+
747
+ .dropdown-mode-mobile .section-header .section-count {
748
+ font-weight: 500;
749
+ color: var(--ty-text-faint);
750
+ margin-left: 0.25rem;
751
+ }
752
+
753
+ /* ===== SELECTED STRIP - pinned, capped height, collapses when empty ===== */
754
+
755
+ .dropdown-mode-mobile .mobile-selected-section {
756
+ display: flex;
757
+ flex-direction: column;
758
+ background: var(--ty-input-bg);
759
+ overflow: hidden;
760
+ flex: 0 0 auto;
761
+ max-height: 40%;
762
+ transition: max-height 200ms cubic-bezier(0.4, 0, 0.2, 1);
763
+ }
764
+
765
+ /* Empty: collapse to header only */
766
+ .dropdown-mode-mobile .mobile-selected-section[data-empty="true"] {
767
+ max-height: 36px;
768
+ flex: 0 0 36px;
769
+ }
770
+
771
+ .dropdown-mode-mobile .mobile-selected-section .section-content {
772
+ flex: 1;
773
+ min-height: 0;
774
+ overflow-y: auto;
775
+ overscroll-behavior: contain;
776
+ padding: 0.75rem 1rem;
777
+ display: flex;
778
+ flex-wrap: wrap;
779
+ gap: 0.5rem;
780
+ align-content: flex-start;
781
+ /* Soft fade at bottom edge — hints at scrollable overflow */
782
+ -webkit-mask-image: linear-gradient(to bottom, black calc(100% - 12px), transparent);
783
+ mask-image: linear-gradient(to bottom, black calc(100% - 12px), transparent);
784
+ }
785
+
786
+ /* ===== AVAILABLE LIST - takes remaining space ===== */
787
+
788
+ .dropdown-mode-mobile .mobile-available-section {
789
+ display: flex;
790
+ flex-direction: column;
791
+ background: var(--ty-input-bg);
792
+ overflow: hidden;
793
+ flex: 1 1 auto;
794
+ min-height: 0;
795
+ }
796
+
797
+ .dropdown-mode-mobile .mobile-available-section .section-content {
798
+ flex: 1;
799
+ min-height: 0;
800
+ overflow-y: auto;
801
+ overscroll-behavior: contain;
802
+ padding: 0.75rem 1rem;
803
+ display: flex;
804
+ flex-wrap: wrap;
805
+ gap: 0.5rem;
806
+ align-content: flex-start;
807
+ }
808
+
809
+ /* ===== EMPTY STATES =====
810
+ Selected strip collapses (no text needed).
811
+ Available shows the empty message only when there are zero tags total. */
812
+
813
+ .dropdown-mode-mobile .empty-state {
814
+ display: none;
815
+ width: 100%;
816
+ padding: 2rem 1rem;
817
+ text-align: center;
818
+ color: var(--ty-text-faint);
819
+ font-size: var(--ty-font-sm);
820
+ line-height: var(--ty-leading-sm);
821
+ letter-spacing: var(--ty-tracking-sm);
822
+ font-style: italic;
823
+ }
824
+
825
+ .dropdown-mode-mobile .mobile-available-section[data-empty="true"] .empty-state {
826
+ display: block;
827
+ }
828
+
829
+ .dropdown-mode-mobile .mobile-selected-section[data-empty="true"] .section-content,
830
+ .dropdown-mode-mobile .mobile-available-section[data-empty="true"] slot {
831
+ display: none;
832
+ }
833
+
834
+ /* ===== TAG STYLING IN MOBILE ===== */
835
+
836
+ .dropdown-mode-mobile .section-content ::slotted(ty-tag) {
837
+ cursor: pointer;
838
+ user-select: none;
839
+ transition: transform 0.1s ease;
840
+ margin: 2px 0; /* Vertical spacing like dropdown options */
841
+ /* Fade + scale entry — replays on each (re)insertion when a tag moves
842
+ between selected and available slots */
843
+ animation: ty-select-tag-enter 180ms cubic-bezier(0.16, 1, 0.3, 1);
844
+ }
845
+
846
+ @media (hover: hover) {
847
+ .dropdown-mode-mobile .section-content ::slotted(ty-tag:hover) {
848
+ transform: scale(1.02);
849
+ }
850
+ }
851
+
852
+ .dropdown-mode-mobile .section-content ::slotted(ty-tag:active) {
853
+ transform: scale(0.96);
854
+ }
855
+
856
+ /* Dimmed appearance for hidden filtered tags */
857
+ .dropdown-mode-mobile .section-content ::slotted(ty-tag[hidden]) {
858
+ display: none !important;
859
+ }
860
+
861
+ @keyframes ty-select-tag-enter {
862
+ from { opacity: 0; transform: scale(0.85); }
863
+ to { opacity: 1; transform: scale(1); }
864
+ }
865
+
866
+ @media (prefers-reduced-motion: reduce) {
867
+ .dropdown-mode-mobile .section-content ::slotted(ty-tag) {
868
+ animation: none;
869
+ }
870
+ }
871
+
872
+ /* ==================== LOADING STATE ====================
873
+ Spinner overlay shown inside the options area while the parent
874
+ (external-search mode) is fetching. Search input stays usable.
875
+
876
+ Carries its own surface so it stays visible even when the host
877
+ has restyled the popup with a transparent or unusual background.
878
+ Override with --ty-loader-bg / --ty-loader-radius / --ty-loader-border.
879
+ */
880
+ .dropdown-loading {
881
+ display: none;
882
+ align-items: center;
883
+ justify-content: center;
884
+ gap: var(--ty-spacing-2);
885
+ padding: var(--ty-spacing-4) var(--ty-spacing-3);
886
+ color: var(--ty-text-soft);
887
+ font-size: var(--ty-font-sm);
888
+ min-height: 4rem;
889
+ /* Match the .dropdown-options popup look — same background, border, radius, shadow */
890
+ background: var(--ty-loader-bg, var(--ty-surface-floating));
891
+ border: 1px solid var(--ty-loader-border, var(--ty-floating-border));
892
+ border-radius: var(--ty-loader-radius, var(--ty-radius-lg));
893
+ box-shadow: var(--ty-loader-shadow, 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04));
894
+ box-sizing: border-box;
895
+ }
896
+
897
+ .dropdown-options-wrapper.loading .dropdown-loading {
898
+ display: flex;
899
+ }
900
+
901
+ /* Slot is transparent for layout — fallback (spinner + text) and user-provided
902
+ slotted content both act as direct flex children of .dropdown-loading. */
903
+ .dropdown-loading > slot[name="loading"] {
904
+ display: contents;
905
+ }
906
+
907
+ /* Mobile: full-screen dialog is the surface — drop the card chrome,
908
+ stack vertically, scale up so it feels native to a fullscreen view.
909
+ Slotted content adapts automatically since the slot is display:contents. */
910
+ .dropdown-mode-mobile .dropdown-loading {
911
+ background: transparent;
912
+ border: none;
913
+ box-shadow: none;
914
+ flex: 1;
915
+ flex-direction: column;
916
+ gap: var(--ty-spacing-3);
917
+ padding: var(--ty-spacing-8) var(--ty-spacing-4);
918
+ min-height: 12rem;
919
+ }
920
+
921
+ .dropdown-mode-mobile .dropdown-loading-spinner {
922
+ width: 2rem;
923
+ height: 2rem;
924
+ }
925
+
926
+ .dropdown-mode-mobile .dropdown-loading-text {
927
+ font-size: var(--ty-font-base);
928
+ }
929
+
930
+ .dropdown-options-wrapper.loading .dropdown-options,
931
+ .dropdown-options-wrapper.loading > slot#options-slot,
932
+ .dropdown-options-wrapper.loading .empty-state {
933
+ display: none;
934
+ }
935
+
936
+ .dropdown-loading-spinner {
937
+ display: inline-flex;
938
+ align-items: center;
939
+ justify-content: center;
940
+ width: 1.125rem;
941
+ height: 1.125rem;
942
+ flex-shrink: 0;
943
+ animation: ty-select-spin 0.7s linear infinite;
944
+ /* Active-state indicator — full accent, not the soft resting shade */
945
+ color: var(--select-accent-bold, var(--ty-color-primary));
946
+ }
947
+
948
+ .dropdown-loading-spinner svg {
949
+ width: 100%;
950
+ height: 100%;
951
+ }
952
+
953
+ .dropdown-loading-text {
954
+ color: var(--ty-text-soft);
955
+ }
956
+
957
+ @keyframes ty-select-spin {
958
+ to { transform: rotate(360deg); }
959
+ }
960
+
961
+ @media (prefers-reduced-motion: reduce) {
962
+ .dropdown-loading-spinner {
963
+ animation-duration: 1.6s;
964
+ }
965
+ }
966
+
967
+ /* Custom scrollbar styles */
968
+ ${customScrollbarStyles}
969
+
970
+ /* ===== FLAVOR VARIANTS (set --select-accent*, consumed above) ===== */
971
+ ${FLAVORS.filter((f) => f !== "neutral").map((f) => selectFlavor(f)).join("")}
972
+ `;
973
+ //# sourceMappingURL=select-base.js.map