@keenmate/web-multiselect 2.0.0-rc11 → 2.0.0

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,33 +21,53 @@ 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-rc11
24
+ ## What's New in v2.0.0
25
25
 
26
- - **Imperative open/close API — drive the dropdown from code.** The `<web-multiselect>` element and the underlying `WebMultiSelect` now expose `open()`, `close()`, `toggle()`, and a read/write `isOpen` property, mirroring the calendar API in web-daterangepicker. Each element method flushes pending property writes first (the same contract as `getSelected()`/`setSelected()`), so `el.options = data; el.open()` works with no `await` in between. Calling `open()` from your own button's click handler now opens *and stays open* — previously the same click bubbled to the outside-click listener and re-closed it. See the new `examples-data-api.html` §API07 demo.
26
+ - **"Add new" creation mode — the picker doubles as a creation tool.** Turn on `allow-add-new`
27
+ and a search with no matches shows a clickable "Add new …" prompt (label via `add-new-text` /
28
+ `getAddNewTextCallback`). Choosing it (click, Enter, or arrow-to-focus) fires a new bubbling
29
+ `add` event; `addNewCallback` creates the option and is **async and cancelable** (return
30
+ `null`/`undefined` to abort after a confirm dialog or server round-trip) and may return a
31
+ **rich option** that renders like any other. While it runs the prompt shows a spinner +
32
+ `add-new-pending-text`. Creation also works with no callback — handle it entirely off the `add`
33
+ event. See `examples-events-callbacks.html` §EV4b/EV4c.
27
34
 
28
- - **Inline clear (✕) button — wipe the whole selection from inside the input.** A new opt-in `show-clear` attribute renders a small ✕ at the input's trailing edge that appears only while something is selected. Clicking it clears the selection and any search text, fires a single `change`, refocuses the input, and closes the selected-items popover if it was open — without popping the dropdown open. It's drawn as a themeable CSS mask icon (`--ms-input-clear-*`, whose corner radius follows `--ms-border-radius`). See the new `examples-basic.html` §BU01b demo.
35
+ - **Scroll-to imperative API — jump to any option or group.** New `scrollToIndex()` /
36
+ `scrollToValue()` / `scrollToGroup()` on the element and picker bring a row into view — pair with
37
+ `open()` for an "open + jump" gesture. Mode-aware (virtual scroll, tree, mobile fullscreen sheet),
38
+ they align to the top by default (`{ block: 'center' }` to center) and return `false` when the
39
+ target isn't in the current filtered list. A companion public `clearSearch()` reveals a
40
+ filtered-out option so you can then scroll to it, plus a `searchText` getter and a `search(term)`
41
+ method to read and programmatically drive the query. See §BU06b, §VS03, §TR09b.
29
42
 
30
- - **Input decorations rebuilt as a flex "field shell" — no more overlap or text bleed.** `.ms__input-wrapper` is now the bordered field (border, background, radius, focus ring via `:focus-within`), with the `<input>`, the `[N]` counter, the ✕ clear, and the chevron as real flex children in a spaced row. Previously each was absolutely pinned by a hard-coded inset, so `show-counter` + `show-clear` collided and long text could slide under the icons. Now they space themselves via `--ms-input-gap`, long text clips cleanly inside the input's own box, and RTL mirroring falls out of the flex direction for free. Several obsolete positioning vars were removed (`--ms-input-padding`, `--ms-input-padding-right`, `--ms-toggle-right`, `--ms-counter-offset`, `--ms-input-clear-inset`, `--ms-input-clear-gutter`, `--ms-transform-center-y`).
43
+ - **Checkbox check/dash + filter funnel now flow from the shared `--base-icon-*` contract.** The
44
+ checkmark and indeterminate dash are now `currentColor` mask glyphs (not CSS-border shapes) reading
45
+ `--base-icon-check` / `--base-icon-indeterminate`, and the search-mode funnel reads
46
+ `--base-icon-filter` — so one base override reskins them across every KeenMate component. Inline
47
+ Lucide fallbacks keep the default look unchanged.
31
48
 
32
- - **Selected-items popover now lines up with the field.** `--ms-selected-popover-width` used to default to a fixed 32rem independent of the control, which looked detached under a wide field; it now defaults to `var(--ms-input-current-width)`, so the popover and the dropdown both track the field width and align under it. Set `selected-popover-width` (or the CSS var) to a fixed length to restore the old constant-width behavior.
49
+ - **`--ms-rem` now bridges to the shared `--base-rem` knob.** The global sizing unit resolves
50
+ `var(--base-rem, 10px)`, so a theme that sets `--base-rem` rescales the whole component from one
51
+ variable (the `10px` fallback and per-instance overrides still work with no base layer loaded).
33
52
 
