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

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 +55 -13
  2. package/dist/css/main.css +554 -7
  3. package/package.json +3 -1
  4. package/snippets/buttons.html +51 -0
  5. package/snippets/cards.html +132 -47
  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 +95 -6
  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 +41 -2
@@ -10,12 +10,10 @@
10
10
  by packages/core/src/js/splitter.js. The script auto-initializes on
11
11
  [data-pa-splitter] at DOMContentLoaded.
12
12
 
13
- Two markup flavours:
14
- * 2-pane shorthand uses --start / --end modifiers and root-level
15
- min-start / max-start / default attributes (sections 1–3 below).
16
- * N-pane (N ≥ 2) — generic panes with per-pane size / min / max
17
- attributes (section 4 below). Use this when you have 3+ panes
18
- or want more than one minimizable pane.
13
+ Markup model: panes and gutters alternate (pane, gutter, pane, gutter,
14
+ …, pane). Each pane carries its own sizing attributes there's no
15
+ "start" / "end" shorthand, every pane is generic. Works for any
16
+ N ≥ 2.
19
17
 
20
18
  Orientation modifiers (match flexbox direction of the panes):
21
19
  --horizontal panes side-by-side, vertical gutter (default)
@@ -28,33 +26,34 @@
28
26
 
29
27
 
30
28
  <!-- ================================
31
- HORIZONTAL SPLIT
32
- Side-by-side panes; vertical gutter. Start pane has a 200px floor
33
- and 60% ceiling, defaults to 280px, persists under "demo-sidebar".
29
+ HORIZONTAL SPLIT (2 panes)
30
+ Side-by-side panes; vertical gutter. The first pane has a 200px
31
+ floor and 60% ceiling, defaults to 280px; the second pane absorbs
32
+ the leftover. Persists under "demo-sidebar".
34
33
  ================================ -->
35
34
 
36
35
  <div class="pa-splitter pa-splitter--horizontal"
37
36
  data-pa-splitter
38
37
  data-pa-splitter-id="demo-sidebar"
39
- data-pa-splitter-min-start="200px"
40
- data-pa-splitter-max-start="60%"
41
- data-pa-splitter-default="280px"
42
38
  style="height: 400px;">
43
- <div class="pa-splitter__pane pa-splitter__pane--start">
39
+ <div class="pa-splitter__pane"
40
+ data-pa-splitter-size="280px"
41
+ data-pa-splitter-min="200px"
42
+ data-pa-splitter-max="60%">
44
43
  <!-- Your sidebar / list / nav content -->
45
44
  </div>
46
45
  <div class="pa-splitter__gutter"
47
46
  role="separator"
48
47
  aria-orientation="vertical"
49
48
  tabindex="0"></div>
50
- <div class="pa-splitter__pane pa-splitter__pane--end">
51
- <!-- Your main / detail content -->
49
+ <div class="pa-splitter__pane">
50
+ <!-- Your main / detail content (absorbs the leftover) -->
52
51
  </div>
53
52
  </div>
54
53
 
55
54
 
56
55
  <!-- ================================
57
- VERTICAL SPLIT
56
+ VERTICAL SPLIT (2 panes)
58
57
  Stacked panes; horizontal gutter. Useful for editor + console / log
59
58
  pane patterns.
60
59
  ================================ -->
@@ -62,18 +61,18 @@
62
61
  <div class="pa-splitter pa-splitter--vertical"
63
62
  data-pa-splitter
64
63
  data-pa-splitter-id="demo-console"
65
- data-pa-splitter-min-start="80px"
66
- data-pa-splitter-max-start="80%"
67
- data-pa-splitter-default="60%"
68
64
  style="height: 400px;">
69
- <div class="pa-splitter__pane pa-splitter__pane--start">
65
+ <div class="pa-splitter__pane"
66
+ data-pa-splitter-size="60%"
67
+ data-pa-splitter-min="80px"
68
+ data-pa-splitter-max="80%">
70
69
  <!-- Editor / primary content -->
71
70
  </div>
72
71
  <div class="pa-splitter__gutter"
