@keenmate/pure-admin-core 2.9.0-rc03 → 2.9.0-rc04

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.
Files changed (35) hide show
  1. package/README.md +53 -8
  2. package/dist/css/main.css +540 -2
  3. package/package.json +3 -1
  4. package/snippets/buttons.html +51 -0
  5. package/snippets/cards.html +34 -4
  6. package/snippets/comparison.html +26 -22
  7. package/snippets/manifest.json +180 -115
  8. package/snippets/range-group.html +125 -0
  9. package/snippets/splitter.html +44 -38
  10. package/snippets/statistics.html +31 -0
  11. package/src/js/btn-split-auto-absorb.js +327 -0
  12. package/src/js/command-palette.js +472 -0
  13. package/src/js/file-selector.js +1275 -0
  14. package/src/js/internal/logging.js +121 -0
  15. package/src/js/logic-tree-renderer.js +303 -0
  16. package/src/js/modal-dialogs.js +460 -0
  17. package/src/js/overflow.js +371 -0
  18. package/src/js/pa-stat-fit.js +184 -0
  19. package/src/js/range-group.js +663 -0
  20. package/src/js/search-autocomplete-v2.js +907 -0
  21. package/src/js/search-autocomplete.js +434 -0
  22. package/src/js/settings-panel.js +245 -0
  23. package/src/js/split-button.js +141 -0
  24. package/src/js/splitter.js +1323 -0
  25. package/src/js/toast-service.js +302 -0
  26. package/src/js/tooltips-popovers.js +275 -0
  27. package/src/js/virtual-scroll.js +143 -0
  28. package/src/js/virtual-textbox.js +803 -0
  29. package/src/scss/_core.scss +7 -0
  30. package/src/scss/core-components/_buttons.scss +44 -0
  31. package/src/scss/core-components/_cards.scss +57 -0
  32. package/src/scss/core-components/_overflow.scss +50 -0
  33. package/src/scss/core-components/_range-group.scss +474 -0
  34. package/src/scss/core-components/_statistics.scss +163 -0
  35. package/src/scss/variables/_components.scss +34 -2
package/README.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  Lightweight, data-focused CSS/SCSS admin framework with Corporate theme as default.
4
4
 
5
+ ## What's New in 2.9.0-rc04
6
+
7
+ - **New: Range Group — a compact multi-range filter (`.pa-range-group` + a reusable `.pa-range` slider).** One toggle summarises several numeric filters inline (`AGE 25–60 / SALARY $40,000–$200,000 / CHILDREN 2+`) and expands into a Floating-UI panel with one slider per dimension — standing in for a row of separate sliders in a filter bar. Per-row dual-thumb range or single-thumb threshold, RTL-correct by construction, with a `--pa-range-*` theming token namespace and a bubbling `change` / `apply` / `reset` event contract keyed by dimension.
8
+ - **Range Group sliders are richly interactive.** Major/minor tick marks with optional value labels, opt-in snap-to-tick, click-anywhere-to-seek tracks that grab the nearest handle, and five selectable handle shapes (circle, rectangle, bar, chevron, needle) — all pure CSS, mixable per row. Behaviour ships in the package as `range-group.js` (exposed via the `./js/*` export).
9
+ - **The package now ships the components' JavaScript (`src/js/`, via the `./js/*` export).** Interactive behaviours (split-button dropdowns, splitter drag, tooltips/popovers, toasts, command palette, …) were previously referenced in markup but never published; consumers now get the actual implementations instead of dangling handlers.
10
+ - **Stat square — fit-to-box mode (`data-pa-stat-fit` + `pa-stat-fit.js`).** The primary number scales to fill the tile at the largest font that still fits, and supporting rows (symbol, label, change, context) reveal by priority as the tile earns both width and height — one tile that adapts across dashboard sizes instead of a fixed layout.
11
+ - **Auto-absorb split-button toolbars (`.pa-btn-toolbar` + `pa-btn-split--auto-absorb`).** Sibling buttons fold into a split button's dropdown when the row runs out of width and pop back out as space returns, with per-button drop priority — progressive collapse without a parallel "more" menu.
12
+ - **`.pa-splitter` — `pa-splitter:resize` / `:collapse` / `:expand` `CustomEvent`s.** Three bubbling events fire from the affected pane (`resize.detail = { index, pane, size }`, collapse / expand detail = `{ index, pane }`) so consumers can wire sidebars, analytics, or persistence-coupled toolbars on the splitter root with a single delegated listener. `resize` is unfiltered — debounce in the handler. `collapse` / `expand` fire only on user-initiated transitions; init from saved state stays silent.
13
+ - **`.pa-splitter` — drag against a rail'd primary is now asymmetric.** Dragging outward (the direction that would grow the pane) releases the rail and follows the cursor; dragging inward (into the rail, would shrink further) is fully inert. Previously the drag-from-rail path pre-cleared `isMin` on `pointerdown` regardless of direction, so the card visually flashed out of its rail state before the user could express expand intent in the opposite direction — read as the splitter "guessing wrong". Mirrors `svelte-fluentui/MultiSplitter`'s drag-to-be-explicit gate.
14
+ - **`.pa-splitter` — tap-without-drag on the gutter no longer restores a rail'd pane.** Restore gestures are now: rail-body click, dblclick on the gutter, focus + `Enter`/`Space`, `[data-pa-splitter-toggle]` button, or drag outward past the snap threshold. Users expected drag-to-be-explicit and didn't connect "tap the gutter" with the rail-pane body as a restore surface.
15
+ - **`.pa-splitter` — per-pane orientation class** (`pa-splitter__pane--horizontal` / `--vertical`) stamped at registration. Forward-compat hook for theme authors writing nested-splitter-safe rail CSS without walking the DOM to figure out the enclosing splitter's orientation.
16
+
5
17
  ## What's New in 2.9.0-rc03