34
- - **Toggle chevron is now a crisp icon, not a text character.** The dropdown indicator renders the shared `--ms-icon-chevron` glyph through a CSS mask — consistent with the ✕, count-clear, and badge-remove icons — instead of the Unicode `▼`, so it no longer depends on font rendering and themes uniformly via `--ms-toggle-icon-color` / `--ms-toggle-icon-size`. It still points down when closed and rotates up when open.
53
+ - **Fixed: the count-chip clear ✕ and popover close ✕ vanished on hover.** Both derived their hover
54
+ background *and* glyph colour from the same accent, so the ✕ melted into its own hover state
55
+ (fully invisible on near-white accents like Minimal dark). They now fill with a solid accent
56
+ background and flip the glyph to the on-accent colour, matching the badge remove button.
35
57
 
36
- ## What's New in v2.0.0-rc10
58
+ ## What's New in v2.0.0-rc12
37
59
 
38
- - **`collapse-badges-below` — container-responsive badge collapse** — A picker in a narrow column could overflow with pills even on a wide monitor, because badge behavior keyed off the *window*, not the control. This new opt-in attribute/property (off by default) makes the control watch its **own border box** via the core `resized` hook (a shared page-wide `ResizeObserver`) and collapse `badges-display-mode` to `count` ("N selected") while the box is narrower than the given pixel width, restoring the configured mode when it widens back. The override is applied to the live picker only — never written to your config — so custom modes (`compact`, `partial`) come back exactly, it survives a structural rebuild, and the hook is throttled so a drag-resize reflows a bounded number of times. It's a separate axis from `mobile-presentation` and composes with it. New demo on `examples-responsive.html`.
60
+ - **`overlay-group` — one overlay open at a time, across components.** The dropdown now joins a cross-component single-active-overlay group (core `registerOverlay`): opening it dismisses every other participating overlay — other multiselects, datepickers, or any external popover that fires the `km-overlay-activated` document event — and it closes itself when another overlay in its group opens. The new `overlay-group` attribute/property scopes this to a named group (same group = coordinate, different groups = independent, unset = the default ungrouped pool). Outside-click dismissal is unchanged and always on; this only governs the open-broadcast. Fixes the old behavior where two multiselects could sit open simultaneously.
39
61
 
40
- - **`renderBadgeCallback` — own the whole badge, not just its content** — `renderBadgeContentCallback` only fills the built-in pill, so a selected item couldn't become, say, a full card. The new property-only callback returns the *entire* badge markup; the component wraps it in a `.ms__badge.ms__badge--custom` element carrying `data-value` (the modifier drops the pill's fixed height / overflow / radius so a card lays out freely) and delegates removal to any inner element with `data-action="remove"` (or the built-in `.ms__badge-remove`) — value resolved from the wrapper, so no event wiring. It falls back to the default pill when the callback returns null/empty, and `getBadgeClassCallback` classes still land on the wrapper. New Custom Rendering demo: an icon-pack picker where each selection is a card with its own Remove button.
62
+ - **Icon glyphs now flow from the shared `--base-icon-*` contract.** The four `--ms-icon-*` glyphs with a shared counterpart (chevron, field-clear, badge-remove, search) are wired through the `@keenmate/base-css-variables` layer, so a single `--base-icon-*` override re-skins that affordance across every KeenMate component at once — matching how the ~95 other `--ms-*` tokens already fall back to `--base-*`. The toggle/pager chevron now flows from `--base-icon-chevron` (a Lucide angle that keeps the previous optical size); field-clear, badge-remove, and search follow their base equivalents. Each keeps its inline Lucide SVG as the standalone fallback, so default appearance is unchanged when no base layer is loaded.
41
63
 
42
- - **`enable-selected-popover` — opt out of the selected-items popover** — When you render your own selection UI (suppress badges with `badges-display-mode="none"`, keep only the in-field `[N]` counter, and drive a panel from the `change` event), the built-in popover that opened on clicking the counter or badge was pointless. This new boolean (default `true`, so nothing changes by default) gates `showPopover()` at the source — every trigger (badge click, counter click, "+X more", keyboard) becomes a no-op — and adds a `ms--no-selected-popover` host class that drops the pointer cursor so those affordances no longer advertise as openable. New Custom Rendering demo: a playlist builder that owns its selection panel end to end.
64
+ - **`--ms-toggle-rotate-closed` / `--ms-toggle-rotate-open` — themeable chevron rotation.** The toggle rotates the *directional* base chevron into place (defaults `90deg` closed → down, `-90deg` open → up). A theme that supplies a **pre-oriented** glyph (one that already points down) can now opt out: set both to `0deg` for a static icon, or `0deg` / `180deg` for a down-glyph that flips up on open — without touching the base contract. The new Material Design card in `examples-theming.html` demonstrates the pairing.
43
65
 
