@keenmate/web-multiselect 1.10.0 → 1.12.0-rc01

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
@@ -7,27 +7,27 @@ A lightweight, accessible multiselect web component with typeahead search, RTL l
7
7
 
8
8
  > **⚠️ Security Notice:** This component intentionally allows raw HTML in rendering callbacks to give developers full control over content display. If you display user-generated content, you must sanitize it yourself. See [HTML Injection (XSS) Notice](#html-injection-xss-notice) for the complete list of affected callbacks.
9
9
 
10
- ## What's New in v1.10.0
11
-
12
- - **`data-options` attribute on `<web-multiselect>`** set options declaratively from HTML, no JS bootstrap required (works alongside `initial-values` for pure-HTML / server-rendered / SharePoint workbench scenarios).
13
- - **`form.reset()` now clears the selection** — the element is now form-associated (`static formAssociated = true` + `ElementInternals` + `formResetCallback()`).
14
- - **Dropdown / hint / selected popover no longer clipped inside scrollable ancestors** Floating UI now uses `strategy: 'fixed'` for all three panels, so they escape `overflow: hidden|auto|scroll` containers (e.g. SharePoint Framework workbenches).
15
- - **`initial-values` now works when options arrive after init** — values are reconciled on every `options` mutation, not just at construction.
16
- - **Remove / close (×) buttons render as SVG masks** pixel-centered regardless of font; color still flows through the existing `--ms-*-color` variables via `currentColor`; three new `--ms-*-icon-size` variables for theming.
17
- - **Keyboard navigation now keeps working after a mouse click on an option** — previously, clicking an option moved focus from the search input to the option's checkbox (knocking the `keydown` listener offline) *and* left `focusedIndex` at its pre-click value, so subsequent ArrowDown / ArrowUp / Enter went nowhere visible. Click now anchors `focusedIndex` to the clicked option and refocuses the search input, so arrow keys continue from where you clicked and Enter toggles the option under the cursor.
18
- - **Count-clear / popover-close hover backdrop now matches the rest of the component** — was a circle (`border-radius: 50%`), now a small rounded rectangle (`--ms-border-radius-sm`) consistent with every other interactive element. Themes that prefer the circle can set `--ms-count-clear-border-radius` and `--ms-selected-popover-close-border-radius` back to `50%`.
19
- - **Keyboard `Enter` respects disabled options** previously only the click handler did.
20
- - **End-to-end test suite** — 114 Playwright specs across 19 fixture pages (`npm run test:e2e`).
21
- - **`THEMING.md`** new reference cataloguing every theme-able component state and the CSS variables that drive it.
22
-
23
- ## What's New in v1.9.0
24
-
25
- - **Live attribute / callback updates no longer rebuild the DOM** — `updateOptions(partial)` merges in place; selection state, scroll position, focus, and tooltips are preserved across attribute changes.
26
- - **9 previously-dead per-component CSS override hooks are now wired** — `--ms-hint-border-color`, `--ms-dropdown-border-color`, `--ms-actions-border-color`, `--ms-group-border-color`, `--ms-badge-counter-border-color`, `--ms-selected-popover-border-color`, `--ms-selected-popover-header-border-color`, `--ms-option-outline-color-focused`, `--ms-option-border-matched-color`.
27
- - **`selectAll` / `clearAll` now fire per-item `selectCallback` / `deselectCallback`** consumers wiring per-item analytics or side effects no longer silently miss bulk operations.
28
- - **New `Tooltip` class** consolidating three previous tooltip implementations; fixes a handle leak and a popover-vs-main-container collision.
29
- - **`--base-primary-bg` theming variable** `--ms-primary-bg` reads it first, then `--base-main-bg`, then a hardcoded default.
30
- - Plus many fixes across custom action buttons, grouped-option focus, badge cursors, focus rings, and logging.
10
+ ## What's New in v1.12.0-rc01
11
+
12
+ - **Dark mode now responds to framework theme classes** — `data-bs-theme="dark"` (Bootstrap 5.3+), `.dark` (Tailwind), and `data-theme="dark"` on any ancestor flip the component to dark, even on apps that flip classes without declaring `color-scheme`. Your own `--base-*` overrides on `:root` flow through unchanged — the component picks the dark branch of its built-in fallbacks without clobbering your theme. Symmetric `light` selectors let you force one widget back to light on a dark page. Per-instance `<web-multiselect data-theme="dark">` works as the highest-priority escape hatch.
13
+ - **CSS cascade layers (`@layer variables, component, overrides`)** — every internal rule lives in a named layer, so consumer CSS in the light DOM can override any component rule without `!important` or specificity arms races. Document any unlayered rule in your app's CSS and it wins automatically.
14
+ - **CSS file structure refactored into canonical Tier 1+2+3** `controls.css` for input chrome, `floating.css` for dropdown/hint/tooltip/popover, `states.css` for block-level state modifiers, `badges.css` / `count-display.css` for the two display modes, etc. Each file now owns one logical concern; the old mixed-bag `input-dropdown.css`, `tooltips-popover.css`, `badges-display.css` are gone. Only affects deep CSS imports; bundle output unchanged.
15
+ - **CSS source files no longer have underscore prefix** — `_variables.css` `variables.css`, etc. The underscore was a leftover SASS partial convention; these are plain CSS modules.
16
+ - **BEM-aligned class names** `.ms-wrapper` `.ms__wrapper`, `.ms-debug-info` `.ms__debug-info`, `.ms-debug-stats` `.ms__debug-stats`. The wrapper class is internal layout chrome; debug classes are dev-only. See CHANGELOG migration table if you'd styled any of these externally.
17
+ - **Debug panel now fully themeable** — 11 new `--ms-debug-*` variables (was hardcoded hex literals).
18
+ - **`"./manifest"` short-import** — `import manifest from '@keenmate/web-multiselect/manifest'` for tooling that reads the variable catalog.
19
+ - **`test/AUDIT.md`** living compliance tracker against the BlissFramework web-component guidelines (CSS structure, base variables, color scheme).
20
+
21
+ ## What's New in v1.11.0
22
+
23
+ - **OS-aware light/dark defaults via `light-dark()`** — set `color-scheme: dark` on your page (`:root`, `body`, etc.) and the multiselect picks readable dark text/background colors automatically. No more enumerating ~15 `--base-*` overrides just to get usable defaults on a dark theme.
24
+ - **Drift-detection warning for positioning edge cases** — if an exotic ancestor CSS property (e.g. `contain: paint`, or `container-type` in certain shadow-DOM layouts) makes the dropdown land somewhere other than where the library told the browser to put it, a `console.warn` fires once with the likely culprit element and an actionable fix suggestion.
25
+ - **Dropdown no longer stranded to the side when an ancestor uses `container-type`** Floating UI was walking up to a `container-type: inline-size` ancestor (notably pure-admin's `.pa-layout__main`), but the browser wouldn't actually anchor the fixed panel there. The library now uses a custom `getOffsetParent` that only walks up properties browsers reliably honor for fixed positioning.
26
+ - **Dropdown no longer opens shifted to the side of its input** — when the dropdown's natural content was wider than the input, Floating UI's `shift()` middleware was measuring the unclamped panel and pushing it left, then the subsequent width clamp left it stranded next to the input. Panel sizing now happens before positioning. Same fix applies to the selected-items popover.
27
+ - **`--base-*` taxonomy aligned with KeenMate cross-component naming** (theming change — see CHANGELOG migration table): `--base-primary-bg` `--base-hover-bg`, `--base-primary-bg-hover` `--base-active-bg`. `--base-dropdown-bg` and `--base-tooltip-bg` continue to work; new chain fallbacks to `--base-elevated-bg` / `--base-inverse-bg`.
28
+ - **Option hover stays visible on dark themes** `--ms-primary-bg` now mixes 8% of the text color into the main background by default, so the hover is always a visible step toward the text. No more invisible hover when the consumer forgets to override `--base-hover-bg`.
29
+ - **New `examples-positioning.html`** walks through baseline / transformed-ancestor / container-type / drift-detection scenarios.
30
+ - **New dark-mode e2e suite** 4 specs verifying WCAG-AA option contrast on a dark page across fully-themed, minimal-override, and pure OS-inheritance configurations.
31
31
 
32
32
  ## Features
33
33
 
@@ -1662,8 +1662,9 @@ KeenMate components support a **two-layer theming architecture**:
1662
1662
  :root {
1663
1663
  /* Base layer - single source of truth */
1664
1664
  --base-accent-color: #3b82f6;
1665
- --base-primary-bg: #ffffff;
1666
- --base-text-primary: #111827;
1665
+ --base-main-bg: #ffffff;
1666
+ --base-hover-bg: #f3f4f6;
1667
+ --base-text-color-1: #111827;
1667
1668
 
1668
1669
  /* Components reference base layer */
1669
1670
  --ms-accent-color: var(--base-accent-color);
@@ -1730,6 +1731,31 @@ You can customize the component using CSS variables even with just a `<script>`
1730
1731
  </style>
1731
1732
  ```
1732
1733
 
1734
+ ### Dark mode — supported signals
1735
+
1736
+ Since v1.12.0 the multiselect honors **five different signals** for switching to dark mode. Pick whichever fits your app; you don't need to wire them all up.
1737
+
1738
+ | # | Signal | Set by | Example |
1739
+ |---|--------|--------|---------|
1740
+ | 1 | OS preference + page `color-scheme` | App author | `html { color-scheme: light dark }` — multiselect picks the OS branch automatically. |
1741
+ | 2 | Page-level `color-scheme: dark` | App author | `body { color-scheme: dark }` — flips every multiselect on the page to dark. |
1742
+ | 3 | Framework data-attribute on ancestor | Bootstrap, Pure Admin, custom apps | `<html data-bs-theme="dark">` or `<div data-theme="dark">…</div>` |
1743
+ | 4 | Framework class on ancestor | Tailwind, hand-rolled toggles | `<html class="dark">` |
1744
+ | 5 | Per-instance attribute on host | App author, for one widget | `<web-multiselect data-theme="dark">` |
1745
+
1746
+ **Precedence** (highest wins): per-instance (#5) → framework ancestor (#3, #4) → page color-scheme (#1, #2).
1747
+
1748
+ #### Forcing a single widget back to light
1749
+
1750
+ If your page is dark but you want one multiselect to render light:
1751
+
1752
+ ```html
1753
+ <!-- on a body { color-scheme: dark } page -->
1754
+ <web-multiselect data-theme="light"></web-multiselect>
1755
+ ```
1756
+
1757
+ This works for any of signals #3–#5. The symmetric `data-theme="light"`, `data-bs-theme="light"`, `.light` selectors restore the light palette inside the affected scope.
1758
+
1733
1759
  ### Available CSS Variables
1734
1760
 
1735
1761
  The component exposes **150+ CSS custom properties** defined at the `:host` level, making them inspectable and overridable. Below are the **50+ most commonly customized variables** organized by category.
@@ -1756,7 +1782,7 @@ All CSS custom properties are now defined at the `:host` level in the compiled C
1756
1782
  ```
1757
1783
 
1758
1784
  For the complete list of all available CSS variables, see:
1759
- - [_variables.css](./src/css/_variables.css) - All 150+ CSS custom properties at `:host` level
1785
+ - [variables.css](./src/css/variables.css) - All 150+ CSS custom properties at `:host` level
1760
1786
 
1761
1787
  #### Colors
1762
1788
 
@@ -1788,7 +1814,7 @@ For the complete list of all available CSS variables, see:
1788
1814
 
1789
1815
  | Variable | Default | Description |
1790
1816
  |----------|---------|-------------|
1791
- | `--ms-dropdown-bg` | `var(--base-dropdown-bg, #ffffff)` | Dropdown background |
1817
+ | `--ms-dropdown-bg` | `var(--base-dropdown-bg, var(--base-elevated-bg, light-dark(#ffffff, #1a1a1a)))` | Dropdown background (auto-adapts to OS dark mode) |
1792
1818
  | `--ms-dropdown-border` | `var(--ms-border-color)` | Dropdown border color |
1793
1819
  | `--ms-dropdown-shadow` | (box shadow) | Dropdown shadow |
1794
1820
  | `--ms-dropdown-max-height` | `20rem` | Max height of dropdown |
@@ -1860,7 +1886,7 @@ For the complete list of all available CSS variables, see:
1860
1886
 
1861
1887
  | Variable | Default | Description |
1862
1888
  |----------|---------|-------------|
1863
- | `--ms-tooltip-bg` | `var(--base-tooltip-bg, #333333)` | Tooltip background color |
1889
+ | `--ms-tooltip-bg` | `var(--base-tooltip-bg, var(--base-inverse-bg, light-dark(#333333, #f5f5f5)))` | Tooltip background (auto-adapts to OS dark mode) |
1864
1890
  | `--ms-tooltip-color` | `var(--ms-tooltip-text-color)` | Tooltip text color |
1865
1891
  | `--ms-tooltip-padding` | `0.5rem 0.75rem` | Tooltip padding |
1866
1892
  | `--ms-tooltip-border-radius` | `0.375rem` | Tooltip border radius |
@@ -1890,6 +1916,25 @@ For the complete list of all available CSS variables, see:
1890
1916
  | `--ms-shadow-xl` | (box shadow) | Extra large shadow |
1891
1917
  | `--ms-disabled-opacity` | `0.5` | Opacity for disabled state |
1892
1918
 
1919
+ ### Cascade layers / override contract
1920
+
1921
+ Since v1.12.0, the component's internal CSS is organized into named `@layer`s:
1922
+
1923
+ ```css
1924
+ @layer variables, component, overrides;
1925
+ ```
1926
+
1927
+ This gives consumers a predictable escape hatch when they need to override a rule from outside the shadow DOM (e.g. via `web-multiselect ::part(...)` or descendant selectors that reach into composed light DOM):
1928
+
1929
+ | Where your rule lives | Wins against |
1930
+ |---|---|
1931
+ | Unlayered consumer rule | Every internal layer (no `!important` needed) |
1932
+ | Consumer `@layer overrides` block | Component's `overrides` layer if loaded later in the stylesheet stack |
1933
+ | `:root { --base-* }` declaration | Component's `variables` layer trivially |
1934
+ | `web-multiselect { --ms-* }` element selector | Same as above, with higher specificity |
1935
+
1936
+ In practice you rarely need to think about layers — variables-first theming (`--ms-*` and `--base-*` overrides) covers ~95% of customization. Layers exist for the residual 5% where you need to flip a property the variable system doesn't expose.
1937
+
1893
1938
  ### Advanced: Direct CSS Import
1894
1939
 
1895
1940
  For users who want to import the raw CSS source files:
@@ -1899,8 +1944,8 @@ For users who want to import the raw CSS source files:
1899
1944
  @import '@keenmate/web-multiselect/css';
1900
1945
 
1901
1946
  /* Or import individual partials */
1902
- @import '@keenmate/web-multiselect/src/css/_variables.css';
1903
- @import '@keenmate/web-multiselect/src/css/_base.css';
1947
+ @import '@keenmate/web-multiselect/src/css/variables.css';
1948
+ @import '@keenmate/web-multiselect/src/css/base.css';
1904
1949
  /* ... etc */
1905
1950
  ```
1906
1951
 
@@ -14,7 +14,8 @@
14
14
  { "name": "base-text-color-4", "required": false, "usage": "Quaternary text (placeholders)" },
15
15
  { "name": "base-text-color-on-accent", "required": false, "usage": "Text on accent backgrounds (badges, checkboxes)" },
16
16
  { "name": "base-main-bg", "required": true, "usage": "Primary background, hint background, actions background" },
17
- { "name": "base-hover-bg", "required": false, "usage": "Hover states for options, badges, buttons" },
17
+ { "name": "base-hover-bg", "required": false, "usage": "Hover background (drives --ms-primary-bg: option hover/focus, action-button hover, counter hover)" },
18
+ { "name": "base-active-bg", "required": false, "usage": "Active/pressed background (drives --ms-primary-bg-hover: selected-active, counter badge)" },
18
19
  { "name": "base-disabled-bg", "required": false, "usage": "Disabled/readonly surface backgrounds" },
19
20
  { "name": "base-border-color", "required": true, "usage": "Input, dropdown, action button borders" },
20
21
  { "name": "base-border", "required": false, "usage": "Full border shorthand" },
@@ -25,10 +26,12 @@
25
26
  { "name": "base-input-border-focus", "required": false, "usage": "Input border when focused" },
26
27
  { "name": "base-input-placeholder-color", "required": false, "usage": "Placeholder text color" },
27
28
  { "name": "base-input-bg-disabled", "required": false, "usage": "Disabled input background" },
28
- { "name": "base-dropdown-bg", "required": false, "usage": "Dropdown and popover backgrounds" },
29
+ { "name": "base-dropdown-bg", "required": false, "usage": "Dropdown and popover backgrounds (primary; falls back to base-elevated-bg)" },
30
+ { "name": "base-elevated-bg", "required": false, "usage": "Elevated surface (fallback for dropdown and popover backgrounds)" },
29
31
  { "name": "base-dropdown-border", "required": false, "usage": "Dropdown border" },
30
32
  { "name": "base-dropdown-box-shadow", "required": false, "usage": "Dropdown shadow" },
31
- { "name": "base-tooltip-bg", "required": false, "usage": "Tooltip background" },
33
+ { "name": "base-tooltip-bg", "required": false, "usage": "Tooltip background (primary; falls back to base-inverse-bg)" },
34
+ { "name": "base-inverse-bg", "required": false, "usage": "Inverse surface (fallback for tooltip background)" },
32
35
  { "name": "base-tooltip-text-color", "required": false, "usage": "Tooltip text color" },
33
36
  { "name": "base-font-family", "required": false, "usage": "All text in component" },
34
37
  { "name": "base-font-size-2xs", "required": false, "usage": "Smallest text (multiplier)" },
@@ -69,8 +72,8 @@
69
72
  { "name": "ms-text-primary", "category": "text", "usage": "Legacy alias for text-color-1" },
70
73
  { "name": "ms-text-secondary", "category": "text", "usage": "Legacy alias for text-color-3" },
71
74
 
72
- { "name": "ms-primary-bg", "category": "surface", "usage": "Primary background color" },
73
- { "name": "ms-primary-bg-hover", "category": "surface", "usage": "Primary background hover" },
75
+ { "name": "ms-primary-bg", "category": "surface", "usage": "Hover/focus background (options, action buttons, counter); reads --base-hover-bg" },
76
+ { "name": "ms-primary-bg-hover", "category": "surface", "usage": "Active/stronger background (counter badge text); reads --base-active-bg" },
74
77
 
75
78
  { "name": "ms-border-color", "category": "border", "usage": "Default border color" },
76
79
  { "name": "ms-border", "category": "border", "usage": "Full border shorthand" },
@@ -387,6 +390,18 @@
387
390
  { "name": "ms-scrollbar-thumb-bg-hover", "category": "scrollbar", "usage": "Scrollbar thumb hover background" },
388
391
  { "name": "ms-scrollbar-thumb-border-radius", "category": "scrollbar", "usage": "Scrollbar thumb border radius" },
389
392
 
393
+ { "name": "ms-debug-bg", "category": "debug", "usage": "Debug panel background" },
394
+ { "name": "ms-debug-border-color", "category": "debug", "usage": "Debug panel border" },
395
+ { "name": "ms-debug-text-color", "category": "debug", "usage": "Debug panel text" },
396
+ { "name": "ms-debug-border-radius", "category": "debug", "usage": "Debug panel corner radius" },
397
+ { "name": "ms-debug-summary-color", "category": "debug", "usage": "Debug section heading color" },
398
+ { "name": "ms-debug-summary-bg-hover", "category": "debug", "usage": "Debug section heading hover background" },
399
+ { "name": "ms-debug-summary-outline-color", "category": "debug", "usage": "Debug section heading focus outline" },
400
+ { "name": "ms-debug-summary-border-radius", "category": "debug", "usage": "Debug section heading corner radius" },
401
+ { "name": "ms-debug-stats-bg", "category": "debug", "usage": "Debug stats nested panel background" },
402
+ { "name": "ms-debug-stats-border-radius", "category": "debug", "usage": "Debug stats nested panel corner radius" },
403
+ { "name": "ms-debug-bullet-color", "category": "debug", "usage": "Debug stat bullet marker color" },
404
+
390
405
  { "name": "ms-transform-center-y", "category": "transform", "usage": "Center Y transform" },
391
406
  { "name": "ms-transform-rotate-180", "category": "transform", "usage": "180 degree rotation" },
392
407
  { "name": "ms-transform-scale-hover", "category": "transform", "usage": "Hover scale transform" },
package/dist/index.d.ts CHANGED
@@ -553,6 +553,7 @@ export declare class WebMultiSelect<T = any> {
553
553
  private isRTL;
554
554
  private effectiveBadgesPosition;
555
555
  private justClosedViaClick;
556
+ private positioningDriftWarned;
556
557
  private dropdownCleanup;
557
558
  private hintCleanup;
558
559
  private selectedPopoverCleanup;
@@ -665,6 +666,18 @@ export declare class WebMultiSelect<T = any> {
665
666
  * compute then lock the resulting placement, optionally clamp by dropdownMin/MaxWidth.
666
667
  */
667
668
  private anchorFloatingPanel;
669
+ /**
670
+ * Sanity-check that the browser placed the panel where we told it to. With `position: fixed`
671
+ * and no transformed/perspective/filter ancestor, `left: ${x}px` must render at viewport-x = x.
672
+ * If the rendered position drifts, the consumer has an ancestor that establishes a fixed
673
+ * containing block but isn't on our reliable-anchors list (likely `contain: paint|layout|strict`
674
+ * or `container-type` — which the spec says creates a CB but the browser's actual behavior
675
+ * varies across shadow-DOM scenarios). We can't fix it from inside the library, but we can
676
+ * surface a clear warning so the developer knows where to look.
677
+ *
678
+ * Fires at most once per multiselect instance to avoid flooding the console during autoUpdate.
679
+ */
680
+ private verifyPanelLanded;
668
681
  private positionDropdown;
669
682
  private positionHint;
670
683
  private parseInitialSelection;