73
72
  role="separator"
74
73
  aria-orientation="horizontal"
75
74
  tabindex="0"></div>
76
- <div class="pa-splitter__pane pa-splitter__pane--end">
75
+ <div class="pa-splitter__pane">
77
76
  <!-- Console / log / secondary -->
78
77
  </div>
79
78
  </div>
@@ -90,14 +89,18 @@
90
89
  <div class="pa-splitter pa-splitter--horizontal"
91
90
  data-pa-splitter
92
91
  style="height: 280px; gap: 1.6rem; --pa-splitter-gutter-size: 1rem;">
93
- <div class="pa-splitter__pane pa-splitter__pane--start" style="padding: 0;">
92
+ <div class="pa-splitter__pane"
93
+ data-pa-splitter-size="40%"
94
+ data-pa-splitter-min="25%"
95
+ data-pa-splitter-max="75%"
96
+ style="padding: 0;">
94
97
  <div class="pa-card" style="height: 100%; margin: 0;">…</div>
95
98
  </div>
96
99
  <div class="pa-splitter__gutter"
97
100
  role="separator"
98
101
  aria-orientation="vertical"
99
102
  tabindex="0"></div>
100
- <div class="pa-splitter__pane pa-splitter__pane--end" style="padding: 0;">
103
+ <div class="pa-splitter__pane" style="padding: 0;">
101
104
  <div class="pa-card" style="height: 100%; margin: 0;">…</div>
102
105
  </div>
103
106
  </div>
@@ -105,8 +108,8 @@
105
108
 
106
109
  <!-- ================================
107
110
  N-PANE LAYOUT (3 panes — sidebar + content + inspector)
108
- Per-pane attributes replace the root-level --start ones. Only the
109
- first and last panes honour the minimize marker. Panes without a
111
+ Per-pane attributes drive sizing. The first and last panes opt into
112
+ rail collapse via the minimize marker. Panes without a
110
113
  data-pa-splitter-size share the leftover equally.
111
114
  ================================ -->
112
115
 
@@ -144,7 +147,7 @@
144
147
 
145
148
 
146
149
  <!-- ================================
147
- DATA ATTRIBUTES — root (both modes)
150
+ DATA ATTRIBUTES — root
148
151
  data-pa-splitter marker; required
149
152
  data-pa-splitter-id enables localStorage persistence
150
153
  under "pa-splitter:<id>"
@@ -152,25 +155,22 @@
152
155
  data-pa-splitter-rail-size rail width in px (default: 40)
153
156
  data-pa-splitter-minimize-threshold drag-to-rail snap ratio (default: 0.40)
154
157
 
155
- DATA ATTRIBUTES — root (legacy 2-pane only)
156
- data-pa-splitter-min-start "200px" or "20%" (default: 0)
157
- data-pa-splitter-max-start "60%" or "800px" (default: available)
158
- data-pa-splitter-default initial size when no saved state
159
- data-pa-splitter-minimize "start" or "end" — opt into rail collapse
160
-
161
- DATA ATTRIBUTES — per pane (N-pane only)
158
+ DATA ATTRIBUTES — per pane
162
159
  data-pa-splitter-size "240px" or "30%" (default: shared leftover)
163
160
  data-pa-splitter-min "150px" or "10%" (default: 0)
164
161
  data-pa-splitter-max "400px" or "50%" (default: available)
165
- data-pa-splitter-minimize marker — only honoured on the first
166
- and last panes
162
+ data-pa-splitter-minimize marker — pane can collapse to a rail.
163
+ First / last panes dock against the
164
+ container edge; middle panes collapse
165
+ in place and split released slack
166
+ between both neighbours.
167
167
 
168
- CHILD ATTRIBUTES (both modes)
168
+ CHILD ATTRIBUTES
169
169
  data-pa-splitter-toggle put on any element inside the splitter
170
170
  (typically a button in the card header)
171
171
  to trigger collapse / restore on click.