44
- - **Device-detection helpers re-exported from the package entry** — Per-device configuration used to mean pulling in the core separately. `observeEnvironment`, `classifyDevice`, `getEnvironment`, `observeViewport`, `configureBreakpoints`, and `TABLET_MIN_SHORT_SIDE` (plus the `EnvironmentSnapshot` / `DeviceClass` types) are now re-exported from `@keenmate/web-multiselect` — the same device signal the component reacts to internally — so you get it from one import and one dependency. The pattern is plain: react to the event and assign a different `actionButtons` set on mobile vs. desktop. Demonstrated in `examples-action-buttons.html` §10, which previously crammed 12 buttons into one unreadable row on phones.
66
+ - **`--ms-fullscreen-nav-btn-icon` — the pager glyph is now independent.** The fullscreen match-navigator's prev/next buttons previously masked the shared `--ms-icon-chevron`, so a theme that repointed the base chevron at a pre-oriented toggle glyph would leak it into the pager (which rotates its source ±90° and expects a right-pointing chevron). The pager now reads its own token, defaulting to `--ms-icon-chevron` — nothing changes by default, but a theme can diverge the pager glyph on its own.
45
67
 
46
- - **Fullscreen overlay warns when an ancestor mis-anchors it** — The phone overlay is a `position: fixed` full-viewport sheet, so a `transform` / `perspective` / `filter` / `backdrop-filter` / `will-change` on any ancestor of the host anchors it to that ancestor's box instead of the viewport — and it silently stops covering the screen. The floating dropdown already surfaced this via drift detection; the fullscreen path had no equivalent. It now checks core's containing-block heuristic when the sheet opens and, if the true offset parent is an element rather than the viewport, emits a once-per-instance `console.warn` naming the culprit and the fix. Note the asymmetry: an ancestor `transform` is harmless for the floating dropdown but breaks the sheet; `contain` / `container-type` don't break the sheet at all. Documented on the Positioning Edge Cases page (new PO05 card).
68
+ - **More tokens flow from `--base-*` for dark-mode fidelity.** A follow-up audit wired five more `--ms-*` tokens that hardcoded a value where a dedicated `--base-*` counterpart exists: disabled-input background and dropdown box-shadow are now `light-dark()`-aware (they no longer stay light on dark themes), the field-clear color/hover knobs gain dedicated base hooks, and the snappy easing matches the base standard curve. All keep their prior value as the standalone fallback; transition *durations* and the z-index stack are deliberately left local.
47
69
 
