@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.
- package/README.md +55 -13
- package/dist/css/main.css +554 -7
- package/package.json +3 -1
- package/snippets/buttons.html +51 -0
- package/snippets/cards.html +132 -47
- package/snippets/comparison.html +26 -22
- package/snippets/manifest.json +180 -115
- package/snippets/range-group.html +125 -0
- package/snippets/splitter.html +44 -38
- package/snippets/statistics.html +31 -0
- package/src/js/btn-split-auto-absorb.js +327 -0
- package/src/js/command-palette.js +472 -0
- package/src/js/file-selector.js +1275 -0
- package/src/js/internal/logging.js +121 -0
- package/src/js/logic-tree-renderer.js +303 -0
- package/src/js/modal-dialogs.js +460 -0
- package/src/js/overflow.js +371 -0
- package/src/js/pa-stat-fit.js +184 -0
- package/src/js/range-group.js +663 -0
- package/src/js/search-autocomplete-v2.js +907 -0
- package/src/js/search-autocomplete.js +434 -0
- package/src/js/settings-panel.js +245 -0
- package/src/js/split-button.js +141 -0
- package/src/js/splitter.js +1323 -0
- package/src/js/toast-service.js +302 -0
- package/src/js/tooltips-popovers.js +275 -0
- package/src/js/virtual-scroll.js +143 -0
- package/src/js/virtual-textbox.js +803 -0
- package/src/scss/_core.scss +7 -0
- package/src/scss/core-components/_buttons.scss +44 -0
- package/src/scss/core-components/_cards.scss +95 -6
- package/src/scss/core-components/_overflow.scss +50 -0
- package/src/scss/core-components/_range-group.scss +474 -0
- package/src/scss/core-components/_statistics.scss +163 -0
- package/src/scss/variables/_components.scss +41 -2
package/snippets/splitter.html
CHANGED
|
@@ -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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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.
|
|
33
|
-
and 60% ceiling, defaults to 280px
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
109
|
-
|
|
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
|
|
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 —
|
|
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 —
|
|
166
|
-
|
|
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
|
|
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
|
-
|
|
173
|
-
|
|
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)
|
package/snippets/statistics.html
CHANGED
|
@@ -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
|
+
})();
|