mnfst 0.5.198-next.3 → 0.5.198
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.bindings.js +31 -0
- package/lib/manifest.data.js +211 -66
- package/lib/manifest.defer.js +49 -13
- package/lib/manifest.integrity.json +5 -4
- package/lib/manifest.js +1 -0
- package/lib/manifest.router.js +109 -93
- package/package.json +1 -1
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/* Manifest Bindings */
|
|
2
|
+
|
|
3
|
+
(function () {
|
|
4
|
+
|
|
5
|
+
// x-text: same as Alpine's, but never rewrites an unchanged string (each write is a DOM mutation)
|
|
6
|
+
function initializeBindingsPlugin() {
|
|
7
|
+
Alpine.directive('text', (el, { expression }, { effect, evaluateLater }) => {
|
|
8
|
+
const evaluate = evaluateLater(expression);
|
|
9
|
+
effect(() => {
|
|
10
|
+
evaluate((value) => {
|
|
11
|
+
const next = value == null ? '' : String(value);
|
|
12
|
+
if (el.textContent === next) return;
|
|
13
|
+
Alpine.mutateDom(() => { el.textContent = next; });
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let bindingsPluginInitialized = false;
|
|
20
|
+
|
|
21
|
+
function ensureBindingsPluginInitialized() {
|
|
22
|
+
if (bindingsPluginInitialized) return;
|
|
23
|
+
if (!window.Alpine || typeof window.Alpine.directive !== 'function') return;
|
|
24
|
+
bindingsPluginInitialized = true;
|
|
25
|
+
initializeBindingsPlugin();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
document.addEventListener('alpine:init', ensureBindingsPluginInitialized);
|
|
29
|
+
if (window.Alpine && typeof window.Alpine.directive === 'function') ensureBindingsPluginInitialized();
|
|
30
|
+
|
|
31
|
+
})();
|
package/lib/manifest.data.js
CHANGED
|
@@ -279,6 +279,9 @@ const pendingLocal = new Map(); // source -> Map<$id, { patch, removed }>
|
|
|
279
279
|
let allCache = null;
|
|
280
280
|
let allDirty = true;
|
|
281
281
|
|
|
282
|
+
// Per-source freshness: `$fresh` resolves on the first network-fresh landing of this page-load
|
|
283
|
+
const freshness = new Map(); // source -> { promise, resolve, done }
|
|
284
|
+
|
|
282
285
|
// Deep seal so Alpine won't proxy (double-proxying causes recursion)
|
|
283
286
|
function deepSeal(obj) {
|
|
284
287
|
if (obj === null || typeof obj !== 'object') {
|
|
@@ -372,10 +375,48 @@ function mergeRowFields(target, fields, dataSourceName) {
|
|
|
372
375
|
}
|
|
373
376
|
|
|
374
377
|
function attachMethods(array, dataSourceName) {
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
|
|
378
|
+
const main = window.ManifestDataMain;
|
|
379
|
+
const reload = main?.reloadDataSource || main?.loadDataSource;
|
|
380
|
+
if (Array.isArray(array) && reload && window.ManifestDataProxies?.attachArrayMethods) {
|
|
381
|
+
window.ManifestDataProxies.attachArrayMethods(array, dataSourceName, reload);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function sourceFreshness(dataSourceName) {
|
|
386
|
+
let f = freshness.get(dataSourceName);
|
|
387
|
+
if (!f) {
|
|
388
|
+
f = { done: false, resolve: null, promise: null };
|
|
389
|
+
f.promise = new Promise(resolve => { f.resolve = resolve; });
|
|
390
|
+
freshness.set(dataSourceName, f);
|
|
378
391
|
}
|
|
392
|
+
return f;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function markFresh(dataSourceName) {
|
|
396
|
+
const f = sourceFreshness(dataSourceName);
|
|
397
|
+
if (!f.done) { f.done = true; f.resolve(); }
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// State-only write (loading/error) — rows stay live, no version bump
|
|
401
|
+
function setSourceState(dataSourceName, patch) {
|
|
402
|
+
const store = typeof Alpine !== 'undefined' ? Alpine.store('data') : null;
|
|
403
|
+
if (!store) return null;
|
|
404
|
+
const key = `_${dataSourceName}_state`;
|
|
405
|
+
const current = store[key] || { loading: false, error: null, ready: false, stale: true };
|
|
406
|
+
const next = { ...current, ...patch };
|
|
407
|
+
if (patch.error !== undefined) next.errorTime = patch.error ? (patch.errorTime || Date.now()) : null;
|
|
408
|
+
store[key] = next;
|
|
409
|
+
return next;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Authoritative in-flight map: one promise per key, cleared on settle
|
|
413
|
+
function runDeduped(key, factory) {
|
|
414
|
+
if (loadingPromises.has(key)) return loadingPromises.get(key);
|
|
415
|
+
const promise = Promise.resolve().then(factory).finally(() => {
|
|
416
|
+
if (loadingPromises.get(key) === promise) loadingPromises.delete(key);
|
|
417
|
+
});
|
|
418
|
+
loadingPromises.set(key, promise);
|
|
419
|
+
return promise;
|
|
379
420
|
}
|
|
380
421
|
|
|
381
422
|
// Reactive source array, created on demand; raw map tracks the same identity
|
|
@@ -466,14 +507,18 @@ function writeSource(dataSourceName, data, options = {}) {
|
|
|
466
507
|
rawDataStore.set(dataSourceName, data);
|
|
467
508
|
}
|
|
468
509
|
|
|
469
|
-
const currentState = store[`_${dataSourceName}_state`] || { loading: false, error: null, ready: false };
|
|
510
|
+
const currentState = store[`_${dataSourceName}_state`] || { loading: false, error: null, ready: false, stale: true };
|
|
511
|
+
// `fresh`: network-origin rows → clears `stale` for the rest of the page-load
|
|
512
|
+
const fresh = options.fresh === true && data !== null && data !== undefined;
|
|
470
513
|
const newState = {
|
|
471
514
|
loading: options.loading !== undefined ? options.loading : currentState.loading,
|
|
472
515
|
error: options.error !== undefined ? options.error : currentState.error,
|
|
473
516
|
ready: options.ready !== undefined ? options.ready : (data !== null && data !== undefined),
|
|
474
|
-
errorTime: options.error !== undefined && options.error !== null ? Date.now() : (currentState.errorTime || null)
|
|
517
|
+
errorTime: options.error !== undefined && options.error !== null ? Date.now() : (currentState.errorTime || null),
|
|
518
|
+
stale: fresh ? false : currentState.stale !== false
|
|
475
519
|
};
|
|
476
520
|
store[`_${dataSourceName}_state`] = newState;
|
|
521
|
+
if (fresh) markFresh(dataSourceName);
|
|
477
522
|
store._initialized = true;
|
|
478
523
|
store._ready = true;
|
|
479
524
|
|
|
@@ -1386,6 +1431,10 @@ window.ManifestDataStore = {
|
|
|
1386
1431
|
landRemove,
|
|
1387
1432
|
flushLandings,
|
|
1388
1433
|
noteLocalWrite,
|
|
1434
|
+
// Stale-first + dedupe (§11.1)
|
|
1435
|
+
setSourceState,
|
|
1436
|
+
runDeduped,
|
|
1437
|
+
sourceFreshness,
|
|
1389
1438
|
touchSource,
|
|
1390
1439
|
bumpAllVersions,
|
|
1391
1440
|
mergeRowFields,
|
|
@@ -3231,6 +3280,25 @@ function attachArrayMethods(array, dataSourceName, reloadDataSource) {
|
|
|
3231
3280
|
}
|
|
3232
3281
|
});
|
|
3233
3282
|
|
|
3283
|
+
// $stale / $fresh: semantics in manifest.data.proxies.magic.state.js
|
|
3284
|
+
Object.defineProperty(array, '$stale', {
|
|
3285
|
+
enumerable: false,
|
|
3286
|
+
configurable: true,
|
|
3287
|
+
get: function () {
|
|
3288
|
+
const store = Alpine.store('data');
|
|
3289
|
+
const state = store?.[`_${dataSourceName}_state`];
|
|
3290
|
+
return !state || state.stale !== false;
|
|
3291
|
+
}
|
|
3292
|
+
});
|
|
3293
|
+
|
|
3294
|
+
Object.defineProperty(array, '$fresh', {
|
|
3295
|
+
enumerable: false,
|
|
3296
|
+
configurable: true,
|
|
3297
|
+
get: function () {
|
|
3298
|
+
return window.ManifestDataStore?.sourceFreshness?.(dataSourceName)?.promise || Promise.resolve();
|
|
3299
|
+
}
|
|
3300
|
+
});
|
|
3301
|
+
|
|
3234
3302
|
// Attach $search method for client-side text filtering
|
|
3235
3303
|
Object.defineProperty(array, '$search', {
|
|
3236
3304
|
enumerable: false,
|
|
@@ -3902,7 +3970,10 @@ function createArrayProxyWithRoute(arrayTarget, dataSourceName = null, reloadDat
|
|
|
3902
3970
|
return undefined;
|
|
3903
3971
|
}
|
|
3904
3972
|
|
|
3905
|
-
// Handle state properties ($loading, $error, $ready)
|
|
3973
|
+
// Handle state properties ($loading, $error, $ready, $stale, $fresh)
|
|
3974
|
+
if (key === '$stale' || key === '$fresh') {
|
|
3975
|
+
return window.ManifestDataProxiesMagic?.getStateProperty?.(key, dataSourceName);
|
|
3976
|
+
}
|
|
3906
3977
|
if (key === '$loading' || key === '$error' || key === '$ready') {
|
|
3907
3978
|
const store = Alpine.store('data');
|
|
3908
3979
|
// Safely access dataSourceName - if it's not defined, use null
|
|
@@ -4089,7 +4160,7 @@ function createArrayProxyWithRoute(arrayTarget, dataSourceName = null, reloadDat
|
|
|
4089
4160
|
return true;
|
|
4090
4161
|
}
|
|
4091
4162
|
// Check custom methods we've added
|
|
4092
|
-
if (key === '$route' || key === '$loading' || key === '$error' || key === '$ready') {
|
|
4163
|
+
if (key === '$route' || key === '$loading' || key === '$error' || key === '$ready' || key === '$stale' || key === '$fresh') {
|
|
4093
4164
|
return true;
|
|
4094
4165
|
}
|
|
4095
4166
|
// For other string keys, check if they exist on the array
|
|
@@ -6947,16 +7018,22 @@ function createAppwriteMethodsHandler(dataSourceName, reloadDataSource) {
|
|
|
6947
7018
|
queries || [],
|
|
6948
7019
|
scope
|
|
6949
7020
|
);
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
7021
|
+
// Dedupe key: source + serialized queries (never across different queries)
|
|
7022
|
+
const key = `${dataSourceName}:$query:${JSON.stringify(appwriteQueries.map(q => String(q)))}`;
|
|
7023
|
+
const { runDeduped, landRows } = window.ManifestDataStore || {};
|
|
7024
|
+
const run = async () => {
|
|
7025
|
+
const result = await window.ManifestDataAppwrite.loadTableRows(
|
|
7026
|
+
appwriteConfig.databaseId,
|
|
7027
|
+
tableId,
|
|
7028
|
+
appwriteQueries
|
|
7029
|
+
);
|
|
7030
|
+
// Network landing: query result replaces the source (coalesced, identity-preserving)
|
|
7031
|
+
if (result !== undefined && landRows) {
|
|
7032
|
+
await landRows(dataSourceName, result, { mode: 'replace', fresh: true });
|
|
7033
|
+
}
|
|
7034
|
+
return result;
|
|
7035
|
+
};
|
|
7036
|
+
return runDeduped ? await runDeduped(key, run) : await run();
|
|
6960
7037
|
}
|
|
6961
7038
|
}
|
|
6962
7039
|
|
|
@@ -7737,20 +7814,39 @@ window.ManifestDataProxiesAppwrite.createAppwriteMethodsHandler = createAppwrite
|
|
|
7737
7814
|
|
|
7738
7815
|
|
|
7739
7816
|
/* Manifest Data Sources - Magic Method State Properties */
|
|
7740
|
-
// $loading, $error, $ready state properties
|
|
7817
|
+
// $loading, $error, $ready, $stale, $fresh state properties
|
|
7818
|
+
//
|
|
7819
|
+
// $stale: true until the first network-fresh landing of this page-load is
|
|
7820
|
+
// applied (rows from a fetch, `$query`, or the memory cache of a fetch made
|
|
7821
|
+
// this page-load); false from then on — a later reload keeps it false and
|
|
7822
|
+
// reports through $loading. Single-reveal UIs gate on `!$stale`.
|
|
7823
|
+
// $fresh: promise resolving at that same first fresh landing; never rejects
|
|
7824
|
+
// (a failed reload keeps old rows and sets $error). Per page-load.
|
|
7825
|
+
|
|
7826
|
+
const STATE_PROPS = ['$loading', '$error', '$ready', '$stale', '$fresh'];
|
|
7827
|
+
|
|
7828
|
+
function defaultStateValue(prop) {
|
|
7829
|
+
if (prop === '$error') return null;
|
|
7830
|
+
if (prop === '$stale') return true;
|
|
7831
|
+
return false;
|
|
7832
|
+
}
|
|
7741
7833
|
|
|
7742
7834
|
function getStateProperty(prop, dataSourceName) {
|
|
7835
|
+
if (prop === '$fresh') {
|
|
7836
|
+
return window.ManifestDataStore?.sourceFreshness?.(dataSourceName)?.promise || Promise.resolve();
|
|
7837
|
+
}
|
|
7838
|
+
|
|
7743
7839
|
if (typeof Alpine === 'undefined' || !Alpine.store) {
|
|
7744
|
-
return
|
|
7840
|
+
return defaultStateValue(prop);
|
|
7745
7841
|
}
|
|
7746
7842
|
|
|
7747
7843
|
const store = Alpine.store('data');
|
|
7748
7844
|
if (!store) {
|
|
7749
|
-
return
|
|
7845
|
+
return defaultStateValue(prop);
|
|
7750
7846
|
}
|
|
7751
7847
|
|
|
7752
7848
|
const stateKey = `_${dataSourceName}_state`;
|
|
7753
|
-
const state = store[stateKey] || { loading: false, error: null, ready: false };
|
|
7849
|
+
const state = store[stateKey] || { loading: false, error: null, ready: false, stale: true };
|
|
7754
7850
|
|
|
7755
7851
|
if (prop === '$loading') {
|
|
7756
7852
|
return state.loading !== false; // Default to true if loading
|
|
@@ -7758,6 +7854,8 @@ function getStateProperty(prop, dataSourceName) {
|
|
|
7758
7854
|
return state.error || null;
|
|
7759
7855
|
} else if (prop === '$ready') {
|
|
7760
7856
|
return state.ready || false;
|
|
7857
|
+
} else if (prop === '$stale') {
|
|
7858
|
+
return state.stale !== false;
|
|
7761
7859
|
}
|
|
7762
7860
|
|
|
7763
7861
|
return undefined;
|
|
@@ -7766,7 +7864,7 @@ function getStateProperty(prop, dataSourceName) {
|
|
|
7766
7864
|
// State property handler for use in proxy get() traps
|
|
7767
7865
|
function createStatePropertyHandler(dataSourceName) {
|
|
7768
7866
|
return function (key) {
|
|
7769
|
-
if (key
|
|
7867
|
+
if (STATE_PROPS.includes(key)) {
|
|
7770
7868
|
return getStateProperty(key, dataSourceName);
|
|
7771
7869
|
}
|
|
7772
7870
|
return undefined;
|
|
@@ -7777,11 +7875,11 @@ function createStatePropertyHandler(dataSourceName) {
|
|
|
7777
7875
|
if (!window.ManifestDataProxiesMagic) {
|
|
7778
7876
|
window.ManifestDataProxiesMagic = {};
|
|
7779
7877
|
}
|
|
7878
|
+
window.ManifestDataProxiesMagic.STATE_PROPS = STATE_PROPS;
|
|
7780
7879
|
window.ManifestDataProxiesMagic.getStateProperty = getStateProperty;
|
|
7781
7880
|
window.ManifestDataProxiesMagic.createStatePropertyHandler = createStatePropertyHandler;
|
|
7782
7881
|
|
|
7783
7882
|
|
|
7784
|
-
|
|
7785
7883
|
/* Manifest Data Sources - Magic Method $files Handler */
|
|
7786
7884
|
// Handles $files method for reactive file arrays
|
|
7787
7885
|
|
|
@@ -8250,10 +8348,14 @@ function registerXMagicMethod(loadDataSource) {
|
|
|
8250
8348
|
window._manifestXAccessed = true;
|
|
8251
8349
|
}
|
|
8252
8350
|
const pendingLoads = new Map();
|
|
8351
|
+
const preDataProxies = new Map();
|
|
8253
8352
|
const store = Alpine.store('data');
|
|
8254
8353
|
|
|
8255
|
-
//
|
|
8256
|
-
const reloadDataSource =
|
|
8354
|
+
// Reload variant for Appwrite methods: bypasses the memory cache, keeps rows live
|
|
8355
|
+
const reloadDataSource = (name, locale) => {
|
|
8356
|
+
const reload = window.ManifestDataMain?.reloadDataSource;
|
|
8357
|
+
return reload ? reload(name, locale) : loadDataSource(name, locale, { reload: true });
|
|
8358
|
+
};
|
|
8257
8359
|
|
|
8258
8360
|
const ACTIVE_PROPS = Symbol('activeProps');
|
|
8259
8361
|
|
|
@@ -8285,7 +8387,8 @@ function registerXMagicMethod(loadDataSource) {
|
|
|
8285
8387
|
const stamped = Array.isArray(data)
|
|
8286
8388
|
? data.map(d => (d && typeof d === 'object' && !('contentType' in d)) ? { contentType: name, ...d } : d)
|
|
8287
8389
|
: data;
|
|
8288
|
-
|
|
8390
|
+
// App-supplied rows are authoritative → fresh
|
|
8391
|
+
window.ManifestDataStore?.updateStore?.(name, stamped, { loading: false, error: null, ready: true, fresh: true, allowDuringInit: true });
|
|
8289
8392
|
return true;
|
|
8290
8393
|
};
|
|
8291
8394
|
}
|
|
@@ -8353,7 +8456,21 @@ function registerXMagicMethod(loadDataSource) {
|
|
|
8353
8456
|
track();
|
|
8354
8457
|
const createLoadingProxy = window.ManifestDataProxiesCore?.createLoadingProxy;
|
|
8355
8458
|
if (createLoadingProxy) {
|
|
8356
|
-
|
|
8459
|
+
// Cached per source so re-evaluations see one identity; state props
|
|
8460
|
+
// ($loading/$error/$ready/$stale/$fresh) answer before any rows exist
|
|
8461
|
+
if (!preDataProxies.has(prop)) {
|
|
8462
|
+
const loading = createLoadingProxy(prop);
|
|
8463
|
+
const stateProps = window.ManifestDataProxiesMagic?.STATE_PROPS || [];
|
|
8464
|
+
preDataProxies.set(prop, new Proxy(loading, {
|
|
8465
|
+
get(target, key) {
|
|
8466
|
+
if (stateProps.includes(key)) {
|
|
8467
|
+
return window.ManifestDataProxiesMagic.getStateProperty(key, prop);
|
|
8468
|
+
}
|
|
8469
|
+
return target[key];
|
|
8470
|
+
}
|
|
8471
|
+
}));
|
|
8472
|
+
}
|
|
8473
|
+
return preDataProxies.get(prop);
|
|
8357
8474
|
}
|
|
8358
8475
|
return window.ManifestDataProxiesCore?.getChainingFallback?.() ?? '';
|
|
8359
8476
|
}
|
|
@@ -8461,7 +8578,7 @@ function registerXMagicMethod(loadDataSource) {
|
|
|
8461
8578
|
const arrayForMethods = valueIsArray ? value : (rawIsArray ? rawValue : arrayToProxy);
|
|
8462
8579
|
|
|
8463
8580
|
if (attachArrayMethods) {
|
|
8464
|
-
const arrayWithMethods = attachArrayMethods(arrayForMethods, prop,
|
|
8581
|
+
const arrayWithMethods = attachArrayMethods(arrayForMethods, prop, reloadDataSource);
|
|
8465
8582
|
// has() trap so Alpine sees $search/$query/etc. before accessing
|
|
8466
8583
|
return new Proxy(arrayWithMethods, {
|
|
8467
8584
|
get(target, key) {
|
|
@@ -8479,7 +8596,7 @@ function registerXMagicMethod(loadDataSource) {
|
|
|
8479
8596
|
const manifest = window.ManifestComponentsRegistry?.manifest || null;
|
|
8480
8597
|
const dataSource = manifest?.data?.[prop] || manifest?.appwrite?.[prop];
|
|
8481
8598
|
if (dataSource && window.ManifestDataConfig?.isAppwriteCollection?.(dataSource)) {
|
|
8482
|
-
const methodsHandler = createAppwriteMethodsHandler(prop,
|
|
8599
|
+
const methodsHandler = createAppwriteMethodsHandler(prop, reloadDataSource);
|
|
8483
8600
|
return methodsHandler.bind(null, key);
|
|
8484
8601
|
}
|
|
8485
8602
|
} catch { /* fall through */ }
|
|
@@ -8497,7 +8614,8 @@ function registerXMagicMethod(loadDataSource) {
|
|
|
8497
8614
|
has(target, key) {
|
|
8498
8615
|
// Report that base plugin methods exist
|
|
8499
8616
|
if (key === '$search' || key === '$query' || key === '$route' ||
|
|
8500
|
-
key === '$loading' || key === '$error' || key === '$ready'
|
|
8617
|
+
key === '$loading' || key === '$error' || key === '$ready' ||
|
|
8618
|
+
key === '$stale' || key === '$fresh') {
|
|
8501
8619
|
return key in target;
|
|
8502
8620
|
}
|
|
8503
8621
|
// Report that array methods exist
|
|
@@ -10751,37 +10869,56 @@ async function checkFileMatchesScope(file, scope) {
|
|
|
10751
10869
|
});
|
|
10752
10870
|
}
|
|
10753
10871
|
|
|
10754
|
-
//
|
|
10755
|
-
|
|
10872
|
+
// Rows already in the store (array or object) — a reload keeps them live
|
|
10873
|
+
function hasLiveRows(dataSourceName) {
|
|
10874
|
+
const store = typeof Alpine !== 'undefined' ? Alpine.store('data') : null;
|
|
10875
|
+
const raw = store ? (window.ManifestDataStore?.rawOf?.(store) || store) : null;
|
|
10876
|
+
const current = raw ? raw[dataSourceName] : undefined;
|
|
10877
|
+
return current !== null && current !== undefined;
|
|
10878
|
+
}
|
|
10879
|
+
|
|
10880
|
+
function liveLocale() {
|
|
10881
|
+
return (typeof document !== 'undefined' && document.documentElement?.lang)
|
|
10882
|
+
|| (typeof Alpine !== 'undefined' && Alpine.store('locale')?.current)
|
|
10883
|
+
|| 'en';
|
|
10884
|
+
}
|
|
10885
|
+
|
|
10886
|
+
// Load dataSource data. options.reload: bypass the memory cache and refetch;
|
|
10887
|
+
// existing rows stay live ($loading only), fresh rows merge by $id.
|
|
10888
|
+
async function loadDataSource(dataSourceName, locale = 'en', options = {}) {
|
|
10889
|
+
locale = locale || 'en';
|
|
10756
10890
|
const cacheKey = `${dataSourceName}:${locale}`;
|
|
10757
|
-
const { dataSourceCache, loadingPromises, isInitializing, updateStore, landRows } = window.ManifestDataStore;
|
|
10891
|
+
const { dataSourceCache, loadingPromises, isInitializing, updateStore, landRows, setSourceState, runDeduped } = window.ManifestDataStore;
|
|
10758
10892
|
|
|
10759
|
-
// Memory cache. Serving cached data is fine, but
|
|
10760
|
-
// live store would clobber a concurrent locale
|
|
10761
|
-
// write via the `_locale` stamp
|
|
10762
|
-
if (dataSourceCache.has(cacheKey)) {
|
|
10893
|
+
// Memory cache (this page-load's fetches). Serving cached data is fine, but
|
|
10894
|
+
// writing an old locale to the live store would clobber a concurrent locale
|
|
10895
|
+
// switch — so guard the store write via the `_locale` stamp.
|
|
10896
|
+
if (!options.reload && dataSourceCache.has(cacheKey)) {
|
|
10763
10897
|
const cachedData = dataSourceCache.get(cacheKey);
|
|
10764
|
-
const
|
|
10898
|
+
const live = (window.Alpine && Alpine.store('locale')?.current)
|
|
10765
10899
|
|| document.documentElement.lang || locale;
|
|
10766
|
-
const staleLocaleHit = locale !==
|
|
10900
|
+
const staleLocaleHit = locale !== live && !!(cachedData && cachedData._locale);
|
|
10767
10901
|
if (!isInitializing && !staleLocaleHit) {
|
|
10768
10902
|
// Landing (coalesced): resolves once the cached rows are visible
|
|
10769
|
-
await landRows(dataSourceName, cachedData, { mode: 'replace', loading: false, error: null, ready: true });
|
|
10903
|
+
await landRows(dataSourceName, cachedData, { mode: 'replace', loading: false, error: null, ready: true, fresh: true });
|
|
10770
10904
|
}
|
|
10771
10905
|
return cachedData;
|
|
10772
10906
|
}
|
|
10773
10907
|
|
|
10774
|
-
//
|
|
10908
|
+
// In flight for this source+locale: share it (every entry path lands here)
|
|
10775
10909
|
if (loadingPromises.has(cacheKey)) {
|
|
10776
10910
|
return loadingPromises.get(cacheKey);
|
|
10777
10911
|
}
|
|
10778
10912
|
|
|
10779
|
-
//
|
|
10913
|
+
// Loading state: rows already landed stay live; first-ever load clears
|
|
10914
|
+
const keepRows = hasLiveRows(dataSourceName);
|
|
10780
10915
|
if (!isInitializing) {
|
|
10781
|
-
|
|
10916
|
+
if (keepRows) setSourceState(dataSourceName, { loading: true, error: null });
|
|
10917
|
+
else updateStore(dataSourceName, null, { loading: true, error: null, ready: false });
|
|
10782
10918
|
}
|
|
10783
10919
|
|
|
10784
|
-
|
|
10920
|
+
return runDeduped(cacheKey, async () => {
|
|
10921
|
+
let landed = false;
|
|
10785
10922
|
try {
|
|
10786
10923
|
const manifest = await window.ManifestDataConfig.ensureManifest();
|
|
10787
10924
|
if (!manifest) {
|
|
@@ -11043,23 +11180,29 @@ async function loadDataSource(dataSourceName, locale = 'en') {
|
|
|
11043
11180
|
|| document.documentElement.lang || locale;
|
|
11044
11181
|
const staleLocale = localeSensitive && liveLocale !== locale;
|
|
11045
11182
|
|
|
11046
|
-
// Network landing (coalesced per frame); resolves once visible
|
|
11183
|
+
// Network landing (coalesced per frame, merges by $id); resolves once visible
|
|
11047
11184
|
if (!isInitializing && !staleLocale) {
|
|
11048
|
-
|
|
11185
|
+
landed = true;
|
|
11186
|
+
await landRows(dataSourceName, enhancedData, { mode: 'replace', loading: false, error: null, ready: true, fresh: true });
|
|
11049
11187
|
}
|
|
11050
11188
|
|
|
11051
11189
|
// Return unsealed version for our proxy system
|
|
11052
11190
|
return enhancedData;
|
|
11053
11191
|
} catch (error) {
|
|
11054
|
-
//
|
|
11192
|
+
// Error state (timestamped to prevent rapid retries); live rows stay
|
|
11055
11193
|
if (!isInitializing) {
|
|
11194
|
+
landed = true;
|
|
11056
11195
|
const errorMessage = error?.message || error?.toString() || `Failed to load dataSource "${dataSourceName}"`;
|
|
11057
|
-
|
|
11058
|
-
loading: false,
|
|
11059
|
-
|
|
11060
|
-
|
|
11061
|
-
|
|
11062
|
-
|
|
11196
|
+
if (hasLiveRows(dataSourceName)) {
|
|
11197
|
+
setSourceState(dataSourceName, { loading: false, error: errorMessage, errorTime: Date.now() });
|
|
11198
|
+
} else {
|
|
11199
|
+
updateStore(dataSourceName, null, {
|
|
11200
|
+
loading: false,
|
|
11201
|
+
error: errorMessage,
|
|
11202
|
+
ready: false,
|
|
11203
|
+
errorTime: Date.now() // Track when error occurred
|
|
11204
|
+
});
|
|
11205
|
+
}
|
|
11063
11206
|
}
|
|
11064
11207
|
|
|
11065
11208
|
// Only log non-auth errors to reduce noise (401 is expected until user authenticates)
|
|
@@ -11073,12 +11216,15 @@ async function loadDataSource(dataSourceName, locale = 'en') {
|
|
|
11073
11216
|
}
|
|
11074
11217
|
return null;
|
|
11075
11218
|
} finally {
|
|
11076
|
-
|
|
11219
|
+
// Early exits (no source, stale locale) must not strand a live source in $loading
|
|
11220
|
+
if (!landed && keepRows && !isInitializing) setSourceState(dataSourceName, { loading: false });
|
|
11077
11221
|
}
|
|
11078
|
-
})
|
|
11222
|
+
});
|
|
11223
|
+
}
|
|
11079
11224
|
|
|
11080
|
-
|
|
11081
|
-
|
|
11225
|
+
// Reload: refetch past the memory cache; rows stay live and merge by $id
|
|
11226
|
+
function reloadDataSource(dataSourceName, locale) {
|
|
11227
|
+
return loadDataSource(dataSourceName, locale || liveLocale(), { reload: true });
|
|
11082
11228
|
}
|
|
11083
11229
|
|
|
11084
11230
|
// Listen for URL changes to trigger reactivity
|
|
@@ -11203,7 +11349,8 @@ async function initializeDataSourcesPlugin() {
|
|
|
11203
11349
|
// Export loadDataSource for use by team change listener
|
|
11204
11350
|
window.ManifestDataMain = {
|
|
11205
11351
|
loadDataSource,
|
|
11206
|
-
|
|
11352
|
+
reloadDataSource,
|
|
11353
|
+
_loadDataSource: reloadDataSource, // Internal: every user is a reload-after-mutation
|
|
11207
11354
|
filterFilesByScope // Export for use by getFilesForEntry
|
|
11208
11355
|
};
|
|
11209
11356
|
|
|
@@ -11216,18 +11363,16 @@ async function initializeDataSourcesPlugin() {
|
|
|
11216
11363
|
const locale = document.documentElement.lang ||
|
|
11217
11364
|
(typeof Alpine !== 'undefined' && Alpine.store('locale')?.current) || 'en';
|
|
11218
11365
|
|
|
11219
|
-
const { dataSourceCache
|
|
11366
|
+
const { dataSourceCache } = window.ManifestDataStore;
|
|
11220
11367
|
const isAppwriteCollection = window.ManifestDataConfig.isAppwriteCollection;
|
|
11221
11368
|
|
|
11222
11369
|
for (const [name, source] of Object.entries(manifest.data)) {
|
|
11223
11370
|
if (isAppwriteCollection(source)) continue;
|
|
11224
11371
|
if (source && typeof source === 'object' && source.url) continue;
|
|
11225
11372
|
|
|
11226
|
-
|
|
11227
|
-
|
|
11228
|
-
|
|
11229
|
-
|
|
11230
|
-
try { await loadDataSource(name, locale); } catch { /* skip failed sources */ }
|
|
11373
|
+
dataSourceCache.delete(`${name}:${locale}`);
|
|
11374
|
+
// Reload keeps rows live and merges (identity survives an edit-save)
|
|
11375
|
+
try { await loadDataSource(name, locale, { reload: true }); } catch { /* skip failed sources */ }
|
|
11231
11376
|
}
|
|
11232
11377
|
});
|
|
11233
11378
|
|
|
@@ -11265,7 +11410,7 @@ async function initializeDataSourcesPlugin() {
|
|
|
11265
11410
|
const data = await loadDataSource(name, locale);
|
|
11266
11411
|
if (data != null) {
|
|
11267
11412
|
// Boot landing: every pre-loaded source lands in ONE flush
|
|
11268
|
-
await landRows(name, data, { mode: 'replace', loading: false, error: null, ready: true, allowDuringInit: true });
|
|
11413
|
+
await landRows(name, data, { mode: 'replace', loading: false, error: null, ready: true, fresh: true, allowDuringInit: true });
|
|
11269
11414
|
}
|
|
11270
11415
|
} catch (err) {
|
|
11271
11416
|
console.warn(`[Manifest Data] Failed to pre-load ${name}:`, err);
|
|
@@ -11283,7 +11428,7 @@ async function initializeDataSourcesPlugin() {
|
|
|
11283
11428
|
// allowDuringInit: setIsInitializing(true) is active, so without this
|
|
11284
11429
|
// flag updateStore short-circuits and $x.manifest stays unpopulated.
|
|
11285
11430
|
window.ManifestDataStore.dataSourceCache.set(`manifest:${locale}`, publicManifest);
|
|
11286
|
-
updateStore('manifest', publicManifest, { loading: false, error: null, ready: true, allowDuringInit: true });
|
|
11431
|
+
updateStore('manifest', publicManifest, { loading: false, error: null, ready: true, fresh: true, allowDuringInit: true });
|
|
11287
11432
|
}
|
|
11288
11433
|
Alpine.store('data')._ready = true;
|
|
11289
11434
|
} catch (error) {
|
package/lib/manifest.defer.js
CHANGED
|
@@ -12,7 +12,17 @@ const OWNS_CHILDREN = new Set([
|
|
|
12
12
|
// Kill switch: data-defer="off" on the loader script; prerender snapshots keep eager markup
|
|
13
13
|
const killed = !!document.querySelector('script[data-defer="off"]') || window.__manifestRender === true;
|
|
14
14
|
|
|
15
|
+
// Route deferral (spike, opt-in): data-defer-routes on the loader script or ManifestDeferConfig.routes
|
|
16
|
+
let routesScriptFlag = null;
|
|
17
|
+
function routesOn() {
|
|
18
|
+
const cfg = window.ManifestDeferConfig;
|
|
19
|
+
if (cfg && cfg.routes !== undefined) return cfg.routes === true;
|
|
20
|
+
if (routesScriptFlag === null) routesScriptFlag = !!document.querySelector('script[data-defer-routes]');
|
|
21
|
+
return routesScriptFlag;
|
|
22
|
+
}
|
|
23
|
+
|
|
15
24
|
const pending = new Set();
|
|
25
|
+
const routes = new Set(); // deferred route panes: render on activation only, never prewarmed
|
|
16
26
|
let ready = false;
|
|
17
27
|
let bootDrained = false;
|
|
18
28
|
let idleHandle = null;
|
|
@@ -73,12 +83,21 @@ function firstPanel(el) {
|
|
|
73
83
|
return scope.querySelector(`[x-tabpanel="${set}"]`);
|
|
74
84
|
}
|
|
75
85
|
|
|
86
|
+
// A route pane is closed when the router says it does not match the URL (or it is already hidden)
|
|
87
|
+
function routeInactive(el) {
|
|
88
|
+
if (el.hasAttribute('hidden')) return true;
|
|
89
|
+
const vis = window.ManifestRoutingVisibility;
|
|
90
|
+
return !!(vis && typeof vis.isRouteActive === 'function' && !vis.isRouteActive(el));
|
|
91
|
+
}
|
|
92
|
+
|
|
76
93
|
// Closed-container rule, or the visibility rule for explicit x-defer
|
|
77
94
|
function ruleFor(el, explicit) {
|
|
95
|
+
const isRoute = el.hasAttribute('x-route');
|
|
96
|
+
if (isRoute && routesOn() && routeInactive(el)) return 'route';
|
|
78
97
|
if (el.hasAttribute('popover')) return isPopoverOpen(el) ? null : 'popover';
|
|
79
98
|
const tag = el.tagName;
|
|
80
99
|
if (tag === 'DIALOG' || tag === 'DETAILS') return el.hasAttribute('open') ? null : 'toggle';
|
|
81
|
-
if (el.hasAttribute('hidden') && !
|
|
100
|
+
if (el.hasAttribute('hidden') && !isRoute) return 'hidden';
|
|
82
101
|
if (el.hasAttribute('x-tabpanel') && window.ensureTabsPluginInitialized) return firstPanel(el) === el ? null : 'panel';
|
|
83
102
|
return explicit ? 'visible' : null;
|
|
84
103
|
}
|
|
@@ -115,7 +134,8 @@ function register(el, rule, opts) {
|
|
|
115
134
|
cleanup: []
|
|
116
135
|
};
|
|
117
136
|
wire(rec);
|
|
118
|
-
if (
|
|
137
|
+
if (rule === 'route') routes.add(rec);
|
|
138
|
+
else if (!rec.lazy) { pending.add(rec); schedule(); }
|
|
119
139
|
return rec;
|
|
120
140
|
}
|
|
121
141
|
|
|
@@ -208,6 +228,15 @@ function wire(rec) {
|
|
|
208
228
|
});
|
|
209
229
|
arm();
|
|
210
230
|
if (rec.discard) observe(['hidden'], () => { if (el.hasAttribute('hidden')) teardown(rec); });
|
|
231
|
+
} else if (rule === 'route') {
|
|
232
|
+
// Router fires manifest:route-activate before it unhides the pane; hidden removal is the fallback
|
|
233
|
+
listen('manifest:route-activate', () => render(rec));
|
|
234
|
+
const arm = () => Alpine.onAttributeRemoved(el, 'hidden', () => {
|
|
235
|
+
if (!el.hasAttribute('hidden')) render(rec);
|
|
236
|
+
if (!rec.rendered || rec.discard) arm();
|
|
237
|
+
});
|
|
238
|
+
arm();
|
|
239
|
+
if (rec.discard) observe(['hidden'], () => { if (el.hasAttribute('hidden')) teardown(rec); });
|
|
211
240
|
} else if (rule === 'panel') {
|
|
212
241
|
observe(['style', 'x-show', 'x-tabpanel'], () => onState(shown(el, false)));
|
|
213
242
|
} else if (rule === 'visible') {
|
|
@@ -218,11 +247,12 @@ function wire(rec) {
|
|
|
218
247
|
|
|
219
248
|
// ---- Idle prewarm (after manifest:ready; urgent first, then priority asc, then document order) ----
|
|
220
249
|
|
|
221
|
-
|
|
250
|
+
// Genuine idle only: no timeout, so a never-idle page never prewarms (its stashed containers cost nothing)
|
|
251
|
+
const idle = (fn) => {
|
|
222
252
|
idleIsTimer = !window.requestIdleCallback;
|
|
223
253
|
return idleIsTimer
|
|
224
|
-
? setTimeout(() => fn({ didTimeout:
|
|
225
|
-
: window.requestIdleCallback(fn
|
|
254
|
+
? setTimeout(() => fn({ didTimeout: false, timeRemaining: () => IDLE_BUDGET_MS + 1 }), 200)
|
|
255
|
+
: window.requestIdleCallback(fn);
|
|
226
256
|
};
|
|
227
257
|
function cancelIdle() {
|
|
228
258
|
if (idleHandle === null) return;
|
|
@@ -232,7 +262,6 @@ function cancelIdle() {
|
|
|
232
262
|
|
|
233
263
|
// A gesture that revealed a pane (tab switch, panel open) makes its containers urgent
|
|
234
264
|
function promoteNearViewport() {
|
|
235
|
-
promoteTimer = null;
|
|
236
265
|
if (!ready || !bootDrained) return;
|
|
237
266
|
// The on-screen containers nearest the gesture point (a revealed pane sits under the click)
|
|
238
267
|
const candidates = [];
|
|
@@ -255,9 +284,11 @@ function promoteNearViewport() {
|
|
|
255
284
|
function onGesture(e) {
|
|
256
285
|
lastGestureAt = now();
|
|
257
286
|
if (e && typeof e.clientX === 'number' && (e.clientX || e.clientY)) { gestureX = e.clientX; gestureY = e.clientY; }
|
|
258
|
-
if (
|
|
287
|
+
if (now() - lastPromoteAt < 300) return;
|
|
288
|
+
clearTimeout(promoteTimer);
|
|
259
289
|
lastPromoteAt = now();
|
|
260
|
-
|
|
290
|
+
// Three passes: a revealed pane lays out and mounts its menus over the next few hundred ms
|
|
291
|
+
promoteTimer = setTimeout(() => { promoteNearViewport(); promoteTimer = setTimeout(() => { promoteNearViewport(); promoteTimer = setTimeout(promoteNearViewport, 400); }, 150); }, 50);
|
|
261
292
|
}
|
|
262
293
|
|
|
263
294
|
// Only containers the user can reach: not under a hidden route; visible parents first
|
|
@@ -309,19 +340,20 @@ function schedule() {
|
|
|
309
340
|
if (!head.urgent && warm.size >= PREWARM_CAP) return; // paused at the cap; resumes when a warm one opens or evicts
|
|
310
341
|
idleHandle = idle((deadline) => {
|
|
311
342
|
idleHandle = null;
|
|
312
|
-
//
|
|
343
|
+
// Render while the browser reports idle time; the first container of a slice always gets its turn
|
|
313
344
|
let n = 0;
|
|
314
|
-
|
|
345
|
+
while (n < SLICE_MAX && (n === 0 || (deadline && deadline.timeRemaining() > IDLE_BUDGET_MS))) {
|
|
346
|
+
if (deadline && deadline.didTimeout) break;
|
|
315
347
|
const rec = next();
|
|
316
348
|
if (!rec) break;
|
|
317
349
|
if (!rec.urgent && warm.size >= PREWARM_CAP) break; // cap = max warm; only urgent renders may exceed it
|
|
318
350
|
for (const other of pending) other.near = undefined;
|
|
319
351
|
render(rec, true);
|
|
320
352
|
n++;
|
|
321
|
-
}
|
|
353
|
+
}
|
|
322
354
|
evict();
|
|
323
355
|
schedule();
|
|
324
|
-
}
|
|
356
|
+
});
|
|
325
357
|
}
|
|
326
358
|
|
|
327
359
|
function markReady() {
|
|
@@ -370,7 +402,11 @@ window.ManifestDefer = {
|
|
|
370
402
|
defer,
|
|
371
403
|
render: (el) => { if (el && el.__mnfstDefer) render(el.__mnfstDefer); },
|
|
372
404
|
isPending: (el) => !!(el && el.__mnfstDefer && !el.__mnfstDefer.rendered),
|
|
373
|
-
stats: () => ({
|
|
405
|
+
stats: () => ({
|
|
406
|
+
pending: pending.size, warm: warm.size, cap: PREWARM_CAP, ready, bootDrained, armed: idleHandle !== null,
|
|
407
|
+
head: (() => { const h = next(); return h ? (h.el.id || h.el.tagName) : null; })(),
|
|
408
|
+
routes: { enabled: routesOn(), stashed: Array.from(routes).filter((r) => !r.rendered).length, rendered: Array.from(routes).filter((r) => r.rendered).length }
|
|
409
|
+
})
|
|
374
410
|
};
|
|
375
411
|
if (window.Manifest && typeof window.Manifest === 'object') window.Manifest.defer = defer;
|
|
376
412
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"manifest.appwrite.auth.js": "sha384-lWvffK6C2Zs9dOiMh/0OQTnU2ycZq1USOeJDlPUgJsbtrdA9+3zq4VHcIdS40qR0",
|
|
3
3
|
"manifest.appwrite.data.js": "sha384-FusD+MQKkRQexKYl7j6egjDzsPYMWgtNublkgOl595aNqbx4yh8/PfbhLEyI06T1",
|
|
4
4
|
"manifest.appwrite.presences.js": "sha384-JyVDWFT69b7Svq8J8p9tNWe2v9PZNQBY5UyhtOl1vnuHFAX0CRQ3MA9SiKq5wzNh",
|
|
5
|
+
"manifest.bindings.js": "sha384-lPtMf8Ykaic9gNVZKq8Scia1881ZeMAyxer3WCIkUNwwrgzGBWto1OXADUwjvORF",
|
|
5
6
|
"manifest.charts.js": "sha384-qQrjhWkIDi4oU5uQrpy8DdlMjY+gfiivBuX6mssNiFdkawgkVBvKneGgp+nd3PYJ",
|
|
6
7
|
"manifest.chat.js": "sha384-UN5BmhqyafJ8OfLFaOLszrvUW5r001dRheQWqsEBCCLnfBcW1bjXaGOdZvURtrnL",
|
|
7
8
|
"manifest.code.js": "sha384-aoQ4E26vrhNe/+tL7K0h90c8/0ies7e8oIR6Ay92he2X3dnWZ+OPcn2Dna8fmc+c",
|
|
@@ -10,9 +11,9 @@
|
|
|
10
11
|
"manifest.combobox.js": "sha384-SN3XHumoebxcduFQqx5zt65/Cd10y8nuf9Gkco6UnsZ42sadRd+gvVLfWPdkkuPH",
|
|
11
12
|
"manifest.components.js": "sha384-HEyeJtV20k7po8WH0LEj800MlsqgDH0vE5GTAl62SgdEhn1aJ7VfryQcA6yIi18U",
|
|
12
13
|
"manifest.computed.js": "sha384-LDxER8pyYyYwUxIYIyiJA02s+49g8CzpSlOj28On0DFIUgetT3UmvE/9xOsTwT66",
|
|
13
|
-
"manifest.data.js": "sha384-
|
|
14
|
+
"manifest.data.js": "sha384-LhX1a36AITeixPrC4RHz0X+TNMEoDwxi6JVclaIhNAlTYCtkYOHE2KmxJZhfJheD",
|
|
14
15
|
"manifest.datepicker.js": "sha384-8vTJYkLqqvgXgm1w1becuaRR/jUEpiLBjBJ6RsRbUr+ZkypvspD6EsssuDrX4SQq",
|
|
15
|
-
"manifest.defer.js": "sha384-
|
|
16
|
+
"manifest.defer.js": "sha384-t/g++osVS3qDXeY8+gfxUtLF9o0SCQQJKNxHGpb8NkIciDvxOAvA6sFIVcM0AKeA",
|
|
16
17
|
"manifest.dropdowns.js": "sha384-4HuHkBt4i8sTPUDLB9rNLli2/lWLY1tjtCLzey37gRwueG+FRLCnVH1q7ljiVvJR",
|
|
17
18
|
"manifest.edit.js": "sha384-ZvgV3ax7yHtSC23zdvRYJPzBsBpGFBLuBJMgfQ1IDumv97Cn2bpHOePxyQikfl5L",
|
|
18
19
|
"manifest.export.js": "sha384-HCy3n6ifrcAGIyzTRgZ5JIRvSX9q9/8ykMKRSbt1QTnhXz6PgYlV0AE2N2Yi2re+",
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
"manifest.native.js": "sha384-nIngj4CM5hV4bvQvgs8C7JmSy1UUzobsbhvDE2NH5dXqQVNW/Hiat34wHUxMqSaO",
|
|
23
24
|
"manifest.payments.js": "sha384-QGctMZtxF0Wb2JUJbb9sJ4bFbyQvUdr9HaKs4afyaTXb7zedAAIWeB27XINMe3VZ",
|
|
24
25
|
"manifest.resize.js": "sha384-+0IsyrGP/4gZmNbD6oU8dCgEyhpBsXMm74NIenYeUGuGRWyJDurcG27xE/CA0/aA",
|
|
25
|
-
"manifest.router.js": "sha384-
|
|
26
|
+
"manifest.router.js": "sha384-4qmGEkRpgAo9xoQkW6+hHIhowvcyAggKB2J3Wf8tJtXp+L0hufoVAqzfhnqw4cNR",
|
|
26
27
|
"manifest.slides.js": "sha384-3uRTkyK9XPLmnxI2+igZlpi4EyPlU/7IHj5j3BZJJ2KN455vXyk99fiXV3feO/XY",
|
|
27
28
|
"manifest.status.js": "sha384-fXqDnKwldB7XRxcfEQYTm/WqLl2oP24xY2Znq8FqnFM9LQo0zrQkXszqAOksArUX",
|
|
28
29
|
"manifest.svg.js": "sha384-qr/8H9+2cIhSXNbRWRtIdrDlTOxKlNJqwEefHvCYoiBNW/kKhPTFa109Orv/EBnb",
|
|
@@ -34,5 +35,5 @@
|
|
|
34
35
|
"manifest.url.parameters.js": "sha384-J2aSVBOPsoqsbafHndrWLHEDpKb4AKJAoQnVmKA4gZh5P/8HHjFWHO/lkusmG84l",
|
|
35
36
|
"manifest.utilities.js": "sha384-YcGKTEviTv9FLkw890CTociA7Rjp6xuhLyfpn3Gk4mT0fexDFqpW7VSuohcpGfsw",
|
|
36
37
|
"manifest.virtual.js": "sha384-YwwmAsZNddajnAkLhBWtrK8edHMJOhD7cJkQi3qmm5o1Thg0wbtDU0dsfrCHKTIQ",
|
|
37
|
-
"manifest.js": "sha384-
|
|
38
|
+
"manifest.js": "sha384-4r0X5QSeiQ6bGMrw4Y8CDhTAS38w7CPXyxrid+l+sGWEwJ2XRG0STzrLU6XF+GIX"
|
|
38
39
|
}
|
package/lib/manifest.js
CHANGED
package/lib/manifest.router.js
CHANGED
|
@@ -636,39 +636,21 @@ function isPrerenderedStaticMPA() {
|
|
|
636
636
|
}
|
|
637
637
|
}
|
|
638
638
|
|
|
639
|
-
//
|
|
640
|
-
function
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
const routeElements = document.querySelectorAll('[x-route]');
|
|
646
|
-
|
|
647
|
-
// First pass: collect all defined routes (excluding !* and other negative conditions)
|
|
648
|
-
const definedRoutes = [];
|
|
649
|
-
routeElements.forEach(element => {
|
|
650
|
-
const routeCondition = element.getAttribute('x-route');
|
|
651
|
-
if (!routeCondition) return;
|
|
652
|
-
|
|
653
|
-
const conditions = routeCondition.split(',').map(cond => cond.trim());
|
|
654
|
-
conditions.forEach(cond => {
|
|
655
|
-
// Only collect positive conditions and wildcards (not negative ones)
|
|
656
|
-
if (!cond.startsWith('!') && cond !== '!*') {
|
|
657
|
-
definedRoutes.push(cond);
|
|
658
|
-
}
|
|
659
|
-
});
|
|
660
|
-
});
|
|
639
|
+
// Logical path for the current URL, normalized the way route conditions expect
|
|
640
|
+
function currentNormalizedPath() {
|
|
641
|
+
const currentPath = window.ManifestRoutingNavigation?.getCurrentRoute() ?? window.location.pathname;
|
|
642
|
+
return currentPath === '/' ? '/' : currentPath.replace(/^\/|\/$/g, '');
|
|
643
|
+
}
|
|
661
644
|
|
|
662
|
-
|
|
645
|
+
// Localization codes from manifest.json data sources
|
|
646
|
+
function getLocalizationCodes() {
|
|
663
647
|
const localizationCodes = [];
|
|
664
648
|
try {
|
|
665
|
-
// Check if manifest is available and has data sources
|
|
666
649
|
const manifest = window.ManifestComponentsRegistry?.manifest || window.manifest;
|
|
667
650
|
if (manifest && manifest.data) {
|
|
668
651
|
Object.values(manifest.data).forEach(dataSource => {
|
|
669
652
|
if (typeof dataSource === 'object' && dataSource !== null) {
|
|
670
653
|
Object.keys(dataSource).forEach(key => {
|
|
671
|
-
// Check if this looks like a localization key (common language codes)
|
|
672
654
|
if (key.match(/^[a-z]{2}(-[A-Z]{2})?$/)) {
|
|
673
655
|
localizationCodes.push(key);
|
|
674
656
|
}
|
|
@@ -679,80 +661,117 @@ function processRouteVisibility(normalizedPath) {
|
|
|
679
661
|
} catch (e) {
|
|
680
662
|
// Ignore errors if manifest is not available
|
|
681
663
|
}
|
|
664
|
+
return localizationCodes;
|
|
665
|
+
}
|
|
682
666
|
|
|
683
|
-
|
|
684
|
-
|
|
667
|
+
// Positive conditions of every route, including routes stashed inside a deferred route
|
|
668
|
+
function collectDefinedRoutes() {
|
|
669
|
+
const definedRoutes = [];
|
|
670
|
+
const collect = (element) => {
|
|
671
|
+
const routeCondition = element.getAttribute('x-route');
|
|
672
|
+
if (!routeCondition) return;
|
|
673
|
+
routeCondition.split(',').map(cond => cond.trim()).forEach(cond => {
|
|
674
|
+
if (!cond.startsWith('!') && cond !== '!*') definedRoutes.push(cond);
|
|
675
|
+
});
|
|
676
|
+
};
|
|
677
|
+
document.querySelectorAll('[x-route]').forEach(collect);
|
|
678
|
+
document.querySelectorAll('[x-route] > template[data-mnfst-defer]').forEach(tpl => {
|
|
679
|
+
tpl.content.querySelectorAll('[x-route]').forEach(collect);
|
|
680
|
+
});
|
|
681
|
+
return definedRoutes;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// Whether any defined route (or its localized form) covers the path — drives x-route="!*"
|
|
685
|
+
function isRouteDefined(normalizedPath, definedRoutes) {
|
|
686
|
+
let defined = definedRoutes.some(route =>
|
|
685
687
|
window.ManifestRouting.matchesCondition(normalizedPath, route)
|
|
686
688
|
);
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
if (!isRouteDefined && localizationCodes.length > 0) {
|
|
689
|
+
const localizationCodes = getLocalizationCodes();
|
|
690
|
+
if (!defined && localizationCodes.length > 0) {
|
|
690
691
|
const pathSegments = normalizedPath.split('/').filter(segment => segment);
|
|
691
|
-
if (pathSegments.length > 0) {
|
|
692
|
-
const
|
|
693
|
-
if (
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
);
|
|
703
|
-
} else {
|
|
704
|
-
// Check if the remaining path matches any defined route
|
|
705
|
-
isRouteDefined = definedRoutes.some(route =>
|
|
706
|
-
window.ManifestRouting.matchesCondition(remainingPath, route)
|
|
707
|
-
);
|
|
708
|
-
}
|
|
692
|
+
if (pathSegments.length > 0 && localizationCodes.includes(pathSegments[0])) {
|
|
693
|
+
const remainingPath = pathSegments.slice(1).join('/');
|
|
694
|
+
if (remainingPath === '') {
|
|
695
|
+
defined = definedRoutes.some(route =>
|
|
696
|
+
window.ManifestRouting.matchesCondition('/', route) ||
|
|
697
|
+
window.ManifestRouting.matchesCondition('', route)
|
|
698
|
+
);
|
|
699
|
+
} else {
|
|
700
|
+
defined = definedRoutes.some(route =>
|
|
701
|
+
window.ManifestRouting.matchesCondition(remainingPath, route)
|
|
702
|
+
);
|
|
709
703
|
}
|
|
710
704
|
}
|
|
711
705
|
}
|
|
706
|
+
return defined;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// Match one route element against a path: true/false, or null when it carries no condition
|
|
710
|
+
function routeMatches(element, normalizedPath, defined) {
|
|
711
|
+
const routeCondition = element.getAttribute('x-route');
|
|
712
|
+
if (!routeCondition) return null;
|
|
713
|
+
|
|
714
|
+
const conditions = routeCondition.split(',').map(cond => cond.trim());
|
|
715
|
+
if (conditions.includes('!*')) {
|
|
716
|
+
if (defined === undefined) defined = isRouteDefined(normalizedPath, collectDefinedRoutes());
|
|
717
|
+
return !defined;
|
|
718
|
+
}
|
|
712
719
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
720
|
+
const positiveConditions = conditions.filter(cond => !cond.startsWith('!'));
|
|
721
|
+
const negativeConditions = conditions
|
|
722
|
+
.filter(cond => cond.startsWith('!'))
|
|
723
|
+
.map(cond => cond.slice(1));
|
|
724
|
+
const hasNegativeMatch = negativeConditions.some(cond =>
|
|
725
|
+
window.ManifestRouting.matchesCondition(normalizedPath, cond)
|
|
726
|
+
);
|
|
727
|
+
const hasPositiveMatch = positiveConditions.length === 0 || positiveConditions.some(cond =>
|
|
728
|
+
window.ManifestRouting.matchesCondition(normalizedPath, cond)
|
|
729
|
+
);
|
|
730
|
+
return hasPositiveMatch && !hasNegativeMatch;
|
|
731
|
+
}
|
|
716
732
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
.map(cond => cond.slice(1));
|
|
723
|
-
|
|
724
|
-
// Special handling for !* (undefined routes)
|
|
725
|
-
if (conditions.includes('!*')) {
|
|
726
|
-
const shouldShow = !isRouteDefined;
|
|
727
|
-
if (shouldShow) {
|
|
728
|
-
element.removeAttribute('hidden');
|
|
729
|
-
element.style.display = '';
|
|
730
|
-
} else {
|
|
731
|
-
element.setAttribute('hidden', '');
|
|
732
|
-
element.style.display = 'none';
|
|
733
|
-
}
|
|
734
|
-
return;
|
|
735
|
-
}
|
|
733
|
+
// Cooperative check (defer plugin): is this route active for the current URL? Static MPA output always is.
|
|
734
|
+
function isRouteActive(element, normalizedPath) {
|
|
735
|
+
if (isPrerenderedStaticMPA()) return true;
|
|
736
|
+
return routeMatches(element, normalizedPath === undefined ? currentNormalizedPath() : normalizedPath) !== false;
|
|
737
|
+
}
|
|
736
738
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
);
|
|
739
|
+
// Activation hook fires before the route becomes visible so deferred content renders first
|
|
740
|
+
function showRoute(element) {
|
|
741
|
+
element.dispatchEvent(new CustomEvent('manifest:route-activate'));
|
|
742
|
+
element.removeAttribute('hidden');
|
|
743
|
+
element.style.display = '';
|
|
744
|
+
}
|
|
744
745
|
|
|
745
|
-
|
|
746
|
+
function hideRoute(element) {
|
|
747
|
+
element.setAttribute('hidden', '');
|
|
748
|
+
element.style.display = 'none';
|
|
749
|
+
}
|
|
746
750
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
+
// Process visibility for all elements with x-route
|
|
752
|
+
function processRouteVisibility(normalizedPath) {
|
|
753
|
+
// Static prerender output already contains only this route's sections; x-cloak + toggling here
|
|
754
|
+
// causes a visible flash (content → hidden via x-cloak → shown when Alpine boots).
|
|
755
|
+
if (isPrerenderedStaticMPA()) return;
|
|
756
|
+
|
|
757
|
+
const defined = isRouteDefined(normalizedPath, collectDefinedRoutes());
|
|
758
|
+
|
|
759
|
+
// Worklist: activating a deferred route can reveal nested routes that need this pass too
|
|
760
|
+
const seen = new Set();
|
|
761
|
+
const queue = Array.from(document.querySelectorAll('[x-route]'));
|
|
762
|
+
while (queue.length) {
|
|
763
|
+
const element = queue.shift();
|
|
764
|
+
if (seen.has(element)) continue;
|
|
765
|
+
seen.add(element);
|
|
766
|
+
const match = routeMatches(element, normalizedPath, defined);
|
|
767
|
+
if (match === null) continue;
|
|
768
|
+
if (match) {
|
|
769
|
+
showRoute(element);
|
|
770
|
+
element.querySelectorAll('[x-route]').forEach(nested => { if (!seen.has(nested)) queue.push(nested); });
|
|
751
771
|
} else {
|
|
752
|
-
element
|
|
753
|
-
element.style.display = 'none';
|
|
772
|
+
hideRoute(element);
|
|
754
773
|
}
|
|
755
|
-
}
|
|
774
|
+
}
|
|
756
775
|
}
|
|
757
776
|
|
|
758
777
|
// Add x-cloak to route elements that don't have it
|
|
@@ -770,9 +789,7 @@ function initializeVisibility() {
|
|
|
770
789
|
addXCloakToRouteElements();
|
|
771
790
|
|
|
772
791
|
// Process initial visibility (use logical path when app is in a subpath)
|
|
773
|
-
|
|
774
|
-
const normalizedPath = currentPath === '/' ? '/' : currentPath.replace(/^\/|\/$/g, '');
|
|
775
|
-
processRouteVisibility(normalizedPath);
|
|
792
|
+
processRouteVisibility(currentNormalizedPath());
|
|
776
793
|
|
|
777
794
|
// Listen for route changes
|
|
778
795
|
window.addEventListener('manifest:route-change', (event) => {
|
|
@@ -785,10 +802,7 @@ function initializeVisibility() {
|
|
|
785
802
|
if (isPrerenderedStaticMPA()) return;
|
|
786
803
|
// Add x-cloak to any new route elements
|
|
787
804
|
addXCloakToRouteElements();
|
|
788
|
-
|
|
789
|
-
const currentPath = window.ManifestRoutingNavigation?.getCurrentRoute() ?? window.location.pathname;
|
|
790
|
-
const normalizedPath = currentPath === '/' ? '/' : currentPath.replace(/^\/|\/$/g, '');
|
|
791
|
-
processRouteVisibility(normalizedPath);
|
|
805
|
+
processRouteVisibility(currentNormalizedPath());
|
|
792
806
|
});
|
|
793
807
|
}
|
|
794
808
|
|
|
@@ -809,8 +823,10 @@ if (document.readyState === 'loading') {
|
|
|
809
823
|
window.ManifestRoutingVisibility = {
|
|
810
824
|
initialize: initializeVisibility,
|
|
811
825
|
processRouteVisibility,
|
|
826
|
+
isRouteActive,
|
|
812
827
|
isPrerenderedStaticMPA
|
|
813
|
-
};
|
|
828
|
+
};
|
|
829
|
+
|
|
814
830
|
|
|
815
831
|
// Router head
|
|
816
832
|
|