48
- - **Dropdown corners — a focused first/last row no longer pokes a square corner past the rounded panel** — The panel clips with `overflow: hidden` + `border-radius`, but a row's focus `outline` and background trace the row's own box and follow its own radius, not an ancestor's clip, so the top/bottom rows' square corners bled through the rounded panel corner. The fix rounds the inner scroll wrapper to a new themeable `--ms-dropdown-inner-border-radius` (panel radius − border width, clamped at 0) and rounds the actual top/bottom rows on every render — in DOM order (so a grouped list rounds the top group label, not the first option), with logical corners so it mirrors in RTL, keeping the scrollbar-side corners square, and staying correct under virtual scrolling.
49
-
50
- - **Example pages — coded section headings, a Data & API split, and filename alignment** — Every example section now carries a short code in its heading (page-prefix + ordinal, e.g. `DA01`, `API03`), mirroring the showcase index, with the decorative emoji removed. The old `examples-classic.html` kitchen sink was split: genuine data/API content stays in the renamed `examples-data-api.html` (DA01–05 + API01–06), and its basic/cross-cutting demos moved to a new `examples-basic.html` (BU01–08). Three more pages were renamed to match their titles (`performance` → `virtual-scrolling`, `search-index` → `external-search`, `templating` → `custom-rendering`), and `index.html` and the docs links were repointed.
70
+ - **Dropdown / selected-items popover no longer render 2px wider than the field.** Both panels size from `--ms-input-current-width` (the field wrapper's border-box `offsetWidth`) but were themselves `content-box`, so each added its own 1px border on top and overhung the input it anchors to. Both now use `box-sizing: border-box`, so their outer width matches the field exactly.
51
71
 
52
72
  ## Demos & docs
53
73
 
@@ -53,7 +53,26 @@
53
53
  { "name": "base-input-size-sm-height", "required": false, "usage": "Small input height (multiplier)" },
54
54
  { "name": "base-input-size-md-height", "required": false, "usage": "Medium input height (multiplier)" },
55
55
  { "name": "base-input-size-lg-height", "required": false, "usage": "Large input height (multiplier)" },
56
- { "name": "base-input-size-xl-height", "required": false, "usage": "Extra large input height (multiplier)" }
56
+ { "name": "base-input-size-xl-height", "required": false, "usage": "Extra large input height (multiplier)" },
57
+ { "name": "base-rem", "required": false, "usage": "Global sizing unit — drives --ms-rem (falls back to 10px); scales the whole component" },
58
+ { "name": "base-ease-standard", "required": false, "usage": "Standard easing curve (drives --ms-easing-snappy)" },
59
+ { "name": "base-checkbox-border-color", "required": false, "usage": "Option checkbox border color" },
60
+ { "name": "base-input-clear-color", "required": false, "usage": "Inline field-clear (✕) icon color" },
61
+ { "name": "base-input-clear-bg-hover", "required": false, "usage": "Inline field-clear (✕) hover background" },
62
+ { "name": "base-icon-chevron", "required": false, "usage": "Shared chevron glyph (toggle indicator + fullscreen pager); inline Lucide SVG fallback" },
63
+ { "name": "base-icon-clear", "required": false, "usage": "Inline field-clear (✕) glyph; falls back to --ms-icon-remove" },
64
+ { "name": "base-icon-remove", "required": false, "usage": "Badge-remove glyph; falls back to base-icon-close then inline Lucide SVG" },
65
+ { "name": "base-icon-close", "required": false, "usage": "Generic close glyph (fallback for badge-remove)" },
66
+ { "name": "base-icon-search", "required": false, "usage": "Search glyph; inline Lucide SVG fallback" },
67
+ { "name": "base-icon-filter", "required": false, "usage": "Funnel/filter glyph (fullscreen search-mode toggle); chained by --ms-icon-filter; inline Lucide SVG fallback" },
68
+ { "name": "base-icon-check", "required": false, "usage": "Checkbox/option checkmark glyph; chained by --ms-icon-check; inline Lucide SVG fallback" },
69
+ { "name": "base-icon-indeterminate", "required": false, "usage": "Tri-state (partial) checkbox dash glyph; chained by --ms-icon-indeterminate; inline Lucide SVG fallback" },
70
+ { "name": "base-danger-bg", "required": false, "usage": "Error message background" },
71
+ { "name": "base-danger-color", "required": false, "usage": "Error message text color" },
72
+ { "name": "base-warning-bg", "required": false, "usage": "Warning message background" },
73
+ { "name": "base-warning-color", "required": false, "usage": "Warning message text color" },
74
+ { "name": "base-success-bg", "required": false, "usage": "Success message background" },
75
+ { "name": "base-success-color", "required": false, "usage": "Success message text color" }
57
76
  ],
58
77
  "componentVariables": [
59
78
  { "name": "ms-rem", "category": "sizing", "usage": "Base sizing unit for proportional scaling (default: 10px)" },
@@ -280,6 +299,9 @@
280
299
  { "name": "ms-badge-remove-bg-hover", "category": "badge", "usage": "Badge remove button hover background" },
281
300
  { "name": "ms-badge-remove-box-shadow-focus", "category": "badge", "usage": "Badge remove button focus shadow" },
282
301
  { "name": "ms-icon-remove", "category": "badge", "usage": "Remove icon as CSS url() to a mask-friendly SVG; color comes from currentColor" },
302
+ { "name": "ms-icon-filter", "category": "icon", "usage": "Funnel glyph (fullscreen search-mode toggle); chains to --base-icon-filter" },
303
+ { "name": "ms-icon-check", "category": "checkbox", "usage": "Checkmark glyph (mask) for a checked checkbox/option; chains to --base-icon-check" },
304
+ { "name": "ms-icon-indeterminate", "category": "checkbox", "usage": "Dash glyph (mask) for a tri-state/partial checkbox; chains to --base-icon-indeterminate" },
283
305
  { "name": "ms-badge-counter-bg", "category": "badge", "usage": "Counter badge background" },
284
306
  { "name": "ms-badge-counter-border", "category": "badge", "usage": "Counter badge border" },
285
307
  { "name": "ms-badge-counter-border-color", "category": "badge", "usage": "Counter badge border color" },