6
18
 
7
19
  - **`.pa-splitter` drag model reworked to "rebalance-on-drag".** Each gutter now resizes only its primary neighbour (edge-closer side, LTR/RTL tiebreaker on ties); the matching delta is absorbed by the immediate adjacent non-rail pane (classic boundary feel), or — when the adjacent pane is rail — tunnels through the rail wall to the contiguous non-rail block beyond. Eliminates the "boundary locks when both sides are rail" deadlock from the prior model — drag-from-rail with a railed neighbour now grows the primary by pulling room from the next section.
@@ -11,14 +23,6 @@ Lightweight, data-focused CSS/SCSS admin framework with Corporate theme as defau
11
23
  - **Gutter highlighting now isolates the dragged gutter only.** During a drag, the splitter `--dragging` modifier suppresses `:hover` / `:focus-visible` on sibling gutters so they don't light up as the cursor passes over them or as a stale focus ring lingers — only the active gutter carries the highlight.
12
24
  - **Splitter padding subtracted from available space.** Flex children sit inside the splitter's content box but `clientWidth` includes padding — without subtracting it the last pane's `flex-basis` overflowed and got clipped by `overflow: hidden`. Visible in the N-pane demo where the rightmost rail's right edge touched the splitter border.
13
25
 
14
- ## What's New in 2.9.0-rc02
15
-
16
- - **`.pa-splitter` — resizable container with optional collapse-to-rail.** Vanilla JS + SCSS, two orientations (`--horizontal` / `--vertical`), per-instance constraints (`min-start` / `max-start` accept px or %), and `localStorage` persistence via `data-pa-splitter-id`. Pointer-event drag (mouse / touch / pen), `gap`-aware constraint math, keyboard a11y on the gutter (`role="separator"`, arrow keys, `Home`/`End`, `Enter`/`Space` to toggle), and a `ResizeObserver`-driven re-clamp on container resize. Opt-in `data-pa-splitter-minimize="start" | "end"` collapses a pane to a thin sideways-rotated header rail; dragging past a snap threshold commits, dragging back outward restores.
17
- - **`.pa-splitter` N-pane mode (3+ panes).** `init()` is now a dispatcher: the legacy 2-pane path (selected by `pane--start` + `pane--end` markup) is preserved byte-identical; everything else goes through `initNPane()`. Per-pane API: `data-pa-splitter-size="240px|30%"` for initial sizing, `-min` / `-max` for per-pane clamps, `data-pa-splitter-minimize` as presence marker (first / last pane only — middle-pane rail rotation has nowhere clean to dock). Drag math is per-gutter stop-at-min (Split.js basic-mode semantics). Container-resize redistributes proportionally to unminimized panes; minimized panes stay pinned to rail. Storage shape versioned under the same `pa-splitter:<id>` key.
18
- - **`pa-card__actions--responsive` — CSS-only collapse to a split button when the header runs out of space.** Render both forms inline (`pa-card__actions-full` + `pa-card__actions-collapsed`, the latter hosting a `pa-btn-split`); a container query on `.pa-card__header:has(> .pa-card__actions--responsive)` swaps which is visible. Threshold lives in one SCSS variable (`$card-actions-collapse-at`, default `28rem`). No JS, no `ResizeObserver` — but action data is duplicated in the DOM.
19
- - **`pa-card__actions--overflow` — JS-driven progressive collapse into a "..." menu.** Complement to `--responsive`: walks buttons into an overflow menu one at a time as space shrinks, restoring them in original DOM order as it grows. `data-pa-actions-overflow-from="end" | "start"` controls drop direction; `data-pa-actions-priority="N"` per button pins primary actions. Menu reuses `pa-btn-split__menu` styling (one menu source across split-button and overflow). Positioning via Floating UI (`computePosition` + `offset` + `flip` + `shift` + `autoUpdate`), with a hand-rolled fallback when `FloatingUIDOM` isn't loaded. Auto-closes on any wrapper resize or when the trigger goes `display: none` (e.g. splitter rail mode).
20
- - **`pa-btn--ghost` variant defined in core.** Previously referenced by demo files (`alerts`, `notifications`, `splitter`) but had no SCSS rule — every usage silently rendered as a base `.pa-btn`. Now defined in `_buttons.scss`: transparent background and border, `var(--pa-text-secondary)` text, hover snaps to `var(--pa-surface-hover)` + `--pa-text-color-1`. No new tokens introduced; themes need a rebuild to pick up the variant.
21
-
22
26
  ## Installation
