mnfst 0.5.261 → 0.5.263

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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  (function () {
4
4
 
5
- const MANIFEST_BUILD_VERSION = '0.5.261';
5
+ const MANIFEST_BUILD_VERSION = '0.5.263';
6
6
 
7
7
  /* Manifest Data Sources - Appwrite Integration */
8
8
 
@@ -13,6 +13,23 @@
13
13
  if (!window.ManifestUI) {
14
14
  window.ManifestUI = {
15
15
  /* Loaded data sources (current locale); never force-loads others. */
16
+ // One cached evaluator per expression: each Alpine.evaluate would register a cleanup on <body>, never run
17
+ _eval(expr) {
18
+ const A = window.Alpine;
19
+ // Magics registered after an evaluator was built stay invisible to it, so a registration drops the cache
20
+ if (!A.magic.__manifestMagicCounted) {
21
+ const magic = A.magic;
22
+ A.magic = function () { window.__manifestMagicVersion = (window.__manifestMagicVersion || 0) + 1; return magic.apply(this, arguments); };
23
+ A.magic.__manifestMagicCounted = true;
24
+ }
25
+ const version = window.__manifestMagicVersion || 0;
26
+ if (!this._evals || this._evalsVersion !== version) { this._evals = new Map(); this._evalsVersion = version; }
27
+ let fn = this._evals.get(expr);
28
+ if (!fn) this._evals.set(expr, fn = A.evaluateLater(document.body, expr));
29
+ let out;
30
+ fn(v => { out = v; });
31
+ return out;
32
+ },
16
33
  _loadedSourceNames() {
17
34
  try {
18
35
  const store = window.ManifestDataStore && window.ManifestDataStore.rawDataStore;
@@ -24,11 +41,11 @@
24
41
  resolve(component, fallbacks) {
25
42
  const merged = JSON.parse(JSON.stringify(fallbacks || {}));
26
43
  try {
27
- if (!window.Alpine || typeof Alpine.evaluate !== 'function') return merged;
28
- try { Alpine.evaluate(document.body, 'typeof $locale !== "undefined" && $locale.current'); } catch (_) { } // dep → re-resolve on locale switch
44
+ if (!window.Alpine || typeof Alpine.evaluateLater !== 'function') return merged;
45
+ try { this._eval('typeof $locale !== "undefined" && $locale.current'); } catch (_) { } // dep → re-resolve on locale switch
29
46
  for (const name of this._loadedSourceNames()) {
30
47
  let ui;
31
- try { ui = Alpine.evaluate(document.body, `$x['${name}'] && $x['${name}']._ui && $x['${name}']._ui['${component}']`); } catch (_) { ui = null; }
48
+ try { ui = this._eval(`$x['${name}'] && $x['${name}']._ui && $x['${name}']._ui['${component}']`); } catch (_) { ui = null; }
32
49
  if (ui && typeof ui === 'object' && !Array.isArray(ui)) this._deepOverlay(merged, ui);
33
50
  }
34
51
  } catch (_) { }
@@ -10,6 +10,23 @@ function initializeColorpickerPlugin() {
10
10
  if (!window.ManifestUI) {
11
11
  window.ManifestUI = {
12
12
  // Names of loaded data sources (current locale); never force-loads others.
13
+ // One cached evaluator per expression: each Alpine.evaluate would register a cleanup on <body>, never run
14
+ _eval(expr) {
15
+ const A = window.Alpine;
16
+ // Magics registered after an evaluator was built stay invisible to it, so a registration drops the cache
17
+ if (!A.magic.__manifestMagicCounted) {
18
+ const magic = A.magic;
19
+ A.magic = function () { window.__manifestMagicVersion = (window.__manifestMagicVersion || 0) + 1; return magic.apply(this, arguments); };
20
+ A.magic.__manifestMagicCounted = true;
21
+ }
22
+ const version = window.__manifestMagicVersion || 0;
23
+ if (!this._evals || this._evalsVersion !== version) { this._evals = new Map(); this._evalsVersion = version; }
24
+ let fn = this._evals.get(expr);
25
+ if (!fn) this._evals.set(expr, fn = A.evaluateLater(document.body, expr));
26
+ let out;
27
+ fn(v => { out = v; });
28
+ return out;
29
+ },
13
30
  _loadedSourceNames() {
14
31
  try {
15
32
  const store = window.ManifestDataStore && window.ManifestDataStore.rawDataStore;
@@ -22,11 +39,11 @@ function initializeColorpickerPlugin() {
22
39
  resolve(component, fallbacks) {
23
40
  const merged = JSON.parse(JSON.stringify(fallbacks || {}));
24
41
  try {
25
- if (!window.Alpine || typeof Alpine.evaluate !== 'function') return merged;
26
- try { Alpine.evaluate(document.body, 'typeof $locale !== "undefined" && $locale.current'); } catch (_) { } // dep → re-resolve on locale switch
42
+ if (!window.Alpine || typeof Alpine.evaluateLater !== 'function') return merged;
43
+ try { this._eval('typeof $locale !== "undefined" && $locale.current'); } catch (_) { } // dep → re-resolve on locale switch
27
44
  for (const name of this._loadedSourceNames()) {
28
45
  let ui;
29
- try { ui = Alpine.evaluate(document.body, `$x['${name}'] && $x['${name}']._ui && $x['${name}']._ui['${component}']`); } catch (_) { ui = null; }
46
+ try { ui = this._eval(`$x['${name}'] && $x['${name}']._ui && $x['${name}']._ui['${component}']`); } catch (_) { ui = null; }
30
47
  if (ui && typeof ui === 'object' && !Array.isArray(ui)) this._deepOverlay(merged, ui);
31
48
  }
32
49
  } catch (_) { }
@@ -563,7 +580,7 @@ function initializeColorpickerPlugin() {
563
580
  try {
564
581
  if (window.Alpine?.evaluate) {
565
582
  const expr = isBareRef && !hasInterp ? trimmed : '`' + trimmed + '`';
566
- const out = Alpine.evaluate(document.body, expr);
583
+ const out = window.ManifestUI?._eval ? window.ManifestUI._eval(expr) : Alpine.evaluate(document.body, expr);
567
584
  if (out == null) return val;
568
585
  return typeof out === 'string' ? out : String(out);
569
586
  }
@@ -1068,6 +1085,8 @@ function initializeColorpickerPlugin() {
1068
1085
 
1069
1086
  const state = {
1070
1087
  rootEl,
1088
+ _effects: [],
1089
+ _observers: [],
1071
1090
  pickerUid,
1072
1091
 
1073
1092
  // Data
@@ -1615,9 +1634,9 @@ function initializeColorpickerPlugin() {
1615
1634
  if (this.libraryRootValue && window.Alpine?.effect && window.Alpine?.evaluateLater) {
1616
1635
  // Explicit expression — bypass discovery, re-render on dep change.
1617
1636
  const evalFn = Alpine.evaluateLater(evalHost, this.libraryRootValue);
1618
- Alpine.effect(() => {
1637
+ this._effects.push(Alpine.effect(() => {
1619
1638
  evalFn(v => { this._libraryResolvedData = v; this._doRenderLibrary(); });
1620
- });
1639
+ }));
1621
1640
  } else if (window.Alpine?.effect) {
1622
1641
  // Zero-config — scan $x.manifest.data for `colorpicker:`-flagged
1623
1642
  // entries, collect their $x.<name> values, merge in declaration
@@ -1644,12 +1663,12 @@ function initializeColorpickerPlugin() {
1644
1663
  // `colorpicker` key flagged on the data entry itself (back-compat).
1645
1664
  const names = [];
1646
1665
  try {
1647
- const ptr = Alpine.evaluate(evalCtx, '$x && $x.manifest && $x.manifest.colorpicker');
1666
+ const ptr = (window.ManifestUI?._eval ? (x) => window.ManifestUI._eval(x) : (x) => Alpine.evaluate(evalCtx, x))('$x && $x.manifest && $x.manifest.colorpicker');
1648
1667
  if (typeof ptr === 'string' && ptr) names.push(ptr);
1649
1668
  else if (Array.isArray(ptr)) ptr.forEach(n => { if (typeof n === 'string' && n) names.push(n); });
1650
1669
  } catch {}
1651
1670
  let dataMap = null;
1652
- try { dataMap = Alpine.evaluate(evalCtx, '$x && $x.manifest && $x.manifest.data'); } catch {}
1671
+ try { dataMap = (window.ManifestUI?._eval ? (x) => window.ManifestUI._eval(x) : (x) => Alpine.evaluate(evalCtx, x))('$x && $x.manifest && $x.manifest.data'); } catch {}
1653
1672
  if (dataMap && typeof dataMap === 'object') {
1654
1673
  for (const name of Object.keys(dataMap)) {
1655
1674
  if (name.startsWith('$') || name.startsWith('_')) continue;
@@ -1663,7 +1682,7 @@ function initializeColorpickerPlugin() {
1663
1682
  }
1664
1683
  }
1665
1684
  const collected = names.map(name => {
1666
- try { return Alpine.evaluate(evalCtx, '$x.' + name); } catch { return null; }
1685
+ try { return (window.ManifestUI?._eval ? (x) => window.ManifestUI._eval(x) : (x) => Alpine.evaluate(evalCtx, x))('$x.' + name); } catch { return null; }
1667
1686
  });
1668
1687
  const ready = names.length === 0 || collected.every(src => {
1669
1688
  if (!src || typeof src !== 'object') return false;
@@ -1688,9 +1707,9 @@ function initializeColorpickerPlugin() {
1688
1707
  };
1689
1708
 
1690
1709
  // Reactive deps trigger re-runs on locale switch / manifest load.
1691
- Alpine.effect(() => {
1692
- try { Alpine.evaluate(evalCtx, 'typeof $locale !== "undefined" && $locale.current'); } catch {}
1693
- try { Alpine.evaluate(evalCtx, '$x && $x.manifest && $x.manifest._loadedFrom'); } catch {}
1710
+ this._effects.push(Alpine.effect(() => {
1711
+ try { (window.ManifestUI?._eval ? (x) => window.ManifestUI._eval(x) : (x) => Alpine.evaluate(evalCtx, x))('typeof $locale !== "undefined" && $locale.current'); } catch {}
1712
+ try { (window.ManifestUI?._eval ? (x) => window.ManifestUI._eval(x) : (x) => Alpine.evaluate(evalCtx, x))('$x && $x.manifest && $x.manifest._loadedFrom'); } catch {}
1694
1713
  runDiscovery();
1695
1714
  // Re-localize the whole picker on locale switch.
1696
1715
  this._applyUi(this.rootEl);
@@ -1705,7 +1724,7 @@ function initializeColorpickerPlugin() {
1705
1724
  }
1706
1725
  }, 150);
1707
1726
  }
1708
- });
1727
+ }));
1709
1728
  } else {
1710
1729
  this._doRenderLibrary();
1711
1730
  }
@@ -2022,6 +2041,7 @@ function initializeColorpickerPlugin() {
2022
2041
  if (r.width > 0 && r.height > 0) { self.drawCanvas(); self.updateCanvasMarker(); }
2023
2042
  });
2024
2043
  ro.observe(refs.canvas);
2044
+ self._observers.push(ro);
2025
2045
  }
2026
2046
 
2027
2047
  if (refs.hueSlider) {
@@ -2453,10 +2473,17 @@ function initializeColorpickerPlugin() {
2453
2473
 
2454
2474
  // Defer mount so child directives register first. setTimeout (not rAF)
2455
2475
  // so it still fires when backgrounded.
2456
- setTimeout(() => state.mount(), 0);
2476
+ setTimeout(() => { if (!state._destroyed) state.mount(); }, 0);
2457
2477
 
2458
2478
  cleanup(() => {
2459
2479
  if (el.id) delete _pickerRegistry[el.id];
2480
+ state._destroyed = true;
2481
+ state._effects.forEach((fx) => Alpine.release(fx));
2482
+ state._effects.length = 0;
2483
+ state._observers.forEach((ro) => ro.disconnect());
2484
+ state._observers.length = 0;
2485
+ clearInterval(state._libraryPollTimer);
2486
+ state._libraryPollTimer = null;
2460
2487
  delete el._colorpickerState;
2461
2488
  });
2462
2489
  return;
@@ -2468,18 +2495,12 @@ function initializeColorpickerPlugin() {
2468
2495
  // Swatches trigger OUTSIDE any picker — handle before the ancestor check.
2469
2496
  // We assign IDs + the popover tag; x-dropdown owns popover mechanics.
2470
2497
  if (role === 'swatch') {
2471
- if (el._cpSwatchWired) return; // guard against re-firing via initTree
2472
- el._cpSwatchWired = true;
2473
-
2474
- // ---- Optional x-model binding ----
2475
- // x-model is the swatch's source of truth: reactive background preview,
2476
- // plus read (open) / write (close) accessors the picker uses.
2477
2498
  const modelExpr = el.getAttribute('x-model');
2478
2499
  if (modelExpr && window.Alpine?.evaluateLater && window.Alpine?.effect) {
2479
2500
  try {
2480
2501
  const readFn = Alpine.evaluateLater(el, modelExpr);
2481
2502
  // Reactive background preview
2482
- Alpine.effect(() => {
2503
+ const modelEffect = Alpine.effect(() => {
2483
2504
  readFn(v => {
2484
2505
  if (typeof v === 'string' && v.length) {
2485
2506
  el.style.setProperty('--color-picker-swatch', v);
@@ -2487,6 +2508,7 @@ function initializeColorpickerPlugin() {
2487
2508
  }
2488
2509
  });
2489
2510
  });
2511
+ cleanup(() => Alpine.release(modelEffect));
2490
2512
  el._cpModelGetter = (cb) => readFn(cb);
2491
2513
  // Writer: `<modelExpr> = <JSON value>` (color/gradient strings are JSON-safe).
2492
2514
  el._cpModelSetter = (v) => {
@@ -2495,6 +2517,12 @@ function initializeColorpickerPlugin() {
2495
2517
  } catch {}
2496
2518
  }
2497
2519
 
2520
+ if (el._cpSwatchWired) return; // guard against re-firing via initTree
2521
+ el._cpSwatchWired = true;
2522
+
2523
+ // ---- Optional x-model binding ----
2524
+ // x-model is the swatch's source of truth: reactive background preview,
2525
+ // plus read (open) / write (close) accessors the picker uses.
2498
2526
  // ---- Initial color via `value` attribute ----
2499
2527
  // Mirrors native <input type=color>: seeds the picker on first open and
2500
2528
  // the swatch CSS var so border-color paints before interaction.
@@ -12,6 +12,23 @@
12
12
  * ------------------------------------------------------------------ */
13
13
  if (!window.ManifestUI) {
14
14
  window.ManifestUI = {
15
+ // One cached evaluator per expression: each Alpine.evaluate would register a cleanup on <body>, never run
16
+ _eval(expr) {
17
+ const A = window.Alpine;
18
+ // Magics registered after an evaluator was built stay invisible to it, so a registration drops the cache
19
+ if (!A.magic.__manifestMagicCounted) {
20
+ const magic = A.magic;
21
+ A.magic = function () { window.__manifestMagicVersion = (window.__manifestMagicVersion || 0) + 1; return magic.apply(this, arguments); };
22
+ A.magic.__manifestMagicCounted = true;
23
+ }
24
+ const version = window.__manifestMagicVersion || 0;
25
+ if (!this._evals || this._evalsVersion !== version) { this._evals = new Map(); this._evalsVersion = version; }
26
+ let fn = this._evals.get(expr);
27
+ if (!fn) this._evals.set(expr, fn = A.evaluateLater(document.body, expr));
28
+ let out;
29
+ fn(v => { out = v; });
30
+ return out;
31
+ },
15
32
  _loadedSourceNames() {
16
33
  try {
17
34
  const store = window.ManifestDataStore && window.ManifestDataStore.rawDataStore;
@@ -22,11 +39,11 @@ if (!window.ManifestUI) {
22
39
  resolve(component, fallbacks) {
23
40
  const merged = JSON.parse(JSON.stringify(fallbacks || {}));
24
41
  try {
25
- if (!window.Alpine || typeof Alpine.evaluate !== 'function') return merged;
26
- try { Alpine.evaluate(document.body, 'typeof $locale !== "undefined" && $locale.current'); } catch (_) { }
42
+ if (!window.Alpine || typeof Alpine.evaluateLater !== 'function') return merged;
43
+ try { this._eval('typeof $locale !== "undefined" && $locale.current'); } catch (_) { }
27
44
  for (const name of this._loadedSourceNames()) {
28
45
  let ui;
29
- try { ui = Alpine.evaluate(document.body, `$x['${name}'] && $x['${name}']._ui && $x['${name}']._ui['${component}']`); } catch (_) { ui = null; }
46
+ try { ui = this._eval(`$x['${name}'] && $x['${name}']._ui && $x['${name}']._ui['${component}']`); } catch (_) { ui = null; }
30
47
  if (ui && typeof ui === 'object' && !Array.isArray(ui)) this._deepOverlay(merged, ui);
31
48
  }
32
49
  } catch (_) { }
@@ -165,7 +182,9 @@ function initializeComboboxPlugin() {
165
182
  Alpine.directive('combobox', (el, { modifiers, expression }, { cleanup }) => {
166
183
  captureModel(el); // fallback for any path that bypasses the above
167
184
  // Build after the current tick so sibling sources (datalist/menu) exist.
168
- setTimeout(() => build(el, modifiers, expression || '', cleanup), 0);
185
+ let removed = false;
186
+ cleanup(() => { removed = true; });
187
+ setTimeout(() => { if (!removed) build(el, modifiers, expression || '', cleanup); }, 0);
169
188
  });
170
189
 
171
190
  // Resolve the menu's owning top-layer context fresh, every time — never cache it.
@@ -201,6 +220,8 @@ function initializeComboboxPlugin() {
201
220
  // it into wherever the owner lives now. Harmless no-op otherwise.
202
221
  const m = el.__mnfstComboboxMenu;
203
222
  if (m) reattach(el, m);
223
+ // A re-initialized field: its cleanup released these
224
+ el.__cbBindEffects?.(cleanup);
204
225
  return;
205
226
  }
206
227
  el.__mnfstCombobox = true;
@@ -941,6 +962,8 @@ function initializeComboboxPlugin() {
941
962
  disabledObserver.observe(el, { attributes: true, attributeFilter: ['disabled'] });
942
963
  if (cleanup) cleanup(() => disabledObserver.disconnect());
943
964
 
965
+ const effectFactories = [];
966
+
944
967
  // ----- Reactive model (x-model) -----
945
968
  // Render chips from the bound value on init and external change; write back on
946
969
  // add/remove. Read inside an Alpine effect so $x / nested state stay reactive.
@@ -951,7 +974,7 @@ function initializeComboboxPlugin() {
951
974
  : (modelArrayShape === false ? vals.join(',') : vals.slice());
952
975
  try { Alpine.evaluate(el, `${modelExpr} = ${JSON.stringify(out)}`); } catch (_) { }
953
976
  };
954
- Alpine.effect(() => {
977
+ effectFactories.push(() => Alpine.effect(() => {
955
978
  read(raw => {
956
979
  if (modelArrayShape === null && raw != null && raw !== '') modelArrayShape = Array.isArray(raw);
957
980
  const incoming = modelToValues(raw);
@@ -960,20 +983,26 @@ function initializeComboboxPlugin() {
960
983
  if (!multiple && selected.length > 1) selected = selected.slice(-1);
961
984
  render();
962
985
  });
963
- });
986
+ }));
964
987
  }
965
988
 
966
989
  // ----- Reactive locked list (config-object form, so `locked` can gate on state) -----
967
990
  if (expr.startsWith('{')) {
968
- Alpine.effect(() => {
991
+ effectFactories.push(() => Alpine.effect(() => {
969
992
  let c; try { c = Alpine.evaluate(el, expr); } catch (_) { return; }
970
993
  const next = computeLocked(c && typeof c === 'object' && !Array.isArray(c) ? c : null);
971
994
  if (next.size === lockedSet.size && [...next].every(v => lockedSet.has(v))) return;
972
995
  lockedSet = next;
973
996
  render();
974
- });
997
+ }));
975
998
  }
976
999
 
1000
+ el.__cbBindEffects = (onCleanup) => {
1001
+ const effects = effectFactories.map((make) => make());
1002
+ if (onCleanup) onCleanup(() => effects.forEach((fx) => Alpine.release(fx)));
1003
+ };
1004
+ el.__cbBindEffects(cleanup);
1005
+
977
1006
  // ----- Dynamic options: re-read when x-for / $x fills the source after build,
978
1007
  // keeping the menu, chip labels, and data-locked flags current. -----
979
1008
  if (src) {
package/lib/manifest.css CHANGED
@@ -2580,14 +2580,21 @@
2580
2580
  background-color: rgba(0, 0, 0, 0.35)
2581
2581
  }
2582
2582
 
2583
- /* Hide dropdowns when dialogs are open */
2583
+ /* Hide dropdowns when dialogs are open: <html> sets flags, menus read them via style queries */
2584
2584
  html:has(dialog:popover-open) {
2585
+ --dialog-open: 1;
2586
+ }
2585
2587
 
2586
- /* Hide dropdowns outside of any dialog. A generated popover (a datepicker
2587
- calendar, a colorpicker panel) is appended to <body>, so DOM position alone
2588
- can't tell page chrome from a menu the dialog itself opened —
2589
- data-popover-nested marks the latter, which CSS has no way to work out. */
2590
- & menu[popover]:not(dialog *):not([data-popover-nested]) {
2588
+ html:has(dialog:popover-open ~ dialog:popover-open) {
2589
+ --dialog-stacked: 1;
2590
+ }
2591
+
2592
+ /* Hide dropdowns outside of any dialog. A generated popover (a datepicker
2593
+ calendar, a colorpicker panel) is appended to <body>, so DOM position alone
2594
+ can't tell page chrome from a menu the dialog itself opened —
2595
+ data-popover-nested marks the latter, which CSS has no way to work out. */
2596
+ @container style(--dialog-open: 1) {
2597
+ menu[popover]:not(dialog *):not([data-popover-nested]) {
2591
2598
  opacity: 0;
2592
2599
  scale: .9;
2593
2600
  pointer-events: none;
@@ -2600,8 +2607,8 @@
2600
2607
  }
2601
2608
 
2602
2609
  /* When multiple dialogs are open, hide dropdowns in all but closest dialog */
2603
- html:has(dialog:popover-open ~ dialog:popover-open) {
2604
- & dialog:popover-open:not(:last-of-type) menu[popover] {
2610
+ @container style(--dialog-stacked: 1) {
2611
+ dialog:popover-open:not(:last-of-type) menu[popover] {
2605
2612
  opacity: 0;
2606
2613
  scale: .9;
2607
2614
  pointer-events: none;
@@ -2,7 +2,7 @@
2
2
 
3
3
  (function () {
4
4
 
5
- const MANIFEST_BUILD_VERSION = '0.5.261';
5
+ const MANIFEST_BUILD_VERSION = '0.5.263';
6
6
 
7
7
  /* Manifest Data Sources - Configuration */
8
8
 
@@ -784,7 +784,8 @@ function createReactiveReferences(data, dataSourceName = null) {
784
784
  dataSourceName,
785
785
  data.$id,
786
786
  bucketName,
787
- columnName
787
+ columnName,
788
+ { owner: newObj }
788
789
  );
789
790
 
790
791
  // Assign computed array to entry
@@ -6011,13 +6012,9 @@ const routeProxyCache = new Map();
6011
6012
  // Clear route proxy cache for a specific data source (called when store updates)
6012
6013
  function clearRouteProxyCacheForDataSource(dataSourceName) {
6013
6014
  if (!dataSourceName) return;
6014
- const keysToDelete = [];
6015
- for (const [key] of routeProxyCache.entries()) {
6016
- if (key.startsWith(`${dataSourceName}:`)) {
6017
- keysToDelete.push(key);
6018
- }
6015
+ for (const [key, entry] of routeProxyCache.entries()) {
6016
+ if (key.startsWith(`${dataSourceName}:`)) entry.stale = true;
6019
6017
  }
6020
- keysToDelete.forEach(key => routeProxyCache.delete(key));
6021
6018
  }
6022
6019
 
6023
6020
  // Create proxy for route-specific lookups
@@ -6025,8 +6022,13 @@ function createRouteProxy(dataSourceData, pathKey, dataSourceName) {
6025
6022
  // Use cache if dataSourceName is provided to avoid recreating proxies
6026
6023
  if (dataSourceName && pathKey) {
6027
6024
  const cacheKey = `${dataSourceName}:${pathKey}`;
6028
- if (routeProxyCache.has(cacheKey)) {
6029
- return routeProxyCache.get(cacheKey);
6025
+ const cached = routeProxyCache.get(cacheKey);
6026
+ if (cached) {
6027
+ if (cached.stale) {
6028
+ cached.stale = false;
6029
+ cached.refresh(dataSourceData);
6030
+ }
6031
+ return cached.proxy;
6030
6032
  }
6031
6033
  }
6032
6034
 
@@ -6271,7 +6273,15 @@ function createRouteProxy(dataSourceData, pathKey, dataSourceName) {
6271
6273
  // Cache the proxy if dataSourceName and pathKey are provided
6272
6274
  if (dataSourceName && pathKey) {
6273
6275
  const cacheKey = `${dataSourceName}:${pathKey}`;
6274
- routeProxyCache.set(cacheKey, proxy);
6276
+ routeProxyCache.set(cacheKey, {
6277
+ proxy,
6278
+ stale: false,
6279
+ refresh(data) {
6280
+ dataSourceData = data;
6281
+ lastUpdateUrl = null;
6282
+ updateReactiveTarget();
6283
+ }
6284
+ });
6275
6285
  }
6276
6286
 
6277
6287
  return proxy;
@@ -6299,7 +6309,10 @@ const reactiveFileManagers = new Map();
6299
6309
 
6300
6310
  // Create a computed files array for an entry that filters the bucket array by fileIds
6301
6311
  // This is the new single-source-of-truth approach
6302
- function createComputedFilesArray(tableName, entryId, bucketName, columnName = 'fileIds') {
6312
+ // owner: the row object holding this array; the effect releases itself once the row leaves its table
6313
+ function createComputedFilesArray(tableName, entryId, bucketName, columnName = 'fileIds', options = {}) {
6314
+ const owner = options.owner || null;
6315
+
6303
6316
  if (typeof Alpine === 'undefined') {
6304
6317
  // Return empty array proxy if Alpine not available
6305
6318
  return new Proxy([], {
@@ -6405,8 +6418,9 @@ function createComputedFilesArray(tableName, entryId, bucketName, columnName = '
6405
6418
  };
6406
6419
 
6407
6420
  // Set up Alpine effect to watch both bucket array and entry's fileIds
6421
+ let filesEffect = null;
6408
6422
  if (typeof Alpine !== 'undefined' && Alpine.effect) {
6409
- Alpine.effect(() => {
6423
+ filesEffect = Alpine.effect(() => {
6410
6424
  // Access store to track changes
6411
6425
  const store = Alpine.store('data');
6412
6426
  if (!store) {
@@ -6414,6 +6428,14 @@ function createComputedFilesArray(tableName, entryId, bucketName, columnName = '
6414
6428
  return;
6415
6429
  }
6416
6430
 
6431
+ if (owner) {
6432
+ const rows = store[tableName];
6433
+ if (Array.isArray(rows) && !rows.some(row => (Alpine.raw ? Alpine.raw(row) : row) === owner)) {
6434
+ queueMicrotask(() => { if (filesEffect) Alpine.release(filesEffect); });
6435
+ return;
6436
+ }
6437
+ }
6438
+
6417
6439
  // Track bucket array changes - access the array itself and iterate to track all elements
6418
6440
  const bucketFiles = store[bucketName];
6419
6441
  if (bucketFiles && Array.isArray(bucketFiles)) {
@@ -6460,7 +6482,7 @@ function createComputedFilesArray(tableName, entryId, bucketName, columnName = '
6460
6482
  recomputeFiles();
6461
6483
 
6462
6484
  // Create proxy that adds loading/error properties
6463
- return new Proxy(computedFiles, {
6485
+ const computedProxy = new Proxy(computedFiles, {
6464
6486
  get(target, key) {
6465
6487
  if (key === '$loading') return loadingState.value;
6466
6488
  if (key === '$error') return errorState.value;
@@ -6475,6 +6497,7 @@ function createComputedFilesArray(tableName, entryId, bucketName, columnName = '
6475
6497
  return key in target || key === '$loading' || key === '$error';
6476
6498
  }
6477
6499
  });
6500
+ return computedProxy;
6478
6501
  }
6479
6502
 
6480
6503
  // Create reactive file array that watches a function/expression for entryId changes
@@ -6908,9 +6931,7 @@ function createReactiveFileManager(tableName, entryId, bucketName, columnName) {
6908
6931
  });
6909
6932
 
6910
6933
  unsubscribeCallbacks.push(() => {
6911
- if (unwatch && typeof unwatch === 'function') {
6912
- unwatch();
6913
- }
6934
+ if (unwatch) Alpine.release(unwatch);
6914
6935
  });
6915
6936
  };
6916
6937