@keenmate/web-multiselect 2.0.0-rc02 → 2.0.0-rc04

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.
@@ -54,6 +54,384 @@
54
54
  flex-direction: column;
55
55
  }
56
56
 
57
+ /* ------------------------------------------------------------------------------
58
+ FULLSCREEN OVERLAY — phone / touch presentation
59
+ Set by JS (setPresentation('fullscreen'), driven by the element's
60
+ environmentChanged hook) INSTEAD of anchoring the panel. The panel becomes a
61
+ fixed, full-viewport sheet with its own search + close header, so the input it
62
+ would otherwise float beneath is fully covered. `dvh` tracks the mobile URL bar.
63
+ ------------------------------------------------------------------------------ */
64
+ /* Phone-view scaling. Every size is calc(N × --ms-rem), but a custom property bakes
65
+ its var(--ms-rem) at its DECLARATION site — the semantic vars are declared at :host
66
+ (10px), so overriding --ms-rem on the panel alone does NOT re-scale them. So we both
67
+ (a) set the panel's --ms-rem to --ms-fullscreen-rem, and (b) RE-DECLARE the shared
68
+ option/checkbox/group vars here in the panel scope, with the same formulas as
69
+ variables.css, so their var(--ms-rem) now resolves to the enlarged value. Overlay-
70
+ only --ms-fullscreen-* vars already derive from --ms-fullscreen-rem at :host, and
71
+ JS-driven row heights (virtual/fixed) are scaled to match in code. */
72
+ .ms__dropdown--fullscreen,
73
+ .ms__selected-popover--fullscreen {
74
+ --ms-rem: var(--ms-fullscreen-rem);
75
+ --ms-option-padding: calc(0.8 * var(--ms-rem)) calc(1.2 * var(--ms-rem));
76
+ --ms-option-gap: calc(0.8 * var(--ms-rem));
77
+ --ms-option-title-font-size: calc(var(--base-font-size-sm, 1.4) * var(--ms-rem));
78
+ --ms-option-subtitle-font-size: calc(var(--base-font-size-xs, 1.2) * var(--ms-rem));
79
+ --ms-option-icon-font-size: calc(var(--base-font-size-base, 1.6) * var(--ms-rem));
80
+ --ms-checkbox-size: calc(1.6 * var(--ms-rem));
81
+ --ms-group-label-font-size: calc(var(--base-font-size-xs, 1.2) * var(--ms-rem));
82
+ --ms-group-label-padding: calc(0.4 * var(--ms-rem)) calc(1.2 * var(--ms-rem));
83
+ /* Reclaim horizontal space for labels: tighter indent than the floating default
84
+ (--ms-tree-base-indent 0.75rem / --ms-tree-indent 1.25rem in tree.css). */
85
+ --ms-tree-base-indent: var(--ms-fullscreen-tree-base-indent);
86
+ --ms-tree-indent: var(--ms-fullscreen-tree-indent);
87
+ }
88
+
89
+ /* Bigger, dropdown-like selected-item rows in the phone overlay. The badge sizing
90
+ vars are declared at :host against the base --ms-rem (baked at 10px), so without
91
+ this they'd keep base-size text/padding inside the enlarged sheet and read as
92
+ cramped. Re-declaring them here — where --ms-rem is --ms-fullscreen-rem — scales
93
+ the pill height, text, padding and remove button up with the rest of the overlay.
94
+ The row height is also mirrored in JS (scaledBadgeHeight → --ms-badge-height-virtual)
95
+ for the virtual list, so both paths agree. */
96
+ .ms__selected-popover--fullscreen {
97
+ --ms-badge-gap: calc(0.8 * var(--ms-rem));
98
+ --ms-badge-height: calc(4.2 * var(--ms-rem));
99
+ --ms-badge-font-size: calc(var(--base-font-size-sm, 1.4) * var(--ms-rem));
100
+ --ms-badge-text-padding: 0 calc(1.2 * var(--ms-rem));
101
+ --ms-badge-remove-width: calc(3.6 * var(--ms-rem));
102
+ --ms-badge-remove-icon-size: calc(1.2 * var(--ms-rem));
103
+ --ms-badge-remove-font-size: calc(var(--base-font-size-sm, 1.4) * var(--ms-rem));
104
+ }
105
+
106
+ /* Action buttons (Select All / Clear All / custom) scale up in the overlay too — their
107
+ sizing vars are baked at :host against the base --ms-rem, so re-declare them here
108
+ (where --ms-rem is --ms-fullscreen-rem) for a comfortable touch target. Dropdown-only:
109
+ the selected-items popover has no actions row. */
110
+ .ms__dropdown--fullscreen {
111
+ --ms-actions-gap: calc(0.4 * var(--ms-rem));
112
+ --ms-actions-padding: calc(0.8 * var(--ms-rem));
113
+ --ms-action-btn-padding: calc(0.6 * var(--ms-rem)) calc(1.2 * var(--ms-rem));
114
+ --ms-action-btn-font-size: calc(var(--base-font-size-sm, 1.4) * var(--ms-rem));
115
+ }
116
+
117
+ .ms__dropdown--fullscreen {
118
+ position: fixed;
119
+ /* top/left only — NOT `inset: 0`. `inset` also sets `bottom: 0`, and a fixed box with
120
+ both `top` and `bottom` constrained ignores its `height`. observeKeyboardInset()
121
+ shrinks the sheet above the soft keyboard by writing `height` (+ `top`), so a pinned
122
+ `bottom` would silently defeat it — the list and bottom actions would stay behind the
123
+ keyboard. With only top/left pinned, height governs and the keyboard reflow works. */
124
+ top: 0;
125
+ inset-inline-start: 0;
126
+ width: 100vw;
127
+ max-width: 100vw;
128
+ height: 100dvh;
129
+ max-height: 100dvh;
130
+ border: none;
131
+ border-radius: 0;
132
+ background: var(--ms-fullscreen-bg);
133
+ color: var(--ms-fullscreen-text-color);
134
+ z-index: var(--ms-z-index-fullscreen);
135
+ }
136
+
137
+ .ms__dropdown--fullscreen .ms__dropdown-inner {
138
+ /* The panel is a full-height flex column; the list fills the space under the header. */
139
+ display: flex;
140
+ flex-direction: column;
141
+ flex: 1;
142
+ min-height: 0;
143
+ max-height: none;
144
+ /* The .ms__options child is the sole scroller in fullscreen (flex-fill + overflow-y
145
+ below). The base rule makes THIS element scroll too; two nested scrollers let the
146
+ last row get trapped in the outer one (partially visible, unreachable). Clip here so
147
+ scrolling happens in exactly one place. */
148
+ overflow: hidden;
149
+ }
150
+
151
+ /* The options list fills the sheet between the header and any actions row, scrolling
152
+ within the panel instead of at a fixed height — for BOTH the virtual list (its
153
+ inline maxHeight is dropped in fullscreen, see renderDropdownVirtual) and the plain
154
+ non-virtual list (small selections). Without flex-fill, a short non-virtual list
155
+ sits content-height at the top and a bottom actions row floats mid-sheet with empty
156
+ space beneath it. */
157
+ .ms__dropdown--fullscreen .ms__options {
158
+ flex: 1 1 0;
159
+ min-height: 0;
160
+ height: auto;
161
+ max-height: none;
162
+ overflow-y: auto;
163
+ /* Breathing room so the last row always clears the sheet's (or keyboard's) bottom edge
164
+ instead of sitting flush against it, half-cut. */
165
+ padding-bottom: var(--ms-option-gap);
166
+ }
167
+
168
+ /* Pin the action row (Select All / Clear / custom) so it never shrinks and stays docked
169
+ at the bottom of the sheet. Once observeKeyboardInset() shrinks the panel above the
170
+ soft keyboard, only .ms__options scrolls — the actions stay reachable above the keys. */
171
+ .ms__dropdown--fullscreen .ms__actions {
172
+ flex: 0 0 auto;
173
+ }
174
+
175
+ /* Shared fullscreen header bar — ONE treatment for both the dropdown (search
176
+ field) and the selected-items popover (title). Same padding, height, border, and
177
+ close button, so the two mobile overlays are visually identical. The popover's
178
+ header/close reuse their own class names but adopt these rules under
179
+ `--fullscreen`, and the selectors are listed together so there's one source of
180
+ truth (no drift between the two headers). */
181
+ .ms__fullscreen-header,
182
+ .ms__selected-popover--fullscreen .ms__selected-popover-header {
183
+ flex: 0 0 auto;
184
+ display: flex;
185
+ align-items: center;
186
+ /* Wrap lets the navigate-mode match navigator (flex-basis 100%) drop onto its own
187
+ row under the search box while search + close stay on the first row. */
188
+ flex-wrap: wrap;
189
+ gap: var(--ms-fullscreen-header-gap);
190
+ min-height: var(--ms-fullscreen-header-min-height);
191
+ padding: var(--ms-fullscreen-header-padding);
192
+ background: var(--ms-fullscreen-header-bg);
193
+ border-bottom: var(--ms-fullscreen-header-border);
194
+ }
195
+
196
+ /* Match navigator (navigate searchMode only): result count on the lead edge, round
197
+ prev/next buttons on the trailing edge, forced onto its own full-width row. Hidden
198
+ inline (display:none) by JS until there's a search term. */
199
+ .ms__fullscreen-nav {
200
+ flex: 0 0 100%;
201
+ display: flex;
202
+ align-items: center;
203
+ justify-content: space-between;
204
+ gap: var(--ms-fullscreen-nav-gap);
205
+ padding-top: var(--ms-fullscreen-nav-padding-top);
206
+ }
207
+
208
+ .ms__fullscreen-nav-count {
209
+ font-size: var(--ms-fullscreen-nav-count-font-size);
210
+ color: var(--ms-fullscreen-nav-count-color);
211
+ }
212
+
213
+ .ms__fullscreen-nav-controls {
214
+ display: flex;
215
+ align-items: center;
216
+ gap: var(--ms-fullscreen-nav-gap);
217
+ }
218
+
219
+ .ms__fullscreen-nav-btn {
220
+ flex: 0 0 auto;
221
+ display: inline-flex;
222
+ align-items: center;
223
+ justify-content: center;
224
+ width: var(--ms-fullscreen-nav-btn-size);
225
+ height: var(--ms-fullscreen-nav-btn-size);
226
+ padding: 0;
227
+ color: var(--ms-fullscreen-nav-btn-color);
228
+ background: var(--ms-fullscreen-nav-btn-bg);
229
+ border: none;
230
+ border-radius: 50%;
231
+ cursor: pointer;
232
+ }
233
+
234
+ .ms__fullscreen-nav-btn:hover:not(:disabled) {
235
+ background: var(--ms-fullscreen-nav-btn-bg-hover);
236
+ }
237
+
238
+ .ms__fullscreen-nav-btn:disabled {
239
+ opacity: 0.4;
240
+ cursor: default;
241
+ }
242
+
243
+ .ms__fullscreen-nav-btn::before {
244
+ content: "";
245
+ display: block;
246
+ width: var(--ms-fullscreen-nav-btn-icon-size);
247
+ height: var(--ms-fullscreen-nav-btn-icon-size);
248
+ background-color: currentColor;
249
+ -webkit-mask: var(--ms-icon-chevron) center / contain no-repeat;
250
+ mask: var(--ms-icon-chevron) center / contain no-repeat;
251
+ }
252
+
253
+ /* Chevron points up by default; the "next" (down) button flips it vertically. */
254
+ .ms__fullscreen-nav-btn--next::before {
255
+ transform: scaleY(-1);
256
+ }
257
+
258
+ /* The leading element (search field's wrapper or the title) fills the bar, pushing
259
+ the close button to the trailing edge in both overlays. */
260
+ .ms__fullscreen-header > .ms__fullscreen-search-wrapper,
261
+ .ms__selected-popover--fullscreen .ms__selected-popover-header > span {
262
+ flex: 1 1 auto;
263
+ min-width: 0;
264
+ }
265
+
266
+ /* Positioned context for the inline clear (✕); the input fills it fully. */
267
+ .ms__fullscreen-search-wrapper {
268
+ position: relative;
269
+ display: flex;
270
+ }
271
+
272
+ /* Popover title typography in fullscreen (matches a header title, not the small
273
+ floating-popover label). */
274
+ .ms__selected-popover--fullscreen .ms__selected-popover-header {
275
+ font-size: var(--ms-fullscreen-title-font-size);
276
+ font-weight: var(--ms-fullscreen-title-font-weight);
277
+ color: var(--ms-fullscreen-text-color);
278
+ }
279
+
280
+ .ms__fullscreen-search {
281
+ flex: 1 1 auto;
282
+ min-width: 0;
283
+ font-family: inherit;
284
+ font-size: var(--ms-fullscreen-search-font-size);
285
+ color: inherit;
286
+ background: transparent;
287
+ border: var(--ms-fullscreen-search-border);
288
+ border-radius: var(--ms-fullscreen-search-border-radius);
289
+ padding: var(--ms-fullscreen-search-padding);
290
+ /* Reserve trailing room so typed text never slides under the clear (✕). */
291
+ padding-inline-end: var(--ms-fullscreen-search-clear-gutter);
292
+ }
293
+
294
+ .ms__fullscreen-search:focus {
295
+ outline: none;
296
+ border: var(--ms-input-border-focus);
297
+ }
298
+
299
+ /* Inline clear pinned to the trailing edge of the search field. Small, round,
300
+ mask-icon glyph — a distinct Lucide search-x (see --ms-icon-search-clear), NOT the
301
+ overlay's bare ✕. JS toggles display so it appears only while the field has text. */
302
+ .ms__fullscreen-search-clear {
303
+ position: absolute;
304
+ inset-inline-end: var(--ms-fullscreen-search-clear-inset);
305
+ top: 50%;
306
+ transform: translateY(-50%);
307
+ display: inline-flex;
308
+ align-items: center;
309
+ justify-content: center;
310
+ width: var(--ms-fullscreen-search-clear-size);
311
+ height: var(--ms-fullscreen-search-clear-size);
312
+ padding: 0;
313
+ color: var(--ms-fullscreen-search-clear-color);
314
+ background: transparent;
315
+ border: none;
316
+ border-radius: 50%;
317
+ cursor: pointer;
318
+ }
319
+
320
+ .ms__fullscreen-search-clear:hover {
321
+ background: var(--ms-fullscreen-search-clear-bg-hover);
322
+ }
323
+
324
+ .ms__fullscreen-search-clear::before {
325
+ content: "";
326
+ display: block;
327
+ width: var(--ms-fullscreen-search-clear-icon-size);
328
+ height: var(--ms-fullscreen-search-clear-icon-size);
329
+ background-color: currentColor;
330
+ /* Lucide search-x (magnifier + x), not the bare ✕ — see --ms-icon-search-clear. */
331
+ -webkit-mask: var(--ms-icon-search-clear) center / contain no-repeat;
332
+ mask: var(--ms-icon-search-clear) center / contain no-repeat;
333
+ }
334
+
335
+ /* Shared close button — a fixed-size chip in both overlays: mask-icon ✕ centered in a
336
+ --close-size box that's round and borderless by default, but fully themeable into a
337
+ bordered "button" (see the --ms-fullscreen-close-bg / -border / -border-radius vars,
338
+ e.g. a light border + small radius like a command-palette close). The visible box is
339
+ a contained square so a border reads as a button, NOT a full-height strip; a separate
340
+ transparent ::after (below) keeps the tap target large and corner-reaching. A small
341
+ negative inline-end margin slides the button (and glyph) closer to the edge. */
342
+ .ms__fullscreen-close,
343
+ .ms__selected-popover--fullscreen .ms__selected-popover-close {
344
+ flex: 0 0 auto;
345
+ position: relative; /* anchor for the ::after hit-area extension */
346
+ box-sizing: border-box; /* a border must not grow the --close-size box */
347
+ display: inline-flex;
348
+ align-items: center;
349
+ justify-content: center;
350
+ width: var(--ms-fullscreen-close-size);
351
+ height: var(--ms-fullscreen-close-size);
352
+ margin-inline-end: calc(-1 * var(--ms-fullscreen-close-edge-nudge)); /* slide toward the edge */
353
+ padding: 0;
354
+ color: var(--ms-fullscreen-close-color);
355
+ background: var(--ms-fullscreen-close-bg);
356
+ border: var(--ms-fullscreen-close-border);
357
+ border-radius: var(--ms-fullscreen-close-border-radius);
358
+ cursor: pointer;
359
+ }
360
+
361
+ /* Invisible hit-area extension — grows the tap target well beyond the visible chip so
362
+ the whole top-trailing corner (the easiest place to tap, and the old dead padding
363
+ around a centered button) dismisses the sheet, without changing the chip's look. It
364
+ overshoots UP past the button (harmless — that's off the top of the sheet) and past
365
+ the trailing edge (clipped), and reaches a bounded amount below and into the leading
366
+ gap (kept short so it never covers the first option row or the search field). */
367
+ .ms__fullscreen-close::after,
368
+ .ms__selected-popover--fullscreen .ms__selected-popover-close::after {
369
+ content: "";
370
+ position: absolute;
371
+ inset-block-start: calc(-1 * var(--ms-fullscreen-close-size));
372
+ inset-block-end: calc(-1 * var(--ms-fullscreen-header-padding-v));
373
+ inset-inline-end: calc(-1 * var(--ms-fullscreen-header-padding-h));
374
+ inset-inline-start: calc(-1 * var(--ms-fullscreen-header-gap));
375
+ }
376
+
377
+ .ms__fullscreen-close:hover,
378
+ .ms__selected-popover--fullscreen .ms__selected-popover-close:hover {
379
+ background: var(--ms-fullscreen-close-bg-hover);
380
+ color: var(--ms-fullscreen-close-color);
381
+ }
382
+
383
+ .ms__fullscreen-close::before,
384
+ .ms__selected-popover--fullscreen .ms__selected-popover-close::before {
385
+ content: "";
386
+ display: block;
387
+ width: var(--ms-fullscreen-close-icon-size);
388
+ height: var(--ms-fullscreen-close-icon-size);
389
+ background-color: currentColor;
390
+ -webkit-mask: var(--ms-icon-remove) center / contain no-repeat;
391
+ mask: var(--ms-icon-remove) center / contain no-repeat;
392
+ }
393
+
394
+ /* Truncated-label reveal — a circled "i" at the trailing edge of a fullscreen row
395
+ whose label is clipped. Fullscreen-only markup (the renderer emits it only for the
396
+ overlay), and CSS keeps it hidden until JS tags the row .ms__option--truncated
397
+ (measured per render). Tapping it reveals the full label — the touch-friendly
398
+ substitute for the hover option tooltip, which never fires on touch. */
399
+ .ms__dropdown--fullscreen .ms__option-info {
400
+ flex: 0 0 auto;
401
+ display: none;
402
+ align-items: center;
403
+ justify-content: center;
404
+ width: var(--ms-fullscreen-info-size);
405
+ height: var(--ms-fullscreen-info-size);
406
+ margin-inline-start: var(--ms-option-gap);
407
+ padding: 0;
408
+ color: var(--ms-fullscreen-info-color);
409
+ background: transparent;
410
+ border: none;
411
+ border-radius: 50%;
412
+ cursor: pointer;
413
+ }
414
+
415
+ .ms__dropdown--fullscreen .ms__option--truncated .ms__option-info {
416
+ display: inline-flex;
417
+ }
418
+
419
+ @media (hover: hover) {
420
+ .ms__dropdown--fullscreen .ms__option-info:hover {
421
+ background: var(--ms-fullscreen-info-bg-hover);
422
+ }
423
+ }
424
+
425
+ .ms__dropdown--fullscreen .ms__option-info::before {
426
+ content: "";
427
+ display: block;
428
+ width: var(--ms-fullscreen-info-icon-size);
429
+ height: var(--ms-fullscreen-info-icon-size);
430
+ background-color: currentColor;
431
+ -webkit-mask: var(--ms-icon-info) center / contain no-repeat;
432
+ mask: var(--ms-icon-info) center / contain no-repeat;
433
+ }
434
+
57
435
  /* Inner scrolling container — clips scrollbar to rounded corners */