23
27
 
24
28
  ```bash
@@ -65,6 +69,47 @@ $btn-primary-bg: #your-button-color;
65
69
  @import '@keenmate/pure-admin-core/src/scss/main';
66
70
  ```
67
71
 
72
+ ### JavaScript (Interactive Components)
73
+
74
+ The framework is CSS-first, but several components need a small vanilla-JS
75
+ behavior to work (no framework runtime, no build step required). These ship
76
+ in `src/js/` and are exposed under the `./js/*` export:
77
+
78
+ ```js
79
+ // Split buttons + the auto-absorb button toolbar
80
+ import '@keenmate/pure-admin-core/js/split-button.js'; // toggleSplitMenu()
81
+ import '@keenmate/pure-admin-core/js/btn-split-auto-absorb.js';
82
+ ```
83
+
84
+ Or via a plain script tag:
85
+
86
+ ```html
87
+ <script src="node_modules/@keenmate/pure-admin-core/src/js/split-button.js"></script>
88
+ ```
89
+
90
+ Each file is a self-contained IIFE that wires itself up on load (and exposes a
91
+ small global / `window.Pa*` API for re-init after dynamic markup injection).
92
+ Pull in only the files for the components you use:
93
+
94
+ | Component | File(s) |
95
+ |---|---|
96
+ | Split button dropdown | `split-button.js` (defines `toggleSplitMenu`) |
97
+ | Auto-absorb toolbar (`.pa-btn-toolbar`) | `split-button.js` **+** `btn-split-auto-absorb.js` |
98
+ | Tooltips / popovers | `tooltips-popovers.js` |
99
+ | Splitter | `splitter.js` |
100
+ | Toasts | `toast-service.js` |
101
+ | Command palette | `command-palette.js` |
102
+ | Progressive overflow | `overflow.js` |
103
+ | Fit-to-box stat | `pa-stat-fit.js` |
104
+ | Modal dialogs | `modal-dialogs.js` |
105
+ | Range group (`.pa-range-group` / `.pa-range`) | `range-group.js` |
106
+
107
+ **Positioning dependency:** `split-button.js`, `tooltips-popovers.js`, and
108
+ `range-group.js` use
109
+ [Floating UI](https://floating-ui.com) for dropdown/tooltip placement and
110
+ expect it on the page as the `window.FloatingUIDOM` global (e.g. the
111
+ `@floating-ui/dom` UMD build). Load it before those scripts.
112
+
68
113
  ## Using HTML Snippets
69
114
 
70
115
  The `snippets/` directory contains clean HTML patterns for all components:
package/dist/css/main.css CHANGED
@@ -5490,11 +5490,29 @@ body:not(.sidebar-hidden) .pa-layout__sidebar--icon-collapse .pa-sidebar__icon {
5490
5490
  .pa-card__header:has(> .pa-card__actions--overflow) > .pa-card__title {
5491
5491
  min-width: 6rem;
5492
5492
  }
5493
+ .pa-card__header:has(.pa-btn-toolbar) > h1, .pa-card__header:has(.pa-btn-toolbar) > h2, .pa-card__header:has(.pa-btn-toolbar) > h3, .pa-card__header:has(.pa-btn-toolbar) > h4, .pa-card__header:has(.pa-btn-toolbar) > h5, .pa-card__header:has(.pa-btn-toolbar) > h6, .pa-card__header:has(.pa-btn-toolbar) > .pa-card__title {
5494
+ flex-shrink: 9999;
5495
+ min-width: 8rem;
5496
+ }
5497
+ .pa-card__header .pa-card__actions:has(> .pa-btn-toolbar) {
5498
+ flex-shrink: 1;
5499
+ min-width: 0;
5500
+ }
5493
5501
  .pa-card__header .pa-btn {
5494
5502
  margin-top: -0.25rem;
5495
5503
  margin-bottom: -0.25rem;
5496
5504
  flex-shrink: 0;
5497
5505
  }
5506
+ .pa-card__header .pa-btn-toolbar,
5507
+ .pa-card__header .pa-card__actions--overflow {
5508
+ margin-top: -0.25rem;
5509
+ margin-bottom: -0.25rem;
5510
+ }
5511
+ .pa-card__header .pa-btn-toolbar .pa-btn,
5512
+ .pa-card__header .pa-card__actions--overflow .pa-btn {
5513
+ margin-top: 0;
5514
+ margin-bottom: 0;
5515
+ }
5498
5516
  .pa-card__header--underlined h1, .pa-card__header--underlined h2, .pa-card__header--underlined h3, .pa-card__header--underlined h4, .pa-card__header--underlined h5, .pa-card__header--underlined h6 {
5499
5517
  border-bottom: 2px solid var(--pa-accent);
5500
5518
  padding-bottom: 0.8rem;
@@ -6469,6 +6487,7 @@ a.pa-card p {
6469
6487
  padding: 2.4rem;
6470
6488
  min-height: 12.8rem;
6471
6489
  min-width: 12.8rem;
6490
+ max-height: 16rem;
6472
6491
  position: relative;
6473
6492
  border-radius: var(--pa-border-radius);
6474
6493
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
@@ -6480,7 +6499,7 @@ a.pa-card p {
6480
6499
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
6481
6500
  }
6482
6501
  .pa-stat--square .pa-stat__number {
6483
- font-size: clamp(3.2rem, 25cqi, 9.6rem);
6502
+ font-size: clamp(3.2rem, 25cqi, 7.2rem);
6484
6503
  font-weight: 700;
6485
6504
  line-height: 1;
6486
6505
  color: inherit;
@@ -6490,7 +6509,7 @@ a.pa-card p {
6490
6509
  white-space: nowrap;
6491
6510
  }
6492
6511
  .pa-stat--square .pa-stat__symbol {
6493
- font-size: clamp(1.6rem, 12cqi, 4.8rem);
6512
+ font-size: clamp(1.6rem, 12cqi, 3.6rem);
6494
6513
  font-weight: 700;
6495
6514
  line-height: 1;
6496
6515
  color: inherit;
@@ -6526,6 +6545,10 @@ a.pa-card p {
6526
6545
  background-color: var(--pa-warning-bg);
6527
6546
  color: var(--pa-btn-warning-text);
6528
6547
  }
6548
+ .pa-stat--square.pa-stat--warning .pa-stat__number {
6549
+ text-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
6550
+ filter: none;
6551
+ }
6529
6552
  .pa-stat--square.pa-stat--danger {
6530
6553
  background-color: var(--pa-danger-bg);
6531
6554
  color: var(--pa-btn-danger-text);
@@ -6534,6 +6557,127 @@ a.pa-card p {
6534
6557
  background-color: var(--pa-text-color-2);
6535
6558
  color: var(--pa-btn-primary-text);
6536
6559
  }
6560
+ .pa-stat--square[data-pa-stat-fit] {
6561
+ container-type: size;
6562
+ container-name: pa-stat;
6563
+ flex-direction: column;
6564
+ flex-wrap: nowrap;
6565
+ align-content: stretch;
6566
+ align-items: stretch;
6567
+ max-height: none;
6568
+ }
6569
+ .pa-stat--square[data-pa-stat-fit] .pa-stat__slot {
6570
+ flex: 1 1 auto;
6571
+ min-height: 0;
6572
+ display: flex;
6573
+ align-items: center;
6574
+ justify-content: flex-start;
6575
+ }
6576
+ .pa-stat--square[data-pa-stat-fit] .pa-stat__group {
6577
+ display: inline-flex;
6578
+ align-items: baseline;
6579
+ column-gap: 0.15em;
6580
+ line-height: 1;
6581
+ white-space: nowrap;
6582
+ font-weight: 700;
6583
+ }
6584
+ .pa-stat--square[data-pa-stat-fit] .pa-stat__group .pa-stat__number {
6585
+ font-size: 1em;
6586
+ }
6587
+ .pa-stat--square[data-pa-stat-fit] .pa-stat__group .pa-stat__symbol {
6588
+ font-size: 0.5em;
6589
+ opacity: 0.85;
6590
+ }
6591
+ .pa-stat--square[data-pa-stat-fit] .pa-stat__meta {
6592
+ display: flex;
6593
+ flex-direction: column;
6594
+ min-width: 0;
6595
+ flex: 0 0 auto;
6596
+ font-size: 1.2rem;
6597
+ }
6598
+ .pa-stat--square[data-pa-stat-fit] .pa-stat__label {
6599
+ flex: 0 0 auto;
6600
+ margin-top: 0.35em;
6601
+ font-size: 1em;
6602
+ }
6603
+ .pa-stat--square[data-pa-stat-fit] .pa-stat__change {
6604
+ flex: 0 0 auto;
6605
+ margin-top: 0.35em;
6606
+ font-size: 0.9em;
6607
+ font-weight: 600;
6608
+ line-height: 1.2;
6609
+ color: inherit;
6610
+ white-space: nowrap;
6611
+ overflow: hidden;
6612
+ text-overflow: ellipsis;
6613
+ }
6614
+ .pa-stat--square[data-pa-stat-fit] .pa-stat__change--positive {
6615
+ color: var(--pa-positive);
6616
+ }
6617
+ .pa-stat--square[data-pa-stat-fit] .pa-stat__change--neutral {
6618
+ color: var(--pa-neutral);
6619
+ }
6620
+ .pa-stat--square[data-pa-stat-fit] .pa-stat__change--negative {
6621
+ color: var(--pa-negative);
6622
+ }
6623
+ .pa-stat--square[data-pa-stat-fit] .pa-stat__context {
6624
+ flex: 0 0 auto;
6625
+ margin-top: 0.35em;
6626
+ font-size: 0.8em;
6627
+ opacity: 0.7;
6628
+ white-space: nowrap;
6629
+ overflow: hidden;
6630
+ text-overflow: ellipsis;
6631
+ }
6632
+ .pa-stat--square[data-pa-stat-fit] .pa-stat__symbol,
6633
+ .pa-stat--square[data-pa-stat-fit] .pa-stat__label,
6634
+ .pa-stat--square[data-pa-stat-fit] .pa-stat__change,
6635
+ .pa-stat--square[data-pa-stat-fit] .pa-stat__context {
6636
+ display: none;
6637
+ }
6638
+ @container pa-stat (min-width: 8rem) and (min-height: 4.5rem) {
6639
+ .pa-stat--square[data-pa-stat-fit] .pa-stat__symbol {
6640
+ display: inline;
6641
+ }
6642
+ }
6643
+ @container pa-stat (min-width: 13rem) and (min-height: 6.5rem) {
6644
+ .pa-stat--square[data-pa-stat-fit] .pa-stat__label {
6645
+ display: block;
6646
+ }
6647
+ }
6648
+ @container pa-stat (min-width: 18rem) and (min-height: 9.5rem) {
6649
+ .pa-stat--square[data-pa-stat-fit] .pa-stat__change {
6650
+ display: block;
6651
+ }
6652
+ }
6653
+ @container pa-stat (min-width: 24rem) and (min-height: 13rem) {
6654
+ .pa-stat--square[data-pa-stat-fit] .pa-stat__context {
6655
+ display: block;
6656
+ }
6657
+ }
6658
+ .pa-stat--square[data-pa-stat-fit].pa-stat--fit-wide {
6659
+ flex-direction: row;
6660
+ align-items: stretch;
6661
+ column-gap: 2.4rem;
6662
+ }
6663
+ .pa-stat--square[data-pa-stat-fit].pa-stat--fit-wide .pa-stat__slot {
6664
+ flex: 0 0 44%;
6665
+ min-width: 0;
6666
+ justify-content: center;
6667
+ }
6668
+ .pa-stat--square[data-pa-stat-fit].pa-stat--fit-wide .pa-stat__meta {
6669
+ flex: 1 1 auto;
6670
+ min-width: 0;
6671
+ justify-content: center;
6672
+ }
6673
+ .pa-stat--square[data-pa-stat-fit].pa-stat--fit-wide .pa-stat__symbol {
6674
+ display: inline;
6675
+ }
6676
+ .pa-stat--square[data-pa-stat-fit].pa-stat--fit-wide .pa-stat__label,
6677
+ .pa-stat--square[data-pa-stat-fit].pa-stat--fit-wide .pa-stat__change,
6678
+ .pa-stat--square[data-pa-stat-fit].pa-stat--fit-wide .pa-stat__context {
6679
+ display: block;
6680
+ }
6537
6681
 
6538
6682
  .pa-kpi-grid {
6539
6683
  margin: -0.8rem;
@@ -7886,6 +8030,10 @@ a.pa-card p {
7886
8030
  border-start-start-radius: 0;
7887
8031
  border-end-start-radius: 0;
7888
8032
  }
8033
+ .pa-btn-split--collapsed > .pa-btn-split__toggle:first-child {
8034
+ border-radius: var(--pa-border-radius);
8035
+ border-inline-start: none;
8036
+ }
7889
8037
  .pa-btn-split__toggle {
7890
8038
  border-inline-start: 1px solid rgba(255, 255, 255, 0.25);
7891
8039
  padding-inline: 0;
@@ -7983,6 +8131,18 @@ a.pa-card p {
7983
8131
  opacity: 0.7;
7984
8132
  }
7985
8133
 
8134
+ .pa-btn-toolbar {
8135
+ display: flex;
8136
+ align-items: center;
8137
+ gap: 0.8rem;
8138
+ min-width: 0;
8139
+ overflow: hidden;
8140
+ flex-shrink: 1;
8141
+ }
8142
+ .pa-btn-toolbar > * {
8143
+ flex-shrink: 0;
8144
+ }
8145
+
7986
8146
  /* ========================================
7987
8147
  Loader Components
7988
8148
  Spinners, dots, bars, pulse, ring, wave loaders and animations
@@ -11858,6 +12018,356 @@ web-multiselect {
11858
12018
  pointer-events: none;
11859
12019
  }
11860
12020
 
12021
+ /* ========================================
12022
+ Range Group
12023
+ A compact multi-range filter: one toggle that summarises several
12024
+ numeric range filters (e.g. "Age / Salary / Children") inline, and
12025
+ expands into a floating panel with one slider per dimension.
12026
+
12027
+ Two blocks live here, on purpose:
12028
+
12029
+ - .pa-range — the slider primitive. Single- OR dual-thumb,
12030
+ driven entirely by CSS custom properties that JS
12031
+ sets (--_pos on each thumb, --_fill-start /
12032
+ --_fill-end on the fill). Positioning uses LOGICAL
12033
+ properties (inset-inline-*) so RTL mirrors for
12034
+ free — JS only ever deals in 0–100 percentages.
12035
+ - .pa-range-group — the compact control: a __toggle summarising the
12036
+ rows, and a floating __panel of .pa-range rows.
12037
+ The panel is anchored with Floating UI (see the
12038
+ demo's range-group.js, mirroring split-button.js).
12039
+
12040
+ The group is meant to drop into a filter-card __filters row as a
12041
+ single control that stands in for N sliders. See snippets/range-group.html.
12042
+ ======================================== */
12043
+ /* ----------------------------------------
12044
+ Slider primitive — pa-range
12045
+ ---------------------------------------- */
12046
+ .pa-range {
12047
+ position: relative;
12048
+ height: 2.2rem;
12049
+ touch-action: none;
12050
+ user-select: none;
12051
+ cursor: pointer;
12052
+ }
12053
+
12054
+ .pa-range--ticks-labeled {
12055
+ height: calc(2.2rem + 1.6rem);
12056
+ }
12057
+ .pa-range--ticks-labeled .pa-range__rail {
12058
+ top: calc(2.2rem / 2);
12059
+ }
12060
+
12061
+ .pa-range__rail {
12062
+ position: absolute;
12063
+ inset-inline: calc(var(--pa-range-thumb-size, 1.6rem) / 2);
12064
+ top: 50%;
12065
+ transform: translateY(-50%);
12066
+ height: var(--pa-range-track-height, 0.4rem);
12067
+ }
12068
+
12069
+ .pa-range__track {
12070
+ position: absolute;
12071
+ inset: 0;
12072
+ border-radius: 999px;
12073
+ background-color: var(--pa-range-track, var(--pa-surface-track));
12074
+ }
12075
+
12076
+ .pa-range__fill {
12077
+ position: absolute;
12078
+ top: 0;
12079
+ bottom: 0;
12080
+ height: var(--pa-range-track-height, 0.4rem);
12081
+ border-radius: 999px;
12082
+ background-color: var(--pa-range-fill, var(--pa-accent));
12083
+ inset-inline-start: var(--_fill-start, 0%);
12084
+ inset-inline-end: var(--_fill-end, 0%);
12085
+ }
12086
+
12087
+ /* ---- Tick marks ----
12088
+ Built by range-group.js when a row has data-ticks. The container lives
12089
+ INSIDE the rail (so 0–100% maps to the same travel as the thumbs) and is
12090
+ the rail's first child — painted behind the track/fill, so each tick shows
12091
+ only as protruding ends. Marks are pointer-events:none, so a track click
12092
+ still reaches the .pa-range click-to-seek handler. */
12093
+ .pa-range__ticks {
12094
+ position: absolute;
12095
+ inset: 0;
12096
+ pointer-events: none;
12097
+ }
12098
+
12099
+ .pa-range__tick {
12100
+ position: absolute;
12101
+ top: 50%;
12102
+ inset-inline-start: var(--_pos, 0%);
12103
+ width: 0.1rem;
12104
+ height: 0.9rem;
12105
+ margin-inline-start: calc(0.1rem / -2);
12106
+ transform: translateY(-50%);
12107
+ border-radius: 999px;
12108
+ background-color: var(--pa-range-tick, var(--pa-border-color));
12109
+ }
12110
+
12111
+ .pa-range__tick--major {
12112
+ width: 0.2rem;
12113
+ height: 1.4rem;
12114
+ margin-inline-start: calc(0.2rem / -2);
12115
+ background-color: var(--pa-range-tick-major, var(--pa-text-tertiary));
12116
+ }
12117
+
12118
+ .pa-range__tick-labels {
12119
+ position: absolute;
12120
+ inset-inline: 0;
12121
+ top: calc(100% + 0.3rem);
12122
+ height: 0;
12123
+ pointer-events: none;
12124
+ }
12125
+
12126
+ .pa-range__tick-label {
12127
+ position: absolute;
12128
+ inset-inline-start: var(--_pos, 0%);
12129
+ transform: translateX(-50%);
12130
+ font-size: 1rem;
12131
+ color: var(--pa-text-tertiary);
12132
+ white-space: nowrap;
12133
+ font-variant-numeric: tabular-nums;
12134
+ }
12135
+
12136
+ .pa-range__thumb {
12137
+ --_thumb-w: var(--pa-range-thumb-size, 1.6rem);
12138
+ --_thumb-h: var(--pa-range-thumb-size, 1.6rem);
12139
+ position: absolute;
12140
+ top: 50%;
12141
+ inset-inline-start: var(--_pos, 0%);
12142
+ margin-inline-start: calc(var(--_thumb-w) / -2);
12143
+ transform: translateY(-50%);
12144
+ width: var(--_thumb-w);
12145
+ height: var(--_thumb-h);
12146
+ padding: 0;
12147
+ border: 0.2rem solid var(--pa-range-thumb-border, var(--pa-accent));
12148
+ border-radius: 50%;
12149
+ background-color: var(--pa-range-thumb-bg, var(--pa-card-bg));
12150
+ cursor: grab;
12151
+ touch-action: none;
12152
+ transition: box-shadow 0.15s ease, border-color 0.15s ease;
12153
+ }
12154
+ .pa-range__thumb:hover {
12155
+ border-color: var(--pa-range-thumb-border-hover, var(--pa-accent-hover));
12156
+ }
12157
+ .pa-range__thumb:focus-visible {
12158
+ outline: none;
12159
+ box-shadow: 0 0 0 0.3rem var(--pa-range-focus-ring, var(--pa-accent-light));
12160
+ }
12161
+ .pa-range__thumb:active, .pa-range__thumb.pa-range__thumb--grabbing {
12162
+ cursor: grabbing;
12163
+ box-shadow: 0 0 0 0.3rem var(--pa-range-focus-ring, var(--pa-accent-light));
12164
+ }
12165
+
12166
+ /* ---- Handle shapes ----
12167
+ Modifiers on .pa-range that restyle its thumbs. Purely cosmetic — no JS
12168
+ involvement — so a shape can be mixed per row. Each just overrides the
12169
+ private size props + border-radius; centring follows automatically. */
12170
+ .pa-range--handle-rect .pa-range__thumb {
12171
+ --_thumb-w: 1.2rem;
12172
+ --_thumb-h: 1.9rem;
12173
+ border-radius: var(--pa-border-radius-sm);
12174
+ }
12175
+
12176
+ .pa-range--handle-bar .pa-range__thumb {
12177
+ --_thumb-w: 0.35rem;
12178
+ --_thumb-h: 2.2rem;
12179
+ border-radius: 999px;
12180
+ background-color: var(--pa-range-thumb-border, var(--pa-accent));
12181
+ }
12182
+ .pa-range--handle-bar .pa-range__thumb:hover {
12183
+ background-color: var(--pa-range-thumb-border-hover, var(--pa-accent-hover));
12184
+ }
12185
+
12186
+ .pa-range--handle-arrow .pa-range__thumb {
12187
+ --_thumb-w: 1.6rem;
12188
+ --_thumb-h: 2rem;
12189
+ border-radius: var(--pa-border-radius-sm);
12190
+ }
12191
+ .pa-range--handle-arrow .pa-range__thumb::before {
12192
+ content: "";
12193
+ position: absolute;
12194
+ top: 50%;
12195
+ inset-inline-start: 50%;
12196
+ width: 0.5rem;
12197
+ height: 0.5rem;
12198
+ border-block-start: 0.2rem solid var(--pa-range-thumb-border, var(--pa-accent));
12199
+ border-inline-end: 0.2rem solid var(--pa-range-thumb-border, var(--pa-accent));
12200
+ transform: translateX(-0.177rem) translate(-50%, -50%) rotate(45deg);
12201
+ }
12202
+
12203
+ .pa-range--handle-arrow .pa-range__thumb--min::before {
12204
+ transform: translateX(0.177rem) translate(-50%, -50%) rotate(-135deg);
12205
+ }
12206
+
12207
+ .pa-range--handle-needle .pa-range__thumb {
12208
+ --_thumb-w: 1.2rem;
12209
+ --_thumb-h: 2.2rem;
12210
+ background-color: transparent;
12211
+ border-color: transparent;
12212
+ border-radius: var(--pa-border-radius-sm);
12213
+ }
12214
+ .pa-range--handle-needle .pa-range__thumb::before {
12215
+ content: "";
12216
+ position: absolute;
12217
+ inset-inline-start: 50%;
12218
+ bottom: 50%;
12219
+ width: 1.2rem;
12220
+ height: 1.1rem;
12221
+ margin-inline-start: calc(1.2rem / -2);
12222
+ background-color: var(--pa-range-thumb-border, var(--pa-accent));
12223
+ clip-path: polygon(50% 100%, 0 0, 100% 0);
12224
+ transition: background-color 0.15s ease;
12225
+ }
12226
+ .pa-range--handle-needle .pa-range__thumb:hover::before {
12227
+ background-color: var(--pa-range-thumb-border-hover, var(--pa-accent-hover));
12228
+ }
12229
+
12230
+ .pa-range--single .pa-range__thumb--min {
12231
+ display: none;
12232
+ }
12233
+
12234
+ .pa-range--disabled {
12235
+ opacity: 0.5;
12236
+ pointer-events: none;
12237
+ }
12238
+
12239
+ /* ----------------------------------------
12240
+ Compact control — pa-range-group
12241
+ ---------------------------------------- */
12242
+ .pa-range-group {
12243
+ position: relative;
12244
+ display: inline-flex;
12245
+ }
12246
+
12247
+ .pa-range-group__toggle {
12248
+ display: inline-flex;
12249
+ align-items: center;
12250
+ gap: 1.2rem;
12251
+ width: 100%;
12252
+ padding: 0.8rem 1.2rem;
12253
+ border: 1px solid var(--pa-border-color);
12254
+ border-radius: var(--pa-border-radius);
12255
+ background-color: var(--pa-input-bg);
12256
+ color: var(--pa-text-color-1);
12257
+ font-size: 1.4rem;
12258
+ line-height: 1.3;
12259
+ text-align: start;
12260
+ cursor: pointer;
12261
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
12262
+ }
12263
+ .pa-range-group__toggle:hover {
12264
+ border-color: var(--pa-accent-hover);
12265
+ }
12266
+ .pa-range-group__toggle:focus-visible {
12267
+ outline: none;
12268
+ border-color: var(--pa-accent);
12269
+ box-shadow: 0 0 0 0.3rem var(--pa-accent-light);
12270
+ }
12271
+
12272
+ .pa-range-group__summary {
12273
+ flex: 1;
12274
+ min-width: 0;
12275
+ font-size: 1.4rem;
12276
+ white-space: nowrap;
12277
+ overflow: hidden;
12278
+ text-overflow: ellipsis;
12279
+ }
12280
+
12281
+ .pa-range-group__seg-label {
12282
+ font-size: 1rem;
12283
+ font-weight: 600;
12284
+ letter-spacing: 0.02em;
12285
+ text-transform: uppercase;
12286
+ color: var(--pa-text-tertiary);
12287
+ }
12288
+
12289
+ .pa-range-group__seg-value {
12290
+ font-weight: 500;
12291
+ color: var(--pa-text-color-1);
12292
+ font-variant-numeric: tabular-nums;
12293
+ }
12294
+
12295
+ .pa-range-group__seg-value--empty {
12296
+ color: var(--pa-text-tertiary);
12297
+ font-weight: 400;
12298
+ }
12299
+
12300
+ .pa-range-group__seg-sep {
12301
+ color: var(--pa-text-tertiary);
12302
+ margin-inline: 0.4rem;
12303
+ }
12304
+
12305
+ .pa-range-group__caret {
12306
+ flex-shrink: 0;
12307
+ font-size: 1.2rem;
12308
+ color: var(--pa-text-secondary);
12309
+ transition: transform 0.2s ease;
12310
+ }
12311
+
12312
+ .pa-range-group--open .pa-range-group__caret {
12313
+ transform: rotate(180deg);
12314
+ }
12315
+
12316
+ /* ---- Floating panel ---- */
12317
+ .pa-range-group__panel {
12318
+ display: none;
12319
+ flex-direction: column;
12320
+ gap: 2.4rem;
12321
+ min-width: var(--pa-range-group-panel-min-width, 32rem);
12322
+ padding: 1.6rem;
12323
+ border: 1px solid var(--pa-border-color);
12324
+ border-radius: var(--pa-border-radius-lg);
12325
+ background-color: var(--pa-card-bg);
12326
+ box-shadow: var(--pa-detail-shadow);
12327
+ z-index: 7500;
12328
+ }
12329
+ .pa-range-group__panel.pa-range-group__panel--open {
12330
+ display: flex;
12331
+ }
12332
+
12333
+ .pa-range-group__row {
12334
+ display: flex;
12335
+ flex-direction: column;
12336
+ gap: 0.8rem;
12337
+ }
12338
+
12339
+ .pa-range-group__row-head {
12340
+ display: flex;
12341
+ align-items: baseline;
12342
+ justify-content: space-between;
12343
+ gap: 0.8rem;
12344
+ }
12345
+
12346
+ .pa-range-group__row-label {
12347
+ font-size: 1.4rem;
12348
+ font-weight: 600;
12349
+ color: var(--pa-text-color-1);
12350
+ }
12351
+
12352
+ .pa-range-group__row-value {
12353
+ font-size: 1.4rem;
12354
+ font-weight: 500;
12355
+ font-variant-numeric: tabular-nums;
12356
+ color: var(--pa-range-fill, var(--pa-accent));
12357
+ }
12358
+ .pa-range-group__row-value.pa-range-group__row-value--empty {
12359
+ color: var(--pa-text-tertiary);
12360
+ }
12361
+
12362
+ .pa-range-group__actions {
12363
+ display: flex;
12364
+ justify-content: flex-end;
12365
+ gap: 0.8rem;
12366
+ margin-top: 0.4rem;
12367
+ padding-top: 2.4rem;
12368
+ border-top: 1px solid var(--pa-border-color);
12369
+ }
12370
+
11861
12371
  /* ========================================
11862
12372
  Comparison Table Component
11863
12373
  Two-column and three-column comparison tables for version control, data changes, and merge operations
@@ -15450,6 +15960,34 @@ code {
15450
15960
  background-color: #007bff;
15451
15961
  }
15452
15962
 
15963
+ /* ========================================
15964
+ Overflow (progressive-collapse row)
15965
+
15966
+ Generic primitive: a horizontal flex row whose children get walked into
15967
+ a "more" menu when the row can't fit, driven by `overflow.js`. Use
15968
+ anywhere you want a button toolbar, breadcrumb row, chip row, or tab bar
15969
+ to collapse gracefully on narrow widths.
15970
+
15971
+ <div class="pa-overflow" data-pa-actions-overflow-from="end">
15972
+ <button class="pa-btn pa-btn--xs">Save</button>
15973
+ <button class="pa-btn pa-btn--xs" data-pa-actions-priority="10">Run</button>
15974
+ </div>
15975
+
15976
+ The card-flavoured alias `pa-card__actions--overflow` carries equivalent
15977
+ rules under `.pa-card__header` (see `_cards.scss`); both wire into the
15978
+ same JS module. ======================================== */
15979
+ .pa-overflow {
15980
+ display: flex;
15981
+ align-items: center;
15982
+ gap: 0.8rem;
15983
+ min-width: 0;
15984
+ overflow: hidden;
15985
+ flex-shrink: 1;
15986
+ }
15987
+ .pa-overflow > * {
15988
+ flex-shrink: 0;
15989
+ }
15990
+
15453
15991
  /* ========================================
15454
15992
  Data Display Components
15455
15993
  Read-only label-value field pairs