@keenmate/web-multiselect 2.0.1 → 2.1.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 +5 -34
- package/component-variables.manifest.json +2 -1
- package/custom-elements.json +86 -3
- package/dist/index.d.ts +28 -0
- package/dist/multiselect.js +852 -802
- package/dist/multiselect.umd.js +11 -11
- package/dist/style.css +1 -1
- package/docs/examples.md +69 -0
- package/docs/usage.md +4 -0
- package/package.json +1 -1
- package/src/css/base.css +10 -0
- package/src/css/options.css +8 -7
- package/src/css/variables.css +5 -0
- package/vscode.css-custom-data.json +5 -0
- package/vscode.html-custom-data.json +6 -1
- package/web-types.json +33 -2
package/README.md
CHANGED
|
@@ -21,6 +21,11 @@ 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
|
|
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.
|
|
28
|
+
|
|
24
29
|
## What's New in v2.0.1
|
|
25
30
|
|
|
26
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.
|
|
@@ -28,40 +33,6 @@ Reads `--base-*` variables from the page if [`@keenmate/theme-designer`](https:/
|
|
|
28
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.
|
|
29
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
35
|
|
|
31
|
-
## What's New in v2.0.0
|
|
32
|
-
|
|
33
|
-
- **"Add new" creation mode — the picker doubles as a creation tool.** Turn on `allow-add-new`
|
|
34
|
-
and a search with no matches shows a clickable "Add new …" prompt (label via `add-new-text` /
|
|
35
|
-
`getAddNewTextCallback`). Choosing it (click, Enter, or arrow-to-focus) fires a new bubbling
|
|
36
|
-
`add` event; `addNewCallback` creates the option and is **async and cancelable** (return
|
|
37
|
-
`null`/`undefined` to abort after a confirm dialog or server round-trip) and may return a
|
|
38
|
-
**rich option** that renders like any other. While it runs the prompt shows a spinner +
|
|
39
|
-
`add-new-pending-text`. Creation also works with no callback — handle it entirely off the `add`
|
|
40
|
-
event. See `examples-events-callbacks.html` §EV4b/EV4c.
|
|
41
|
-
|
|
42
|
-
- **Scroll-to imperative API — jump to any option or group.** New `scrollToIndex()` /
|
|
43
|
-
`scrollToValue()` / `scrollToGroup()` on the element and picker bring a row into view — pair with
|
|
44
|
-
`open()` for an "open + jump" gesture. Mode-aware (virtual scroll, tree, mobile fullscreen sheet),
|
|
45
|
-
they align to the top by default (`{ block: 'center' }` to center) and return `false` when the
|
|
46
|
-
target isn't in the current filtered list. A companion public `clearSearch()` reveals a
|
|
47
|
-
filtered-out option so you can then scroll to it, plus a `searchText` getter and a `search(term)`
|
|
48
|
-
method to read and programmatically drive the query. See §BU06b, §VS03, §TR09b.
|
|
49
|
-
|
|
50
|
-
- **Checkbox check/dash + filter funnel now flow from the shared `--base-icon-*` contract.** The
|
|
51
|
-
checkmark and indeterminate dash are now `currentColor` mask glyphs (not CSS-border shapes) reading
|
|
52
|
-
`--base-icon-check` / `--base-icon-indeterminate`, and the search-mode funnel reads
|
|
53
|
-
`--base-icon-filter` — so one base override reskins them across every Keenmate component. Inline
|
|
54
|
-
Lucide fallbacks keep the default look unchanged.
|
|
55
|
-
|
|
56
|
-
- **`--ms-rem` now bridges to the shared `--base-rem` knob.** The global sizing unit resolves
|
|
57
|
-
`var(--base-rem, 10px)`, so a theme that sets `--base-rem` rescales the whole component from one
|
|
58
|
-
variable (the `10px` fallback and per-instance overrides still work with no base layer loaded).
|
|
59
|
-
|
|
60
|
-
- **Fixed: the count-chip clear ✕ and popover close ✕ vanished on hover.** Both derived their hover
|
|
61
|
-
background *and* glyph colour from the same accent, so the ✕ melted into its own hover state
|
|
62
|
-
(fully invisible on near-white accents like Minimal dark). They now fill with a solid accent
|
|
63
|
-
background and flip the glyph to the on-accent colour, matching the badge remove button.
|
|
64
|
-
|
|
65
36
|
## Demos & docs
|
|
66
37
|
|
|
67
38
|
- 🚀 [Live demo](https://web-multiselect.keenmate.dev)
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
{ "name": "base-icon-plus", "required": false, "usage": "Plus glyph for the \"add new\" empty-state prompt; chained by --ms-icon-add-new; falls back to base-icon-add then inline Lucide SVG" },
|
|
65
65
|
{ "name": "base-icon-add", "required": false, "usage": "Alternate add glyph (fallback for base-icon-plus in the \"add new\" prompt)" },
|
|
66
66
|
{ "name": "base-icon-check", "required": false, "usage": "Checkbox/option checkmark glyph; chained by --ms-icon-check; inline Lucide SVG fallback" },
|
|
67
|
-
{ "name": "base-icon-check-size", "required": false, "usage": "mask-size for the checkbox glyph inside its box (shared with pure-admin's .pa-checkbox); default 68%. Also sizes the indeterminate dash (shares the mask box)" },
|
|
67
|
+
{ "name": "base-icon-check-size", "required": false, "usage": "mask-size for the checkbox glyph inside its box (shared with pure-admin's .pa-checkbox); default 68%. Chained by --ms-icon-check-size. Also sizes the indeterminate dash (shares the mask box)" },
|
|
68
68
|
{ "name": "base-icon-indeterminate", "required": false, "usage": "Tri-state (partial) checkbox dash glyph; chained by --ms-icon-indeterminate; inline Lucide SVG fallback" },
|
|
69
69
|
{ "name": "base-danger-bg", "required": false, "usage": "Error message background" },
|
|
70
70
|
{ "name": "base-danger-color", "required": false, "usage": "Error message text color" },
|
|
@@ -277,6 +277,7 @@
|
|
|
277
277
|
{ "name": "ms-icon-filter", "category": "icon", "usage": "Funnel glyph (fullscreen search-mode toggle); chains to --base-icon-filter" },
|
|
278
278
|
{ "name": "ms-icon-check", "category": "checkbox", "usage": "Checkmark glyph (mask) for a checked checkbox/option; chains to --base-icon-check" },
|
|
279
279
|
{ "name": "ms-icon-indeterminate", "category": "checkbox", "usage": "Dash glyph (mask) for a tri-state/partial checkbox; chains to --base-icon-indeterminate" },
|
|
280
|
+
{ "name": "ms-icon-check-size", "category": "checkbox", "usage": "mask-size for the checkmark/dash glyph inside the checkbox box; chains to --base-icon-check-size (default 68%, the same knob as pure-admin's .pa-checkbox)" },
|
|
280
281
|
{ "name": "ms-badge-counter-bg", "category": "badge", "usage": "Counter badge background" },
|
|
281
282
|
{ "name": "ms-badge-counter-border", "category": "badge", "usage": "Counter badge border" },
|
|
282
283
|
{ "name": "ms-badge-counter-border-color", "category": "badge", "usage": "Counter badge border color" },
|
package/custom-elements.json
CHANGED
|
@@ -2269,6 +2269,17 @@
|
|
|
2269
2269
|
],
|
|
2270
2270
|
"description": "Re-render and fire callbacks after a selection state change.\n`added` / `removed` drive per-item select/deselect callbacks.\n`onChange` fires once if anything actually changed."
|
|
2271
2271
|
},
|
|
2272
|
+
{
|
|
2273
|
+
"kind": "method",
|
|
2274
|
+
"name": "armClickGuard",
|
|
2275
|
+
"privacy": "private",
|
|
2276
|
+
"return": {
|
|
2277
|
+
"type": {
|
|
2278
|
+
"text": "void"
|
|
2279
|
+
}
|
|
2280
|
+
},
|
|
2281
|
+
"description": "Shield the trailing document `click` for one tick. A consumer that drives the\ndropdown from their OWN button's click handler (`el.open()` / `el.toggle()`, or a\nscrollTo* command re-driving the already-open panel) would otherwise have that\nsame click bubble to our document-level outside-click listener and immediately\nclose it. Centralizing it here means every entry point — open(), the internal\npointer path, and the public scrollTo* API — is covered. Cleared next tick, so a\ngenuine later outside-click still closes as normal. Harmless for non-click\ncallers (typing, programmatic-on-load, server-driven): no trailing click arrives\nbefore it clears."
|
|
2282
|
+
},
|
|
2272
2283
|
{
|
|
2273
2284
|
"kind": "method",
|
|
2274
2285
|
"name": "open",
|
|
@@ -3739,7 +3750,7 @@
|
|
|
3739
3750
|
"name": "inputs",
|
|
3740
3751
|
"privacy": "protected",
|
|
3741
3752
|
"static": true,
|
|
3742
|
-
"default": "[ // ── Strings (cosmetic → update). Optional ones are nullable: absent → null ─ { configKey: 'searchHint', attribute: 'search-hint', converter: toText({ isNullable: true }), on: 'update', description: 'Small hint text shown beneath the search input.' }, { configKey: 'searchPlaceholder', attribute: 'search-placeholder', converter: toText({ isNullable: true }), on: 'update', description: 'Placeholder text for the search input. When unset it defaults to \"Search...\"; if `show-search-mode-toggle` is on, the default instead becomes mode-aware (\"Search…\" in navigate, \"Filter…\" in filter). An explicit value always wins and stays fixed.' }, { configKey: 'selectPlaceholder', attribute: 'select-placeholder', converter: toText({ default: 'Pick an option...' }), on: 'update', description: 'Placeholder shown on the control when nothing is selected.' }, { configKey: 'noDataPlaceholder', attribute: 'no-data-placeholder', converter: toText({ isNullable: true }), on: 'update', description: 'Text shown when there are no options at all.' }, { configKey: 'dropdownMinWidth', attribute: 'dropdown-min-width', converter: toText({ isNullable: true }), on: 'update', description: 'Minimum width of the dropdown panel (any CSS length).' }, { configKey: 'dropdownMaxWidth', attribute: 'dropdown-max-width', converter: toText({ isNullable: true }), on: 'update', description: 'Maximum width of the dropdown panel (any CSS length).' }, { configKey: 'maxHeight', attribute: 'max-height', converter: toText({ default: '20rem' }), on: 'update', description: 'Maximum height of the dropdown list before it scrolls.' }, { configKey: 'emptyMessage', attribute: 'empty-message', converter: toText({ default: 'No results found' }), on: 'update', description: 'Message shown when a search yields no matches.' }, { configKey: 'addNewText', attribute: 'add-new-text', converter: toText({ isNullable: true }), on: 'update', description: 'Template for the clickable \"add new\" prompt shown (when `allow-add-new` is on) in place of the empty message once a search yields no matches. `{value}` is replaced with the typed text. Default: `Add \"{value}\"`. A `getAddNewTextCallback` wins.' }, { configKey: 'addNewPendingText', attribute: 'add-new-pending-text', converter: toText({ isNullable: true }), on: 'update', description: 'Template for the pending prompt (spinner + text) shown while an async `addNewCallback` runs. `{value}` is replaced with the typed text. Default: `Adding \"{value}\"…`.' }, { configKey: 'loadingMessage', attribute: 'loading-message', converter: toText({ default: 'Loading...' }), on: 'update', description: 'Message shown while options are loading.' }, { configKey: 'removeButtonTooltipText', attribute: 'remove-button-tooltip-text', converter: toText({ isNullable: true }), on: 'update', description: 'Tooltip text for a badge remove (×) button.' }, { configKey: 'formFieldId', attribute: 'name', converter: toText({ isNullable: true }), on: 'reinit', description: 'HTML form field name/id used for the hidden input(s).' }, // ── CSS-var sugar (mirrored to a host style prop in reinit()/update()) ──── { configKey: 'dropdownWidth', attribute: 'dropdown-width', converter: toText({ isNullable: true }), on: 'update', description: 'Fixed dropdown width; mirrored to the `--ms-dropdown-width` CSS variable.' }, { configKey: 'selectedPopoverWidth', attribute: 'selected-popover-width', converter: toText({ isNullable: true }), on: 'update', description: 'Selected-items popover width; mirrored to `--ms-selected-popover-width`.' }, // ── Member properties (structural → reinit; optional → nullable) ───────── { configKey: 'valueMember', attribute: 'value-member', converter: toText({ isNullable: true }), reflect: true, on: 'reinit', description: 'Property name on an option object that holds its value.' }, { configKey: 'displayValueMember', attribute: 'display-value-member', converter: toText({ isNullable: true }), reflect: true, on: 'reinit', description: 'Property name that holds an option display label.' }, { configKey: 'searchValueMember', attribute: 'search-value-member', converter: toText({ isNullable: true }), reflect: true, on: 'reinit', description: 'Property name searched against (falls back to the display value).' }, { configKey: 'iconMember', attribute: 'icon-member', converter: toText({ isNullable: true }), reflect: true, on: 'reinit', description: 'Property name that holds an option icon.' }, { configKey: 'subtitleMember', attribute: 'subtitle-member', converter: toText({ isNullable: true }), reflect: true, on: 'reinit', description: 'Property name that holds an option subtitle.' }, { configKey: 'fullTitleMember', attribute: 'full-title-member', converter: toText({ isNullable: true }), reflect: true, on: 'reinit', description: 'Property name that holds an option full/long title.' }, { configKey: 'groupMember', attribute: 'group-member', converter: toText({ isNullable: true }), reflect: true, on: 'reinit', description: 'Property name used to group options under headers.' }, { configKey: 'disabledMember', attribute: 'disabled-member', converter: toText({ isNullable: true }), reflect: true, on: 'reinit', description: 'Property name that marks an option disabled.' }, // ── Tree of options (structural → reinit; optional → nullable) ─────────── { configKey: 'pathMember', attribute: 'path-member', converter: toText({ isNullable: true }), reflect: true, on: 'reinit', description: 'Property name holding a node materialized tree path.' }, { configKey: 'parentPathMember', attribute: 'parent-path-member', converter: toText({ isNullable: true }), reflect: true, on: 'reinit', description: 'Property name holding a node parent path.' }, { configKey: 'levelMember', attribute: 'level-member', converter: toText({ isNullable: true }), reflect: true, on: 'reinit', description: 'Property name holding a node depth level.' }, { configKey: 'hasChildrenMember', attribute: 'has-children-member', converter: toText({ isNullable: true }), reflect: true, on: 'reinit', description: 'Property name flagging that a node has children.' }, { configKey: 'isSelectableMember', attribute: 'is-selectable-member', converter: toText({ isNullable: true }), reflect: true, on: 'reinit', description: 'Property name marking whether a node can be selected.' }, { configKey: 'treePathSeparator', attribute: 'tree-path-separator', converter: toText({ default: '.' }), reflect: true, on: 'reinit', description: 'Separator between segments in a materialized tree path.' }, { configKey: 'isTreeEnabled', converter: toBool('tristate'), on: 'reinit', type: 'boolean', description: 'Force tree mode on/off. Property-only; when unset (null) tree mode auto-enables if a path source (path-member / getPathCallback) is present.' }, { configKey: 'checkboxMode', attribute: 'checkbox-mode', converter: toEnum(['independent', 'cascade'] as const, { default: 'independent' }), reflect: true, on: 'update', description: `Tree checkbox interaction. - \\`independent\\` (default) — toggles only the clicked node. - \\`cascade\\` — checks a node whole subtree and shows a tristate (checked / indeterminate / unchecked) box on branches. Tree + multiple only.` }, { configKey: 'cascadeSelectPolicy', attribute: 'cascade-select-policy', converter: toEnum(['rolled-up', 'leaves', 'all'] as const, { default: 'rolled-up' }), reflect: true, on: 'update', description: `In \\`cascade\\` mode, which values a selection emits (badges / form / change): - \\`rolled-up\\` (default) — minimal cover: a fully-selected subtree collapses to its root; partially-selected branches emit their individually-checked descendants. - \\`leaves\\` — only the checked leaf-level nodes. - \\`all\\` — every fully-checked node (branches and leaves).` }, // ── Enums ──────────────────────────────────────────────────────────────── { configKey: 'badgesDisplayMode', attribute: 'badges-display-mode', converter: toEnum(['badges', 'count', 'compact', 'partial', 'none'] as const, { default: 'badges' }), on: 'reinit', description: 'How the current selection is shown in the control.' }, { configKey: 'badgesPosition', attribute: 'badges-position', converter: toEnum(['top', 'bottom', 'left', 'right'] as const, { default: 'bottom' }), on: 'reinit', description: 'Where the badges/selection appear relative to the input.' }, { configKey: 'badgesThresholdMode', attribute: 'badges-threshold-mode', converter: toEnum(['count', 'partial'] as const, { default: 'count' }), on: 'update', description: 'How `badgesThreshold` is interpreted: collapse to a count badge, or keep partial badges + a \"more\" badge.' }, { configKey: 'searchInputMode', attribute: 'search-input-mode', converter: toEnum(['normal', 'readonly', 'hidden'] as const, { default: 'normal' }), on: 'reinit', description: 'Search field mode: editable, read-only, or hidden.' }, { configKey: 'searchMode', attribute: 'search-mode', converter: toEnum(['filter', 'navigate'] as const, { default: 'filter' }), on: 'reinit', description: 'Whether typing filters the list or navigates it.' }, { configKey: 'overlayGroup', attribute: 'overlay-group', converter: toText({ isNullable: true }), on: 'reinit', description: 'Scope the \"one overlay open at a time\" coordination to a named group. Overlays (multiselects, datepickers, external popovers) sharing a group dismiss each other when one opens; different groups are independent. Unset = the default (ungrouped) group.' }, { configKey: 'actionsLayout', attribute: 'actions-layout', converter: toEnum(['nowrap', 'wrap'] as const, { default: 'nowrap' }), on: 'reinit', description: 'Whether the action bar wraps or stays on one line.' }, { configKey: 'actionsPosition', attribute: 'actions-position', converter: toEnum(['top', 'bottom'] as const, { default: 'top' }), on: 'reinit', description: 'Whether the action bar sits above or below the list.' }, { configKey: 'actionsAlign', attribute: 'actions-align', converter: toEnum(['stretch', 'left', 'right', 'center', 'space-between'] as const, { default: 'stretch' }), on: 'update', description: 'Horizontal alignment of the action buttons.' }, { configKey: 'checkboxAlign', attribute: 'checkbox-align', converter: toEnum(['top', 'center', 'bottom'] as const, { default: 'center' }), on: 'update', description: 'Vertical alignment of an option checkbox.' }, { configKey: 'valueFormat', attribute: 'value-format', converter: toEnum(['json', 'csv', 'array'] as const, { default: 'json' }), on: 'reinit', description: 'Serialization format the control emits its value in.' }, { configKey: 'badgeTooltipPlacement', attribute: 'badge-tooltip-placement', converter: toEnum(PLACEMENTS, { default: 'top' }), on: 'update', description: 'Preferred placement of a badge tooltip relative to its badge (floating-ui placement).' }, { configKey: 'optionTooltipPlacement', attribute: 'option-tooltip-placement', converter: toEnum(PLACEMENTS, { default: 'top-start' }), on: 'update', description: 'Preferred placement of an option tooltip (floating-ui placement).' }, { configKey: 'mobilePresentation', attribute: 'mobile-presentation', converter: toEnum(['auto', 'floating', 'fullscreen'] as const, { default: 'auto' }), reflect: true, on: 'update', description: 'How the open dropdown is presented on phones. `auto` (default) keeps the floating panel on desktop/tablet and switches to a full-screen overlay on phone-sized touch devices (touch primary + shorter viewport side < 600px, orientation-robust); `floating` forces the anchored panel everywhere; `fullscreen` forces the full-screen overlay on any device (handy for previews/testing). Resolved reactively from the device/viewport environment.' }, { configKey: 'fullscreenAutofocus', attribute: 'fullscreen-autofocus', converter: toBool('default-false'), on: 'update', description: 'In the phone fullscreen overlay, auto-focus the search field on open (pops the soft keyboard immediately). Default `false`: the sheet opens with the list visible and the keyboard closed, appearing only when the user taps the search. Set `true` to type-to-filter right away. No effect in the floating presentation.' }, // ── Numbers ────────────────────────────────────────────────────────────── { configKey: 'badgesThreshold', attribute: 'badges-threshold', converter: toInt(), on: 'update', description: 'Threshold at which badges collapse to a count/compact view.' }, { configKey: 'badgesMaxVisible', attribute: 'badges-max-visible', converter: toInt(), on: 'update', description: 'Maximum number of badges rendered before overflow.' }, { configKey: 'collapseBadgesBelow', attribute: 'collapse-badges-below', converter: toInt(), on: 'update', description: 'Container-responsive opt-in (off by default). When set to a px width, the control watches its OWN border box (not the window, via the core `resized` hook / a shared ResizeObserver) and collapses `badges-display-mode` to `count` (\"N selected\") while the box is narrower than this — so a picker in a narrow column/sidebar never overflows with pills, even on a wide monitor. Widening past the threshold restores the configured badges mode. Element-only: the override is applied to the live picker, never to your `badges-display-mode` config.' }, { configKey: 'minSearchLength', attribute: 'min-search-length', converter: toInt({ default: 0 }), on: 'update', description: 'Minimum characters before searching/filtering starts.' }, { configKey: 'searchDebounce', attribute: 'search-debounce', converter: toInt({ default: 0 }), on: 'update', description: 'Debounce delay in ms applied to the search input.' }, { configKey: 'virtualScrollThreshold', attribute: 'virtual-scroll-threshold', converter: toInt({ default: 100 }), on: 'reinit', description: 'Option count above which virtual scrolling turns on.' }, { configKey: 'optionHeight', attribute: 'option-height', converter: toInt({ default: 50 }), on: 'update', description: 'Fixed row height in px used by virtual scrolling.' }, { configKey: 'badgeHeight', attribute: 'badge-height', converter: toInt({ default: 36 }), on: 'update', description: 'Fixed badge height in px used for layout/virtualization.' }, { configKey: 'virtualScrollBuffer', attribute: 'virtual-scroll-buffer', converter: toInt({ default: 10 }), on: 'update', description: 'Extra rows rendered above/below the viewport when virtualizing.' }, { configKey: 'badgeTooltipDelay', attribute: 'badge-tooltip-delay', converter: toInt({ default: 100 }), on: 'update', description: 'Delay in ms before a badge tooltip appears.' }, { configKey: 'badgeTooltipOffset', attribute: 'badge-tooltip-offset', converter: toInt({ default: 8 }), on: 'update', description: 'Gap in px between a badge and its tooltip.' }, { configKey: 'optionTooltipDelay', attribute: 'option-tooltip-delay', converter: toInt(), on: 'update', description: 'Delay in ms before an option tooltip appears (falls back to badgeTooltipDelay).' }, { configKey: 'optionTooltipOffset', attribute: 'option-tooltip-offset', converter: toInt(), on: 'update', description: 'Gap in px between an option and its tooltip.' }, // ── Booleans (default true) ────────────────────────────────────────────── { configKey: 'isMultipleEnabled', attribute: 'multiple', converter: toBool('default-true'), on: 'reinit', description: 'Allow selecting multiple options. When off, selecting one replaces the previous.' }, { configKey: 'isGroupsAllowed', attribute: 'allow-groups', converter: toBool('default-true'), on: 'reinit', description: 'Allow grouping options under group headers.' }, { configKey: 'isCheckboxesShown', attribute: 'show-checkboxes', converter: toBool('default-true'), on: 'reinit', description: 'Show a checkbox on each option.' }, { configKey: 'isActionsSticky', attribute: 'sticky-actions', converter: toBool('default-true'), on: 'update', description: 'Keep the action bar pinned while the list scrolls.' }, { configKey: 'isPlacementLocked', attribute: 'lock-placement', converter: toBool('default-true'), on: 'update', description: 'Keep the dropdown initial placement instead of flipping when it fits.' }, { configKey: 'isSearchEnabled', attribute: 'enable-search', converter: toBool('default-true'), on: 'reinit', description: 'Show the search input.' }, { configKey: 'isKeepOptionsOnSearch', attribute: 'keep-options-on-search', converter: toBool('default-true'), on: 'update', description: 'Keep already-selected options visible while filtering.' }, { configKey: 'shouldKeepSearchOnClose', attribute: 'should-keep-search-on-close', converter: toBool('default-true'), on: 'update', description: 'Preserve the search text after the dropdown closes.' }, { configKey: 'isSelectedPopoverEnabled', attribute: 'enable-selected-popover', converter: toBool('default-true'), on: 'update', description: 'Allow the selected-items popover to open (from the count/compact/\"+X more\" badge or the in-input counter). Turn off when you render your own selection UI, so those affordances become inert.' }, // ── Booleans (default false) ───────────────────────────────────────────── { configKey: 'isCloseOnSelect', attribute: 'close-on-select', converter: toBool('default-false'), on: 'update', description: 'Close the dropdown immediately after a selection.' }, { configKey: 'isAddNewAllowed', attribute: 'allow-add-new', converter: toBool('default-false'), on: 'reinit', description: 'Allow adding a new option from the search text.' }, { configKey: 'isCounterShown', attribute: 'show-counter', converter: toBool('default-false'), on: 'update', description: 'Show a selected-count indicator.' }, { configKey: 'isClearShown', attribute: 'show-clear', converter: toBool('default-false'), on: 'update', description: 'Show an inline clear (✕) button inside the input that wipes the whole selection. Appears only while something is selected and the control is enabled; clicking it clears the selection and any search text, fires `change`, and refocuses.' }, { configKey: 'isBadgeFullTitleShown', attribute: 'show-badge-full-title', converter: toBool('default-false'), on: 'update', description: 'Show the full title on badges instead of the short label.' }, { configKey: 'isVirtualScrollEnabled', attribute: 'enable-virtual-scroll', converter: toBool('default-false'), on: 'reinit', description: 'Force virtual scrolling on regardless of the threshold.' }, { configKey: 'isBadgeTooltipsEnabled', attribute: 'enable-badge-tooltips', converter: toBool('default-false'), on: 'update', description: 'Enable tooltips on badges.' }, { configKey: 'isOptionTooltipsEnabled', attribute: 'enable-option-tooltips', converter: toBool('default-false'), on: 'update', description: 'Enable tooltips on options.' }, { configKey: 'isOptionTooltipFollowCursor', attribute: 'option-tooltip-follow-cursor', converter: toBool('default-false'), on: 'update', description: 'Make option tooltips follow the pointer.' }, { configKey: 'isSearchModeToggleShown', attribute: 'show-search-mode-toggle', converter: toBool('default-false'), on: 'update', description: 'Show a clickable toggle in the phone fullscreen overlay search header that flips `search-mode` between `filter` and `navigate` live. Fullscreen-only; no effect in the floating presentation or when search is disabled.' }, // ── Special attributes ─────────────────────────────────────────────────── { configKey: 'initialValues', attribute: 'initial-values', converter: toInitialValues(), default: [], on: 'reinit', type: 'Array<string | number>', description: 'Values selected on first render. Accepts a JSON array (`[\"a\",\"b\"]`) or a bare CSV (`a,b,c`).' }, { configKey: 'showDebugInfo', attribute: 'show-debug-info', converter: toBool('default-false'), on: 'update', description: 'Render an in-component debug panel.', deprecated: 'Use per-instance logging (el.enableLogging()) instead.' }, // ── Complex property (data) ────────────────────────────────────────────── { configKey: 'options', converter: toObjectArray(), on: 'reinit', type: 'ReadonlyArray<Record<string, unknown>>', description: 'The array of option objects to render. The JS API — assign `el.options` directly. For HTML authoring use the `data-options` attribute (parsed per `data-options-format`) or declarative <option> children; both feed the same list and take precedence over this property in the order: <option> children > property > data-options.' }, { configKey: 'optionsSource', attribute: 'data-options', converter: toText({ isNullable: true }), on: 'reinit', type: 'string', description: 'HTML-authoring source for the option list, parsed per `data-options-format`. Reactive: changing either attribute re-renders. Prefer the `options` property in JS; a set `options` property and declarative <option> children both win over this.' }, { configKey: 'optionsFormat', attribute: 'data-options-format', converter: toEnum(OPTIONS_FORMATS, { default: 'json' }), on: 'reinit', type: \"'json' | 'csv' | 'plain'\", description: 'How to parse the `data-options` attribute: `json` (a JSON array of objects or [value, label] tuples), `csv` (rows split on `data-options-row-splitter`, cells on `data-options-splitter`; the first row is a header — map columns via *-member), or `plain` (bare values split on both splitters -> [value, label] tuples, value === label). Default `json`.' }, { configKey: 'optionsSplitter', attribute: 'data-options-splitter', converter: toText({ default: ',' }), on: 'reinit', type: 'string', description: 'Field/cell delimiter for the `csv` and `plain` `data-options` formats. Default `,`. Escapes `\\\\t` `\\\\n` `\\\\r` are honoured (e.g. `data-options-splitter=\"\\\\t\"` for TSV). Ignored for `json`.' }, { configKey: 'optionsRowSplitter', attribute: 'data-options-row-splitter', converter: toText({ default: '\\n' }), on: 'reinit', type: 'string', description: 'Row/record delimiter for the `csv` and `plain` `data-options` formats. Default newline. Escapes honoured (e.g. `data-options-row-splitter=\";\"` for single-line data). Ignored for `json`.' }, { configKey: 'actionButtons', converter: toValue({ validate: (v): v is unknown[] => Array.isArray(v) }), on: 'reinit', type: 'Array<Record<string, unknown>>', description: 'Custom action buttons for the dropdown footer/header. Property-only; when unset the default Select-All / Clear buttons apply.' }, // ── Callbacks: data shape (structural → reinit) ────────────────────────── { configKey: 'getValueCallback', converter: cb(), on: 'reinit', type: '(item: unknown) => string | number', description: 'Extract an option value (overrides valueMember).' }, { configKey: 'getPathCallback', converter: cb(), on: 'reinit', type: '(item: unknown) => string', description: 'Extract a node tree path (enables tree mode; overrides pathMember).' }, { configKey: 'getGroupCallback', converter: cb(), on: 'reinit', type: '(item: unknown) => string', description: 'Extract the group name from an option (overrides groupMember).' }, { configKey: 'getDisabledCallback', converter: cb(), on: 'reinit', type: '(item: unknown) => boolean', description: 'Whether an option is disabled (overrides disabledMember).' }, { configKey: 'getIsSelectableCallback', converter: cb(), on: 'reinit', type: '(node: unknown) => boolean', description: 'Whether a tree node can be selected (overrides is-selectable-member).' }, { configKey: 'getSearchValueCallback', converter: cb(), on: 'reinit', type: '(item: unknown) => string', description: 'Text an option is searched against (overrides searchValueMember).' }, { configKey: 'searchCallback', converter: cb(), on: 'reinit', type: '(searchTerm: string, signal?: AbortSignal) => Promise<unknown[]>', description: 'Custom / async search; return the filtered options.' }, // ── Callbacks: display / render (cosmetic → update) ────────────────────── { configKey: 'getDisplayValueCallback', converter: cb(), on: 'update', type: '(item: unknown) => string', description: 'Compute the display label for an option (overrides displayValueMember).' }, { configKey: 'getBadgeDisplayCallback', converter: cb(), on: 'update', type: '(item: unknown) => string', description: 'Compute the text shown on an option badge.' }, { configKey: 'getBadgeClassCallback', converter: cb(), on: 'update', type: '(item: unknown) => string | string[]', description: 'Extra CSS class(es) for an option badge.' }, { configKey: 'getIconCallback', converter: cb(), on: 'update', type: '(item: unknown) => string', description: 'Icon for an option (overrides iconMember).' }, { configKey: 'getSubtitleCallback', converter: cb(), on: 'update', type: '(item: unknown) => string', description: 'Subtitle for an option (overrides subtitleMember).' }, { configKey: 'getFullTitleCallback', converter: cb(), on: 'update', type: '(item: unknown) => string', description: 'Full title for an option (used by badges when show-badge-full-title is on).' }, { configKey: 'getCounterCallback', converter: cb(), on: 'update', type: '(count: number, moreCount?: number) => string', description: 'Render the selected-count label.' }, { configKey: 'getValueFormatCallback', converter: cb(), on: 'update', type: '(selectedValues: (string | number)[]) => string', description: 'Serialize the selected values for form submission.' }, { configKey: 'getBadgeTooltipCallback', converter: cb(), on: 'update', type: '(item: unknown) => string | HTMLElement', description: 'Tooltip content for an option badge.' }, { configKey: 'getOptionTooltipCallback', converter: cb(), on: 'update', type: '(item: unknown) => string | HTMLElement', description: 'Tooltip content for an option row.' }, { configKey: 'getRemoveButtonTooltipCallback', converter: cb(), on: 'update', type: '(item: unknown) => string', description: 'Tooltip text for a badge remove button.' }, { configKey: 'getSelectedItemClassCallback', converter: cb(), on: 'update', type: '(item: unknown) => string | string[]', description: 'Extra CSS class(es) for a selected item.' }, { configKey: 'renderOptionContentCallback', converter: cb(), on: 'update', type: '(item: unknown, context: OptionContentRenderContext) => string | HTMLElement', description: 'Custom render for an option row; may return HTML or an element.' }, { configKey: 'renderBadgeContentCallback', converter: cb(), on: 'update', type: '(item: unknown, context: BadgeContentRenderContext) => string | HTMLElement', description: 'Custom render for a badge content (fills the built-in pill); may return HTML or an element.' }, { configKey: 'renderBadgeCallback', converter: cb(), on: 'update', type: '(item: unknown, context: BadgeContentRenderContext) => string | HTMLElement | null', description: 'Custom render for the WHOLE badge (main area), not just its content — return the entire pill/card. The component wraps it in `.ms__badge.ms__badge--custom` with `data-value` and delegates removal to any inner element with `data-action=\"remove\"` (or `.ms__badge-remove`). Return null/empty to fall back to the default pill for that item.' }, { configKey: 'renderGroupLabelContentCallback', converter: cb(), on: 'update', type: '(groupName: string) => string | HTMLElement', description: 'Customize a group label; may return an HTML string or element.' }, { configKey: 'renderSelectedContentCallback', converter: cb(), on: 'update', type: '(item: unknown) => string', description: 'Custom render for the whole selected area.' }, { configKey: 'renderSelectedItemContentCallback', converter: cb(), on: 'update', type: '(item: unknown) => string | HTMLElement', description: 'Custom render for one selected item.' }, { configKey: 'customStylesCallback', converter: cb(), on: 'update', type: '() => string', description: 'Returns a CSS string injected into the component via a replaceable style slot (§12.8).' }, // ── Callbacks: before-hooks (behavior-shaping) ─────────────────────────── { configKey: 'beforeSearchCallback', converter: cb(), on: 'update', type: '(searchTerm: string) => string | null', description: 'Runs before a search; return a rewritten term or null to veto.' }, { configKey: 'beforeSelectCallback', converter: cb(), on: 'update', type: '(option: unknown, selectedOptions: unknown[]) => boolean | string | void', description: 'Runs before selecting; return false to veto, or a string to veto and show it as a message.' }, { configKey: 'beforeDeselectCallback', converter: cb(), on: 'update', type: '(option: unknown, selectedOptions: unknown[]) => boolean | string | void', description: 'Runs before deselecting; return false to veto, or a string to veto and show it as a message.' }, { configKey: 'addNewCallback', converter: cb(), on: 'update', type: '(value: string) => unknown | null | undefined | Promise<unknown | null | undefined>', description: 'Create a new option from the typed text. May return a rich option object (renders via the same get*/render* callbacks as any option). Async + cancelable: return null/undefined to abort (no add, no `add` event). Omit entirely to handle creation yourself via the `add` event.' }, { configKey: 'getAddNewTextCallback', converter: cb(), on: 'update', type: '(value: string) => string', description: 'Dynamically compute the \"add new\" prompt label from the typed text (returns plain text). Takes precedence over `add-new-text`.' }, { configKey: 'keydownCallback', converter: cb(), on: 'update', type: '(context: MultiSelectKeydownContext) => boolean | void', description: 'Intercept keydown before built-in handling; return true to suppress the default. Gets the event, current state, and an imperative controller.' }, ]",
|
|
3753
|
+
"default": "[ // ── Strings (cosmetic → update). Optional ones are nullable: absent → null ─ { configKey: 'searchHint', attribute: 'search-hint', converter: toText({ isNullable: true }), on: 'update', description: 'Small hint text shown beneath the search input.' }, { configKey: 'searchPlaceholder', attribute: 'search-placeholder', converter: toText({ isNullable: true }), on: 'update', description: 'Placeholder text for the search input. When unset it defaults to \"Search...\"; if `show-search-mode-toggle` is on, the default instead becomes mode-aware (\"Search…\" in navigate, \"Filter…\" in filter). An explicit value always wins and stays fixed.' }, { configKey: 'selectPlaceholder', attribute: 'select-placeholder', converter: toText({ default: 'Pick an option...' }), on: 'update', description: 'Placeholder shown on the control when nothing is selected.' }, { configKey: 'noDataPlaceholder', attribute: 'no-data-placeholder', converter: toText({ isNullable: true }), on: 'update', description: 'Text shown when there are no options at all.' }, { configKey: 'dropdownMinWidth', attribute: 'dropdown-min-width', converter: toText({ isNullable: true }), on: 'update', description: 'Minimum width of the dropdown panel (any CSS length).' }, { configKey: 'dropdownMaxWidth', attribute: 'dropdown-max-width', converter: toText({ isNullable: true }), on: 'update', description: 'Maximum width of the dropdown panel (any CSS length).' }, { configKey: 'maxHeight', attribute: 'max-height', converter: toText({ default: '20rem' }), on: 'update', description: 'Maximum height of the dropdown list before it scrolls.' }, { configKey: 'emptyMessage', attribute: 'empty-message', converter: toText({ default: 'No results found' }), on: 'update', description: 'Message shown when a search yields no matches.' }, { configKey: 'addNewText', attribute: 'add-new-text', converter: toText({ isNullable: true }), on: 'update', description: 'Template for the clickable \"add new\" prompt shown (when `allow-add-new` is on) in place of the empty message once a search yields no matches. `{value}` is replaced with the typed text. Default: `Add \"{value}\"`. A `getAddNewTextCallback` wins.' }, { configKey: 'addNewPendingText', attribute: 'add-new-pending-text', converter: toText({ isNullable: true }), on: 'update', description: 'Template for the pending prompt (spinner + text) shown while an async `addNewCallback` runs. `{value}` is replaced with the typed text. Default: `Adding \"{value}\"…`.' }, { configKey: 'loadingMessage', attribute: 'loading-message', converter: toText({ default: 'Loading...' }), on: 'update', description: 'Message shown while options are loading.' }, { configKey: 'removeButtonTooltipText', attribute: 'remove-button-tooltip-text', converter: toText({ isNullable: true }), on: 'update', description: 'Tooltip text for a badge remove (×) button.' }, { configKey: 'formFieldId', attribute: 'name', converter: toText({ isNullable: true }), on: 'reinit', description: 'HTML form field name/id used for the hidden input(s).' }, // ── CSS-var sugar (mirrored to a host style prop in reinit()/update()) ──── { configKey: 'dropdownWidth', attribute: 'dropdown-width', converter: toText({ isNullable: true }), on: 'update', description: 'Fixed dropdown width; mirrored to the `--ms-dropdown-width` CSS variable.' }, { configKey: 'selectedPopoverWidth', attribute: 'selected-popover-width', converter: toText({ isNullable: true }), on: 'update', description: 'Selected-items popover width; mirrored to `--ms-selected-popover-width`.' }, // ── Member properties (structural → reinit; optional → nullable) ───────── { configKey: 'valueMember', attribute: 'value-member', converter: toText({ isNullable: true }), reflect: true, on: 'reinit', description: 'Property name on an option object that holds its value.' }, { configKey: 'displayValueMember', attribute: 'display-value-member', converter: toText({ isNullable: true }), reflect: true, on: 'reinit', description: 'Property name that holds an option display label.' }, { configKey: 'searchValueMember', attribute: 'search-value-member', converter: toText({ isNullable: true }), reflect: true, on: 'reinit', description: 'Property name searched against (falls back to the display value).' }, { configKey: 'iconMember', attribute: 'icon-member', converter: toText({ isNullable: true }), reflect: true, on: 'reinit', description: 'Property name that holds an option icon.' }, { configKey: 'subtitleMember', attribute: 'subtitle-member', converter: toText({ isNullable: true }), reflect: true, on: 'reinit', description: 'Property name that holds an option subtitle.' }, { configKey: 'fullTitleMember', attribute: 'full-title-member', converter: toText({ isNullable: true }), reflect: true, on: 'reinit', description: 'Property name that holds an option full/long title.' }, { configKey: 'groupMember', attribute: 'group-member', converter: toText({ isNullable: true }), reflect: true, on: 'reinit', description: 'Property name used to group options under headers.' }, { configKey: 'disabledMember', attribute: 'disabled-member', converter: toText({ isNullable: true }), reflect: true, on: 'reinit', description: 'Property name that marks an option disabled.' }, // ── Tree of options (structural → reinit; optional → nullable) ─────────── { configKey: 'pathMember', attribute: 'path-member', converter: toText({ isNullable: true }), reflect: true, on: 'reinit', description: 'Property name holding a node materialized tree path.' }, { configKey: 'parentPathMember', attribute: 'parent-path-member', converter: toText({ isNullable: true }), reflect: true, on: 'reinit', description: 'Property name holding a node parent path.' }, { configKey: 'levelMember', attribute: 'level-member', converter: toText({ isNullable: true }), reflect: true, on: 'reinit', description: 'Property name holding a node depth level.' }, { configKey: 'hasChildrenMember', attribute: 'has-children-member', converter: toText({ isNullable: true }), reflect: true, on: 'reinit', description: 'Property name flagging that a node has children.' }, { configKey: 'isSelectableMember', attribute: 'is-selectable-member', converter: toText({ isNullable: true }), reflect: true, on: 'reinit', description: 'Property name marking whether a node can be selected.' }, { configKey: 'treePathSeparator', attribute: 'tree-path-separator', converter: toText({ default: '.' }), reflect: true, on: 'reinit', description: 'Separator between segments in a materialized tree path.' }, { configKey: 'isTreeEnabled', converter: toBool('tristate'), on: 'reinit', type: 'boolean', description: 'Force tree mode on/off. Property-only; when unset (null) tree mode auto-enables if a path source (path-member / getPathCallback) is present.' }, { configKey: 'checkboxMode', attribute: 'checkbox-mode', converter: toEnum(['independent', 'cascade'] as const, { default: 'independent' }), reflect: true, on: 'update', description: `Tree checkbox interaction. - \\`independent\\` (default) — toggles only the clicked node. - \\`cascade\\` — checks a node whole subtree and shows a tristate (checked / indeterminate / unchecked) box on branches. Tree + multiple only.` }, { configKey: 'cascadeSelectPolicy', attribute: 'cascade-select-policy', converter: toEnum(['rolled-up', 'leaves', 'all'] as const, { default: 'rolled-up' }), reflect: true, on: 'update', description: `In \\`cascade\\` mode, which values a selection emits (badges / form / change): - \\`rolled-up\\` (default) — minimal cover: a fully-selected subtree collapses to its root; partially-selected branches emit their individually-checked descendants. - \\`leaves\\` — only the checked leaf-level nodes. - \\`all\\` — every fully-checked node (branches and leaves).` }, // ── Enums ──────────────────────────────────────────────────────────────── { configKey: 'badgesDisplayMode', attribute: 'badges-display-mode', converter: toEnum(['badges', 'count', 'compact', 'partial', 'none'] as const, { default: 'badges' }), on: 'reinit', description: 'How the current selection is shown in the control.' }, { configKey: 'badgesPosition', attribute: 'badges-position', converter: toEnum(['top', 'bottom', 'left', 'right'] as const, { default: 'bottom' }), on: 'reinit', description: 'Where the badges/selection appear relative to the input.' }, { configKey: 'badgesThresholdMode', attribute: 'badges-threshold-mode', converter: toEnum(['count', 'partial'] as const, { default: 'count' }), on: 'update', description: 'How `badgesThreshold` is interpreted: collapse to a count badge, or keep partial badges + a \"more\" badge.' }, { configKey: 'searchInputMode', attribute: 'search-input-mode', converter: toEnum(['normal', 'readonly', 'hidden'] as const, { default: 'normal' }), on: 'reinit', description: 'Search field mode: editable, read-only, or hidden.' }, { configKey: 'searchMode', attribute: 'search-mode', converter: toEnum(['filter', 'navigate'] as const, { default: 'filter' }), on: 'reinit', description: 'Whether typing filters the list or navigates it.' }, { configKey: 'overlayGroup', attribute: 'overlay-group', converter: toText({ isNullable: true }), on: 'reinit', description: 'Scope the \"one overlay open at a time\" coordination to a named group. Overlays (multiselects, datepickers, external popovers) sharing a group dismiss each other when one opens; different groups are independent. Unset = the default (ungrouped) group.' }, { configKey: 'actionsLayout', attribute: 'actions-layout', converter: toEnum(['nowrap', 'wrap'] as const, { default: 'nowrap' }), on: 'reinit', description: 'Whether the action bar wraps or stays on one line.' }, { configKey: 'actionsPosition', attribute: 'actions-position', converter: toEnum(['top', 'bottom'] as const, { default: 'top' }), on: 'reinit', description: 'Whether the action bar sits above or below the list.' }, { configKey: 'actionsAlign', attribute: 'actions-align', converter: toEnum(['stretch', 'left', 'right', 'center', 'space-between'] as const, { default: 'stretch' }), on: 'update', description: 'Horizontal alignment of the action buttons.' }, { configKey: 'checkboxAlign', attribute: 'checkbox-align', converter: toEnum(['top', 'center', 'bottom'] as const, { default: 'center' }), on: 'update', description: 'Vertical alignment of an option checkbox.' }, { configKey: 'valueFormat', attribute: 'value-format', converter: toEnum(['json', 'csv', 'array'] as const, { default: 'json' }), on: 'reinit', description: 'Serialization format the control emits its value in.' }, { configKey: 'badgeTooltipPlacement', attribute: 'badge-tooltip-placement', converter: toEnum(PLACEMENTS, { default: 'top' }), on: 'update', description: 'Preferred placement of a badge tooltip relative to its badge (floating-ui placement).' }, { configKey: 'optionTooltipPlacement', attribute: 'option-tooltip-placement', converter: toEnum(PLACEMENTS, { default: 'top-start' }), on: 'update', description: 'Preferred placement of an option tooltip (floating-ui placement).' }, { configKey: 'mobilePresentation', attribute: 'mobile-presentation', converter: toEnum(['auto', 'floating', 'fullscreen'] as const, { default: 'auto' }), reflect: true, on: 'update', description: 'How the open dropdown is presented on phones. `auto` (default) keeps the floating panel on desktop/tablet and switches to a full-screen overlay on phone-sized touch devices (touch primary + shorter viewport side < 600px, orientation-robust); `floating` forces the anchored panel everywhere; `fullscreen` forces the full-screen overlay on any device (handy for previews/testing). Resolved reactively from the device/viewport environment.' }, { configKey: 'fullscreenAutofocus', attribute: 'fullscreen-autofocus', converter: toBool('default-false'), on: 'update', description: 'In the phone fullscreen overlay, auto-focus the search field on open (pops the soft keyboard immediately). Default `false`: the sheet opens with the list visible and the keyboard closed, appearing only when the user taps the search. Set `true` to type-to-filter right away. No effect in the floating presentation.' }, // ── Numbers ────────────────────────────────────────────────────────────── { configKey: 'badgesThreshold', attribute: 'badges-threshold', converter: toInt(), on: 'update', description: 'Threshold at which badges collapse to a count/compact view.' }, { configKey: 'badgesMaxVisible', attribute: 'badges-max-visible', converter: toInt(), on: 'update', description: 'Maximum number of badges rendered before overflow.' }, { configKey: 'collapseBadgesBelow', attribute: 'collapse-badges-below', converter: toInt(), on: 'update', description: 'Container-responsive opt-in (off by default). When set to a px width, the control watches its OWN border box (not the window, via the core `resized` hook / a shared ResizeObserver) and collapses `badges-display-mode` to `count` (\"N selected\") while the box is narrower than this — so a picker in a narrow column/sidebar never overflows with pills, even on a wide monitor. Widening past the threshold restores the configured badges mode. Element-only: the override is applied to the live picker, never to your `badges-display-mode` config.' }, { configKey: 'minSearchLength', attribute: 'min-search-length', converter: toInt({ default: 0 }), on: 'update', description: 'Minimum characters before searching/filtering starts.' }, { configKey: 'searchDebounce', attribute: 'search-debounce', converter: toInt({ default: 0 }), on: 'update', description: 'Debounce delay in ms applied to the search input.' }, { configKey: 'virtualScrollThreshold', attribute: 'virtual-scroll-threshold', converter: toInt({ default: 100 }), on: 'reinit', description: 'Option count above which virtual scrolling turns on.' }, { configKey: 'optionHeight', attribute: 'option-height', converter: toInt({ default: 50 }), on: 'update', description: 'Fixed row height in px used by virtual scrolling.' }, { configKey: 'badgeHeight', attribute: 'badge-height', converter: toInt({ default: 36 }), on: 'update', description: 'Fixed badge height in px used for layout/virtualization.' }, { configKey: 'virtualScrollBuffer', attribute: 'virtual-scroll-buffer', converter: toInt({ default: 10 }), on: 'update', description: 'Extra rows rendered above/below the viewport when virtualizing.' }, { configKey: 'badgeTooltipDelay', attribute: 'badge-tooltip-delay', converter: toInt({ default: 100 }), on: 'update', description: 'Delay in ms before a badge tooltip appears.' }, { configKey: 'badgeTooltipOffset', attribute: 'badge-tooltip-offset', converter: toInt({ default: 8 }), on: 'update', description: 'Gap in px between a badge and its tooltip.' }, { configKey: 'optionTooltipDelay', attribute: 'option-tooltip-delay', converter: toInt(), on: 'update', description: 'Delay in ms before an option tooltip appears (falls back to badgeTooltipDelay).' }, { configKey: 'optionTooltipOffset', attribute: 'option-tooltip-offset', converter: toInt(), on: 'update', description: 'Gap in px between an option and its tooltip.' }, // ── Booleans (default true) ────────────────────────────────────────────── { configKey: 'isMultipleEnabled', attribute: 'multiple', converter: toBool('default-true'), on: 'reinit', description: 'Allow selecting multiple options. When off, selecting one replaces the previous.' }, { configKey: 'isGroupsAllowed', attribute: 'allow-groups', converter: toBool('default-true'), on: 'reinit', description: 'Allow grouping options under group headers.' }, { configKey: 'isCheckboxesShown', attribute: 'show-checkboxes', converter: toBool('default-true'), on: 'reinit', description: 'Show a checkbox on each option.' }, { configKey: 'isActionsSticky', attribute: 'sticky-actions', converter: toBool('default-true'), on: 'update', description: 'Keep the action bar pinned while the list scrolls.' }, { configKey: 'isPlacementLocked', attribute: 'lock-placement', converter: toBool('default-true'), on: 'update', description: 'Keep the dropdown initial placement instead of flipping when it fits.' }, { configKey: 'isSearchEnabled', attribute: 'enable-search', converter: toBool('default-true'), on: 'reinit', description: 'Show the search input.' }, { configKey: 'isKeepOptionsOnSearch', attribute: 'keep-options-on-search', converter: toBool('default-true'), on: 'update', description: 'Keep already-selected options visible while filtering.' }, { configKey: 'shouldKeepSearchOnClose', attribute: 'should-keep-search-on-close', converter: toBool('default-true'), on: 'update', description: 'Preserve the search text after the dropdown closes.' }, { configKey: 'isSelectedPopoverEnabled', attribute: 'enable-selected-popover', converter: toBool('default-true'), on: 'update', description: 'Allow the selected-items popover to open (from the count/compact/\"+X more\" badge or the in-input counter). Turn off when you render your own selection UI, so those affordances become inert.' }, // ── Booleans (default false) ───────────────────────────────────────────── { configKey: 'isCloseOnSelect', attribute: 'close-on-select', converter: toBool('default-false'), on: 'update', description: 'Close the dropdown immediately after a selection.' }, { configKey: 'isAddNewAllowed', attribute: 'allow-add-new', converter: toBool('default-false'), on: 'reinit', description: 'Allow adding a new option from the search text.' }, { configKey: 'isCounterShown', attribute: 'show-counter', converter: toBool('default-false'), on: 'update', description: 'Show a selected-count indicator.' }, { configKey: 'isClearShown', attribute: 'show-clear', converter: toBool('default-false'), on: 'update', description: 'Show an inline clear (✕) button inside the input that wipes the whole selection. Appears only while something is selected and the control is enabled; clicking it clears the selection and any search text, fires `change`, and refocuses.' }, { configKey: 'isBadgeFullTitleShown', attribute: 'show-badge-full-title', converter: toBool('default-false'), on: 'update', description: 'Show the full title on badges instead of the short label.' }, { configKey: 'isVirtualScrollEnabled', attribute: 'enable-virtual-scroll', converter: toBool('default-false'), on: 'reinit', description: 'Force virtual scrolling on regardless of the threshold.' }, { configKey: 'isBadgeTooltipsEnabled', attribute: 'enable-badge-tooltips', converter: toBool('default-false'), on: 'update', description: 'Enable tooltips on badges.' }, { configKey: 'isOptionTooltipsEnabled', attribute: 'enable-option-tooltips', converter: toBool('default-false'), on: 'update', description: 'Enable tooltips on options.' }, { configKey: 'isOptionTooltipFollowCursor', attribute: 'option-tooltip-follow-cursor', converter: toBool('default-false'), on: 'update', description: 'Make option tooltips follow the pointer.' }, { configKey: 'isSearchModeToggleShown', attribute: 'show-search-mode-toggle', converter: toBool('default-false'), on: 'update', description: 'Show a clickable toggle in the phone fullscreen overlay search header that flips `search-mode` between `filter` and `navigate` live. Fullscreen-only; no effect in the floating presentation or when search is disabled.' }, // ── Special attributes ─────────────────────────────────────────────────── { configKey: 'initialValues', attribute: 'initial-values', converter: toInitialValues(), default: [], on: 'reinit', type: 'Array<string | number>', description: 'Values selected on first render. Accepts a JSON array (`[\"a\",\"b\"]`) or a bare CSV (`a,b,c`).' }, { configKey: 'showDebugInfo', attribute: 'show-debug-info', converter: toBool('default-false'), on: 'update', description: 'Render an in-component debug panel.', deprecated: 'Use per-instance logging (el.enableLogging()) instead.' }, // ── Render gate (element-only; NON_PICKER) ─────────────────────────────── { configKey: 'deferRender', attribute: 'defer', converter: toBool('presence'), on: 'reinit', description: 'Hold the initial render. When the `defer` attribute is present on upgrade the component builds nothing (it only reserves space) — so options, callbacks (e.g. `customStylesCallback`) and event listeners can all be wired first, then released with `el.ready()` (or by removing the `defer` attribute, for server-driven frameworks). The release builds the picker ONCE with everything already in place, avoiding the upgrade-then-restyle flash. Absent (default): builds immediately on connect. Latched — once released the gate never re-closes.' }, // ── Complex property (data) ────────────────────────────────────────────── { configKey: 'options', converter: toObjectArray(), on: 'reinit', type: 'ReadonlyArray<Record<string, unknown>>', description: 'The array of option objects to render. The JS API — assign `el.options` directly. For HTML authoring use the `data-options` attribute (parsed per `data-options-format`) or declarative <option> children; both feed the same list and take precedence over this property in the order: <option> children > property > data-options.' }, { configKey: 'optionsSource', attribute: 'data-options', converter: toText({ isNullable: true }), on: 'reinit', type: 'string', description: 'HTML-authoring source for the option list, parsed per `data-options-format`. Reactive: changing either attribute re-renders. Prefer the `options` property in JS; a set `options` property and declarative <option> children both win over this.' }, { configKey: 'optionsFormat', attribute: 'data-options-format', converter: toEnum(OPTIONS_FORMATS, { default: 'json' }), on: 'reinit', type: \"'json' | 'csv' | 'plain'\", description: 'How to parse the `data-options` attribute: `json` (a JSON array of objects or [value, label] tuples), `csv` (rows split on `data-options-row-splitter`, cells on `data-options-splitter`; the first row is a header — map columns via *-member), or `plain` (bare values split on both splitters -> [value, label] tuples, value === label). Default `json`.' }, { configKey: 'optionsSplitter', attribute: 'data-options-splitter', converter: toText({ default: ',' }), on: 'reinit', type: 'string', description: 'Field/cell delimiter for the `csv` and `plain` `data-options` formats. Default `,`. Escapes `\\\\t` `\\\\n` `\\\\r` are honoured (e.g. `data-options-splitter=\"\\\\t\"` for TSV). Ignored for `json`.' }, { configKey: 'optionsRowSplitter', attribute: 'data-options-row-splitter', converter: toText({ default: '\\n' }), on: 'reinit', type: 'string', description: 'Row/record delimiter for the `csv` and `plain` `data-options` formats. Default newline. Escapes honoured (e.g. `data-options-row-splitter=\";\"` for single-line data). Ignored for `json`.' }, { configKey: 'actionButtons', converter: toValue({ validate: (v): v is unknown[] => Array.isArray(v) }), on: 'reinit', type: 'Array<Record<string, unknown>>', description: 'Custom action buttons for the dropdown footer/header. Property-only; when unset the default Select-All / Clear buttons apply.' }, // ── Callbacks: data shape (structural → reinit) ────────────────────────── { configKey: 'getValueCallback', converter: cb(), on: 'reinit', type: '(item: unknown) => string | number', description: 'Extract an option value (overrides valueMember).' }, { configKey: 'getPathCallback', converter: cb(), on: 'reinit', type: '(item: unknown) => string', description: 'Extract a node tree path (enables tree mode; overrides pathMember).' }, { configKey: 'getGroupCallback', converter: cb(), on: 'reinit', type: '(item: unknown) => string', description: 'Extract the group name from an option (overrides groupMember).' }, { configKey: 'getDisabledCallback', converter: cb(), on: 'reinit', type: '(item: unknown) => boolean', description: 'Whether an option is disabled (overrides disabledMember).' }, { configKey: 'getIsSelectableCallback', converter: cb(), on: 'reinit', type: '(node: unknown) => boolean', description: 'Whether a tree node can be selected (overrides is-selectable-member).' }, { configKey: 'getSearchValueCallback', converter: cb(), on: 'reinit', type: '(item: unknown) => string', description: 'Text an option is searched against (overrides searchValueMember).' }, { configKey: 'searchCallback', converter: cb(), on: 'reinit', type: '(searchTerm: string, signal?: AbortSignal) => Promise<unknown[]>', description: 'Custom / async search; return the filtered options.' }, // ── Callbacks: display / render (cosmetic → update) ────────────────────── { configKey: 'getDisplayValueCallback', converter: cb(), on: 'update', type: '(item: unknown) => string', description: 'Compute the display label for an option (overrides displayValueMember).' }, { configKey: 'getBadgeDisplayCallback', converter: cb(), on: 'update', type: '(item: unknown) => string', description: 'Compute the text shown on an option badge.' }, { configKey: 'getBadgeClassCallback', converter: cb(), on: 'update', type: '(item: unknown) => string | string[]', description: 'Extra CSS class(es) for an option badge.' }, { configKey: 'getIconCallback', converter: cb(), on: 'update', type: '(item: unknown) => string', description: 'Icon for an option (overrides iconMember).' }, { configKey: 'getSubtitleCallback', converter: cb(), on: 'update', type: '(item: unknown) => string', description: 'Subtitle for an option (overrides subtitleMember).' }, { configKey: 'getFullTitleCallback', converter: cb(), on: 'update', type: '(item: unknown) => string', description: 'Full title for an option (used by badges when show-badge-full-title is on).' }, { configKey: 'getCounterCallback', converter: cb(), on: 'update', type: '(count: number, moreCount?: number) => string', description: 'Render the selected-count label.' }, { configKey: 'getValueFormatCallback', converter: cb(), on: 'update', type: '(selectedValues: (string | number)[]) => string', description: 'Serialize the selected values for form submission.' }, { configKey: 'getBadgeTooltipCallback', converter: cb(), on: 'update', type: '(item: unknown) => string | HTMLElement', description: 'Tooltip content for an option badge.' }, { configKey: 'getOptionTooltipCallback', converter: cb(), on: 'update', type: '(item: unknown) => string | HTMLElement', description: 'Tooltip content for an option row.' }, { configKey: 'getRemoveButtonTooltipCallback', converter: cb(), on: 'update', type: '(item: unknown) => string', description: 'Tooltip text for a badge remove button.' }, { configKey: 'getSelectedItemClassCallback', converter: cb(), on: 'update', type: '(item: unknown) => string | string[]', description: 'Extra CSS class(es) for a selected item.' }, { configKey: 'renderOptionContentCallback', converter: cb(), on: 'update', type: '(item: unknown, context: OptionContentRenderContext) => string | HTMLElement', description: 'Custom render for an option row; may return HTML or an element.' }, { configKey: 'renderBadgeContentCallback', converter: cb(), on: 'update', type: '(item: unknown, context: BadgeContentRenderContext) => string | HTMLElement', description: 'Custom render for a badge content (fills the built-in pill); may return HTML or an element.' }, { configKey: 'renderBadgeCallback', converter: cb(), on: 'update', type: '(item: unknown, context: BadgeContentRenderContext) => string | HTMLElement | null', description: 'Custom render for the WHOLE badge (main area), not just its content — return the entire pill/card. The component wraps it in `.ms__badge.ms__badge--custom` with `data-value` and delegates removal to any inner element with `data-action=\"remove\"` (or `.ms__badge-remove`). Return null/empty to fall back to the default pill for that item.' }, { configKey: 'renderGroupLabelContentCallback', converter: cb(), on: 'update', type: '(groupName: string) => string | HTMLElement', description: 'Customize a group label; may return an HTML string or element.' }, { configKey: 'renderSelectedContentCallback', converter: cb(), on: 'update', type: '(item: unknown) => string', description: 'Custom render for the whole selected area.' }, { configKey: 'renderSelectedItemContentCallback', converter: cb(), on: 'update', type: '(item: unknown) => string | HTMLElement', description: 'Custom render for one selected item.' }, { configKey: 'customStylesCallback', converter: cb(), on: 'update', type: '() => string', description: 'Returns a CSS string injected into the component via a replaceable style slot (§12.8).' }, // ── Callbacks: before-hooks (behavior-shaping) ─────────────────────────── { configKey: 'beforeSearchCallback', converter: cb(), on: 'update', type: '(searchTerm: string) => string | null', description: 'Runs before a search; return a rewritten term or null to veto.' }, { configKey: 'beforeSelectCallback', converter: cb(), on: 'update', type: '(option: unknown, selectedOptions: unknown[]) => boolean | string | void', description: 'Runs before selecting; return false to veto, or a string to veto and show it as a message.' }, { configKey: 'beforeDeselectCallback', converter: cb(), on: 'update', type: '(option: unknown, selectedOptions: unknown[]) => boolean | string | void', description: 'Runs before deselecting; return false to veto, or a string to veto and show it as a message.' }, { configKey: 'addNewCallback', converter: cb(), on: 'update', type: '(value: string) => unknown | null | undefined | Promise<unknown | null | undefined>', description: 'Create a new option from the typed text. May return a rich option object (renders via the same get*/render* callbacks as any option). Async + cancelable: return null/undefined to abort (no add, no `add` event). Omit entirely to handle creation yourself via the `add` event.' }, { configKey: 'getAddNewTextCallback', converter: cb(), on: 'update', type: '(value: string) => string', description: 'Dynamically compute the \"add new\" prompt label from the typed text (returns plain text). Takes precedence over `add-new-text`.' }, { configKey: 'keydownCallback', converter: cb(), on: 'update', type: '(context: MultiSelectKeydownContext) => boolean | void', description: 'Intercept keydown before built-in handling; return true to suppress the default. Gets the event, current state, and an imperative controller.' }, ]",
|
|
3743
3754
|
"type": {
|
|
3744
3755
|
"text": "readonly InputDef[]"
|
|
3745
3756
|
}
|
|
@@ -3749,9 +3760,9 @@
|
|
|
3749
3760
|
"name": "events",
|
|
3750
3761
|
"privacy": "protected",
|
|
3751
3762
|
"static": true,
|
|
3752
|
-
"default": "[ { name: 'select', description: 'An option was selected. `detail.option` is the selected option; `detail.selectedOptions`/`detail.selectedValues` are the full selection.' }, { name: 'deselect', description: 'An option was removed from the selection. `detail.option` is that option.' }, { name: 'change', description: 'The selection changed. `detail.selectedOptions`/`detail.selectedValues` are the full selection.' }, { name: 'add', description: 'The user chose to create a new option from the typed text (via the \"add new\" prompt or Enter) — requires `allow-add-new`. `detail.value` is the typed text; `detail.option` is the created item when `addNewCallback` produced one.' }, ]",
|
|
3763
|
+
"default": "[ { name: 'select', description: 'An option was selected. `detail.option` is the selected option; `detail.selectedOptions`/`detail.selectedValues` are the full selection.' }, { name: 'deselect', description: 'An option was removed from the selection. `detail.option` is that option.' }, { name: 'change', description: 'The selection changed. `detail.selectedOptions`/`detail.selectedValues` are the full selection.' }, { name: 'add', description: 'The user chose to create a new option from the typed text (via the \"add new\" prompt or Enter) — requires `allow-add-new`. `detail.value` is the typed text; `detail.option` is the created item when `addNewCallback` produced one.' }, { name: 'ready', description: 'The picker was built and painted for the first time (once per element lifetime). Fires right after the first build — synchronously during upgrade for a normal element, or when the render gate is released (`el.ready()` / removing `defer`) for a deferred one. No detail.' }, ]",
|
|
3753
3764
|
"type": {
|
|
3754
|
-
"text": "[\r\n { name: 'select', description: 'An option was selected. `detail.option` is the selected option; `detail.selectedOptions`/`detail.selectedValues` are the full selection.' },\r\n { name: 'deselect', description: 'An option was removed from the selection. `detail.option` is that option.' },\r\n { name: 'change', description: 'The selection changed. `detail.selectedOptions`/`detail.selectedValues` are the full selection.' },\r\n { name: 'add', description: 'The user chose to create a new option from the typed text (via the \"add new\" prompt or Enter) — requires `allow-add-new`. `detail.value` is the typed text; `detail.option` is the created item when `addNewCallback` produced one.' },\r\n]"
|
|
3765
|
+
"text": "[\r\n { name: 'select', description: 'An option was selected. `detail.option` is the selected option; `detail.selectedOptions`/`detail.selectedValues` are the full selection.' },\r\n { name: 'deselect', description: 'An option was removed from the selection. `detail.option` is that option.' },\r\n { name: 'change', description: 'The selection changed. `detail.selectedOptions`/`detail.selectedValues` are the full selection.' },\r\n { name: 'add', description: 'The user chose to create a new option from the typed text (via the \"add new\" prompt or Enter) — requires `allow-add-new`. `detail.value` is the typed text; `detail.option` is the created item when `addNewCallback` produced one.' },\r\n { name: 'ready', description: 'The picker was built and painted for the first time (once per element lifetime). Fires right after the first build — synchronously during upgrade for a normal element, or when the render gate is released (`el.ready()` / removing `defer`) for a deferred one. No detail.' },\r\n]"
|
|
3755
3766
|
}
|
|
3756
3767
|
},
|
|
3757
3768
|
{
|
|
@@ -3782,6 +3793,13 @@
|
|
|
3782
3793
|
"text": "((e: CustomEvent<MultiSelectEventDetail<T>>) => void) | null"
|
|
3783
3794
|
}
|
|
3784
3795
|
},
|
|
3796
|
+
{
|
|
3797
|
+
"kind": "field",
|
|
3798
|
+
"name": "onReady",
|
|
3799
|
+
"type": {
|
|
3800
|
+
"text": "((e: CustomEvent<undefined>) => void) | null"
|
|
3801
|
+
}
|
|
3802
|
+
},
|
|
3785
3803
|
{
|
|
3786
3804
|
"kind": "field",
|
|
3787
3805
|
"name": "#shadow",
|
|
@@ -3806,6 +3824,15 @@
|
|
|
3806
3824
|
"text": "HTMLDivElement | undefined"
|
|
3807
3825
|
}
|
|
3808
3826
|
},
|
|
3827
|
+
{
|
|
3828
|
+
"kind": "field",
|
|
3829
|
+
"name": "#released",
|
|
3830
|
+
"privacy": "private",
|
|
3831
|
+
"type": {
|
|
3832
|
+
"text": "boolean"
|
|
3833
|
+
},
|
|
3834
|
+
"default": "false"
|
|
3835
|
+
},
|
|
3809
3836
|
{
|
|
3810
3837
|
"kind": "field",
|
|
3811
3838
|
"name": "#customStyles",
|
|
@@ -3925,6 +3952,17 @@
|
|
|
3925
3952
|
},
|
|
3926
3953
|
"description": "Activate: ensure the picker exists (a DOM move destroyed it in disconnect())."
|
|
3927
3954
|
},
|
|
3955
|
+
{
|
|
3956
|
+
"kind": "method",
|
|
3957
|
+
"name": "#renderHeld",
|
|
3958
|
+
"privacy": "private",
|
|
3959
|
+
"return": {
|
|
3960
|
+
"type": {
|
|
3961
|
+
"text": "boolean"
|
|
3962
|
+
}
|
|
3963
|
+
},
|
|
3964
|
+
"description": "Whether the initial render is being held by the `defer` gate (not yet released)."
|
|
3965
|
+
},
|
|
3928
3966
|
{
|
|
3929
3967
|
"kind": "method",
|
|
3930
3968
|
"name": "disconnect",
|
|
@@ -4494,6 +4532,25 @@
|
|
|
4494
4532
|
}
|
|
4495
4533
|
}
|
|
4496
4534
|
},
|
|
4535
|
+
{
|
|
4536
|
+
"kind": "method",
|
|
4537
|
+
"name": "ready",
|
|
4538
|
+
"return": {
|
|
4539
|
+
"type": {
|
|
4540
|
+
"text": "void"
|
|
4541
|
+
}
|
|
4542
|
+
},
|
|
4543
|
+
"description": "Release the `defer` render gate: build the picker now (once), with every\r\noption, callback and listener wired while deferred already in place. No-op\r\nwhen the element wasn't deferred or is already built. `flush()` first so a\r\nsynchronous `el.options = …; el.customStylesCallback = …; el.ready()` lands\r\nthose pending writes in the single build rather than after it. Latched — the\r\ngate never re-closes. Fires the `ready` event on the first build."
|
|
4544
|
+
},
|
|
4545
|
+
{
|
|
4546
|
+
"kind": "field",
|
|
4547
|
+
"name": "isReady",
|
|
4548
|
+
"type": {
|
|
4549
|
+
"text": "boolean"
|
|
4550
|
+
},
|
|
4551
|
+
"description": "Whether the picker has been built (the `ready` event has fired). False while a `defer` gate is still held.",
|
|
4552
|
+
"readonly": true
|
|
4553
|
+
},
|
|
4497
4554
|
{
|
|
4498
4555
|
"kind": "field",
|
|
4499
4556
|
"name": "searchHint",
|
|
@@ -5350,6 +5407,16 @@
|
|
|
5350
5407
|
"description": "Render an in-component debug panel.",
|
|
5351
5408
|
"deprecated": "Use per-instance logging (el.enableLogging()) instead."
|
|
5352
5409
|
},
|
|
5410
|
+
{
|
|
5411
|
+
"kind": "field",
|
|
5412
|
+
"name": "deferRender",
|
|
5413
|
+
"privacy": "public",
|
|
5414
|
+
"type": {
|
|
5415
|
+
"text": "boolean"
|
|
5416
|
+
},
|
|
5417
|
+
"attribute": "defer",
|
|
5418
|
+
"description": "Hold the initial render. When the `defer` attribute is present on upgrade the component builds nothing (it only reserves space) — so options, callbacks (e.g. `customStylesCallback`) and event listeners can all be wired first, then released with `el.ready()` (or by removing the `defer` attribute, for server-driven frameworks). The release builds the picker ONCE with everything already in place, avoiding the upgrade-then-restyle flash. Absent (default): builds immediately on connect. Latched — once released the gate never re-closes."
|
|
5419
|
+
},
|
|
5353
5420
|
{
|
|
5354
5421
|
"kind": "field",
|
|
5355
5422
|
"name": "options",
|
|
@@ -6375,6 +6442,14 @@
|
|
|
6375
6442
|
"description": "Render an in-component debug panel.",
|
|
6376
6443
|
"deprecated": "Use per-instance logging (el.enableLogging()) instead."
|
|
6377
6444
|
},
|
|
6445
|
+
{
|
|
6446
|
+
"name": "defer",
|
|
6447
|
+
"fieldName": "deferRender",
|
|
6448
|
+
"type": {
|
|
6449
|
+
"text": "boolean"
|
|
6450
|
+
},
|
|
6451
|
+
"description": "Hold the initial render. When the `defer` attribute is present on upgrade the component builds nothing (it only reserves space) — so options, callbacks (e.g. `customStylesCallback`) and event listeners can all be wired first, then released with `el.ready()` (or by removing the `defer` attribute, for server-driven frameworks). The release builds the picker ONCE with everything already in place, avoiding the upgrade-then-restyle flash. Absent (default): builds immediately on connect. Latched — once released the gate never re-closes."
|
|
6452
|
+
},
|
|
6378
6453
|
{
|
|
6379
6454
|
"name": "data-options",
|
|
6380
6455
|
"fieldName": "optionsSource",
|
|
@@ -6427,6 +6502,10 @@
|
|
|
6427
6502
|
{
|
|
6428
6503
|
"name": "add",
|
|
6429
6504
|
"description": "The user chose to create a new option from the typed text (via the \"add new\" prompt or Enter) — requires `allow-add-new`. `detail.value` is the typed text; `detail.option` is the created item when `addNewCallback` produced one."
|
|
6505
|
+
},
|
|
6506
|
+
{
|
|
6507
|
+
"name": "ready",
|
|
6508
|
+
"description": "The picker was built and painted for the first time (once per element lifetime). Fires right after the first build — synchronously during upgrade for a normal element, or when the render gate is released (`el.ready()` / removing `defer`) for a deferred one. No detail."
|
|
6430
6509
|
}
|
|
6431
6510
|
],
|
|
6432
6511
|
"tagName": "web-multiselect",
|
|
@@ -7172,6 +7251,10 @@
|
|
|
7172
7251
|
"name": "--ms-icon-indeterminate",
|
|
7173
7252
|
"description": "Dash glyph (mask) for a tri-state/partial checkbox; chains to --base-icon-indeterminate"
|
|
7174
7253
|
},
|
|
7254
|
+
{
|
|
7255
|
+
"name": "--ms-icon-check-size",
|
|
7256
|
+
"description": "mask-size for the checkmark/dash glyph inside the checkbox box; chains to --base-icon-check-size (default 68%, the same knob as pure-admin's .pa-checkbox)"
|
|
7257
|
+
},
|
|
7175
7258
|
{
|
|
7176
7259
|
"name": "--ms-badge-counter-bg",
|
|
7177
7260
|
"description": "Counter badge background"
|
package/dist/index.d.ts
CHANGED
|
@@ -657,11 +657,15 @@ export declare class MultiSelectElement<T = any> extends BlissElement<MultiSelec
|
|
|
657
657
|
}, {
|
|
658
658
|
readonly name: "add";
|
|
659
659
|
readonly description: "The user chose to create a new option from the typed text (via the \"add new\" prompt or Enter) — requires `allow-add-new`. `detail.value` is the typed text; `detail.option` is the created item when `addNewCallback` produced one.";
|
|
660
|
+
}, {
|
|
661
|
+
readonly name: "ready";
|
|
662
|
+
readonly description: "The picker was built and painted for the first time (once per element lifetime). Fires right after the first build — synchronously during upgrade for a normal element, or when the render gate is released (`el.ready()` / removing `defer`) for a deferred one. No detail.";
|
|
660
663
|
}];
|
|
661
664
|
onSelect: ((e: CustomEvent<MultiSelectEventDetail<T>>) => void) | null;
|
|
662
665
|
onDeselect: ((e: CustomEvent<MultiSelectEventDetail<T>>) => void) | null;
|
|
663
666
|
onChange: ((e: CustomEvent<MultiSelectEventDetail<T>>) => void) | null;
|
|
664
667
|
onAdd: ((e: CustomEvent<MultiSelectEventDetail<T>>) => void) | null;
|
|
668
|
+
onReady: ((e: CustomEvent<undefined>) => void) | null;
|
|
665
669
|
constructor();
|
|
666
670
|
/**
|
|
667
671
|
* Called by the browser when the surrounding <form> is reset. Clears the
|
|
@@ -766,6 +770,17 @@ export declare class MultiSelectElement<T = any> extends BlissElement<MultiSelec
|
|
|
766
770
|
get isOpen(): boolean;
|
|
767
771
|
set isOpen(value: boolean);
|
|
768
772
|
destroy(): void;
|
|
773
|
+
/**
|
|
774
|
+
* Release the `defer` render gate: build the picker now (once), with every
|
|
775
|
+
* option, callback and listener wired while deferred already in place. No-op
|
|
776
|
+
* when the element wasn't deferred or is already built. `flush()` first so a
|
|
777
|
+
* synchronous `el.options = …; el.customStylesCallback = …; el.ready()` lands
|
|
778
|
+
* those pending writes in the single build rather than after it. Latched — the
|
|
779
|
+
* gate never re-closes. Fires the `ready` event on the first build.
|
|
780
|
+
*/
|
|
781
|
+
ready(): void;
|
|
782
|
+
/** Whether the picker has been built (the `ready` event has fired). False while a `defer` gate is still held. */
|
|
783
|
+
get isReady(): boolean;
|
|
769
784
|
}
|
|
770
785
|
|
|
771
786
|
/**
|
|
@@ -788,6 +803,7 @@ declare type MultiSelectEvents = {
|
|
|
788
803
|
deselect: MultiSelectEventDetail;
|
|
789
804
|
change: MultiSelectEventDetail;
|
|
790
805
|
add: MultiSelectEventDetail;
|
|
806
|
+
ready: undefined;
|
|
791
807
|
};
|
|
792
808
|
|
|
793
809
|
/**
|
|
@@ -1481,6 +1497,18 @@ export declare class WebMultiSelect<T = any> {
|
|
|
1481
1497
|
* `onChange` fires once if anything actually changed.
|
|
1482
1498
|
*/
|
|
1483
1499
|
private commit;
|
|
1500
|
+
/**
|
|
1501
|
+
* Shield the trailing document `click` for one tick. A consumer that drives the
|
|
1502
|
+
* dropdown from their OWN button's click handler (`el.open()` / `el.toggle()`, or a
|
|
1503
|
+
* scrollTo* command re-driving the already-open panel) would otherwise have that
|
|
1504
|
+
* same click bubble to our document-level outside-click listener and immediately
|
|
1505
|
+
* close it. Centralizing it here means every entry point — open(), the internal
|
|
1506
|
+
* pointer path, and the public scrollTo* API — is covered. Cleared next tick, so a
|
|
1507
|
+
* genuine later outside-click still closes as normal. Harmless for non-click
|
|
1508
|
+
* callers (typing, programmatic-on-load, server-driven): no trailing click arrives
|
|
1509
|
+
* before it clears.
|
|
1510
|
+
*/
|
|
1511
|
+
private armClickGuard;
|
|
1484
1512
|
/** Open the dropdown (no-op if already open, or if there is nothing to show). */
|
|
1485
1513
|
open(): void;
|
|
1486
1514
|
/** Close the dropdown (no-op if already closed). */
|