mnfst 0.5.232 → 0.5.233

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.232';
5
+ const MANIFEST_BUILD_VERSION = '0.5.233';
6
6
 
7
7
  /* Manifest Data Sources - Appwrite Integration */
8
8
 
package/lib/manifest.d.ts CHANGED
@@ -433,11 +433,13 @@ export interface ManifestEdit {
433
433
  /** Set an attribute: a string sets it, `true` sets it empty, `null`/`false` removes it. */
434
434
  attr(name: string, value: string | boolean | null, el?: Element): boolean;
435
435
  classes: ManifestEditClasses;
436
- /** Component scope of the target's area: read with no value, set 'instance' or 'main'.
436
+ /** Component scope of `el`'s area (default `$edit.target`): `scope(el)` reads it.
437
437
  * Instance overrides apply to an area that wraps exactly one component with no x-edit region
438
438
  * of its own; a component among other content is a host piece of a static area instead, and
439
439
  * a component's own region is edited in its own file. */
440
- scope(value?: 'instance' | 'main', el?: Element): 'instance' | 'main' | null | boolean;
440
+ scope(el?: Element | null): 'instance' | 'main' | null;
441
+ /** Set the component scope of `el`'s area (default `$edit.target`); false if it has none. */
442
+ scope(value: 'instance' | 'main', el?: Element | null): boolean;
441
443
  /** Revert a component element's instance overrides (the area element reverts all). */
442
444
  revert(el?: Element): boolean;
443
445
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  (function () {
4
4
 
5
- const MANIFEST_BUILD_VERSION = '0.5.232';
5
+ const MANIFEST_BUILD_VERSION = '0.5.233';
6
6
 
7
7
  /* Manifest Data Sources - Configuration */
8
8
 
@@ -1163,7 +1163,8 @@
1163
1163
  if (kind === 'component') return false;
1164
1164
  const block = asPiece(el);
1165
1165
  if (!block) return false;
1166
- if (action === 'copy' || action === 'cut') return !isHostEl(block);
1166
+ if (action === 'copy') return !isHostEl(block);
1167
+ if (action === 'cut') return canDo('copy', block) && canDo('remove', block); // a cut is a copy and a remove
1167
1168
  if (action === 'move') return movable(block);
1168
1169
  const lockAs = action === 'duplicate' ? 'duplicate' : 'remove';
1169
1170
  return capOf(block.parentElement, 'move') && !lockedFor(block, lockAs);
@@ -1408,6 +1409,8 @@
1408
1409
  const region = s.crossOK ? dropArea(x, y, s.area) : s.area;
1409
1410
  if (!region) return null;
1410
1411
  const hit = document.elementFromPoint ? document.elementFromPoint(x, y) : null;
1412
+ // Over the stand-in itself: it stays where it is
1413
+ if (hit && s.ghost && (hit === s.ghost || s.ghost.contains(hit))) return null;
1411
1414
  if (hit && hit !== region && region.contains(hit)) {
1412
1415
  // Inside a component host: the host is what is hit (its markup is another file's)
1413
1416
  let from = hit;
@@ -1417,6 +1420,9 @@
1417
1420
  const parent = el.parentElement;
1418
1421
  if (!isContainer(parent)) continue;
1419
1422
  if (isHostEl(el)) { if (accepts(parent) && fits(s.item, parent) && !s.item.contains(parent)) return beside(el, x, y, parent); continue; }
1423
+ // Over the container the piece came from, or one holding the stand-in (a gap, its padding):
1424
+ // it stays in, placed by reading order; it leaves only when the pointer does
1425
+ if (s.ghost && (el === s.home || el.contains(s.ghost)) && accepts(el) && fits(s.item, el)) return { container: el, ref: afterElement(el, x, y) };
1420
1426
  if (isContainer(el) && accepts(el) && fits(s.item, el) && inMiddle(el, x, y, parent)) {
1421
1427
  if (s.ghost && s.ghost.parentElement !== el) s.nested = { el, rect: el.getBoundingClientRect(), horiz: flowAxis(parent) === 'x' };
1422
1428
  return { container: el, ref: afterElement(el, x, y) };
@@ -1429,9 +1435,12 @@
1429
1435
  }
1430
1436
  // Reading-order insertion: the first sibling past the pointer.
1431
1437
  function afterElement(c, x, y) {
1438
+ const column = flowAxis(c) === 'y';
1432
1439
  for (const el of sortableChildren(c)) {
1433
1440
  if (el === dragged || el.hasAttribute('data-edit-dragging') || lockedFor(el, 'move')) continue;
1434
1441
  const b = el.getBoundingClientRect(), cx = b.left + b.width / 2, cy = b.top + b.height / 2;
1442
+ // Stacked rows: past a row once the pointer is past its middle (wherever it is across)
1443
+ if (column && !/inline/.test(getComputedStyle(el).display)) { if (cy > y) return el; continue; }
1435
1444
  const sameRow = Math.abs(cy - y) < b.height / 2;
1436
1445
  if (cy - y > b.height / 2 || (sameRow && cx > x)) return el;
1437
1446
  }
@@ -2332,10 +2341,24 @@
2332
2341
  });
2333
2342
  cssvarControls.forEach(syncCssVarInput);
2334
2343
  }
2335
- function rebaseTheme() {
2336
- Object.keys(themeBaseline).forEach(kk => { const sep = kk.indexOf('|'); themeBaseline[kk] = themeTargetEl(kk.slice(0, sep) || null).style.getPropertyValue(kk.slice(sep + 1)); });
2337
- themeTouched.clear();
2338
- }
2344
+ function rebaseTheme(keep) {
2345
+ Object.keys(themeBaseline).forEach(kk => { if (keep && keep.has('theme|' + kk)) return; const sep = kk.indexOf('|'); themeBaseline[kk] = themeTargetEl(kk.slice(0, sep) || null).style.getPropertyValue(kk.slice(sep + 1)); });
2346
+ [...themeTouched].forEach(kk => { if (!(keep && keep.has('theme|' + kk))) themeTouched.delete(kk); });
2347
+ }
2348
+ // A scoped value lives in its scope element's inline style, and is written there, never to a
2349
+ // stylesheet: with its x-edit area's tree when the element is a static piece of one ('area'),
2350
+ // else found by its own x-edit.theme name ('own'). Inside generated content it can't be (null).
2351
+ function inlineScope(scope) {
2352
+ const el = scope && themeScopes[scope]; if (!el || generatedBy(el)) return null;
2353
+ const a = regionOf(el), k = el.getAttribute('data-edit-key');
2354
+ if (a && classify(a) === 'static' && !sharedKey(a) && k && /^[@~]/.test(k)) return 'area';
2355
+ if (a && a !== el) return null;
2356
+ const named = areaByKey(scope);
2357
+ return !named || named === el ? 'own' : null;
2358
+ }
2359
+ const inlineScoped = () => new Set(Object.values(themeState()).filter(d => d.scope && inlineScope(d.scope) === 'area').map(d => themeScopes[d.scope]));
2360
+ // The component file a scope element is authored in, if any.
2361
+ const scopeComponent = (el) => { for (let n = el; n; n = n.parentElement) if (n._manifestComponent) return n._manifestComponent; return null; };
2339
2362
  // A custom-property value must stay inside its declaration (same tokenizer as the
2340
2363
  // source writer): strings and url(...) are opaque; script schemes in url() and
2341
2364
  // expression() are refused anywhere; data: urls only as images.
@@ -2383,12 +2406,16 @@
2383
2406
  const cur = themeState()[kk]; if (cur && cur.value === value) return; // dedupe
2384
2407
  pushDelta({ kind: 'theme', scope: scope || null, var: v, value }, !!persist);
2385
2408
  }
2386
- // Each var → its scope's data-edit-theme-file, else the server's theme file.
2409
+ // Global vars → the page's theme stylesheet (the server's choice); scoped ones inline
2387
2410
  function themePatches() {
2388
- return Object.values(themeState()).map(d => {
2389
- const el = d.scope ? themeScopes[d.scope] : null;
2390
- return { kind: 'theme', scope: d.scope || null, var: d.var, value: d.value, file: el ? el.getAttribute('data-edit-theme-file') || null : null };
2411
+ const st = Object.values(themeState()), out = [];
2412
+ st.filter(d => !inlineScope(d.scope)).forEach(d => out.push({ kind: 'theme', scope: d.scope || null, var: d.var, value: d.value, file: null }));
2413
+ // A scope element of its own: its style attribute, as the page has it now
2414
+ new Set(st.filter(d => d.scope && inlineScope(d.scope) === 'own').map(d => d.scope)).forEach(scope => {
2415
+ const el = themeScopes[scope], comp = scopeComponent(el), tree = { key: '', edits: { style: ownStyle(el) || null } };
2416
+ out.push(comp ? { kind: 'static-tree', region: scope, component: comp, tree } : { kind: 'static-tree', region: scope, tree });
2391
2417
  });
2418
+ return out;
2392
2419
  }
2393
2420
 
2394
2421
 
@@ -2448,8 +2475,21 @@
2448
2475
  };
2449
2476
  walk(area, '');
2450
2477
  }
2451
- const warnedRendered = new Set(), renderedRegions = new Set();
2478
+ const warnedRendered = new Set(), renderedRegions = new Set(), unwritableWhy = new Map();
2479
+ // A list whose records mnfst-run can't write: inline x-data, or a source manifest.json does not declare as a file.
2480
+ function dataUnwritable(area) {
2481
+ if (classify(area) !== 'data') return null;
2482
+ const m = (dataSourceExpr(area) || '').match(/\$x\.([\w$]+)/);
2483
+ if (!m) return 'inline x-data list: mnfst-run writes data only to CSV/JSON/YAML sources declared in manifest.json';
2484
+ const mf = window.ManifestComponentsRegistry && window.ManifestComponentsRegistry.manifest;
2485
+ if (!mf) return null;
2486
+ const src = (mf.data || {})[m[1]];
2487
+ if (src == null) return `data source '${m[1]}' is not declared in manifest.json: mnfst-run writes data only to CSV/JSON/YAML files declared there`;
2488
+ return typeof src === 'string' ? null : `data source '${m[1]}' is not a file (a cloud or remote source): save it with $x.${m[1]}.$update, not mnfst-run`;
2489
+ }
2452
2490
  function armArea(area) {
2491
+ const dw = dataUnwritable(area);
2492
+ if (dw) { renderedRegions.add(key(area)); unwritableWhy.set(key(area), dw); if (!warnedRendered.has(key(area))) { warnedRendered.add(key(area)); console.warn(`[edit] x-edit="${key(area)}": ${dw}; its edits stay on the page (and reach onPublish)`); } }
2453
2493
  if (area._edit.rendered) renderedRegions.add(key(area));
2454
2494
  if (area._edit.rendered && !warnedRendered.has(key(area))) { warnedRendered.add(key(area)); console.warn(`[edit] x-edit="${key(area)}" is inside content ${area._edit.rendered} renders: it edits on the page (and reaches onPublish), but mnfst-run cannot write it to a source file`); }
2455
2495
  uneditIn(null);
@@ -2545,7 +2585,7 @@
2545
2585
  return Object.keys(e).length ? e : null;
2546
2586
  };
2547
2587
  const nontrivial = (n) => n.edits || n.children || n.key[0] === '~';
2548
- const live = keyMap(staticAreas());
2588
+ const live = keyMap(staticAreas()), scoped = inlineScoped();
2549
2589
  staticAreas().forEach(area => {
2550
2590
  const region = key(area), comp = sourceComponent(area);
2551
2591
  const ids = new Proxy({}, { get: (_, k) => { const o = originOf(String(k)), a = areaByKey(decodeURIComponent(o.slice(1, o.indexOf('@', 1)))); const m = baseIds && a && baseIds[sourceFile(a)]; return m ? m[o] : undefined; } });
@@ -2565,6 +2605,7 @@
2565
2605
  const at = originAt.get(originOf(id)); if (at != null) n.at = at;
2566
2606
  if (ids && ids[originOf(id)]) n.fp = ids[originOf(id)];
2567
2607
  const e = editsFor(id); if (e) n.edits = e;
2608
+ if (scoped.has(el)) n.edits = Object.assign({}, n.edits, { style: ownStyle(el) || null });
2568
2609
  if (!(e && 'text' in e) && !isRendered(el) && !el.hasAttribute('data-rich-text')) {
2569
2610
  const kids = treeKids(el).map(c => build(c)).filter(Boolean);
2570
2611
  const structural = ss.touched.has(isRoot ? '#' + region : id);
@@ -2606,7 +2647,8 @@
2606
2647
  return e;
2607
2648
  };
2608
2649
  const dv = dataValueState();
2609
- Object.entries(dv).forEach(([source, recs]) => Object.entries(recs).forEach(([id, fields]) => Object.entries(fields).forEach(([field, value]) => patches.push({ kind: 'data-val', source, id, field, value }))));
2650
+ const dvRegion = {}; log.slice(0, cursor).forEach(d => { if (d.kind === 'data-val') dvRegion[d.source] = d.region; });
2651
+ Object.entries(dv).forEach(([source, recs]) => Object.entries(recs).forEach(([id, fields]) => Object.entries(fields).forEach(([field, value]) => patches.push({ kind: 'data-val', region: dvRegion[source], source, id, field, value }))));
2610
2652
  const { main, inst, reverts } = componentState();
2611
2653
  Object.entries(main).forEach(([component, paths]) => { const edits = toEdits(paths); if (edits.length) patches.push({ kind: 'component', scope: 'main', component, edits }); });
2612
2654
  new Set([...Object.keys(inst), ...Object.keys(reverts)]).forEach(region => {
@@ -2623,7 +2665,7 @@
2623
2665
  // `keep` (areas a publish left out), whose identities are left as they are.
2624
2666
  function rebase(keep) {
2625
2667
  const mounted = (r) => r == null || (!!areaByKey(r) && !(keep && keep.has(r)));
2626
- const pending = (d) => (d.kind === 'st-move' ? [d.from, d.to] : [d.region]).some(r => !mounted(r));
2668
+ const pending = (d) => d.kind === 'theme' ? !!keep && (keep.has('theme|' + (d.scope || '') + '|' + d.var) || keep.has(d.scope)) : (d.kind === 'st-move' ? [d.from, d.to] : [d.region]).some(r => !mounted(r));
2627
2669
  // Runtime locks are app state, not source: carried over by element.
2628
2670
  const locks = [];
2629
2671
  const lockState = lockStateNow();
@@ -2657,7 +2699,7 @@
2657
2699
  if (kind === 'component' && a._armed) armComponent(a);
2658
2700
  lastSnap[key(a)] = snapshot(a, kind);
2659
2701
  });
2660
- rebaseTheme();
2702
+ rebaseTheme(keep);
2661
2703
  relock();
2662
2704
  saveState(); refresh();
2663
2705
  }
@@ -2783,7 +2825,7 @@
2783
2825
  // fingerprints of keys with twins there (which may not be the elements this page shows).
2784
2826
  let overNewer = false;
2785
2827
  function publishNow(opts) {
2786
- const done = (r, keep) => {
2828
+ const done = (r, keep, heldRes) => {
2787
2829
  if (r && r.base && baseFiles) Object.entries(r.base).forEach(([f, h]) => { if (!overNewer || !/\.html?$/i.test(f)) baseFiles[f] = h; });
2788
2830
  if (r && r.ids && baseIds) {
2789
2831
  if (!overNewer) baseIds = r.ids;
@@ -2792,7 +2834,7 @@
2792
2834
  const stale = r && Array.isArray(r.stale) ? r.stale : [];
2793
2835
  if (estore) estore.stale = stale;
2794
2836
  if (stale.length) document.dispatchEvent(new CustomEvent('edit:stale', { detail: { files: stale } }));
2795
- if (published(keep ? { results: r.results.filter(x => !(x && keep.has(x.region))) } : r)) rebase(keep);
2837
+ if (published(keep ? { results: r.results.filter(x => !(x && (keep.has(x.region) || (heldRes && heldRes.has(x))))) } : r)) rebase(keep);
2796
2838
  takeHeldReload();
2797
2839
  return r;
2798
2840
  };
@@ -2801,14 +2843,19 @@
2801
2843
  const leftOut = (r, patches) => {
2802
2844
  if (!r || !Array.isArray(r.results) || (Array.isArray(r.stale) && r.stale.length)) return null;
2803
2845
  const failed = new Set(r.results.filter(x => x && x.failed != null).map(x => x.failed));
2804
- if (!failed.size || r.results.some((x, i) => x && x.status === 'error' && !failed.has(patches[i] && patches[i].region))) return null;
2846
+ const own = new Set(patches.filter((p, i) => r.results[i] && r.results[i].status === 'error' && r.results[i].failed == null && p.kind !== 'static-tree'));
2847
+ if ((!failed.size && !own.size) || r.results.some((x, i) => x && x.status === 'error' && !failed.has(patches[i] && patches[i].region) && !own.has(patches[i]))) return null;
2805
2848
  tie(failed);
2806
- return patches.some(p => !failed.has(p.region)) ? failed : null;
2849
+ const out = (p) => own.has(p) || (p.region != null && failed.has(p.region));
2850
+ if (!patches.some(p => !out(p))) return null;
2851
+ return Object.assign(failed, { own, out });
2807
2852
  };
2853
+ // What a held-back patch keeps in the log: its region's edits, or its theme value.
2854
+ const keepOf = (p) => p.kind === 'theme' ? 'theme|' + (p.scope || '') + '|' + p.var : p.region;
2808
2855
  // An area inside generated content (x-markdown output and the like) edits on the page,
2809
2856
  // but its markup is in no file mnfst-run can write: it is refused by name, not sent.
2810
2857
  const renderedBy = (region) => { const a = areaByKey(region); return a && a._edit.rendered; };
2811
- const unwritable = (p) => (p.kind === 'static-tree' || (p.kind === 'component' && p.scope === 'instance')) && !!renderedBy(p.region);
2858
+ const unwritable = (p) => p.region != null && p.kind !== 'theme' && renderedRegions.has(p.region);
2812
2859
  const send = () => {
2813
2860
  commitFocused(true); // typing under way publishes with the rest (identities are re-derived from the page after)
2814
2861
  const patches = buildPatches();
@@ -2816,24 +2863,25 @@
2816
2863
  if (!patches.length) { takeHeldReload(); return Promise.resolve({ results: [] }); } // nothing to send: a held reload goes now
2817
2864
  const post = (ps) => { inFlight++; return fetch('/__edit/save', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ patches: ps, base: baseFiles }) }).then(r => r.json()).catch(err => { settled(null); throw err; }); };
2818
2865
  const off = tie(new Set(patches.filter(unwritable).map(p => p.region)));
2819
- const offResult = (p) => ({ region: p.region, status: 'error', reason: renderedBy(p.region) && unwritable(p)
2820
- ? `x-edit="${p.region}" is inside content ${renderedBy(p.region)} renders, so mnfst-run can't write it to a source file`
2866
+ const offResult = (p) => ({ region: p.region, status: 'error', reason: unwritableWhy.has(p.region) && unwritable(p) ? `x-edit="${p.region}": ${unwritableWhy.get(p.region)}`
2867
+ : renderedBy(p.region) && unwritable(p) ? `x-edit="${p.region}" is inside content ${renderedBy(p.region)} renders, so mnfst-run can't write it to a source file`
2821
2868
  : `not published: tied by a move to x-edit="${off.via.get(p.region)}", which mnfst-run can't write` });
2822
2869
  const sendable = patches.filter(p => !off.has(p.region));
2823
2870
  // The sent part's answer, merged with what was held back, as one result per patch.
2824
2871
  const finish = (r, held, sent) => {
2825
- const keep = new Set([...off, ...(held || [])]);
2826
- const results = patches.map(p => off.has(p.region) ? offResult(p) : r.results[sent.indexOf(p)]);
2827
- const o = done(Object.assign({}, r, { results }), keep.size ? keep : undefined); settled(r, !!(opts && opts.rebase)); return o;
2872
+ const keep = new Set([...off, ...(held || []), ...(held ? sent.filter(held.out).map(keepOf) : [])]);
2873
+ const heldRes = new Set();
2874
+ const results = patches.map(p => { if (off.has(p.region)) { const x = offResult(p); heldRes.add(x); return x; } const x = r.results[sent.indexOf(p)]; if (held && held.out(p)) heldRes.add(x); return x; });
2875
+ const o = done(Object.assign({}, r, { results }), keep.size ? keep : undefined, heldRes); settled(r, !!(opts && opts.rebase)); return o;
2828
2876
  };
2829
2877
  if (!sendable.length) { const o = done({ results: patches.map(offResult) }, off); return Promise.resolve(o); }
2830
2878
  return post(sendable).then(r => {
2831
2879
  const out = leftOut(r, sendable);
2832
2880
  if (!out) return finish(r, null, sendable);
2833
2881
  settled(r, false);
2834
- const rest = sendable.filter(p => !out.has(p.region));
2882
+ const rest = sendable.filter(p => !out.out(p));
2835
2883
  return post(rest).then(r2 => {
2836
- const results = sendable.map((p, i) => out.has(p.region)
2884
+ const results = sendable.map((p, i) => out.out(p)
2837
2885
  ? (r.results[i] && r.results[i].status === 'error' ? r.results[i] : { region: p.region, status: 'error', reason: `not published: tied by a move to x-edit="${out.via.get(p.region) || [...out].find(x => !out.via.has(x))}", which failed` })
2838
2886
  : r2.results[rest.indexOf(p)]);
2839
2887
  return finish(Object.assign({}, r2, { results }), out, sendable);
@@ -2935,8 +2983,10 @@
2935
2983
  refresh();
2936
2984
  }
2937
2985
  function waitForData(area, cb) { const expr = dataSourceExpr(area), tpl = listTpl(area); let n = 0; const t = setInterval(() => { let r = false; try { r = Array.isArray(window.Alpine.evaluate(tpl, expr)); } catch { } if (r) { clearInterval(t); cb(); } else if (++n > 100) clearInterval(t); }, 50); }
2986
+ let inited = false;
2938
2987
  function init() {
2939
- if (!window.Alpine || !Alpine.directive) return;
2988
+ if (inited || !window.Alpine || !Alpine.directive) return;
2989
+ inited = true;
2940
2990
  // Alpine walks only x-data trees: a page with none still gets its areas
2941
2991
  if (document.body && !document.body.hasAttribute('x-data')) document.body.setAttribute('x-data', '{}');
2942
2992
  Alpine.directive('edit', (el, { modifiers, expression }, { cleanup }) => {
@@ -2968,14 +3018,20 @@
2968
3018
  block(node) { return pieceOf(node); },
2969
3019
  attr(name, value, el) { return setAttr(name, value, el); },
2970
3020
  classes: classesApi,
2971
- scope(value, el) { void this.version; return value === undefined ? scopeOf(el) : setScope(value, el); },
3021
+ scope(value, el) { void this.version; if (value && value.nodeType === 1) return scopeOf(value); return value == null ? scopeOf(el) : setScope(value, el); },
2972
3022
  revert(el) { return revert(el); },
2973
3023
  clearConflicts() { conflicts = []; this.conflicts = []; saveState(); }
2974
3024
  });
2975
3025
  estore = Alpine.store('edit');
2976
- Alpine.magic('edit', () => Alpine.store('edit'));
3026
+ const editMagic = () => Alpine.store('edit');
3027
+ Alpine.magic('edit', editMagic);
3028
+ // The loader's stand-in answered $edit until now: it hands over, and effects that read it run again
3029
+ window.__manifestEditMagic = editMagic;
3030
+ const kept = window.__manifestEditStubWrites; // writes made on the stand-in ($edit.onPublish = …)
3031
+ if (kept) Object.keys(kept).forEach(k => { try { estore[k] = kept[k]; } catch { } delete kept[k]; });
3032
+ if (window.__manifestEditStub) setTimeout(() => { window.__manifestEditStub.v++; }, 0);
2977
3033
  loadState();
2978
- loadBase();
3034
+ if (window.__mnfstEdit) loadBase(); // only mnfst-run --edit serves the base
2979
3035
  setTimeout(() => { armAll(); restore(); }, 450);
2980
3036
  document.addEventListener('keydown', onHistoryKey);
2981
3037
  window.addEventListener('click', onAreaLinkClick, true);
@@ -2984,6 +3040,16 @@
2984
3040
  document.addEventListener('dropdown:context', onContextCapture, true);
2985
3041
  // SPA navigation: the state so far is saved under the page it was made on.
2986
3042
  window.addEventListener('manifest:route-change', () => { if (lsPath !== location.pathname) { lsPath = location.pathname; saveState(); } });
3043
+ adopt();
3044
+ }
3045
+ // Loaded after Alpine started: x-edit elements its walk passed get their attributes re-set
3046
+ // (Alpine runs them as new directives); those it never walked (no x-data above) are walked now.
3047
+ function adopt() {
3048
+ if (!window.__manifestAlpineStarted && ![...document.querySelectorAll('[x-data]')].some(e => e._x_marker)) return;
3049
+ const isEdit = (n) => /^x-edit(\.|$)/.test(n);
3050
+ const found = [...document.querySelectorAll('*')].filter(el => !el._edit && [...el.attributes].some(a => isEdit(a.name)));
3051
+ found.forEach(el => { if (el._x_marker) [...el.attributes].filter(a => isEdit(a.name)).forEach(a => el.setAttribute(a.name, a.value)); });
3052
+ if (found.some(el => !el._x_marker)) Alpine.initTree(document.body);
2987
3053
  }
2988
3054
  document.addEventListener('alpine:init', init);
2989
3055
  if (window.Alpine && Alpine.directive) setTimeout(init, 0);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "manifest.appwrite.auth.js": "sha384-dsIWnH0qCAGSzAXMGrd8fvOrVl4tOIE6j5tC35JPTrQXZ56qLD36ARyvhR7vi3Id",
3
- "manifest.appwrite.data.js": "sha384-enlQBS4B79OAlkgYX0qon8pnr3Twc7O1iGtiQ+L+Hqgoi+rCKA3FOUFrTBuhprxV",
3
+ "manifest.appwrite.data.js": "sha384-ovIuPS7AiQP/dP+NHcF9QaB3YK3dlqOLWN9pYX/RHofc2PLrZZuDjfEFiyNReuBu",
4
4
  "manifest.appwrite.presences.js": "sha384-JyVDWFT69b7Svq8J8p9tNWe2v9PZNQBY5UyhtOl1vnuHFAX0CRQ3MA9SiKq5wzNh",
5
5
  "manifest.bindings.js": "sha384-lPtMf8Ykaic9gNVZKq8Scia1881ZeMAyxer3WCIkUNwwrgzGBWto1OXADUwjvORF",
6
6
  "manifest.charts.js": "sha384-gEAv/98+29RbhvNkiNWTNyBPbLTFr2Ld15wLu29FLCL57j1cwGChRrL9Pfk7dBtT",
@@ -11,24 +11,24 @@
11
11
  "manifest.combobox.js": "sha384-MILTkrgsTzsYUjGo4ZuADqV85Xs4GmfB4xexfe9vbJMIoQTZ42W4zs+DTaVSPEsx",
12
12
  "manifest.components.js": "sha384-JalISaBBWNFl3ggx92Hb3B52EpwwG3gzgra6Q+js0OgZ8vrFKMD48CuBox2N9A5T",
13
13
  "manifest.computed.js": "sha384-GYML/e81gQfN+iDbqhPUgny8bINCyL+SrbTofoGk4hlCnLLjv51mCv42JMqq+ku5",
14
- "manifest.data.js": "sha384-szQophuvtkatLyTtVALy63mbKPGQZQ/lP2v27QExQnS7HMuCbuhxgKdTITZQ1JQq",
14
+ "manifest.data.js": "sha384-cemls8IyDcg61WdlIOT60HmQS7aCef4ifwqnXfw7kA6Jp5D2PdXazWRRzR6QHx10",
15
15
  "manifest.datepicker.js": "sha384-XnbeURni2X91ozVzatQcSjYYIS4MoW12dBRJnYAGBr0nCQ624heH5s+xtQSpLZX6",
16
16
  "manifest.defer.js": "sha384-pMRGqQKiEoJhpUcD40QeM4jEHau7sz9Xn/YCpiUamT1T4xJBZDSiYX+xnVrSWoq+",
17
17
  "manifest.device.js": "sha384-g+txmrEszYtuOvRelTp/c1wSNR2AtllVI9rx5NPPD7DzhVUnMNughonZykv2xlBW",
18
18
  "manifest.dropdowns.js": "sha384-acufUps7R9WjqB4XXW5JLACqniuHJGM4CNTb2rF4B2Q/jv7ZRmsAwfr2McanOz3s",
19
- "manifest.edit.js": "sha384-UsKV6oSwdF+yFtpZKr8KD62VajynYVsBcVN3+dAUI4nh2pOOf3/2lyybXbEG/pvT",
19
+ "manifest.edit.js": "sha384-bOvEFgjau1t30LPHKBGkcJ3jH8tRUy1k8qpyYs8GbB6gUJ/R9JQCzeaF5fsKlzu3",
20
20
  "manifest.export.js": "sha384-Rx6XvqBZc4apzP/FwcDr6yIln4/zNa08Q4ZqBZcEqUz1aNZQfl4h0+Ae7mqCQyuG",
21
21
  "manifest.icons.js": "sha384-kKOINpX70YzNO3FvkV6umrmO3f0KIu0DNbwB+ej55a7y/lhU7v/myQtXL0bwCTbt",
22
22
  "manifest.localization.js": "sha384-EImsIw8Z3l13V5eH9ZJF/yfsOfIvR1oPRNSArANkPji7V8O3IRf6zMwsNiAGj3Hk",
23
23
  "manifest.markdown.js": "sha384-heyDR40LXHaAmAuA+fa381uFwShk1oAqpFFY6E2Qq4T3VN0TQfRqyc++MF40K5Hj",
24
24
  "manifest.native.js": "sha384-g+txmrEszYtuOvRelTp/c1wSNR2AtllVI9rx5NPPD7DzhVUnMNughonZykv2xlBW",
25
25
  "manifest.payments.js": "sha384-8pofk2zhxtapPMpwnFyTelPMrYhShNhDCE722uQh/EYFvZFuPLmSdgunqsarVQjv",
26
- "manifest.rich.text.js": "sha384-CBScTgPU6y96e481M7Ba1/YqPBkeQNLvMKpGc9LQLALOdyj8wlv7Faez+MXjT9yn",
26
+ "manifest.rich.text.js": "sha384-QpPHG4uWjJSJoTPY8yOtoIFoVmHbuYPhtghBU0gc2l0PJBc25cNPuNcD4+55Qv3i",
27
27
  "manifest.router.js": "sha384-QkKhRaCbdXEOxhj+ojT4jeLUCtQepBnqP+QWTIWdb6kUWD+mVzPN8hf6jhgschpW",
28
28
  "manifest.slides.js": "sha384-3uRTkyK9XPLmnxI2+igZlpi4EyPlU/7IHj5j3BZJJ2KN455vXyk99fiXV3feO/XY",
29
29
  "manifest.status.js": "sha384-MBO345ab7H6Uq1X2dZLt4Ge1Gcra+Z2qYApvlAeIV8CJhDDJrz2FPXpp+Kcp8zcM",
30
30
  "manifest.svg.js": "sha384-qr/8H9+2cIhSXNbRWRtIdrDlTOxKlNJqwEefHvCYoiBNW/kKhPTFa109Orv/EBnb",
31
- "manifest.sw.js": "sha384-FiEZfwCVI2lKcp4xaRE6nJhjGIbphg6BREeTGvRsIMpqqJ+5AzxFQKE1o84fGfGE",
31
+ "manifest.sw.js": "sha384-vXYxaf8oKd2LT8C9xRmFmEXhh0qDLv0set07o/hI4hkOvpS+CTDdC5UNECLRrSWa",
32
32
  "manifest.tabs.js": "sha384-OIIcyaQnGS0eNYk4zgAFEDboNYect7THvUd9D0V6nqCeDOxQcoiqLrBEmmWVTTet",
33
33
  "manifest.tailwind.js": "sha384-r/Jvqb+Piz21jUts10BbVAJoKkShOdJRVFpywyi4HPIi/OfOJq5CtPyWAPbdkMgt",
34
34
  "manifest.toasts.js": "sha384-3zkWxzw693pxg3QDFsoYCA3CqYaLKmBaMArULXODD66Utolpewl0OAeTB92aWUVv",
@@ -36,5 +36,5 @@
36
36
  "manifest.url.parameters.js": "sha384-J2aSVBOPsoqsbafHndrWLHEDpKb4AKJAoQnVmKA4gZh5P/8HHjFWHO/lkusmG84l",
37
37
  "manifest.utilities.js": "sha384-L5uXMLFBIF6LUM1haRjd4eGjHSWuqsCYSpPZRPYosNfJsMC5U5TnmjBxIhrZRe2d",
38
38
  "manifest.virtual.js": "sha384-ZDNoF9QVhVcVpkjIK6NH15S6I5c6OBshiJpXSOYjNsIB+298I0a7g3aFO+jyq4Vb",
39
- "manifest.js": "sha384-G+ctciC90o/zSB/sRbmX7ANE/7rYvnepyzIsvNbD6N5ynlO5oludSDfzYVCdzU/g"
39
+ "manifest.js": "sha384-m5WiqHv+23ZnBvvIt9NtUGE2Gupaw9jDpKuonIH9HZCHc+LR8wbitDImJoNPH9CI"
40
40
  }
package/lib/manifest.js CHANGED
@@ -597,14 +597,55 @@
597
597
  const A = window.Alpine;
598
598
  if (window.ManifestRichText || !A || !A.magic || !A.reactive || window.__manifestRichStub) return;
599
599
  const state = window.__manifestRichStub = A.reactive({ v: 0 });
600
- const idle = new Proxy({ value: '', link: '', selection: null, all: {}, active: () => false, can: () => false, markdown: () => '', html: () => '' },
601
- { get: (t, k) => k in t ? t[k] : (typeof k === 'string' ? () => { } : undefined), set: () => true });
602
- A.magic('rich', (el, u) => { void state.v; return window.__manifestRichMagic ? window.__manifestRichMagic(el, u) : idle; });
600
+ // Writes ($rich.value = …, $rich.all = …) are kept per element and applied to its field at handover
601
+ const writes = window.__manifestRichStubWrites = [], perEl = new WeakMap();
602
+ const deref = (r) => r && typeof r.deref === 'function' ? r.deref() : r;
603
+ const base = { value: '', link: '', selection: null, all: {}, active: () => false, can: () => false, markdown: () => '', html: () => '' };
604
+ const idleFor = (el) => {
605
+ if (!perEl.has(el)) perEl.set(el, new Proxy(base, {
606
+ get: (t, k) => { const w = writes.find(x => deref(x[0]) === el && x[1] === k); return w ? w[2] : k in t ? t[k] : (typeof k === 'string' ? () => { } : undefined); },
607
+ // The latest write per element and key; elements gone from the page are let go
608
+ set: (t, k, v) => {
609
+ for (let i = writes.length - 1; i >= 0; i--) { const e = deref(writes[i][0]); if (!e || !e.isConnected || (e === el && writes[i][1] === k)) writes.splice(i, 1); }
610
+ writes.push([typeof WeakRef === 'function' ? new WeakRef(el) : el, k, v]);
611
+ return true;
612
+ }
613
+ }));
614
+ return perEl.get(el);
615
+ };
616
+ A.magic('rich', (el, u) => { void state.v; return window.__manifestRichMagic ? window.__manifestRichMagic(el, u) : idleFor(el); });
603
617
  };
604
618
  document.addEventListener('alpine:init', install, { once: true });
605
619
  if (window.Alpine && window.Alpine.magic) install();
606
620
  }
607
621
  stubRichMagic();
622
+ // $edit likewise: Edit loads behind a gate (data-plugins or Manifest.loadPlugin('edit')), so until
623
+ // it registers, $edit is an idle stand-in; Edit bumps `v` and the stand-in delegates to it.
624
+ let editStubArmed = false;
625
+ function stubEditMagic() {
626
+ if (typeof window === 'undefined' || typeof document === 'undefined' || editStubArmed) return;
627
+ editStubArmed = true;
628
+ const install = () => {
629
+ const A = window.Alpine;
630
+ if (window.__manifestEditMagic || !A || !A.magic || !A.reactive || window.__manifestEditStub) return;
631
+ const state = window.__manifestEditStub = A.reactive({ v: 0 });
632
+ const none = () => false;
633
+ // Writes ($edit.onPublish = …) are kept and applied to the store at handover
634
+ const writes = window.__manifestEditStubWrites = {};
635
+ const idle = new Proxy({
636
+ active: false, canUndo: false, canRedo: false, version: 0, stale: [], conflicts: [], onPublish: null, target: null,
637
+ can: none, scope: () => null, block: () => null, patches: () => [], export: () => ({ log: [], cursor: 0 }),
638
+ publish: () => Promise.resolve({ results: [] }), undo: () => Promise.resolve(false), redo: () => Promise.resolve(false),
639
+ classes: new Proxy({}, { get: () => none })
640
+ }, { get: (t, k) => k in writes ? writes[k] : k in t ? t[k] : (typeof k === 'string' ? none : undefined), set: (t, k, v) => { writes[k] = v; return true; } });
641
+ A.magic('edit', (el, u) => { void state.v; return window.__manifestEditMagic ? window.__manifestEditMagic(el, u) : idle; });
642
+ };
643
+ document.addEventListener('alpine:init', install, { once: true });
644
+ if (window.Alpine && window.Alpine.magic) install();
645
+ }
646
+ stubEditMagic();
647
+ // Plugins loaded after Alpine started adopt what its first walk already passed
648
+ if (typeof document !== 'undefined') document.addEventListener('alpine:initialized', () => { window.__manifestAlpineStarted = true; }, { once: true });
608
649
  // Framework web components — <x-*> tags that are NOT project components.
609
650
  const FRAMEWORK_TAG_RE = /^x-(code|code-group)$/;
610
651
  function detectUsagePlugins(alreadyLoaded, root = typeof document === 'undefined' ? null : document) {