mnfst 0.5.198-next.2 → 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 +133 -15
- 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
|
@@ -20,9 +20,17 @@ let idleHandle = null;
|
|
|
20
20
|
// Urgent = mounted after boot, within a gesture window (a pane the user just opened), capped per gesture
|
|
21
21
|
const URGENT_WINDOW_MS = 1500;
|
|
22
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
|
|
23
27
|
let lastGestureAt = -Infinity;
|
|
24
28
|
let urgentWindowAt = -Infinity;
|
|
25
29
|
let urgentInWindow = 0;
|
|
30
|
+
let promoteTimer = null;
|
|
31
|
+
let lastPromoteAt = -Infinity;
|
|
32
|
+
let gestureX = 0, gestureY = 0;
|
|
33
|
+
let idleIsTimer = false;
|
|
26
34
|
const now = () => (typeof performance !== 'undefined' && performance.now) ? performance.now() : Date.now();
|
|
27
35
|
|
|
28
36
|
function isUrgent() {
|
|
@@ -111,10 +119,11 @@ function register(el, rule, opts) {
|
|
|
111
119
|
return rec;
|
|
112
120
|
}
|
|
113
121
|
|
|
114
|
-
function render(rec) {
|
|
115
|
-
if (rec.rendered) return;
|
|
122
|
+
function render(rec, viaPrewarm) {
|
|
123
|
+
if (rec.rendered) { if (!viaPrewarm && warm.delete(rec)) schedule(); return; }
|
|
116
124
|
rec.rendered = true;
|
|
117
125
|
pending.delete(rec);
|
|
126
|
+
if (viaPrewarm) warm.add(rec); else warm.delete(rec);
|
|
118
127
|
const { el, tpl } = rec;
|
|
119
128
|
const content = rec.discard ? tpl.content.cloneNode(true) : tpl.content;
|
|
120
129
|
Alpine.mutateDom(() => {
|
|
@@ -136,6 +145,38 @@ function teardown(rec) {
|
|
|
136
145
|
});
|
|
137
146
|
}
|
|
138
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
|
+
|
|
139
180
|
// ---- Open signals ----
|
|
140
181
|
|
|
141
182
|
function wire(rec) {
|
|
@@ -177,19 +218,84 @@ function wire(rec) {
|
|
|
177
218
|
|
|
178
219
|
// ---- Idle prewarm (after manifest:ready; urgent first, then priority asc, then document order) ----
|
|
179
220
|
|
|
180
|
-
const idle = (fn, timeout) =>
|
|
181
|
-
|
|
182
|
-
|
|
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
|
+
}
|
|
183
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
|
+
}
|
|
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
|
+
}
|
|
184
288
|
function next() {
|
|
185
|
-
let best = null;
|
|
289
|
+
let best = null, bestRank = 0;
|
|
186
290
|
for (const rec of pending) {
|
|
187
291
|
if (!rec.el.isConnected) { pending.delete(rec); continue; }
|
|
292
|
+
if (!reachable(rec.el)) continue;
|
|
293
|
+
const r = rank(rec);
|
|
188
294
|
if (!best
|
|
189
|
-
||
|
|
190
|
-
|| (
|
|
295
|
+
|| r < bestRank
|
|
296
|
+
|| (r === bestRank && (rec.priority < best.priority
|
|
191
297
|
|| (rec.priority === best.priority && (best.el.compareDocumentPosition(rec.el) & Node.DOCUMENT_POSITION_PRECEDING))))) {
|
|
192
|
-
best = rec;
|
|
298
|
+
best = rec; bestRank = r;
|
|
193
299
|
}
|
|
194
300
|
}
|
|
195
301
|
return best;
|
|
@@ -199,12 +305,23 @@ function schedule() {
|
|
|
199
305
|
if (pending.size === 0) { if (ready) bootDrained = true; return; }
|
|
200
306
|
if (idleHandle !== null || !ready) return;
|
|
201
307
|
const head = next();
|
|
202
|
-
|
|
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) => {
|
|
203
311
|
idleHandle = null;
|
|
204
|
-
|
|
205
|
-
|
|
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();
|
|
206
323
|
schedule();
|
|
207
|
-
}, head
|
|
324
|
+
}, head.urgent ? 100 : 500);
|
|
208
325
|
}
|
|
209
326
|
|
|
210
327
|
function markReady() {
|
|
@@ -244,7 +361,7 @@ function intercept(el, skip) {
|
|
|
244
361
|
function initializeDeferPlugin() {
|
|
245
362
|
Alpine.directive('defer', () => { });
|
|
246
363
|
if (!killed) Alpine.interceptInit(Alpine.skipDuringClone(intercept));
|
|
247
|
-
['pointerdown', 'keydown'].forEach((type) => document.addEventListener(type,
|
|
364
|
+
['pointerdown', 'keydown'].forEach((type) => document.addEventListener(type, onGesture, { capture: true, passive: true }));
|
|
248
365
|
armReady();
|
|
249
366
|
}
|
|
250
367
|
|
|
@@ -252,7 +369,8 @@ window.ManifestDefer = {
|
|
|
252
369
|
enabled: !killed,
|
|
253
370
|
defer,
|
|
254
371
|
render: (el) => { if (el && el.__mnfstDefer) render(el.__mnfstDefer); },
|
|
255
|
-
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; })() })
|
|
256
374
|
};
|
|
257
375
|
if (window.Manifest && typeof window.Manifest === 'object') window.Manifest.defer = defer;
|
|
258
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
|
});
|