@keenmate/web-multiselect 2.1.0 → 2.2.0-rc02

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,21 +21,27 @@ 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.1.0
24
+ ## What's New in v2.2.0-rc02
25
25
 
26
- - **Render gate — `defer` / `ready()` for flash-free initialization** — A custom element upgrades the instant its script loads and paints with the component's *default* styles, so anything you wire in afterward — a `customStylesCallback`, a framework's shared stylesheet, your options array — lands a beat too late and the badges visibly restyle: the classic custom-element flash. The new boolean `defer` attribute holds the entire first render: while it's set the element builds nothing (it only reserves space via `:host([defer]:not([is-ready]))`), so you assign options, callbacks and listeners first, then call `el.ready()` — or simply remove the attribute, which suits server-driven frameworks like Phoenix LiveView — to build once with everything already in place. The gate is latched, exposes an `el.isReady` getter reflected as an `is-ready` attribute, and fires a one-time `ready` event right after the first build. Elements without `defer` behave exactly as before.
27
- - **External controls no longer close the dropdown they just re-drove** — Driving an already-open panel from your *own* button — a repeat `open()`/`toggle()`, or a `scrollToIndex()`/`scrollToValue()`/`scrollToGroup()` command — used to let that click bubble to the component's outside-click listener and immediately re-close the panel (the "every second click closes it" symptom). `open()` armed a one-tick guard against exactly this but bailed early when already open, and the `scrollTo*` methods never armed it at all. The guard is now centralized in `armClickGuard()` and armed by `open()` before its early-return and by every `scrollTo*` entry point. Consumers who worked around this with `stopPropagation()` can drop it.
26
+ - **`custom-styles` attribute — style shadow-DOM internals with zero JavaScript** — The existing `customStylesCallback` was the only way to inject custom CSS into the component's shadow root, which locked out consumers who can't reach into script: static HTML pages, server-rendered markup, no-build sites. The new `custom-styles` attribute takes raw CSS as a string (`<web-multiselect custom-styles=".ms__badge { font-weight: bold; }">`) and injects it verbatim — selectors and all — into the same replaceable style slot at the top of the shadow root that the callback uses, so you can restyle badges, options, and your own custom-rendered content declaratively. It's reactive (changing or removing the attribute re-applies or clears the slot), mirrored by a `customStyles` property, and flows through the same dev-mode `--ms-*` lint. When both are set, `customStylesCallback` still wins — the attribute is the static fallback. Demonstrated no-JS on the Custom Rendering page (CR09) and the Basic Usage declarative card.
28
27
 
29
- ## What's New in v2.0.1
28
+ - **Single-select no longer holds a stale multi-selection when seeded with several values** — `parseInitialSelection()` used to add every seeded value to the selection unconditionally, so a `multiple="false"` picker could hold — and highlight — multiple rows at once. It surfaced both from a declarative `initial-values="a,b,c"` on a single-select and, more visibly, from flipping `multiple` from `true` to `false` at runtime while items were selected: the reinit reseeds the live selection into the fresh single-select and left the extra rows highlighted. The picker now trims the seed to the first value when it isn't in multiple mode, so exactly one row stays selected.
30
29
 
