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

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.
package/README.md CHANGED
@@ -21,90 +21,19 @@ Reads `--base-*` variables from the page if [`@keenmate/theme-designer`](https:/
21
21
  - Custom rendering callbacks for options, badges, and group headers.
22
22
  - Form integration via standard hidden inputs (FormData-compatible).
23
23
 
24
- ## What's New in v2.0.0-rc04
25
-
26
- **Phone search — one-tap clear and a keyboard that gets out of the way** — The
27
- full-screen search sheet gained the two things it was missing on touch. A clear button
28
- now sits at the trailing edge of the search field (a distinct Lucide `search-x` glyph,
29
- so it doesn't read as a second close ✕) to wipe the term in one tap and restore the full
30
- list. And the soft keyboard, which used to stay pinned open once you focused the field,
31
- now tucks away on the three natural "done typing" gestures — scrolling the list, tapping
32
- an option, or pressing Enter/Search — while never dismissing itself mid-type from a
33
- programmatic scroll-to-match.
34
-
35
- **A close button you can make your own** — The full-screen close ✕ is now a themeable
36
- chip: the new `--ms-fullscreen-close-bg`, `--ms-fullscreen-close-border`, and
37
- `--ms-fullscreen-close-border-radius` variables turn the bare glyph into a bordered
38
- button (à la a command-palette close) while the defaults keep it a plain round ✕. Its
39
- tap target was also fixed — the previously-dead padding around the button in the sheet's
40
- top-trailing corner (the natural place to reach) now dismisses the sheet, without ever
41
- stealing taps from the first option row or the search field.
42
-
43
- **Right presentation on every device, via core rc07** — This release pins
44
- `@keenmate/web-components-core` at `1.0.0-rc07` and adopts its reworked
45
- `resolvePresentation` / `classifyDevice` API. Behavior is unchanged — full-screen on
46
- phones, floating on tablet and desktop — with one refinement from the new capability
47
- gate: a narrowed desktop window (fine pointer, hover) now stays `desktop` and keeps its
48
- floating dropdown at any width, instead of ever flipping to the full-screen sheet.
49
-
50
- See `CHANGELOG.md` for the full list.
51
-
52
- ## What's New in v2.0.0-rc03
53
-
54
- **Full-screen dropdown on phones (`mobile-presentation`).** On a phone, a dropdown
55
- that floats next to the input fights the on-screen keyboard. `<web-multiselect>`
56
- now detects phones and, by default (`mobile-presentation="auto"`), presents the
57
- open dropdown as a **full-screen overlay** with its own search field and close (✕)
58
- button — while desktop and tablets keep the familiar floating panel, unchanged. A
59
- "phone" is a touch-primary device whose **shorter** viewport side is `< 600px`
60
- (the Material `sw600dp` line), so a phone in **landscape** still gets the overlay
61
- and tablets never do. The **selected-items popover** goes full-screen on phones
62
- too, with a matching header. The phone view is scaled up ~1.2× for comfortable
63
- touch targets via the `--ms-fullscreen-rem` knob (default `12px` vs the base
64
- `--ms-rem: 10px`) — one value grows rows, text, checkboxes, header and search
65
- together. Override the mode per instance with `mobile-presentation="floating"`
66
- (anchored panel everywhere) or `"fullscreen"` (force the overlay on any device —
67
- handy for previews). Theme it with the new `--ms-fullscreen-*` CSS variables.
24
+ ## What's New in v2.0.0-rc06
68
25
 
69
- ```html
70
- <!-- auto (default): full-screen on phones, floating on desktop/tablet -->
71
- <web-multiselect mobile-presentation="auto"></web-multiselect>
26
+ - **Tree search — `search-mode="navigate"` now works on trees** — Navigate mode (keep the whole list on screen and jump focus between matches instead of filtering it down) was silently ignored on tree data: the tree search path always collapsed the hierarchy to matches + ancestors and cleared the match set *before* the navigate branch could run, so `search-mode="navigate"` on a `path-member` tree did nothing. It now honors the mode — the always-expanded tree stays fully visible, matching rows get the highlight, focus jumps to the first match, and the match set powers `Ctrl`+`ArrowUp`/`ArrowDown` on desktop plus the fullscreen `N of M` match navigator (count + prev/next) on touch, which now shows for trees too. Filter mode (the default) is unchanged.
72
27
 
73
- <!-- never go full-screen -->
74
- <web-multiselect mobile-presentation="floating"></web-multiselect>
28
+ - **Fullscreen overlay — no longer slips under the system bar on pages with horizontal overflow** — A host page that overflows sideways (e.g. an unbreakable-wide token in a heading) makes the mobile browser shrink-to-fit — it zooms the page out so the overflow fits, splitting the visual viewport from the layout viewport. Because the fullscreen dropdown and selected-items popover are `position: fixed` (anchored to the layout viewport), that zoom left them inset from the physical screen and their top edge slid under the status/nav bar — easily misread as a safe-area issue, but `env(safe-area-inset-*)` is `0` in that state. Opening a fullscreen sheet now clamps `overflow-x: hidden` on `<html>` (restored on close), which removes the overflow so the browser drops the zoom and the sheet sits flush — regardless of the host page. No-op when the page doesn't overflow, and touches only `<html>` so it doesn't fight core's body scroll-lock.
75
29
 
76
- <!-- always full-screen (preview the mobile view on desktop) -->
77
- <web-multiselect mobile-presentation="fullscreen"></web-multiselect>
78
- ```
30
+ - **Fullscreen selected-items popover — Back gesture closes it instead of leaving the page** — The options dropdown already traps the Android Back gesture so it dismisses the fullscreen sheet rather than navigating away, but the selected-items popover — which also goes fullscreen on phones — skipped that guard, so a Back gesture fell through to a real `history(-1)` and left the page (losing the selection view and any unsaved state). The popover now pushes and consumes the same history entry as the dropdown, and the shared handler closes whichever overlay is open. Floating popovers are unaffected.
31
+
32
+ - **Search results — the current match no longer nudges the row sideways** — The leading accent bar on the current match was a real `border-inline-start` that existed only in the matched state, so the checkbox and label jumped ~3px inward the instant a row became the match — content visibly "walked" as you stepped between results. The bar is now an out-of-flow `::before` overlay pinned to the leading edge, so it paints the same accent without touching the row's box model. Still themeable via `--ms-option-border-matched` and RTL-mirrored.
33
+
34
+ ## What's New in v2.0.0-rc05
79
35
 
80
- This is powered by device/viewport/orientation detection in
81
- [`@keenmate/web-components-core`](https://www.npmjs.com/package/@keenmate/web-components-core)
82
- (via `BlissElement`'s `environmentChanged` hook), which this release pins at
83
- **1.0.0-rc06** — rc04 also **dropped `loglevel`** as a transitive runtime
84
- dependency. The floating dropdown additionally gains a viewport-width safety cap so
85
- a wide panel can't overflow the screen edge.
86
-
87
- **Right-to-left, done properly — including runtime switching.** Give the element (or
88
- any ancestor) `dir="rtl"` and the whole component mirrors: the toggle and in-input
89
- counter move to the left, checkboxes sit on the right of each row, badges reverse,
90
- and the full-screen overlay mirrors too (search/close swap sides, the match
91
- navigator flips). RTL is now built on CSS **logical properties** driven by the
92
- inherited direction, which fixes cases that silently never worked before — the
93
- dropdown, hint, and selected-popover live in the shadow root, so the old `.ms--rtl`
94
- override rules never reached them. And flipping `dir` at runtime — an app-wide
95
- language switch — re-mirrors the live picker without a rebuild (via core rc06's new
96
- `directionChanged` hook).
97
-
98
- **Friendlier phone browsing.** The full-screen sheet opens with the **keyboard
99
- closed** by default, so you can scan long lists and reach the bottom action buttons
100
- before typing (opt into immediate type-to-filter with `fullscreen-autofocus="true"`).
101
- The phone **Back gesture** now closes the sheet instead of navigating the page away.
102
- In `search-mode="navigate"`, an on-screen **match navigator** (an `N of M` count plus
103
- prev/next buttons) stands in for the desktop `Ctrl`+`Arrow` match-stepping that touch
104
- can't do — and the focused match now stays visible above the keyboard instead of
105
- scrolling behind it. Tapping an option no longer pops the keyboard mid-browse.
106
-
107
- See `CHANGELOG.md` for the full list.
36
+ - **Full-screen overlays — content no longer clipped under landscape system bars** — Rotating a phone to landscape with the full-screen dropdown (or the selected-items popover) open used to push the search field and option rows *under* the Android navigation bar and camera cutout — the field looked "too wide for the screen" and each row's leading edge was hidden. Cause: the sheets are `width: 100vw` (the full physical width) with no safe-area handling. They now set `box-sizing: border-box` and inset their content box by `env(safe-area-inset-{top,right,bottom,left})` on all four edges — the background stays edge-to-edge (full-bleed, no corner gaps) while the header · list · actions column is pulled into the visible region, and the fullscreen toast lifts above the bottom safe area. `env()` resolves to `0` unless the host page opts into edge-to-edge, so this is a no-op on non-cutout / letterboxed pages — a fix where needed, never a regression. Consumer note: for the insets to engage the host page must set `<meta name="viewport" content="… viewport-fit=cover">` (the mobile examples/tests now do).
108
37
 
109
38
  ## Demos & docs
110
39
 
@@ -541,6 +541,15 @@
541
541
  "privacy": "private",
542
542
  "default": "null"
543
543
  },
544
+ {
545
+ "kind": "field",
546
+ "name": "overflowXClamp",
547
+ "type": {
548
+ "text": "{ html: string } | null"
549
+ },
550
+ "privacy": "private",
551
+ "default": "null"
552
+ },
544
553
  {
545
554
  "kind": "field",
546
555
  "name": "keyboardInsetCleanup",
@@ -1178,6 +1187,17 @@
1178
1187
  ],
1179
1188
  "description": "Tree mode: derive the visible list from an **external** set of matched\r\noptions — e.g. the results returned by `searchCallback` — keeping each\r\nmatch's ancestors so indentation stays coherent. This is the async-search\r\nanalogue of `rebuildTreeVisible`: the matching is done by the caller (their\r\nown index/engine) instead of a local substring test, but ancestor\r\npreservation and `treeNodes`/`filteredOptions` index-alignment still happen\r\nhere. Pass all options to show the whole tree."
1180
1189
  },
1190
+ {
1191
+ "kind": "method",
1192
+ "name": "rebuildTreeVisibleForNavigate",
1193
+ "privacy": "private",
1194
+ "return": {
1195
+ "type": {
1196
+ "text": "number"
1197
+ }
1198
+ },
1199
+ "description": "Tree + `search-mode=\"navigate\"`: keep the ENTIRE tree visible (the tree is always\r\nfully expanded, so `flatNodes` is the whole thing) and record which visible rows\r\nmatch the term in `matchingIndices` — the flat-list navigate behavior, but over\r\n`treeNodes`. Filter mode collapses the hierarchy to matches + ancestors; navigate\r\nmode instead leaves the structure intact so the user can jump between matches\r\n(Ctrl+Arrow on desktop, the fullscreen navigator on touch). Returns the index of\r\nthe first match, or -1 (no term / no matches), so the caller can set focus."
1200
+ },
1181
1201
  {
1182
1202
  "kind": "method",
1183
1203
  "name": "detectRTL",
@@ -1971,6 +1991,28 @@
1971
1991
  },
1972
1992
  "description": "Restore page scroll (no-op if it wasn't locked)."
1973
1993
  },
1994
+ {
1995
+ "kind": "method",
1996
+ "name": "clampDocumentOverflowX",
1997
+ "privacy": "private",
1998
+ "return": {
1999
+ "type": {
2000
+ "text": "void"
2001
+ }
2002
+ },
2003
+ "description": "Clip the host document's horizontal overflow while a fullscreen sheet is open.\r\n\r\nA page that overflows horizontally (e.g. an unbreakable-wide token in a heading)\r\nmakes the mobile browser SHRINK-TO-FIT: it zooms the page out so the overflow fits,\r\nwhich desyncs the visual viewport from the layout viewport. Our fullscreen sheet is\r\n`position: fixed` — anchored to the LAYOUT viewport — so under that zoom it no longer\r\nlands flush against the physical screen edges, and the top slips under the system bar\r\n(looks like \"the bar covers the sheet\"). This is NOT a safe-area problem; safe-area\r\ninsets are 0 in that state. Clamping `overflow-x: hidden` on <html>/<body> removes the\r\noverflow, so the browser drops the zoom and the sheet sits flush. Complements\r\nlockBodyScroll() (vertical axis); the saved inline value is restored on close.\r\n\r\nOnly <html> is touched (not <body>): clipping the root's horizontal overflow is\r\nenough to collapse the scrollWidth and cancel the shrink-to-fit, and it avoids\r\nconflicting with core's lockBodyScroll(), which owns <body>'s `overflow`. Idempotent."
2004
+ },
2005
+ {
2006
+ "kind": "method",
2007
+ "name": "releaseDocumentOverflowX",
2008
+ "privacy": "private",
2009
+ "return": {
2010
+ "type": {
2011
+ "text": "void"
2012
+ }
2013
+ },
2014
+ "description": "Restore the <html> `overflow-x` clamped by clampDocumentOverflowX() (no-op if unset)."
2015
+ },
1974
2016
  {
1975
2017
  "kind": "method",
1976
2018
  "name": "observeKeyboardInset",
package/dist/index.d.ts CHANGED
@@ -783,6 +783,7 @@ export declare class WebMultiSelect<T = any> {
783
783
  private fullscreenNavPrev;
784
784
  private fullscreenNavNext;
785
785
  private bodyScrollUnlock;
786
+ private overflowXClamp;
786
787
  private keyboardInsetCleanup;
787
788
  private overlayHistoryActive;
788
789
  private readonly onOverlayPopstate;
@@ -916,6 +917,16 @@ export declare class WebMultiSelect<T = any> {
916
917
  * here. Pass all options to show the whole tree.
917
918
  */
918
919
  private rebuildTreeVisibleFromMatches;
920
+ /**
921
+ * Tree + `search-mode="navigate"`: keep the ENTIRE tree visible (the tree is always
922
+ * fully expanded, so `flatNodes` is the whole thing) and record which visible rows
923
+ * match the term in `matchingIndices` — the flat-list navigate behavior, but over
924
+ * `treeNodes`. Filter mode collapses the hierarchy to matches + ancestors; navigate
925
+ * mode instead leaves the structure intact so the user can jump between matches
926
+ * (Ctrl+Arrow on desktop, the fullscreen navigator on touch). Returns the index of
927
+ * the first match, or -1 (no term / no matches), so the caller can set focus.
928
+ */
929
+ private rebuildTreeVisibleForNavigate;
919
930
  /**
920
931
  * (Re)compute `isRTL` from the host's `dir` (or an RTL ancestor) and derive the
921
932
  * direction-mirrored badges position. Pure state — callers apply the DOM effects
@@ -1098,6 +1109,26 @@ export declare class WebMultiSelect<T = any> {
1098
1109
  private lockBodyScroll;
1099
1110
  /** Restore page scroll (no-op if it wasn't locked). */
1100
1111
  private unlockBodyScroll;
1112
+ /**
1113
+ * Clip the host document's horizontal overflow while a fullscreen sheet is open.
1114
+ *
1115
+ * A page that overflows horizontally (e.g. an unbreakable-wide token in a heading)
1116
+ * makes the mobile browser SHRINK-TO-FIT: it zooms the page out so the overflow fits,
1117
+ * which desyncs the visual viewport from the layout viewport. Our fullscreen sheet is
1118
+ * `position: fixed` — anchored to the LAYOUT viewport — so under that zoom it no longer
1119
+ * lands flush against the physical screen edges, and the top slips under the system bar
1120
+ * (looks like "the bar covers the sheet"). This is NOT a safe-area problem; safe-area
1121
+ * insets are 0 in that state. Clamping `overflow-x: hidden` on <html>/<body> removes the
1122
+ * overflow, so the browser drops the zoom and the sheet sits flush. Complements
1123
+ * lockBodyScroll() (vertical axis); the saved inline value is restored on close.
1124
+ *
1125
+ * Only <html> is touched (not <body>): clipping the root's horizontal overflow is
1126
+ * enough to collapse the scrollWidth and cancel the shrink-to-fit, and it avoids
1127
+ * conflicting with core's lockBodyScroll(), which owns <body>'s `overflow`. Idempotent.
1128
+ */
1129
+ private clampDocumentOverflowX;
1130
+ /** Restore the <html> `overflow-x` clamped by clampDocumentOverflowX() (no-op if unset). */
1131
+ private releaseDocumentOverflowX;
1101
1132
  /**
1102
1133
  * While the fullscreen dropdown is open, keep it sitting above the soft keyboard.
1103
1134
  * Delegates to core's `observeKeyboardInset` (which tracks `window.visualViewport`