@keenmate/web-multiselect 2.0.0-rc05 → 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,38 +21,20 @@ 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-rc06
25
+
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.
27
+
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.
29
+
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
+
24
34
  ## What's New in v2.0.0-rc05
25
35
 
26
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).
27
37
 
28
- ## What's New in v2.0.0-rc04
29
-
30
- **Phone search — one-tap clear and a keyboard that gets out of the way** — The
31
- full-screen search sheet gained the two things it was missing on touch. A clear button
32
- now sits at the trailing edge of the search field (a distinct Lucide `search-x` glyph,
33
- so it doesn't read as a second close ✕) to wipe the term in one tap and restore the full
34
- list. And the soft keyboard, which used to stay pinned open once you focused the field,
35
- now tucks away on the three natural "done typing" gestures — scrolling the list, tapping
36
- an option, or pressing Enter/Search — while never dismissing itself mid-type from a
37
- programmatic scroll-to-match.
38
-
39
- **A close button you can make your own** — The full-screen close ✕ is now a themeable
40
- chip: the new `--ms-fullscreen-close-bg`, `--ms-fullscreen-close-border`, and
41
- `--ms-fullscreen-close-border-radius` variables turn the bare glyph into a bordered
42
- button (à la a command-palette close) while the defaults keep it a plain round ✕. Its
43
- tap target was also fixed — the previously-dead padding around the button in the sheet's
44
- top-trailing corner (the natural place to reach) now dismisses the sheet, without ever
45
- stealing taps from the first option row or the search field.
46
-
47
- **Right presentation on every device, via core rc07** — This release pins
48
- `@keenmate/web-components-core` at `1.0.0-rc07` and adopts its reworked
49
- `resolvePresentation` / `classifyDevice` API. Behavior is unchanged — full-screen on
50
- phones, floating on tablet and desktop — with one refinement from the new capability
51
- gate: a narrowed desktop window (fine pointer, hover) now stays `desktop` and keeps its
52
- floating dropdown at any width, instead of ever flipping to the full-screen sheet.
53
-
54
- See `CHANGELOG.md` for the full list.
55
-
56
38
  ## Demos & docs
57
39
 
58
40
  - 🚀 [Live demo](https://web-multiselect.keenmate.dev)
@@ -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`