172
- In N-pane mode, toggles the enclosing
173
- pane (if it's minimizable).
172
+ Toggles the enclosing pane if it's
173
+ minimizable.
174
174
 
175
175
  RAIL-TITLE HOOK (non-card content)
176
176
  Any element marked with `[data-pa-splitter-rail-title]` inside a
@@ -202,6 +202,12 @@
202
202
  (transform: scale(-1,-1) on the heading
203
203
  inside any `[data-pa-splitter-rail-title]`
204
204
  or `.pa-card__header`)
205
+ pa-splitter--accordion (auto, JS-managed) added when the
206
+ container is narrower than the sum of
207
+ all pane mins + gutters/gaps/padding
208
+ AND there are 2+ minimizable panes.
209
+ While active, restoring one pane
210
+ auto-rails the others.
205
211
 
206
212
  JAVASCRIPT API (window.PaSplitter)
207
213
  PaSplitter.init(el) initialize a single element (idempotent)
@@ -140,6 +140,23 @@
140
140
  </div>
141
141
  </div>
142
142
 
143
+ <!-- Fit + progressive-disclosure mode (--square[data-pa-stat-fit])
144
+ Opt in with the data-pa-stat-fit attribute and load pa-stat-fit.js.
145
+ The number is sized to fill the tile (never overflows, any char count);
146
+ lower-priority rows reveal as the tile earns BOTH width and height:
147
+ number → symbol → label → change → context.
148
+ Authored markup stays flat — the JS wraps __number + __symbol into a
149
+ __slot > __group at runtime. REQUIRES a height source on the tile
150
+ (grid cell with a set height / explicit height / aspect-ratio), because
151
+ container-type: size makes the box size independently of its content. -->
152
+ <div class="pa-stat pa-stat--square pa-stat--info" data-pa-stat-fit style="height: 16rem;">
153
+ <span class="pa-stat__symbol">$</span>
154
+ <span class="pa-stat__number">847K</span>
155
+ <div class="pa-stat__label">Monthly Revenue</div>
156
+ <div class="pa-stat__change pa-stat__change--positive">▲ 12.5% vs last month</div>
157
+ <div class="pa-stat__context">Updated 2 min ago</div>
158
+ </div>
159
+
143
160
  <!-- All six square colour variants -->
144
161
  <div class="pa-stat pa-stat--square pa-stat--primary"> <div class="pa-stat__number">…</div><div class="pa-stat__symbol">…</div><div class="pa-stat__label">Primary</div> </div>
145
162
  <div class="pa-stat pa-stat--square pa-stat--success"> <div class="pa-stat__number">…</div><div class="pa-stat__symbol">…</div><div class="pa-stat__label">Success</div> </div>
@@ -169,6 +186,13 @@ SUB-ELEMENTS:
169
186
  (red), --neutral (muted).
170
187
  .pa-stat__symbol Square variant only — the decorative
171
188
  watermark symbol on the inline-end side.
189
+ .pa-stat__context Square fit-mode only — lowest-priority
190
+ (P4) caption row (e.g. "Updated 2m ago").
191
+ .pa-stat__slot Square fit-mode only — created by
192
+ pa-stat-fit.js; wraps the fit group.
193
+ .pa-stat__group Square fit-mode only — created by
194
+ pa-stat-fit.js; holds __number + __symbol
195
+ and carries the JS-computed font-size.
172
196
 
173
197
  VARIANTS (mutually exclusive):
174
198
  (default) Icon + content row. Pair with
@@ -180,6 +204,13 @@ VARIANTS (mutually exclusive):
180
204
  --square Coloured tile, big __number, decorative
181
205
  __symbol, bottom-pinned __label. Apply
182
206
  a colour modifier on the same element.
207
+ [data-pa-stat-fit] Attribute (not a class), combine WITH
208
+ --square. Fit-to-box number + priority
209
+ disclosure ladder (number → symbol →
210
+ label → change → context, revealed as the
211
+ tile gains width AND height). Needs
212
+ pa-stat-fit.js loaded and a height source
213
+ on the tile.
183
214
 
184
215
  SQUARE COLOUR MODIFIERS (apply with --square):
185
216
  --primary, --success, --info, --warning, --danger, --secondary
@@ -0,0 +1,327 @@
1
+ /**
2
+ * Pure Admin — Split Button Auto-Absorb
3
+ *
4
+ * Extends the split button (.pa-btn-split) to automatically absorb adjacent
5
+ * sibling buttons sitting next to it inside a `.pa-btn-toolbar` row, when
6
+ * the row can't fit them all on one line. Absorbed items drop into the
7
+ * split button's own dropdown menu and return to the row as space comes
8
+ * back. Reuses the existing split-button toggle / menu / Floating UI
9
+ * positioning — no parallel chrome.
10
+ *
11
+ * Opt-in markup:
12
+ *
13
+ * <div class="pa-btn-toolbar">
14
+ * <button class="pa-btn pa-btn--primary">Save</button>
15
+ * <button class="pa-btn pa-btn--primary">Format</button>
16
+ * <button class="pa-btn pa-btn--primary"
17
+ * data-pa-absorb-priority="10">Refresh</button>
18
+ * <div class="pa-btn-split pa-btn-split--auto-absorb">
19
+ * <button class="pa-btn pa-btn--primary">Run</button>
20
+ * <button class="pa-btn pa-btn--primary pa-btn-split__toggle"
21
+ * onclick="toggleSplitMenu(event)">
22
+ * <i class="fas fa-chevron-down pa-btn-split__chevron"></i>
23
+ * </button>
24
+ * <div class="pa-btn-split__menu">
25
+ * <div class="pa-btn-split__menu-inner">
26
+ * <button class="pa-btn-split__item">Run with options…</button>
27
+ * </div>
28
+ * </div>
29
+ * </div>
30
+ * </div>
31
+ *
32
+ * Drop order: lowest `data-pa-absorb-priority` (default 0) drops first;
33
+ * ties broken by DOM order with the sibling closest to the split button
34
+ * dropping first (`data-pa-absorb-from="end"`, default). Flip with
35
+ * `="start"` to drop the leftmost sibling first instead.
36
+ *
37
+ * Absorbed items keep their original click handlers (the DOM node is
38
+ * preserved). On absorb, their classList is replaced with
39
+ * `pa-btn-split__item` so they pick up the standard menu-row styling; the
40
+ * original classList is stashed on the element and restored when the item
41
+ * returns to the bar.
42
+ *
43
+ * Existing static menu items (rendered into `__menu-inner` server-side)
44
+ * stay BELOW absorbed items: the first static child is captured at init
45
+ * and used as the insertBefore anchor.
46
+ *
47
+ * Layout contract: the bar needs `min-width: 0; overflow: hidden;
48
+ * flex-shrink: 1` so it actually shrinks below its content (otherwise
49
+ * `scrollWidth > clientWidth` never goes truthful). `_buttons.scss`
50
+ * supplies that for `.pa-btn-toolbar`; any flex row with equivalent
51
+ * styling works.
52
+ *
53
+ * Opt in to console diagnostics: `window.PA_BTN_SPLIT_AUTO_ABSORB_DEBUG = true`
54
+ * before page load.
55
+ *
56
+ * Public API (window.PaBtnSplitAutoAbsorb):
57
+ * init(splitContainer) — idempotent single-container init
58
+ * initAll(scope) — initialize all uninitialized auto-absorb split
59
+ * buttons under scope
60
+ */
61
+ (function () {
62
+ 'use strict';
63
+
64
+ var INIT_FLAG = '__paBtnSplitAutoAbsorbInit';
65
+ var SELECTOR = '.pa-btn-split.pa-btn-split--auto-absorb';
66
+ var STASH_KEY = '__paAbsorbOrigClass';
67
+
68
+ function init(splitContainer) {
69
+ if (!splitContainer || splitContainer[INIT_FLAG]) return;
70
+ if (!splitContainer.classList.contains('pa-btn-split')) return;
71
+ if (!splitContainer.classList.contains('pa-btn-split--auto-absorb')) return;
72
+
73
+ var bar = splitContainer.parentNode;
74
+ if (!bar || bar.nodeType !== 1) return;
75
+
76
+ var menu = splitContainer.querySelector('.pa-btn-split__menu');
77
+ var menuInner = menu && menu.querySelector('.pa-btn-split__menu-inner');
78
+ if (!menuInner) return;
79
+
80
+ splitContainer[INIT_FLAG] = true;
81
+
82
+ // First static menu child captured at init — absorbed items are
83
+ // inserted BEFORE it so static items stay at the bottom of the menu.
84
+ // If there are no static items this stays null and insertBefore
85
+ // degrades to appendChild, which is exactly what we want.
86
+ var firstStaticItem = menuInner.firstElementChild || null;
87
+
88
+ // The split button's OWN primary action (the non-toggle `.pa-btn`) and
89
+ // its toggle. When even an empty bar can't fit the whole split button,
90
+ // the primary folds into the menu as a last resort and only the toggle
91
+ // stays visible (see absorbPrimary / the final relayout step).
92
+ var toggleBtn = splitContainer.querySelector('.pa-btn-split__toggle');
93
+ var primaryBtn = null;
94
+ for (var pc = splitContainer.firstElementChild; pc; pc = pc.nextElementSibling) {
95
+ if (pc !== toggleBtn && pc.classList &&
96
+ pc.classList.contains('pa-btn') &&
97
+ !pc.classList.contains('pa-btn-split__toggle')) {
98
+ primaryBtn = pc;
99
+ break;
100
+ }
101
+ }
102
+ var canCollapsePrimary = !!(primaryBtn && toggleBtn);
103
+
104
+ // Collect siblings that come BEFORE the split container in the bar.
105
+ // We snapshot at init time — siblings added after init won't be
106
+ // tracked (call init again, or refactor to MutationObserver later).
107
+ var candidates = [];
108
+ var idx = 0;
109
+ for (var node = bar.firstElementChild; node && node !== splitContainer; node = node.nextElementSibling) {
110
+ var p = parseInt(node.getAttribute('data-pa-absorb-priority'), 10);
111
+ candidates.push({
112
+ el: node,
113
+ priority: isNaN(p) ? 0 : p,
114
+ domIndex: idx
115
+ });
116
+ idx++;
117
+ }
118
+ if (candidates.length === 0 && !canCollapsePrimary) return;
119
+
120
+ var dropOrder = [];
121
+ function buildDropOrder() {
122
+ var fromAttr = splitContainer.getAttribute('data-pa-absorb-from');
123
+ var dropFromStart = fromAttr === 'start';
124
+ dropOrder = candidates.slice().sort(function (a, b) {
125
+ if (a.priority !== b.priority) return a.priority - b.priority;
126
+ // Default ("end") drops the sibling nearest the split
127
+ // button first — feels right because that's the one
128
+ // visually adjacent to the menu it falls into.
129
+ return dropFromStart ? (a.domIndex - b.domIndex) : (b.domIndex - a.domIndex);
130
+ });
131
+ }
132
+ buildDropOrder();
133
+
134
+ var DEBUG = window.PA_BTN_SPLIT_AUTO_ABSORB_DEBUG === true;
135
+ var label = '[pa-btn-split-auto-absorb#' + (splitContainer.id || candidates.map(function (c) { return c.el.tagName; }).join('-')).slice(0, 40) + ']';
136
+ function log() {
137
+ if (!DEBUG) return;
138
+ var args = Array.prototype.slice.call(arguments);
139
+ console.log.apply(console, [label].concat(args));
140
+ }
141
+
142
+ // Rewrite any `<span class="pa-btn__icon">` inside an absorbed item
143
+ // to the canonical `pa-btn-split__item-icon`. The pa-btn icon column
144
+ // width is scoped to `.pa-btn:has(.pa-btn__icon) .pa-btn__icon` —
145
+ // once the outer button stops being a `.pa-btn`, that rule no
146
+ // longer matches and the icon span goes auto-width, throwing
147
+ // absorbed labels out of column with static items.
148
+ function rewriteIcons(el) {
149
+ var icons = el.querySelectorAll('.pa-btn__icon');
150
+ for (var i = 0; i < icons.length; i++) {
151
+ if (icons[i][STASH_KEY] == null) {
152
+ icons[i][STASH_KEY] = icons[i].className;
153
+ }
154
+ icons[i].className = 'pa-btn-split__item-icon';
155
+ }
156
+ }
157
+
158
+ function restoreIcons(el) {
159
+ // After a previous absorb cycle the icon spans now carry our
160
+ // rewritten class. Find them and put their original class back.
161
+ var icons = el.querySelectorAll('.pa-btn-split__item-icon');
162
+ for (var i = 0; i < icons.length; i++) {
163
+ if (icons[i][STASH_KEY] != null) {
164
+ icons[i].className = icons[i][STASH_KEY];
165
+ }
166
+ }
167
+ }
168
+
169
+ function moveToMenu(el) {
170
+ if (el[STASH_KEY] == null) {
171
+ el[STASH_KEY] = el.className;
172
+ }
173
+ el.className = 'pa-btn-split__item';
174
+ rewriteIcons(el);
175
+ menuInner.insertBefore(el, firstStaticItem);
176
+ }
177
+
178
+ function moveToBar(el) {
179
+ if (el[STASH_KEY] != null) {
180
+ el.className = el[STASH_KEY];
181
+ restoreIcons(el);
182
+ }
183
+ // insertBefore moves the node if already attached, so calling
184
+ // it unconditionally keeps siblings in their original DOM
185
+ // order even after a drop-from-start cycle reshuffled them.
186
+ bar.insertBefore(el, splitContainer);
187
+ }
188
+
189
+ // Last-resort collapse: fold the split button's OWN primary action
190
+ // into the menu (as the top item) so only the toggle chevron remains
191
+ // when even an otherwise-empty bar can't fit the full split button.
192
+ var primaryAbsorbed = false;
193
+ function absorbPrimary() {
194
+ if (primaryAbsorbed || !canCollapsePrimary) return;
195
+ if (primaryBtn[STASH_KEY] == null) primaryBtn[STASH_KEY] = primaryBtn.className;
196
+ primaryBtn.className = 'pa-btn-split__item';
197
+ rewriteIcons(primaryBtn);
198
+ // Above absorbed siblings + static items — the primary action is
199
+ // the most important row, so it sits at the top of the menu.
200
+ menuInner.insertBefore(primaryBtn, menuInner.firstChild);
201
+ splitContainer.classList.add('pa-btn-split--collapsed');
202
+ primaryAbsorbed = true;
203
+ }
204
+ function restorePrimary() {
205
+ if (!primaryAbsorbed || !canCollapsePrimary) return;
206
+ if (primaryBtn[STASH_KEY] != null) {
207
+ primaryBtn.className = primaryBtn[STASH_KEY];
208
+ restoreIcons(primaryBtn);
209
+ }
210
+ // Back to its slot: immediately before the toggle.
211
+ splitContainer.insertBefore(primaryBtn, toggleBtn);
212
+ splitContainer.classList.remove('pa-btn-split--collapsed');
213
+ primaryAbsorbed = false;
214
+ }
215
+
216
+ function relayout() {
217
+ log('relayout entered');
218
+
219
+ // Step 1 — fully expand before measuring: primary action back in
220
+ // the split button, all candidates back in the bar in original
221
+ // DOM order (immediately before the split container).
222
+ restorePrimary();
223
+ candidates.slice().sort(function (a, b) {
224
+ return a.domIndex - b.domIndex;
225
+ }).forEach(function (item) {
226
+ moveToBar(item.el);
227
+ });
228
+
229
+ var clientW = bar.clientWidth;
230
+ var scrollW = bar.scrollWidth;
231
+ log('step 2', {
232
+ clientW: clientW,
233
+ scrollW: scrollW,
234
+ fitsAll: scrollW <= clientW
235
+ });
236
+
237
+ if (scrollW <= clientW) {
238
+ log('all fits');
239
+ return;
240
+ }
241
+
242
+ // Step 3 — absorb siblings in drop order until it fits.
243
+ for (var i = 0; i < dropOrder.length; i++) {
244
+ if (bar.scrollWidth <= bar.clientWidth) break;
245
+ moveToMenu(dropOrder[i].el);
246
+ log('absorbed', dropOrder[i].el);
247
+ }
248
+
249
+ // Step 4 — last resort: every sibling is absorbed and it STILL
250
+ // overflows (a very narrow card header). Fold the split button's
251
+ // own primary action into the menu so only the toggle remains;
252
+ // otherwise the primary + toggle spill past the bar's
253
+ // overflow:hidden edge and the toggle becomes unclickable.
254
+ if (bar.scrollWidth > bar.clientWidth) {
255
+ absorbPrimary();
256
+ log('absorbed primary — collapsed to toggle');
257
+ }
258
+ log('done');
259
+ }
260
+
261
+ // A stable ancestor whose width tracks the card / window, NOT our button
262
+ // shuffling: the nearest `.pa-card__header` (its width is the card width
263
+ // no matter how the shrinkable `.pa-card__actions` collapses around the
264
+ // toolbar), else the toolbar's own parent for standalone use (a sized /
265
+ // resizable wrapper). It must NOT be the shrinkable actions wrapper —
266
+ // that resizes in response to OUR own absorb/restore, which fed a
267
+ // nondeterministic feedback storm (the reload-vs-resize hysteresis).
268
+ var widthHost = (bar.closest && bar.closest('.pa-card__header')) || bar.parentNode;
269
+
270
+ // Coalesce a burst of size changes (ours + real layout) into ONE
271
+ // relayout on the next frame. relayout is deterministic — it always
272
+ // measures from a full restore — so one pass per frame converges without
273
+ // the ResizeObserver loop-limit suppression that can freeze a transient
274
+ // state.
275
+ var relayoutScheduled = false;
276
+ function scheduleRelayout() {
277
+ if (relayoutScheduled) return;
278
+ relayoutScheduled = true;
279
+ requestAnimationFrame(function () {
280
+ relayoutScheduled = false;
281
+ relayout();
282
+ });
283
+ }
284
+
285
+ // First pass after layout settles.
286
+ requestAnimationFrame(relayout);
287
+
288
+ if (typeof ResizeObserver !== 'undefined') {
289
+ var ro = new ResizeObserver(scheduleRelayout);
290
+ // The toolbar itself — catches ANY change in the space available to
291
+ // it, including the header title being given a larger `min-width`
292
+ // floor (which widens the title and narrows the toolbar WITHOUT
293
+ // changing the header's outer width). A bar-only observer isn't
294
+ // enough on its own: once the toolbar collapses to a lone toggle its
295
+ // box stops shrinking, so…
296
+ ro.observe(bar);
297
+ // …also watch the stable width host, so widening the card re-fires
298
+ // and lets relayout re-expand from the collapsed state.
299
+ if (widthHost && widthHost.nodeType === 1 && widthHost !== bar) {
300
+ ro.observe(widthHost);
301
+ }
302
+ }
303
+
304
+ // React to live `data-pa-absorb-from` flips so consumers can change
305
+ // drop direction at runtime without re-initializing.
306
+ if (typeof MutationObserver !== 'undefined') {
307
+ var mo = new MutationObserver(function () {
308
+ buildDropOrder();
309
+ relayout();
310
+ });
311
+ mo.observe(splitContainer, { attributes: true, attributeFilter: ['data-pa-absorb-from'] });
312
+ }
313
+ }
314
+
315
+ function initAll(scope) {
316
+ var nodes = (scope || document).querySelectorAll(SELECTOR);
317
+ for (var i = 0; i < nodes.length; i++) init(nodes[i]);
318
+ }
319
+
320
+ window.PaBtnSplitAutoAbsorb = { init: init, initAll: initAll };
321
+
322
+ if (document.readyState === 'loading') {
323
+ document.addEventListener('DOMContentLoaded', function () { initAll(); });
324
+ } else {
325
+ initAll();
326
+ }
327
+ })();