58
436
  .ms__dropdown-inner {
59
437
  flex: 1;
@@ -150,6 +528,64 @@
150
528
  visibility: visible;
151
529
  }
152
530
 
531
+ /* Truncated-label reveal fired from inside the fullscreen overlay — lift it above
532
+ the overlay (10001), which the plain option tooltip (10000) would sit behind. */
533
+ .ms__option-tooltip--fullscreen {
534
+ z-index: var(--ms-z-index-fullscreen-tooltip);
535
+ }
536
+
537
+ /* ==============================================================================
538
+ TRANSIENT MESSAGE / TOAST (showMessage)
539
+ ==============================================================================
540
+ Rendered above the panel so a veto reason (or any consumer feedback) is visible
541
+ in BOTH presentations: anchored under the control when floating (position set by
542
+ floating-ui), pinned to the bottom-centre of the viewport — above the sheet — when
543
+ fullscreen. One shows at a time; tap to dismiss. */
544
+ .ms__message {
545
+ position: fixed;
546
+ z-index: var(--ms-message-z-index);
547
+ box-sizing: border-box;
548
+ max-width: var(--ms-message-max-width);
549
+ padding: var(--ms-message-padding);
550
+ font-family: inherit;
551
+ font-size: var(--ms-message-font-size);
552
+ font-weight: var(--ms-message-font-weight);
553
+ line-height: var(--ms-line-height-normal);
554
+ color: var(--ms-message-color);
555
+ background: var(--ms-message-bg);
556
+ border: var(--ms-message-border);
557
+ border-radius: var(--ms-message-border-radius);
558
+ box-shadow: var(--ms-message-shadow);
559
+ cursor: pointer;
560
+ opacity: 0;
561
+ transition: opacity var(--ms-transition-normal) ease;
562
+ /* The one tap-highlight declaration NOT covered by :host (base.css): the message can
563
+ be reparented to document.body, outside the shadow subtree, so it sets its own. */
564
+ -webkit-tap-highlight-color: transparent;
565
+ }
566
+
567
+ .ms__message--visible {
568
+ opacity: 1;
569
+ }
570
+
571
+ .ms__message--info { background: var(--ms-message-info-bg); color: var(--ms-message-info-color); }
572
+ .ms__message--warning { background: var(--ms-message-warning-bg); color: var(--ms-message-warning-color); }
573
+ .ms__message--error { background: var(--ms-message-error-bg); color: var(--ms-message-error-color); }
574
+ .ms__message--success { background: var(--ms-message-success-bg); color: var(--ms-message-success-color); }
575
+
576
+ /* Fullscreen: bottom-centre of the viewport, above the overlay. Its sizing vars are
577
+ --ms-rem-based (baked at :host), so rescale the key ones here against
578
+ --ms-fullscreen-rem to match the enlarged phone view. */
579
+ .ms__message--fullscreen {
580
+ left: 50%;
581
+ right: auto;
582
+ bottom: var(--ms-message-fullscreen-offset);
583
+ transform: translateX(-50%);
584
+ max-width: min(var(--ms-message-max-width), calc(100vw - 4 * var(--ms-fullscreen-rem)));
585
+ padding: calc(1.0 * var(--ms-fullscreen-rem)) calc(1.4 * var(--ms-fullscreen-rem));
586
+ font-size: calc(var(--base-font-size-sm, 1.4) * var(--ms-fullscreen-rem));
587
+ }
588
+
153
589
  /* ==============================================================================
154
590
  SELECTED-ITEMS POPOVER
155
591
  ==============================================================================
@@ -182,6 +618,37 @@
182
618
  max-height: none;
183
619
  }
184
620
 
621
+ /* ------------------------------------------------------------------------------
622
+ FULLSCREEN OVERLAY — phone / touch presentation (mirrors the dropdown overlay).
623
+ Set by JS (setPresentation('fullscreen')) instead of anchoring. The popover
624
+ already renders its own header + close button, so it just becomes a fixed,
625
+ full-viewport sheet. Declared AFTER --virtual so its `display:flex` wins the
626
+ large-selection (virtual) case; the virtual body flexes to fill (its inline
627
+ height is dropped in fullscreen — see renderSelectedPopoverVirtual).
628
+ ------------------------------------------------------------------------------ */
629
+ .ms__selected-popover--fullscreen {
630
+ position: fixed;
631
+ inset: 0;
632
+ display: flex;
633
+ flex-direction: column;
634
+ width: 100vw;
635
+ max-width: 100vw;
636
+ height: 100dvh;
637
+ max-height: 100dvh;
638
+ border: none;
639
+ border-radius: 0;
640
+ overflow: hidden;
641
+ background: var(--ms-fullscreen-bg);
642
+ color: var(--ms-fullscreen-text-color);
643
+ z-index: var(--ms-z-index-fullscreen);
644
+ }
645
+
646
+ .ms__selected-popover--fullscreen .ms__selected-popover-body {
647
+ flex: 1 1 0;
648
+ min-height: 0;
649
+ max-height: none;
650
+ }
651
+
185
652
  .ms__selected-popover-header {
186
653
  display: flex;
187
654
  align-items: center;
@@ -261,6 +728,10 @@
261
728
  width: 100%;
262
729
  min-height: fit-content; /* Allow badges to expand based on content */
263
730
  line-height: var(--ms-line-height-relaxed); /* Override line-height: 1 to allow multi-line content */
731
+ /* The body is a flex column; without this, badges shrink (flex-shrink defaults to 1)
732
+ toward their min-content height once the list overflows — so rows visibly get
733
+ shorter as you select more. Pin the height and let the body scroll instead. */
734
+ flex-shrink: 0;
264
735
  }
