tyrell-components 1.0.0-TC52 → 1.0.0-TC53

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