@keenmate/web-multiselect 2.0.0 → 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 CHANGED
@@ -1,208 +1,172 @@
1
- # @keenmate/web-multiselect
2
-
3
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
4
- [![npm version](https://img.shields.io/npm/v/@keenmate/web-multiselect.svg)](https://www.npmjs.com/package/@keenmate/web-multiselect)
5
-
6
- > A lightweight, themeable multi-select web component with typeahead search, RTL support, rich content, and full keyboard navigation.
7
-
8
- ## What is it
9
-
10
- `@keenmate/web-multiselect` is a custom element (`<web-multiselect>`) that turns a list of options into a searchable, themeable multi-select dropdown. Framework-agnostic — works in React, Vue, Svelte, Blazor, plain HTML.
11
-
12
- Reads `--base-*` variables from the page if [`@keenmate/theme-designer`](https://theme-designer.keenmate.dev) is present, falls back to sensible OS-aware defaults otherwise, and ships with first-class dark-mode and per-instance theming.
13
-
14
- **Headline features:**
15
-
16
- - Declarative `<option>` / `<optgroup>` markup — no JavaScript required for simple cases.
17
- - Virtual scrolling for 10,000+ option datasets (25× faster opening, 99.8% memory reduction).
18
- - Filter or navigate search modes; async / hybrid search.
19
- - Five badge display modes (pills, count, compact, partial, none) with positioning on any side.
20
- - Full keyboard navigation, RTL language support, badge tooltips.
21
- - Custom rendering callbacks for options, badges, and group headers.
22
- - Form integration via standard hidden inputs (FormData-compatible).
23
-
24
- ## What's New in v2.0.0
25
-
26
- - **"Add new" creation mode — the picker doubles as a creation tool.** Turn on `allow-add-new`
27
- and a search with no matches shows a clickable "Add new …" prompt (label via `add-new-text` /
28
- `getAddNewTextCallback`). Choosing it (click, Enter, or arrow-to-focus) fires a new bubbling
29
- `add` event; `addNewCallback` creates the option and is **async and cancelable** (return
30
- `null`/`undefined` to abort after a confirm dialog or server round-trip) and may return a
31
- **rich option** that renders like any other. While it runs the prompt shows a spinner +
32
- `add-new-pending-text`. Creation also works with no callback — handle it entirely off the `add`
33
- event. See `examples-events-callbacks.html` §EV4b/EV4c.
34
-
35
- - **Scroll-to imperative API — jump to any option or group.** New `scrollToIndex()` /
36
- `scrollToValue()` / `scrollToGroup()` on the element and picker bring a row into view — pair with
37
- `open()` for an "open + jump" gesture. Mode-aware (virtual scroll, tree, mobile fullscreen sheet),
38
- they align to the top by default (`{ block: 'center' }` to center) and return `false` when the
39
- target isn't in the current filtered list. A companion public `clearSearch()` reveals a
40
- filtered-out option so you can then scroll to it, plus a `searchText` getter and a `search(term)`
41
- method to read and programmatically drive the query. See §BU06b, §VS03, §TR09b.
42
-
43
- - **Checkbox check/dash + filter funnel now flow from the shared `--base-icon-*` contract.** The
44
- checkmark and indeterminate dash are now `currentColor` mask glyphs (not CSS-border shapes) reading
45
- `--base-icon-check` / `--base-icon-indeterminate`, and the search-mode funnel reads
46
- `--base-icon-filter` — so one base override reskins them across every KeenMate component. Inline
47
- Lucide fallbacks keep the default look unchanged.
48
-
49
- - **`--ms-rem` now bridges to the shared `--base-rem` knob.** The global sizing unit resolves
50
- `var(--base-rem, 10px)`, so a theme that sets `--base-rem` rescales the whole component from one
51
- variable (the `10px` fallback and per-instance overrides still work with no base layer loaded).
52
-
53
- - **Fixed: the count-chip clear ✕ and popover close ✕ vanished on hover.** Both derived their hover
54
- background *and* glyph colour from the same accent, so the ✕ melted into its own hover state
55
- (fully invisible on near-white accents like Minimal dark). They now fill with a solid accent
56
- background and flip the glyph to the on-accent colour, matching the badge remove button.
57
-
58
- ## What's New in v2.0.0-rc12
59
-
60
- - **`overlay-group` — one overlay open at a time, across components.** The dropdown now joins a cross-component single-active-overlay group (core `registerOverlay`): opening it dismisses every other participating overlay — other multiselects, datepickers, or any external popover that fires the `km-overlay-activated` document event — and it closes itself when another overlay in its group opens. The new `overlay-group` attribute/property scopes this to a named group (same group = coordinate, different groups = independent, unset = the default ungrouped pool). Outside-click dismissal is unchanged and always on; this only governs the open-broadcast. Fixes the old behavior where two multiselects could sit open simultaneously.
61
-
62
- - **Icon glyphs now flow from the shared `--base-icon-*` contract.** The four `--ms-icon-*` glyphs with a shared counterpart (chevron, field-clear, badge-remove, search) are wired through the `@keenmate/base-css-variables` layer, so a single `--base-icon-*` override re-skins that affordance across every KeenMate component at once — matching how the ~95 other `--ms-*` tokens already fall back to `--base-*`. The toggle/pager chevron now flows from `--base-icon-chevron` (a Lucide angle that keeps the previous optical size); field-clear, badge-remove, and search follow their base equivalents. Each keeps its inline Lucide SVG as the standalone fallback, so default appearance is unchanged when no base layer is loaded.
63
-
64
- - **`--ms-toggle-rotate-closed` / `--ms-toggle-rotate-open` — themeable chevron rotation.** The toggle rotates the *directional* base chevron into place (defaults `90deg` closed → down, `-90deg` open → up). A theme that supplies a **pre-oriented** glyph (one that already points down) can now opt out: set both to `0deg` for a static icon, or `0deg` / `180deg` for a down-glyph that flips up on open — without touching the base contract. The new Material Design card in `examples-theming.html` demonstrates the pairing.
65
-
66
- - **`--ms-fullscreen-nav-btn-icon` — the pager glyph is now independent.** The fullscreen match-navigator's prev/next buttons previously masked the shared `--ms-icon-chevron`, so a theme that repointed the base chevron at a pre-oriented toggle glyph would leak it into the pager (which rotates its source ±90° and expects a right-pointing chevron). The pager now reads its own token, defaulting to `--ms-icon-chevron` — nothing changes by default, but a theme can diverge the pager glyph on its own.
67
-
68
- - **More tokens flow from `--base-*` for dark-mode fidelity.** A follow-up audit wired five more `--ms-*` tokens that hardcoded a value where a dedicated `--base-*` counterpart exists: disabled-input background and dropdown box-shadow are now `light-dark()`-aware (they no longer stay light on dark themes), the field-clear color/hover knobs gain dedicated base hooks, and the snappy easing matches the base standard curve. All keep their prior value as the standalone fallback; transition *durations* and the z-index stack are deliberately left local.
69
-
70
- - **Dropdown / selected-items popover no longer render 2px wider than the field.** Both panels size from `--ms-input-current-width` (the field wrapper's border-box `offsetWidth`) but were themselves `content-box`, so each added its own 1px border on top and overhung the input it anchors to. Both now use `box-sizing: border-box`, so their outer width matches the field exactly.
71
-
72
- ## Demos & docs
73
-
74
- - 🚀 [Live demo](https://web-multiselect.keenmate.dev)
75
- - 📘 [Usage / API reference](./docs/usage.md) — attributes, properties, methods, events.
76
- - 🎨 [Theming](./docs/theming.md) — `--ms-*` variables, dark mode, cascade layers, Theme Designer integration.
77
- - 📚 [Examples / cookbook](./docs/examples.md) — rich content, async search, virtual scroll, custom rendering, forms.
78
- - ♿ [Accessibility](./docs/accessibility.md) — keyboard model, ARIA labels, focus behavior.
79
-
80
- ## Install
81
-
82
- ```bash
83
- npm install @keenmate/web-multiselect
84
- ```
85
-
86
- ## Quick start
87
-
88
- **Declarative — no JavaScript required:**
89
-
90
- ```html
91
- <script type="module">
92
- import '@keenmate/web-multiselect';
93
- </script>
94
-
95
- <web-multiselect placeholder="Pick a country">
96
- <option value="cz">Czech Republic</option>
97
- <option value="sk">Slovakia</option>
98
- <option value="at">Austria</option>
99
- </web-multiselect>
100
- ```
101
-
102
- **Programmatic — dynamic data + events:**
103
-
104
- ```html
105
- <web-multiselect id="picker" search-placeholder="Search…"></web-multiselect>
106
-
107
- <script type="module">
108
- import '@keenmate/web-multiselect';
109
-
110
- const picker = document.getElementById('picker');
111
- picker.options = [
112
- { value: 'js', label: 'JavaScript', icon: '🟨' },
113
- { value: 'ts', label: 'TypeScript', icon: '🔷' },
114
- { value: 'py', label: 'Python', icon: '🐍' }
115
- ];
116
-
117
- picker.addEventListener('change', (e) => {
118
- console.log('Selected:', e.detail.selectedValues);
119
- });
120
- </script>
121
- ```
122
-
123
- See [docs/usage.md](./docs/usage.md) for the full API and [docs/examples.md](./docs/examples.md) for advanced patterns (async data, virtual scrolling, custom rendering, form integration).
124
-
125
- ## Editor IntelliSense
126
-
127
- The package ships editor metadata so you get autocomplete and hover docs for the
128
- element's attributes, events, and all `--ms-*` CSS custom properties. All of it is
129
- generated from the component's source on every build, so it never drifts.
130
-
131
- - **JetBrains** (WebStorm / IntelliJ) — works automatically. The IDE discovers
132
- `web-types.json` via the `web-types` field in `package.json`; no setup needed.
133
- - **VS Code** — the data files ship but VS Code doesn't auto-discover them from a
134
- dependency, so point your workspace at them once in `.vscode/settings.json`:
135
-
136
- ```json
137
- {
138
- "html.customData": [
139
- "./node_modules/@keenmate/web-multiselect/vscode.html-custom-data.json"
140
- ],
141
- "css.customData": [
142
- "./node_modules/@keenmate/web-multiselect/vscode.css-custom-data.json"
143
- ]
144
- }
145
- ```
146
-
147
- `html.customData` powers tag/attribute completion on `<web-multiselect>`;
148
- `css.customData` powers completion for the `--ms-*` theming variables. Reload
149
- the window after adding them.
150
-
151
- ## Browser support
152
-
153
- Modern evergreen browsers — anything with native `customElements`, Shadow DOM, and CSS `@layer` support:
154
-
155
- - Chrome / Edge 99+
156
- - Firefox 97+
157
- - Safari 15.4+
158
-
159
- No polyfills are shipped. SSR-safe: the module imports without crashing in Node, but renders only after hydration in the browser.
160
-
161
- ## Development
162
-
163
- ```bash
164
- # Install dependencies
165
- npm install
166
-
167
- # Start dev server (HMR)
168
- npm run dev
169
-
170
- # Build for production
171
- npm run build
172
-
173
- # Create package tarball
174
- npm run package
175
-
176
- # Run tests
177
- npm run test:unit # Vitest (happy-dom) — fast logic checks
178
- npm run test:e2e # Playwright (browser) — interaction/visual
179
- npm test # both
180
- ```
181
-
182
- ## Code structure
183
-
184
- Follows the BlissFramework four-layer web-component layout:
185
-
186
- | Layer | File | Role |
187
- |-------|------|------|
188
- | Element | `src/web-component.ts` | `MultiSelectElement` — custom-element I/O wrapper, `ATTRIBUTE_TABLE`-driven |
189
- | Logic | `src/multiselect.ts` | `WebMultiSelect<T>` — framework-agnostic core |
190
- | Service | `src/tooltip.ts`, `src/virtual-scroll.ts` | single-purpose helpers (`Tooltip`, `VirtualScroll`) |
191
- | Side | `src/types.ts`, `src/logger.ts`, `src/vendor/` | types, logging, vendored deps |
192
-
193
- Two deviations from the canonical shape, both intentional:
194
-
195
- - **`MultiSelectElement extends BaseElement`, not `HTMLElement` directly.** `BaseElement` is a local `const` resolving to `HTMLElement` in the browser and to a stub class under SSR (`typeof HTMLElement === 'undefined'`), so importing the module in Node doesn't throw. A literal `grep "extends HTMLElement"` structure check will not match here by design.
196
- - **`src/vite-env.d.ts`** is a standard Vite ambient-types file, not part of the four-layer model.
197
-
198
- ## License
199
-
200
- MIT — see [LICENSE](./LICENSE).
201
-
202
- ## Built with BlissFramework
203
-
204
- Follows the [BlissFramework component guidelines](https://blissframework.dev/) for structure, theming, color-scheme, and accessibility. Per-check verifications run via `/validate-web-component`.
205
-
206
- ## Credits
207
-
208
- Created by [Keenmate](https://github.com/keenmate) as part of the Pure Admin design system.
1
+ # @keenmate/web-multiselect
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
4
+ [![npm version](https://img.shields.io/npm/v/@keenmate/web-multiselect.svg)](https://www.npmjs.com/package/@keenmate/web-multiselect)
5
+
6
+ > A lightweight, themeable multi-select web component with typeahead search, RTL support, rich content, and full keyboard navigation.
7
+
8
+ ## What is it
9
+
10
+ `@keenmate/web-multiselect` is a custom element (`<web-multiselect>`) that turns a list of options into a searchable, themeable multi-select dropdown. Framework-agnostic — works in React, Vue, Svelte, Blazor, plain HTML.
11
+
12
+ Reads `--base-*` variables from the page if [`@keenmate/theme-designer`](https://theme-designer.keenmate.dev) is present, falls back to sensible OS-aware defaults otherwise, and ships with first-class dark-mode and per-instance theming.
13
+
14
+ **Headline features:**
15
+
16
+ - Declarative `<option>` / `<optgroup>` markup — no JavaScript required for simple cases.
17
+ - Virtual scrolling for 10,000+ option datasets (25× faster opening, 99.8% memory reduction).
18
+ - Filter or navigate search modes; async / hybrid search.
19
+ - Five badge display modes (pills, count, compact, partial, none) with positioning on any side.
20
+ - Full keyboard navigation, RTL language support, badge tooltips.
21
+ - Custom rendering callbacks for options, badges, and group headers.
22
+ - Form integration via standard hidden inputs (FormData-compatible).
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
+
29
+ ## What's New in v2.0.1
30
+
31
+ - **Single-select — re-clicking the selected option no longer clears the field** — In a `multiple="false"` picker, clicking or pressing Enter on the option that's already selected used to toggle it off and empty the control. Because a single-select row shows no checkbox, there was no cue you were un-picking, so a confirming click read as an accidental wipe. It's now a no-op that simply closes the dropdown, keeping the value selected; clearing stays the dedicated ✕ button's job (`show-clear`). Multi-select toggle-off and single-select replacement are untouched.
32
+ - **Checkbox tick now sizes from the shared `--base-icon-check-size` token** — The selected-row checkmark was masked at `contain` (≈100% of the box), which only looked right when the glyph carried its own viewBox padding; a theme swapping `--base-icon-check` for an edge-to-edge glyph (e.g. a star) rendered it oversized, and it didn't match pure-admin's `.pa-checkbox`. It now reads `var(--base-icon-check-size, 68%)` — the exact knob pure-admin uses — so the mark renders identically in both and a theme can rescale it once for every component. The indeterminate dash inherits the same mask box. The default Lucide check shrinks slightly (≈100% → 68%) to match; that's intended.
33
+ - **Build-time variable-manifest validator** — A new `scripts/check-variable-manifest.mjs` step (wired into `npm run build` as `check:vars`) diffs the `--ms-*` declared and `--base-*` consumed across `src/css/**` against `component-variables.manifest.json` and fails the build on dead or missing entries. This closes the silent-drift gap behind the generated IDE autocomplete and theme-designer surfaces, which are all derived from that manifest.
34
+ - **Manifest drift cleanup** — Removed 24 inert entries the manifest still advertised (the commented-out `--ms-input-size-*` size-preset surface and five positioning vars dropped in the rc11 field-shell rework) and added the missing `--base-icon-plus` / `--base-icon-add` glyphs, so autocomplete no longer offers non-functional knobs and now covers the "add new" prompt icons.
35
+
36
+ ## Demos & docs
37
+
38
+ - 🚀 [Live demo](https://web-multiselect.keenmate.dev)
39
+ - 📘 [Usage / API reference](./docs/usage.md) — attributes, properties, methods, events.
40
+ - 🎨 [Theming](./docs/theming.md) — `--ms-*` variables, dark mode, cascade layers, Theme Designer integration.
41
+ - 📚 [Examples / cookbook](./docs/examples.md) — rich content, async search, virtual scroll, custom rendering, forms.
42
+ - ♿ [Accessibility](./docs/accessibility.md) — keyboard model, ARIA labels, focus behavior.
43
+
44
+ ## Install
45
+
46
+ ```bash
47
+ npm install @keenmate/web-multiselect
48
+ ```
49
+
50
+ ## Quick start
51
+
52
+ **Declarative — no JavaScript required:**
53
+
54
+ ```html
55
+ <script type="module">
56
+ import '@keenmate/web-multiselect';
57
+ </script>
58
+
59
+ <web-multiselect placeholder="Pick a country">
60
+ <option value="cz">Czech Republic</option>
61
+ <option value="sk">Slovakia</option>
62
+ <option value="at">Austria</option>
63
+ </web-multiselect>
64
+ ```
65
+
66
+ **Programmatic — dynamic data + events:**
67
+
68
+ ```html
69
+ <web-multiselect id="picker" search-placeholder="Search…"></web-multiselect>
70
+
71
+ <script type="module">
72
+ import '@keenmate/web-multiselect';
73
+
74
+ const picker = document.getElementById('picker');
75
+ picker.options = [
76
+ { value: 'js', label: 'JavaScript', icon: '🟨' },
77
+ { value: 'ts', label: 'TypeScript', icon: '🔷' },
78
+ { value: 'py', label: 'Python', icon: '🐍' }
79
+ ];
80
+
81
+ picker.addEventListener('change', (e) => {
82
+ console.log('Selected:', e.detail.selectedValues);
83
+ });
84
+ </script>
85
+ ```
86
+
87
+ See [docs/usage.md](./docs/usage.md) for the full API and [docs/examples.md](./docs/examples.md) for advanced patterns (async data, virtual scrolling, custom rendering, form integration).
88
+
89
+ ## Editor IntelliSense
90
+
91
+ The package ships editor metadata so you get autocomplete and hover docs for the
92
+ element's attributes, events, and all `--ms-*` CSS custom properties. All of it is
93
+ generated from the component's source on every build, so it never drifts.
94
+
95
+ - **JetBrains** (WebStorm / IntelliJ) — works automatically. The IDE discovers
96
+ `web-types.json` via the `web-types` field in `package.json`; no setup needed.
97
+ - **VS Code** — the data files ship but VS Code doesn't auto-discover them from a
98
+ dependency, so point your workspace at them once in `.vscode/settings.json`:
99
+
100
+ ```json
101
+ {
102
+ "html.customData": [
103
+ "./node_modules/@keenmate/web-multiselect/vscode.html-custom-data.json"
104
+ ],
105
+ "css.customData": [
106
+ "./node_modules/@keenmate/web-multiselect/vscode.css-custom-data.json"
107
+ ]
108
+ }
109
+ ```
110
+
111
+ `html.customData` powers tag/attribute completion on `<web-multiselect>`;
112
+ `css.customData` powers completion for the `--ms-*` theming variables. Reload
113
+ the window after adding them.
114
+
115
+ ## Browser support
116
+
117
+ Modern evergreen browsers — anything with native `customElements`, Shadow DOM, and CSS `@layer` support:
118
+
119
+ - Chrome / Edge 99+
120
+ - Firefox 97+
121
+ - Safari 15.4+
122
+
123
+ No polyfills are shipped. SSR-safe: the module imports without crashing in Node, but renders only after hydration in the browser.
124
+
125
+ ## Development
126
+
127
+ ```bash
128
+ # Install dependencies
129
+ npm install
130
+
131
+ # Start dev server (HMR)
132
+ npm run dev
133
+
134
+ # Build for production
135
+ npm run build
136
+
137
+ # Create package tarball
138
+ npm run package
139
+
140
+ # Run tests
141
+ npm run test:unit # Vitest (happy-dom) — fast logic checks
142
+ npm run test:e2e # Playwright (browser) — interaction/visual
143
+ npm test # both
144
+ ```
145
+
146
+ ## Code structure
147
+
148
+ Follows the BlissFramework four-layer web-component layout:
149
+
150
+ | Layer | File | Role |
151
+ |-------|------|------|
152
+ | Element | `src/web-component.ts` | `MultiSelectElement` — custom-element I/O wrapper, `ATTRIBUTE_TABLE`-driven |
153
+ | Logic | `src/multiselect.ts` | `WebMultiSelect<T>` — framework-agnostic core |
154
+ | Service | `src/tooltip.ts`, `src/virtual-scroll.ts` | single-purpose helpers (`Tooltip`, `VirtualScroll`) |
155
+ | Side | `src/types.ts`, `src/logger.ts`, `src/vendor/` | types, logging, vendored deps |
156
+
157
+ Two deviations from the canonical shape, both intentional:
158
+
159
+ - **`MultiSelectElement extends BaseElement`, not `HTMLElement` directly.** `BaseElement` is a local `const` resolving to `HTMLElement` in the browser and to a stub class under SSR (`typeof HTMLElement === 'undefined'`), so importing the module in Node doesn't throw. A literal `grep "extends HTMLElement"` structure check will not match here by design.
160
+ - **`src/vite-env.d.ts`** is a standard Vite ambient-types file, not part of the four-layer model.
161
+
162
+ ## License
163
+
164
+ MIT — see [LICENSE](./LICENSE).
165
+
166
+ ## Built with BlissFramework
167
+
168
+ Follows the [BlissFramework component guidelines](https://blissframework.dev/) for structure, theming, color-scheme, and accessibility. Per-check verifications run via `/validate-web-component`.
169
+
170
+ ## Credits
171
+
172
+ Created by [Keenmate](https://github.com/keenmate) as part of the Pure Admin design system.
@@ -49,11 +49,7 @@
49
49
  { "name": "base-border-radius-sm", "required": false, "usage": "Small radius (badges, buttons)" },
50
50
  { "name": "base-border-radius-md", "required": false, "usage": "Medium radius (input)" },
51
51
  { "name": "base-border-radius-lg", "required": false, "usage": "Large radius (dropdown)" },
52
- { "name": "base-input-size-xs-height", "required": false, "usage": "Extra small input height (multiplier)" },
53
- { "name": "base-input-size-sm-height", "required": false, "usage": "Small input height (multiplier)" },
54
- { "name": "base-input-size-md-height", "required": false, "usage": "Medium input height (multiplier)" },
55
- { "name": "base-input-size-lg-height", "required": false, "usage": "Large input height (multiplier)" },
56
- { "name": "base-input-size-xl-height", "required": false, "usage": "Extra large input height (multiplier)" },
52
+ { "name": "base-input-size-md-height", "required": false, "usage": "Medium input height multiplier (consumed by --ms-input-height)" },
57
53
  { "name": "base-rem", "required": false, "usage": "Global sizing unit — drives --ms-rem (falls back to 10px); scales the whole component" },
58
54
  { "name": "base-ease-standard", "required": false, "usage": "Standard easing curve (drives --ms-easing-snappy)" },
59
55
  { "name": "base-checkbox-border-color", "required": false, "usage": "Option checkbox border color" },
@@ -65,7 +61,10 @@
65
61
  { "name": "base-icon-close", "required": false, "usage": "Generic close glyph (fallback for badge-remove)" },
66
62
  { "name": "base-icon-search", "required": false, "usage": "Search glyph; inline Lucide SVG fallback" },
67
63
  { "name": "base-icon-filter", "required": false, "usage": "Funnel/filter glyph (fullscreen search-mode toggle); chained by --ms-icon-filter; inline Lucide SVG fallback" },
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
+ { "name": "base-icon-add", "required": false, "usage": "Alternate add glyph (fallback for base-icon-plus in the \"add new\" prompt)" },
68
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%. Chained by --ms-icon-check-size. Also sizes the indeterminate dash (shares the mask box)" },
69
68
  { "name": "base-icon-indeterminate", "required": false, "usage": "Tri-state (partial) checkbox dash glyph; chained by --ms-icon-indeterminate; inline Lucide SVG fallback" },
70
69
  { "name": "base-danger-bg", "required": false, "usage": "Error message background" },
71
70
  { "name": "base-danger-color", "required": false, "usage": "Error message text color" },
@@ -104,41 +103,17 @@
104
103
  { "name": "ms-input-border-focus", "category": "input", "usage": "Input border when focused" },
105
104
  { "name": "ms-input-placeholder-color", "category": "input", "usage": "Placeholder text color" },
106
105
  { "name": "ms-input-bg-disabled", "category": "input", "usage": "Disabled input background" },
107
- { "name": "ms-input-padding", "category": "input", "usage": "Input padding" },
108
- { "name": "ms-input-padding-right", "category": "input", "usage": "Input right padding (for toggle)" },
109
106
  { "name": "ms-input-height", "category": "input", "usage": "Input height" },
110
107
  { "name": "ms-input-font-size", "category": "input", "usage": "Input font size" },
111
108
  { "name": "ms-input-border-width", "category": "input", "usage": "Input border width" },
112
109
  { "name": "ms-input-border-radius", "category": "input", "usage": "Input border radius" },
113
110
  { "name": "ms-input-text", "category": "input", "usage": "Input text color" },
114
111
 
115
- { "name": "ms-input-size-xs-font", "category": "input-size", "usage": "XS size font" },
116
- { "name": "ms-input-size-xs-padding-v", "category": "input-size", "usage": "XS vertical padding" },
117
- { "name": "ms-input-size-xs-padding-h", "category": "input-size", "usage": "XS horizontal padding" },
118
- { "name": "ms-input-size-xs-height", "category": "input-size", "usage": "XS input height" },
119
- { "name": "ms-input-size-sm-font", "category": "input-size", "usage": "SM size font" },
120
- { "name": "ms-input-size-sm-padding-v", "category": "input-size", "usage": "SM vertical padding" },
121
- { "name": "ms-input-size-sm-padding-h", "category": "input-size", "usage": "SM horizontal padding" },
122
- { "name": "ms-input-size-sm-height", "category": "input-size", "usage": "SM input height" },
123
- { "name": "ms-input-size-md-font", "category": "input-size", "usage": "MD size font" },
124
- { "name": "ms-input-size-md-padding-v", "category": "input-size", "usage": "MD vertical padding" },
125
- { "name": "ms-input-size-md-padding-h", "category": "input-size", "usage": "MD horizontal padding" },
126
- { "name": "ms-input-size-md-height", "category": "input-size", "usage": "MD input height" },
127
- { "name": "ms-input-size-lg-font", "category": "input-size", "usage": "LG size font" },
128
- { "name": "ms-input-size-lg-padding-v", "category": "input-size", "usage": "LG vertical padding" },
129
- { "name": "ms-input-size-lg-padding-h", "category": "input-size", "usage": "LG horizontal padding" },
130
- { "name": "ms-input-size-lg-height", "category": "input-size", "usage": "LG input height" },
131
- { "name": "ms-input-size-xl-font", "category": "input-size", "usage": "XL size font" },
132
- { "name": "ms-input-size-xl-padding-v", "category": "input-size", "usage": "XL vertical padding" },
133
- { "name": "ms-input-size-xl-padding-h", "category": "input-size", "usage": "XL horizontal padding" },
134
- { "name": "ms-input-size-xl-height", "category": "input-size", "usage": "XL input height" },
135
112
 
136
- { "name": "ms-toggle-right", "category": "toggle", "usage": "Toggle icon right position" },
137
113
  { "name": "ms-toggle-color", "category": "toggle", "usage": "Toggle icon color" },
138
114
  { "name": "ms-toggle-icon-color", "category": "toggle", "usage": "Toggle icon color (semantic)" },
139
115
  { "name": "ms-toggle-icon-color-open", "category": "toggle", "usage": "Toggle icon color when open" },
140
116
 
141
- { "name": "ms-counter-offset", "category": "counter", "usage": "Counter badge offset" },
142
117
  { "name": "ms-counter-padding", "category": "counter", "usage": "Counter badge padding" },
143
118
  { "name": "ms-counter-bg", "category": "counter", "usage": "Counter badge background" },
144
119
  { "name": "ms-counter-color", "category": "counter", "usage": "Counter badge text color" },
@@ -302,6 +277,7 @@
302
277
  { "name": "ms-icon-filter", "category": "icon", "usage": "Funnel glyph (fullscreen search-mode toggle); chains to --base-icon-filter" },
303
278
  { "name": "ms-icon-check", "category": "checkbox", "usage": "Checkmark glyph (mask) for a checked checkbox/option; chains to --base-icon-check" },
304
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)" },
305
281
  { "name": "ms-badge-counter-bg", "category": "badge", "usage": "Counter badge background" },
306
282
  { "name": "ms-badge-counter-border", "category": "badge", "usage": "Counter badge border" },
307
283
  { "name": "ms-badge-counter-border-color", "category": "badge", "usage": "Counter badge border color" },
@@ -426,7 +402,6 @@
426
402
  { "name": "ms-debug-stats-border-radius", "category": "debug", "usage": "Debug stats nested panel corner radius" },
427
403
  { "name": "ms-debug-bullet-color", "category": "debug", "usage": "Debug stat bullet marker color" },
428
404
 
429
- { "name": "ms-transform-center-y", "category": "transform", "usage": "Center Y transform" },
430
405
  { "name": "ms-transform-rotate-180", "category": "transform", "usage": "180 degree rotation" },
431
406
  { "name": "ms-transform-scale-hover", "category": "transform", "usage": "Hover scale transform" },
432
407
  { "name": "ms-transform-scale-active", "category": "transform", "usage": "Active scale transform" },