31
- - **Single-select — re-clicking the selected option no longer clears the field** — In a `multiple="false"` picker, clicking or pressing Enter on the option that's already selected used to toggle it off and empty the control. Because a single-select row shows no checkbox, there was no cue you were un-picking, so a confirming click read as an accidental wipe. It's now a no-op that simply closes the dropdown, keeping the value selected; clearing stays the dedicated ✕ button's job (`show-clear`). Multi-select toggle-off and single-select replacement are untouched.
32
- - **Checkbox tick now sizes from the shared `--base-icon-check-size` token** — The selected-row checkmark was masked at `contain` (≈100% of the box), which only looked right when the glyph carried its own viewBox padding; a theme swapping `--base-icon-check` for an edge-to-edge glyph (e.g. a star) rendered it oversized, and it didn't match pure-admin's `.pa-checkbox`. It now reads `var(--base-icon-check-size, 68%)` — the exact knob pure-admin uses — so the mark renders identically in both and a theme can rescale it once for every component. The indeterminate dash inherits the same mask box. The default Lucide check shrinks slightly (≈100% → 68%) to match; that's intended.
33
- - **Build-time variable-manifest validator** — A new `scripts/check-variable-manifest.mjs` step (wired into `npm run build` as `check:vars`) diffs the `--ms-*` declared and `--base-*` consumed across `src/css/**` against `component-variables.manifest.json` and fails the build on dead or missing entries. This closes the silent-drift gap behind the generated IDE autocomplete and theme-designer surfaces, which are all derived from that manifest.
34
- - **Manifest drift cleanup** — Removed 24 inert entries the manifest still advertised (the commented-out `--ms-input-size-*` size-preset surface and five positioning vars dropped in the rc11 field-shell rework) and added the missing `--base-icon-plus` / `--base-icon-add` glyphs, so autocomplete no longer offers non-functional knobs and now covers the "add new" prompt icons.
30
+ - **Async-search dropdown no longer runs off the bottom of the viewport** — An async `searchCallback` that seeds no options anchors its panel while it's still empty or showing the loader — short enough to fit below the input, so with the default `lock-placement` it froze its placement to `bottom`. When results arrived the panel grew to full height but stayed pinned below the input, overflowing the bottom edge instead of flipping up into the free space, because `renderDropdown()` rewrote the list without re-anchoring. `performAsyncSearch()` now calls a new `repositionDropdown()` after results render, tearing down and recreating the Floating-UI anchor so the flip-on-first-compute re-evaluates against the panel's real height, then re-freezes. Scoped to the async path only — locally filtered dropdowns open already populated and are unaffected.
31
+
32
+ ## What's New in v2.2.0-rc01
33
+
34
+ - **Tree cascade is now the default — check a branch, check its subtree** — In a multi-select tree, `checkbox-mode` now defaults to `cascade` (previously `independent`), so ticking a branch selects its whole subtree and branches render a tristate (checked / indeterminate / unchecked) box — what most tree-select UIs do. The emitted selection follows `cascade-select-policy` (default `rolled-up`: a fully-checked subtree collapses to its root value). This is a behavior change for existing tree consumers — set `checkbox-mode="independent"` to keep the old per-node toggling. Flat and single-select lists are unaffected, since there's no subtree to cascade into.
35
+ - **Per-group select-all in flat grouped lists — `group-select-mode="cascade"`** — A new attribute puts a tristate checkbox on each group header in a flat, multi-select, grouped list; clicking it checks or unchecks all of that group's currently-visible members. The group name itself is never a selected value — `getValue()`, badges, and form output carry member values only — a partially-selected group reads indeterminate, and a header toggle fires a single `change`. Disabled members are excluded from the select-all. Default `none` leaves headers inert, as before.
36
+ - **Per-group selected counts + one shared count formatter** — Every group header now shows a count of that group's selected members, rendered as the same small chip as the in-input `[N]` counter (not a new badge style). A new `getCountLabelCallback((selected, total) => string)` formats both the in-input counter and the group chip together so they always read the same way — default `[3]`, or return `` `${s}/${t}` `` for an "x / y of total" style, where `total` is the whole option list for the counter and the group's member count for a header.
37
+ - **Order the selected items — `selected-order`** — Control the sequence chosen items appear in across badges, partial "+N more", and the selected-items popover: `as-selected` (default), `label-asc` / `label-desc`, `member` (by a `selected-order-member` property or `getSelectedOrderCallback`), or `custom` (a comparator). It's display-only — `getValue()`, form output, and `getSelected()` keep insertion order — and because the ordering runs in one shared place, the partial "+N more" split and its remove button always act on the items sorted *after* the visible slice.
38
+ - **Every render callback now receives a context** — `renderGroupLabelContentCallback` gains a second `GroupLabelRenderContext` argument (the group's members and selection, e.g. `selectedCount`), and `renderSelectedItemContentCallback` / `renderSelectedContentCallback` now also receive a context carrying the presentation (`isFullscreen` / `isModal`) — matching the option and badge callbacks. Everything is additive, so existing one-argument callbacks keep working; branch on `isFullscreen` to render leaner content in the phone overlay.
39
+ - **Fixes — selection preservation, hidden search field, "+N more" ✕** — Changing a cosmetic attribute (`badges-display-mode` / `badges-position`) no longer wipes the current selection (it's applied in place, and genuine rebuilds now preserve the runtime selection); `search-input-mode="hidden"` no longer collapses the input row, so the toggle stays at the trailing edge; and the "+N more" badge's ✕ now removes exactly the hidden items instead of silently opening the popover.
35
40
 
36
41
  ## Demos & docs
37
42
 
38
- - 🚀 [Live demo](https://web-multiselect.keenmate.dev)
43
+ - 🚀 [Showcase](https://web-multiselect.keenmate.dev)
44
+ - 🧪 [Live demo](https://examples.web-multiselect.keenmate.dev)
39
45
  - 📘 [Usage / API reference](./docs/usage.md) — attributes, properties, methods, events.
40
46
  - 🎨 [Theming](./docs/theming.md) — `--ms-*` variables, dark mode, cascade layers, Theme Designer integration.
41
47
  - 📚 [Examples / cookbook](./docs/examples.md) — rich content, async search, virtual scroll, custom rendering, forms.