@keenmate/web-multiselect 1.12.0-rc06 → 1.12.0-rc08
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 +13 -23
- package/component-variables.manifest.json +1 -0
- package/dist/index.d.ts +51 -2
- package/dist/multiselect.js +979 -872
- package/dist/multiselect.umd.js +18 -17
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/css/controls.css +10 -2
- package/src/css/floating.css +1 -0
- package/src/css/options.css +4 -0
- package/src/css/variables.css +29 -10
package/README.md
CHANGED
|
@@ -21,29 +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 v1.12.0-
|
|
25
|
-
|
|
26
|
-
- **
|
|
27
|
-
- **
|
|
28
|
-
- **
|
|
29
|
-
- **
|
|
30
|
-
- **
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
> ⚠️ **Breaking change — event-handler rename, no aliases.** The fire-and-forget notification callbacks are renamed to `on*` with **no backward-compatible aliases**: `selectCallback` → `onSelect`, `deselectCallback` → `onDeselect`, `changeCallback` → `onChange`, and on `ActionButton`, `isVisibleCallback` / `isDisabledCallback` → `getIsVisibleCallback` / `getIsDisabledCallback`. If you assign these as element properties or config keys, **rename them or your handlers silently stop firing**. The `select` / `deselect` / `change` **DOM events are unchanged**, so `addEventListener(...)` consumers are unaffected. Full rationale in the first bullet below.
|
|
38
|
-
|
|
39
|
-
- **Events — fire-and-forget callbacks renamed to `on*` (breaking)** — `selectCallback` / `deselectCallback` / `changeCallback` are now `onSelect` / `onDeselect` / `onChange`, and on `ActionButton` the predicate callbacks become `getIsVisibleCallback` / `getIsDisabledCallback` while `onClick` keeps its name. The rule is simple: a function whose return value the component ignores is an *event* and takes the `on*` shape; functions whose return value is consumed stay `get*Callback` / `before*Callback`. The bubbling `select` / `deselect` / `change` DOM events are unchanged — the `on*` properties are just their JS-property twins. There are no deprecated aliases, so update `el.selectCallback` → `el.onSelect` (and the matching config keys); `addEventListener('select' | 'deselect' | 'change', …)` is unaffected.
|
|
40
|
-
- **Selection — new `beforeSelectCallback` / `beforeDeselectCallback` interceptors** — veto a selection or deselection before it happens by returning `false`; each receives the option being toggled plus the current selection. The veto is silent — a blocked action mutates no state and fires no event, so your handler owns any feedback. The deselect veto is enforced across *every* interactive removal path (dropdown toggle, badge `×`, selected-items popover, and the "remove hidden" badge) by routing them through a single funnel, so no affordance can bypass it; programmatic `setSelected()` and the Select-All / Clear-All buttons deliberately skip it. Typical uses: mutually-exclusive options, or a required item that can't be removed.
|
|
41
|
-
- **Dropdown options — hover tooltips, independently configurable** — option rows can now show a hover tooltip via `enable-option-tooltips`, built on the same Floating-UI `Tooltip` engine as the badge and action tooltips (show/hide delays, plus re-attachment as virtual-scrolled rows recycle into view). Default content is the option's display value with its subtitle on a second line; override per option with `getOptionTooltipCallback`. Unlike the badge tooltips they share an engine with, option tooltips are fully independent: `option-tooltip-placement` defaults to `top-start` so the tooltip anchors to a wide row's start edge instead of centering in the middle of a 1920px-wide row, `option-tooltip-follow-cursor` makes it track the pointer, and a dedicated `.ms__option-tooltip` class plus `--ms-option-tooltip-*` variables (each defaulting to the shared `--ms-tooltip-*` surface) let you restyle them separately. Demonstrated on the new `examples-tooltips.html`.
|
|
42
|
-
- **Action buttons — position, rows, and alignment** — the action-buttons block can sit at the `bottom` of the dropdown as a sticky footer (`actions-position`), buttons can be arranged across multiple stacked rows via a per-button `row` property (row 1 always at the panel's outer edge, higher rows stacking inward toward the options list — so `column-reverse` keeps row 1 at the bottom for the footer case), and `actions-align` controls horizontal arrangement (`stretch` default, plus `left` / `right` / `center` / `space-between`). Internally `.ms__actions` became a vertical stack of `.ms__actions-row` lines. All additive and backward compatible — no `row` means a single row, and `stretch` preserves the previous full-width buttons.
|
|
43
|
-
- **Action buttons — smarter built-in defaults** — the built-in `select-all` and `clear-all` actions now manage their own disabled state: Select All disables once every selectable option is chosen, and Clear All disables while nothing is selected. This applies only when you haven't set an explicit static `isDisabled` or a `getIsDisabledCallback` (precedence: dynamic callback › static `isDisabled` › built-in default), so existing custom action configs are untouched.
|
|
44
|
-
- **Single-select — dropdown no longer reopens with a stale filter** — the single-select input doubles as label display and search box; reopening used to blank the visible text while leaving the list filtered by the previous search, so the box and the list silently disagreed. `open()` now mirrors the kept search term back into the box, keeping the visible input and the filtered results in sync (honoring `should-keep-search-on-close`).
|
|
45
|
-
- **Async search — dropdown stops jumping between empty and loading states** — the "No data" empty state and the loading spinner now share a min-height (`--ms-state-min-height`, a new themable variable), so the panel keeps a stable footprint instead of resizing as an async search swaps one block for the other.
|
|
46
|
-
- **Docs — new live demo page for events & interceptors** — `examples-events-callbacks.html` shows the DOM events, the `on*` property twin firing in parallel, and interactive veto demos for the new interceptors.
|
|
24
|
+
## What's New in v1.12.0-rc08
|
|
25
|
+
|
|
26
|
+
- **Panel sizing — dropdown and popover are independently sizable via CSS variables** — The options dropdown and selected-items popover no longer inherit the input's width. `--ms-dropdown-width` (defaults to the live input width) and `--ms-selected-popover-width` (intrinsic 32rem) drive them, alongside the existing max-height variables. Set them at app level (`web-multiselect { --ms-dropdown-width: 60rem }`) or override a single instance with the new `dropdown-width` / `selected-popover-width` attributes, which write those variables inline on the element. This also fixes a latent bug where `--ms-selected-popover-width` was dead (an internal width-sync always overrode it), so the popover now honours its 32rem default. See section 14 of `examples-tree.html`.
|
|
27
|
+
- **Tree rendering — the option render callback receives full tree context** — `renderOptionContentCallback(item, ctx)` gains `isTreeNode`, `isBranch`, `isLeaf`, `childCount`, `level`, `depth`, `path`, `isSelectable`, and the cascade `isIndeterminate` tristate. Rendering a child-count badge on branches or branch/leaf-specific markup is now a one-liner, without re-deriving the hierarchy yourself. All fields are optional (flat options report `isTreeNode: false`), so existing callbacks are untouched. Demoed as the new "Custom Node Rendering" section.
|
|
28
|
+
- **Cascade counter — the `[N]` chip counts what you actually picked** — In cascade mode the counter used to show the emitted value count, which balloons under `cascade-select-policy="leaves"` / `"all"` (one branch click can emit five values) — jarring next to a couple of rolled-up badges. It now counts the rolled-up minimal cover — the branches you selected — regardless of emit policy, and gained a hover tooltip listing those items. Under the default `rolled-up` policy nothing changes.
|
|
29
|
+
- **Theming cleanup — dead input size-variant surface removed from the bundle** — The unused `.ms__input--xs/sm/lg/xl` preset classes and their `--ms-input-size-*` variable chain were never wired to anything (no `size` attribute toggles them), so they've been commented out — kept for a possible future preset API but no longer shipped as dead CSS (the compiled stylesheet shrank ~2 kB). Input sizing still works through `--ms-rem` for proportional global scale or the individual `--ms-input-*` variables for targeted overrides.
|
|
30
|
+
- **Fixes — badge hover and live cascade switching** — Badge hover no longer washes the chip to white (the hover background fell through to the white input background; it now deepens the accent tint, dark-mode aware). And switching `checkbox-mode` or `cascade-select-policy` live rebuilds the cascade index and silently re-projects the current selection, so badges, form value, and checkboxes all reflect the new mode instantly.
|
|
31
|
+
|
|
32
|
+
## What's New in v1.12.0-rc07
|
|
33
|
+
|
|
34
|
+
- **Selection — `setSelected(values, { notify: true })` announces programmatic changes** — `setSelected()` stays silent by default (restoring saved state, cascade/dependent resets, and server-authoritative corrections must not re-fire `change`, or they trip "the user changed it" handlers and can bounce in a feedback loop), but the new `{ notify: true }` option fires a **single aggregate `change`** — no per-item `select`/`deselect` flood — for when a programmatic change is a deliberate user gesture, e.g. a custom action button that sets the selection and should reach the same listeners a manual pick does. Threaded through both the internal picker and the web component's `setSelected`.
|
|
35
|
+
- **Cascade mode — Select All now honours the value policy** — the built-in `select-all` action added every selectable node's value directly, bypassing the cascade projection, so with `checkbox-mode="cascade"` + `cascade-select-policy="rolled-up"` it emitted every node instead of the rolled-up roots (unlike a click, which rolls up). `selectAll()` is now cascade-aware — it fills the checked-atom set from the visible nodes and projects through the active policy via a shared `commitCascadeAtoms` helper — so Select All emits the same shape a click does. Shown in the new "Action Buttons" section of `examples-tree.html`.
|
|
36
|
+
- **Options no longer text-select on click-drag** — clicking a row, or dragging across the dropdown, used to highlight the labels like selectable text, which reads as broken for a pure selection gesture (most visible on the denser tree rows). Options now set `user-select: none` (with the `-webkit-` prefix) on `.ms__option`. Purely presentational — no API or behaviour change.
|
|
47
37
|
|
|
48
38
|
> ⚠️ **Security notice:** This component intentionally allows raw HTML in rendering callbacks to give developers full control over content display. If you display user-generated content, you must sanitize it yourself. See [docs/examples.md → HTML Injection (XSS) notice](./docs/examples.md#html-injection-xss-notice) for the complete list of affected callbacks.
|
|
49
39
|
|
|
@@ -147,6 +147,7 @@
|
|
|
147
147
|
{ "name": "ms-dropdown-border-radius", "category": "dropdown", "usage": "Dropdown border radius" },
|
|
148
148
|
{ "name": "ms-dropdown-box-shadow", "category": "dropdown", "usage": "Dropdown shadow" },
|
|
149
149
|
{ "name": "ms-dropdown-box-shadow-semantic", "category": "dropdown", "usage": "Dropdown semantic shadow" },
|
|
150
|
+
{ "name": "ms-dropdown-width", "category": "dropdown", "usage": "Options dropdown width (defaults to the input width; also set via the dropdown-width attribute)" },
|
|
150
151
|
{ "name": "ms-options-max-height", "category": "dropdown", "usage": "Options list max height" },
|
|
151
152
|
{ "name": "ms-z-index-dropdown", "category": "dropdown", "usage": "Dropdown z-index" },
|
|
152
153
|
{ "name": "ms-z-index-sticky", "category": "dropdown", "usage": "Sticky elements z-index" },
|
package/dist/index.d.ts
CHANGED
|
@@ -667,7 +667,9 @@ export declare class MultiSelectElement<T = any> extends BaseElement {
|
|
|
667
667
|
get selectedValue(): string | number | (string | number)[] | null;
|
|
668
668
|
get selectedItem(): T | null;
|
|
669
669
|
getSelected(): T[];
|
|
670
|
-
setSelected(values: (string | number)[]
|
|
670
|
+
setSelected(values: (string | number)[], opts?: {
|
|
671
|
+
notify?: boolean;
|
|
672
|
+
}): void;
|
|
671
673
|
getValue(): string | number | (string | number)[] | null;
|
|
672
674
|
destroy(): void;
|
|
673
675
|
}
|
|
@@ -743,6 +745,24 @@ declare interface OptionContentRenderContext {
|
|
|
743
745
|
isMatched: boolean;
|
|
744
746
|
/** Whether the option is disabled */
|
|
745
747
|
isDisabled: boolean;
|
|
748
|
+
/** True when this row is a tree node (path-member / tree mode). Absent/false for flat options. */
|
|
749
|
+
isTreeNode?: boolean;
|
|
750
|
+
/** Tree only: the node has children (a branch). */
|
|
751
|
+
isBranch?: boolean;
|
|
752
|
+
/** Tree only: the node has no children (a leaf). */
|
|
753
|
+
isLeaf?: boolean;
|
|
754
|
+
/** Tree only: number of direct children (0 for a leaf). */
|
|
755
|
+
childCount?: number;
|
|
756
|
+
/** Tree only: 1-based depth level as derived from the path (top level = 1). */
|
|
757
|
+
level?: number;
|
|
758
|
+
/** Tree only: 0-based indentation depth (`level - 1`), matching `--ms-tree-depth`. */
|
|
759
|
+
depth?: number;
|
|
760
|
+
/** Tree only: the node's materialized path (e.g. "1.1.2"). */
|
|
761
|
+
path?: string;
|
|
762
|
+
/** Tree only: the node is selectable (branches marked non-selectable are `false`). */
|
|
763
|
+
isSelectable?: boolean;
|
|
764
|
+
/** Tree only: cascade tristate — a partially-checked branch (some but not all descendants). */
|
|
765
|
+
isIndeterminate?: boolean;
|
|
746
766
|
}
|
|
747
767
|
|
|
748
768
|
/**
|
|
@@ -885,6 +905,24 @@ export declare class WebMultiSelect<T = any> {
|
|
|
885
905
|
* so badges / form / change events reflect the policy (rolled-up branches, etc.).
|
|
886
906
|
*/
|
|
887
907
|
private toggleTreeCascade;
|
|
908
|
+
/**
|
|
909
|
+
* Given a checked-atom set, project it to emitted values under the active
|
|
910
|
+
* policy, diff it against the current selection, and commit. Shared by every
|
|
911
|
+
* cascade entry point (node toggle, Select All) so they all emit the same
|
|
912
|
+
* policy-projected shape (e.g. a full subtree rolls up to one value).
|
|
913
|
+
*/
|
|
914
|
+
private commitCascadeAtoms;
|
|
915
|
+
/**
|
|
916
|
+
* The "meaningful selection" list used by the counter chip — the rolled-up
|
|
917
|
+
* minimal cover, regardless of the active emit policy. In cascade mode
|
|
918
|
+
* `leaves`/`all` emit many values for a single branch pick, which made the
|
|
919
|
+
* counter read e.g. `[5]` for what a person experiences as two selections.
|
|
920
|
+
* The counter should count the branches actually chosen, and stay stable when
|
|
921
|
+
* the policy knob flips. Outside cascade this is just the selected options.
|
|
922
|
+
*/
|
|
923
|
+
private counterSelection;
|
|
924
|
+
/** Native `title` for the counter chip: the picked items, capped so it can't grow unbounded. */
|
|
925
|
+
private buildCounterTooltip;
|
|
888
926
|
/**
|
|
889
927
|
* Derive `treeNodes` + `filteredOptions` from the full tree, applying the
|
|
890
928
|
* current search term. Matching nodes keep all their ancestors visible so
|
|
@@ -1081,7 +1119,18 @@ export declare class WebMultiSelect<T = any> {
|
|
|
1081
1119
|
private updateHiddenInput;
|
|
1082
1120
|
private getFormValue;
|
|
1083
1121
|
getSelected(): T[];
|
|
1084
|
-
|
|
1122
|
+
/**
|
|
1123
|
+
* Set the selection programmatically. **Silent by default** — it does not fire
|
|
1124
|
+
* `select`/`deselect`/`change` (so restoring saved state, cascade resets, or a
|
|
1125
|
+
* server-authoritative correction can't loop back or trip "user changed it"
|
|
1126
|
+
* handlers). Pass `{ notify: true }` to announce the result as a **single
|
|
1127
|
+
* aggregate `change`** — for a deliberate user gesture (e.g. an action button)
|
|
1128
|
+
* that should reach the same listeners a manual pick does, without the per-item
|
|
1129
|
+
* `select`/`deselect` flood a bulk change would otherwise cause.
|
|
1130
|
+
*/
|
|
1131
|
+
setSelected(values: (string | number)[], opts?: {
|
|
1132
|
+
notify?: boolean;
|
|
1133
|
+
}): void;
|
|
1085
1134
|
/**
|
|
1086
1135
|
* Merge a partial config update into the live picker without tearing down the DOM.
|
|
1087
1136
|
*
|