mnfst 0.5.198-next.1 → 0.5.198-next.3
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/lib/manifest.defer.js +152 -16
- package/lib/manifest.integrity.json +2 -2
- package/lib/manifest.virtual.js +1 -1
- package/package.json +1 -1
package/lib/manifest.defer.js
CHANGED
|
@@ -14,8 +14,31 @@ const killed = !!document.querySelector('script[data-defer="off"]') || window.__
|
|
|
14
14
|
|
|
15
15
|
const pending = new Set();
|
|
16
16
|
let ready = false;
|
|
17
|
+
let bootDrained = false;
|
|
17
18
|
let idleHandle = null;
|
|
18
19
|
|
|
20
|
+
// Urgent = mounted after boot, within a gesture window (a pane the user just opened), capped per gesture
|
|
21
|
+
const URGENT_WINDOW_MS = 1500;
|
|
22
|
+
const URGENT_CAP = 8;
|
|
23
|
+
const PREWARM_CAP = (window.ManifestDeferConfig && window.ManifestDeferConfig.prewarmCap) || 48; // warm-but-unopened containers kept live
|
|
24
|
+
const IDLE_BUDGET_MS = 4;
|
|
25
|
+
const SLICE_MAX = 8;
|
|
26
|
+
const warm = new Set(); // prewarmed, never opened by the user
|
|
27
|
+
let lastGestureAt = -Infinity;
|
|
28
|
+
let urgentWindowAt = -Infinity;
|
|
29
|
+
let urgentInWindow = 0;
|
|
30
|
+
let promoteTimer = null;
|
|
31
|
+
let lastPromoteAt = -Infinity;
|
|
32
|
+
let gestureX = 0, gestureY = 0;
|
|
33
|
+
let idleIsTimer = false;
|
|
34
|
+
const now = () => (typeof performance !== 'undefined' && performance.now) ? performance.now() : Date.now();
|
|
35
|
+
|
|
36
|
+
function isUrgent() {
|
|
37
|
+
if (!ready || !bootDrained || now() - lastGestureAt > URGENT_WINDOW_MS) return false;
|
|
38
|
+
if (urgentWindowAt !== lastGestureAt) { urgentWindowAt = lastGestureAt; urgentInWindow = 0; }
|
|
39
|
+
return urgentInWindow++ < URGENT_CAP;
|
|
40
|
+
}
|
|
41
|
+
|
|
19
42
|
// ---- Detection ----
|
|
20
43
|
|
|
21
44
|
const isPopoverOpen = (el) => { try { return el.matches(':popover-open'); } catch (_) { return false; } };
|
|
@@ -87,7 +110,7 @@ function register(el, rule, opts) {
|
|
|
87
110
|
lazy: !!(opts && opts.lazy),
|
|
88
111
|
discard: !!(opts && opts.discard),
|
|
89
112
|
priority: opts ? opts.priority : 0,
|
|
90
|
-
urgent:
|
|
113
|
+
urgent: isUrgent(),
|
|
91
114
|
rendered: false,
|
|
92
115
|
cleanup: []
|
|
93
116
|
};
|
|
@@ -96,10 +119,11 @@ function register(el, rule, opts) {
|
|
|
96
119
|
return rec;
|
|
97
120
|
}
|
|
98
121
|
|
|
99
|
-
function render(rec) {
|
|
100
|
-
if (rec.rendered) return;
|
|
122
|
+
function render(rec, viaPrewarm) {
|
|
123
|
+
if (rec.rendered) { if (!viaPrewarm && warm.delete(rec)) schedule(); return; }
|
|
101
124
|
rec.rendered = true;
|
|
102
125
|
pending.delete(rec);
|
|
126
|
+
if (viaPrewarm) warm.add(rec); else warm.delete(rec);
|
|
103
127
|
const { el, tpl } = rec;
|
|
104
128
|
const content = rec.discard ? tpl.content.cloneNode(true) : tpl.content;
|
|
105
129
|
Alpine.mutateDom(() => {
|
|
@@ -121,6 +145,38 @@ function teardown(rec) {
|
|
|
121
145
|
});
|
|
122
146
|
}
|
|
123
147
|
|
|
148
|
+
// Evict a warm, still-closed, never-opened container back to its stash (least reachable first)
|
|
149
|
+
function restash(rec) {
|
|
150
|
+
if (!rec.rendered) return;
|
|
151
|
+
rec.rendered = false;
|
|
152
|
+
rec.near = undefined;
|
|
153
|
+
const { el, tpl } = rec;
|
|
154
|
+
Alpine.mutateDom(() => {
|
|
155
|
+
Array.from(el.children).forEach((child) => Alpine.destroyTree(child));
|
|
156
|
+
while (el.firstChild) tpl.content.appendChild(el.firstChild);
|
|
157
|
+
el.appendChild(tpl);
|
|
158
|
+
});
|
|
159
|
+
warm.delete(rec);
|
|
160
|
+
pending.add(rec);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function evict() {
|
|
164
|
+
if (warm.size <= PREWARM_CAP) return;
|
|
165
|
+
for (const rec of warm) rec.near = undefined;
|
|
166
|
+
while (warm.size > PREWARM_CAP) {
|
|
167
|
+
let victim = null, score = -1;
|
|
168
|
+
for (const rec of warm) {
|
|
169
|
+
if (!rec.el.isConnected) { warm.delete(rec); continue; }
|
|
170
|
+
if (rec.rule === 'popover' && isPopoverOpen(rec.el)) continue;
|
|
171
|
+
if (rec.near === undefined) rec.near = nearViewport(rec);
|
|
172
|
+
const sc = (reachable(rec.el) ? 0 : 2) + (rec.near ? 0 : 1);
|
|
173
|
+
if (sc > score) { victim = rec; score = sc; }
|
|
174
|
+
}
|
|
175
|
+
if (!victim) return;
|
|
176
|
+
restash(victim);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
124
180
|
// ---- Open signals ----
|
|
125
181
|
|
|
126
182
|
function wire(rec) {
|
|
@@ -162,39 +218,117 @@ function wire(rec) {
|
|
|
162
218
|
|
|
163
219
|
// ---- Idle prewarm (after manifest:ready; urgent first, then priority asc, then document order) ----
|
|
164
220
|
|
|
165
|
-
const idle = (fn, timeout) =>
|
|
166
|
-
|
|
167
|
-
|
|
221
|
+
const idle = (fn, timeout) => {
|
|
222
|
+
idleIsTimer = !window.requestIdleCallback;
|
|
223
|
+
return idleIsTimer
|
|
224
|
+
? setTimeout(() => fn({ didTimeout: true, timeRemaining: () => 0 }), 50)
|
|
225
|
+
: window.requestIdleCallback(fn, { timeout });
|
|
226
|
+
};
|
|
227
|
+
function cancelIdle() {
|
|
228
|
+
if (idleHandle === null) return;
|
|
229
|
+
if (idleIsTimer) clearTimeout(idleHandle); else if (window.cancelIdleCallback) window.cancelIdleCallback(idleHandle);
|
|
230
|
+
idleHandle = null;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// A gesture that revealed a pane (tab switch, panel open) makes its containers urgent
|
|
234
|
+
function promoteNearViewport() {
|
|
235
|
+
promoteTimer = null;
|
|
236
|
+
if (!ready || !bootDrained) return;
|
|
237
|
+
// The on-screen containers nearest the gesture point (a revealed pane sits under the click)
|
|
238
|
+
const candidates = [];
|
|
239
|
+
for (const rec of pending) {
|
|
240
|
+
if (rec.urgent || !rec.el.isConnected || !reachable(rec.el)) continue;
|
|
241
|
+
const r = anchorRect(rec);
|
|
242
|
+
rec.near = !!r && r.bottom > 0 && r.top < window.innerHeight && r.right > 0 && r.left < window.innerWidth;
|
|
243
|
+
if (!rec.near) continue;
|
|
244
|
+
const dx = Math.max(r.left - gestureX, 0, gestureX - r.right);
|
|
245
|
+
const dy = Math.max(r.top - gestureY, 0, gestureY - r.bottom);
|
|
246
|
+
candidates.push([dx * dx + dy * dy, rec]);
|
|
247
|
+
}
|
|
248
|
+
if (!candidates.length) return;
|
|
249
|
+
candidates.sort((a, b) => (a[1].priority - b[1].priority) || (a[0] - b[0])); // authored priority first, then nearest
|
|
250
|
+
let n = 0;
|
|
251
|
+
for (const [, rec] of candidates) { if (n++ >= URGENT_CAP) break; rec.urgent = true; }
|
|
252
|
+
cancelIdle();
|
|
253
|
+
schedule();
|
|
254
|
+
}
|
|
255
|
+
function onGesture(e) {
|
|
256
|
+
lastGestureAt = now();
|
|
257
|
+
if (e && typeof e.clientX === 'number' && (e.clientX || e.clientY)) { gestureX = e.clientX; gestureY = e.clientY; }
|
|
258
|
+
if (promoteTimer !== null || now() - lastPromoteAt < 500) return;
|
|
259
|
+
lastPromoteAt = now();
|
|
260
|
+
promoteTimer = setTimeout(promoteNearViewport, 150);
|
|
261
|
+
}
|
|
168
262
|
|
|
263
|
+
// Only containers the user can reach: not under a hidden route; visible parents first
|
|
264
|
+
const reachable = (el) => !el.closest('[x-route][hidden]');
|
|
265
|
+
// Judge by the invoker (popovertarget / aria-controls), else the nearest ancestor with a box
|
|
266
|
+
// Rect of the invoker or the nearest boxed ancestor (null when nothing has a box)
|
|
267
|
+
function anchorRect(rec) {
|
|
268
|
+
const el = rec.el;
|
|
269
|
+
if (!rec.invoker && el.id && window.CSS && CSS.escape) {
|
|
270
|
+
const id = CSS.escape(el.id);
|
|
271
|
+
rec.invoker = document.querySelector(`[popovertarget="${id}"], [aria-controls="${id}"]`) || null;
|
|
272
|
+
}
|
|
273
|
+
let p = (rec.invoker && rec.invoker.isConnected) ? rec.invoker : el.parentElement;
|
|
274
|
+
for (let depth = 0; p && depth < 6; p = p.parentElement, depth++) {
|
|
275
|
+
const r = p.getBoundingClientRect();
|
|
276
|
+
if (r.width || r.height) return r;
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
function nearViewport(rec) {
|
|
281
|
+
const r = anchorRect(rec);
|
|
282
|
+
return !!r && r.bottom > 0 && r.top < window.innerHeight && r.right > 0 && r.left < window.innerWidth;
|
|
283
|
+
}
|
|
284
|
+
function rank(rec) {
|
|
285
|
+
if (rec.near === undefined) rec.near = nearViewport(rec);
|
|
286
|
+
return (rec.urgent ? 0 : 4) + (rec.near ? 0 : 2);
|
|
287
|
+
}
|
|
169
288
|
function next() {
|
|
170
|
-
let best = null;
|
|
289
|
+
let best = null, bestRank = 0;
|
|
171
290
|
for (const rec of pending) {
|
|
172
291
|
if (!rec.el.isConnected) { pending.delete(rec); continue; }
|
|
292
|
+
if (!reachable(rec.el)) continue;
|
|
293
|
+
const r = rank(rec);
|
|
173
294
|
if (!best
|
|
174
|
-
||
|
|
175
|
-
|| (
|
|
295
|
+
|| r < bestRank
|
|
296
|
+
|| (r === bestRank && (rec.priority < best.priority
|
|
176
297
|
|| (rec.priority === best.priority && (best.el.compareDocumentPosition(rec.el) & Node.DOCUMENT_POSITION_PRECEDING))))) {
|
|
177
|
-
best = rec;
|
|
298
|
+
best = rec; bestRank = r;
|
|
178
299
|
}
|
|
179
300
|
}
|
|
180
301
|
return best;
|
|
181
302
|
}
|
|
182
303
|
|
|
183
304
|
function schedule() {
|
|
184
|
-
if (
|
|
305
|
+
if (pending.size === 0) { if (ready) bootDrained = true; return; }
|
|
306
|
+
if (idleHandle !== null || !ready) return;
|
|
185
307
|
const head = next();
|
|
186
|
-
|
|
308
|
+
if (!head) { if (ready) bootDrained = true; return; }
|
|
309
|
+
if (!head.urgent && warm.size >= PREWARM_CAP) return; // paused at the cap; resumes when a warm one opens or evicts
|
|
310
|
+
idleHandle = idle((deadline) => {
|
|
187
311
|
idleHandle = null;
|
|
188
|
-
|
|
189
|
-
|
|
312
|
+
// Forced fire (never-idle page): one container. Genuine idle: batch within the budget.
|
|
313
|
+
let n = 0;
|
|
314
|
+
do {
|
|
315
|
+
const rec = next();
|
|
316
|
+
if (!rec) break;
|
|
317
|
+
if (!rec.urgent && warm.size >= PREWARM_CAP) break; // cap = max warm; only urgent renders may exceed it
|
|
318
|
+
for (const other of pending) other.near = undefined;
|
|
319
|
+
render(rec, true);
|
|
320
|
+
n++;
|
|
321
|
+
} while (deadline && !deadline.didTimeout && deadline.timeRemaining() > IDLE_BUDGET_MS && n < SLICE_MAX);
|
|
322
|
+
evict();
|
|
190
323
|
schedule();
|
|
191
|
-
}, head
|
|
324
|
+
}, head.urgent ? 100 : 500);
|
|
192
325
|
}
|
|
193
326
|
|
|
194
327
|
function markReady() {
|
|
195
328
|
if (ready) return;
|
|
196
329
|
ready = true;
|
|
197
330
|
schedule();
|
|
331
|
+
setTimeout(() => { bootDrained = true; }, 5000); // lazy components may keep the boot queue busy
|
|
198
332
|
}
|
|
199
333
|
|
|
200
334
|
function armReady() {
|
|
@@ -227,6 +361,7 @@ function intercept(el, skip) {
|
|
|
227
361
|
function initializeDeferPlugin() {
|
|
228
362
|
Alpine.directive('defer', () => { });
|
|
229
363
|
if (!killed) Alpine.interceptInit(Alpine.skipDuringClone(intercept));
|
|
364
|
+
['pointerdown', 'keydown'].forEach((type) => document.addEventListener(type, onGesture, { capture: true, passive: true }));
|
|
230
365
|
armReady();
|
|
231
366
|
}
|
|
232
367
|
|
|
@@ -234,7 +369,8 @@ window.ManifestDefer = {
|
|
|
234
369
|
enabled: !killed,
|
|
235
370
|
defer,
|
|
236
371
|
render: (el) => { if (el && el.__mnfstDefer) render(el.__mnfstDefer); },
|
|
237
|
-
isPending: (el) => !!(el && el.__mnfstDefer && !el.__mnfstDefer.rendered)
|
|
372
|
+
isPending: (el) => !!(el && el.__mnfstDefer && !el.__mnfstDefer.rendered),
|
|
373
|
+
stats: () => ({ pending: pending.size, warm: warm.size, cap: PREWARM_CAP, ready, bootDrained, armed: idleHandle !== null, head: (() => { const h = next(); return h ? (h.el.id || h.el.tagName) : null; })() })
|
|
238
374
|
};
|
|
239
375
|
if (window.Manifest && typeof window.Manifest === 'object') window.Manifest.defer = defer;
|
|
240
376
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"manifest.computed.js": "sha384-LDxER8pyYyYwUxIYIyiJA02s+49g8CzpSlOj28On0DFIUgetT3UmvE/9xOsTwT66",
|
|
13
13
|
"manifest.data.js": "sha384-urUiANuQfrc+AfwTB7+nMneF2C1+EfV+S5+eg4PQnZCJ0Aid50KwSARufiVrGAXd",
|
|
14
14
|
"manifest.datepicker.js": "sha384-8vTJYkLqqvgXgm1w1becuaRR/jUEpiLBjBJ6RsRbUr+ZkypvspD6EsssuDrX4SQq",
|
|
15
|
-
"manifest.defer.js": "sha384-
|
|
15
|
+
"manifest.defer.js": "sha384-szMa04R4gWsZl3nICHz3Mtku8vgROEHvrAIMap+wuqITKvMVQHJOa0kVzHHjMcwN",
|
|
16
16
|
"manifest.dropdowns.js": "sha384-4HuHkBt4i8sTPUDLB9rNLli2/lWLY1tjtCLzey37gRwueG+FRLCnVH1q7ljiVvJR",
|
|
17
17
|
"manifest.edit.js": "sha384-ZvgV3ax7yHtSC23zdvRYJPzBsBpGFBLuBJMgfQ1IDumv97Cn2bpHOePxyQikfl5L",
|
|
18
18
|
"manifest.export.js": "sha384-HCy3n6ifrcAGIyzTRgZ5JIRvSX9q9/8ykMKRSbt1QTnhXz6PgYlV0AE2N2Yi2re+",
|
|
@@ -33,6 +33,6 @@
|
|
|
33
33
|
"manifest.tooltips.js": "sha384-uXVVQADl6pY2Pj9i/UaSclZN7dzbKhLkmevv7Ad0+wXR8LgqodALNFrc5jZJjlwm",
|
|
34
34
|
"manifest.url.parameters.js": "sha384-J2aSVBOPsoqsbafHndrWLHEDpKb4AKJAoQnVmKA4gZh5P/8HHjFWHO/lkusmG84l",
|
|
35
35
|
"manifest.utilities.js": "sha384-YcGKTEviTv9FLkw890CTociA7Rjp6xuhLyfpn3Gk4mT0fexDFqpW7VSuohcpGfsw",
|
|
36
|
-
"manifest.virtual.js": "sha384-
|
|
36
|
+
"manifest.virtual.js": "sha384-YwwmAsZNddajnAkLhBWtrK8edHMJOhD7cJkQi3qmm5o1Thg0wbtDU0dsfrCHKTIQ",
|
|
37
37
|
"manifest.js": "sha384-IaRa1ZZNj8C/mcB9e6qNiTqK7oriQr0Vmt4Q7FcGWOqfUgPxojYixbs+g8XR9lNz"
|
|
38
38
|
}
|
package/lib/manifest.virtual.js
CHANGED
|
@@ -421,7 +421,7 @@ function initializeVirtualPlugin() {
|
|
|
421
421
|
|
|
422
422
|
let resizeTimer = null;
|
|
423
423
|
const ro = new ResizeObserver(() => {
|
|
424
|
-
if (!ready) { render
|
|
424
|
+
if (!ready) { requestAnimationFrame(render); return; } // first paint once sized, off the observer tick
|
|
425
425
|
clearTimeout(resizeTimer);
|
|
426
426
|
resizeTimer = setTimeout(() => { rebuild(); render(); }, 100); // debounced re-pack
|
|
427
427
|
});
|