265
736
 
266
737
  .ms__selected-popover-body .ms__badge-text {
@@ -98,11 +98,19 @@
98
98
  not a text-selection one — stop the browser from highlighting the label. */
99
99
  user-select: none;
100
100
  -webkit-user-select: none;
101
+ /* Tap-highlight flash is suppressed once for the whole component on :host (base.css) —
102
+ an inherited property, so it reaches this row without a per-element declaration. */
101
103
  }
102
104
 
103
- .ms__option:hover {
104
- background: var(--ms-option-bg-hover);
105
- color: var(--ms-option-color-hover, inherit);
105
+ /* Hover states are gated to real pointing devices. On touch, `:hover` "sticks"
106
+ after a tap until the next tap elsewhere, so a tapped row (or a tap on its info
107
+ affordance) would stay highlighted as if selected. `@media (hover: hover)` keeps
108
+ hover on mouse/trackpad and drops it on touch (phones, the fullscreen overlay). */
109
+ @media (hover: hover) {
110
+ .ms__option:hover {
111
+ background: var(--ms-option-bg-hover);
112
+ color: var(--ms-option-color-hover, inherit);
113
+ }
106
114
  }
107
115
 
108
116
  .ms__option--focused {
@@ -115,15 +123,17 @@
115
123
  .ms__option--matched {
116
124
  background: var(--ms-option-bg-matched);
117
125
  color: var(--ms-option-color-matched, inherit);
118
- border-left: var(--ms-option-border-matched);
126
+ border-inline-start: var(--ms-option-border-matched); /* leading-edge accent bar; flips in RTL */
119
127
  }
120
128
 
121
129
  .ms__option--selected {
122
130
  background: var(--ms-option-bg-selected);
123
131
  }
124
132
 
125
- .ms__option--selected:hover {
126
- background: var(--ms-option-bg-selected-hover, var(--ms-option-bg-selected));
133
+ @media (hover: hover) {
134
+ .ms__option--selected:hover {
135
+ background: var(--ms-option-bg-selected-hover, var(--ms-option-bg-selected));
136
+ }
127
137
  }
128
138
 
129
139
  .ms__option--disabled {
@@ -137,9 +147,11 @@
137
147
  }
138
148
 
139
149
  /* Focused + Hover */
140
- .ms__option--focused:hover {
141
- background: var(--ms-option-bg-focused-hover);
142
- color: var(--ms-option-color-focused-hover, var(--ms-option-color-focused, var(--ms-option-text-color)));
150
+ @media (hover: hover) {
151
+ .ms__option--focused:hover {
152
+ background: var(--ms-option-bg-focused-hover);
153
+ color: var(--ms-option-color-focused-hover, var(--ms-option-color-focused, var(--ms-option-text-color)));
154
+ }
143
155
  }
144
156
 
145
157
  /* Matched + Hover */
@@ -158,7 +170,7 @@
158
170
  /* Selected + Matched */
159
171
  .ms__option--selected.ms__option--matched {
160
172
  background: var(--ms-option-bg-selected-matched);
161
- border-left: var(--ms-option-border-matched);
173
+ border-inline-start: var(--ms-option-border-matched); /* leading-edge accent bar; flips in RTL */
162
174
  }
163
175
 
164
176
  /* Disabled + Selected */
@@ -195,9 +207,9 @@
195
207
  flex-shrink: 0;
196
208
  position: relative;
197
209
  margin-top: var(--ms-checkbox-margin-top);
198
- margin-right: var(--ms-checkbox-margin-right);
210
+ margin-inline-end: var(--ms-checkbox-margin-right);
199
211
  margin-bottom: var(--ms-checkbox-margin-bottom);
200
- margin-left: var(--ms-checkbox-margin-left);
212
+ margin-inline-start: var(--ms-checkbox-margin-left);
201
213
  width: var(--ms-checkbox-size);
202
214
  height: var(--ms-checkbox-size);
203
215
  transform: scale(var(--ms-checkbox-scale));
@@ -346,16 +358,20 @@
346
358
  }
347
359
 
348
360
  /* Title color states (for solid background themes) */
349
- .ms__option:hover .ms__option-title {
350
- color: var(--ms-option-title-color-hover, var(--ms-option-title-color));
361
+ @media (hover: hover) {
362
+ .ms__option:hover .ms__option-title {
363
+ color: var(--ms-option-title-color-hover, var(--ms-option-title-color));
364
+ }
351
365
  }
352
366
 
353
367
  .ms__option--selected .ms__option-title {
354
368
  color: var(--ms-option-title-color-selected, var(--ms-option-title-color));
355
369
  }
356
370
 
357
- .ms__option--selected:hover .ms__option-title {
358
- color: var(--ms-option-title-color-selected-hover, var(--ms-option-title-color-selected, var(--ms-option-title-color)));
371
+ @media (hover: hover) {
372
+ .ms__option--selected:hover .ms__option-title {
373
+ color: var(--ms-option-title-color-selected-hover, var(--ms-option-title-color-selected, var(--ms-option-title-color)));
374
+ }
359
375
  }
360
376
 
361
377
  /* Highlight matched text */
@@ -374,16 +390,20 @@
374
390
  }
375
391
 
376
392
  /* Subtitle color states (for solid background themes) */
377
- .ms__option:hover .ms__option-subtitle {
378
- color: var(--ms-option-subtitle-color-hover, var(--ms-option-subtitle-color));
393
+ @media (hover: hover) {
394
+ .ms__option:hover .ms__option-subtitle {
395
+ color: var(--ms-option-subtitle-color-hover, var(--ms-option-subtitle-color));
396
+ }
379
397
  }
380
398
 
381
399
  .ms__option--selected .ms__option-subtitle {
382
400
  color: var(--ms-option-subtitle-color-selected, var(--ms-option-subtitle-color));
383
401
  }
384
402
 
385
- .ms__option--selected:hover .ms__option-subtitle {
386
- color: var(--ms-option-subtitle-color-selected-hover, var(--ms-option-subtitle-color-selected, var(--ms-option-subtitle-color)));
403
+ @media (hover: hover) {
404
+ .ms__option--selected:hover .ms__option-subtitle {
405
+ color: var(--ms-option-subtitle-color-selected-hover, var(--ms-option-subtitle-color-selected, var(--ms-option-subtitle-color)));
406
+ }
387
407
  }
388
408
 
389
409
  /* ==============================================================================