mnfst 0.5.150 → 0.5.152

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.
@@ -101,6 +101,14 @@ function initializeComboboxPlugin() {
101
101
  if (el.__mnfstCombobox) return;
102
102
  el.__mnfstCombobox = true;
103
103
 
104
+ // Sweep generated menus left orphaned by a prior render — their controlling
105
+ // editor is gone (a SPA x-markdown re-render) or never hydrated (duplicates
106
+ // an old prerender baked into <body>). Either way, no connected editor points
107
+ // at them, so they can only sit as stale, sometimes-open duplicates.
108
+ document.querySelectorAll('body > menu[id^="combobox-menu-"]').forEach(m => {
109
+ if (!document.querySelector('[aria-controls="' + m.id + '"]')) m.remove();
110
+ });
111
+
104
112
  // ----- Prerender hydration -----
105
113
  // A prerendered page bakes in the wrapper this plugin generated. On load we
106
114
  // adopt that wrapper and re-derive the selection from the baked chips, rather
@@ -6,7 +6,7 @@
6
6
  "manifest.code.js": "sha384-nP6DncLx/UuJtloyVKMCOXwIBAq32DshTb/Lc0vVRBWX7kSbxiBnY5aEyqqvK8Kg",
7
7
  "manifest.color.js": "sha384-6Rv3LxyTcZNjrhtayQfqRdCx0uSZ4BiEbgEI98I62eTvp8Aw7LBIoNJ0Je1oktwL",
8
8
  "manifest.colorpicker.js": "sha384-Wqz0ZIbeIi7KarqqqSLsQk+7E/fMaKhb32hrq5/eWzX1yjqMrpPZKH8y+jZ3mfg+",
9
- "manifest.combobox.js": "sha384-BXiVo0FyDgz23sXBWDNmaT6ad0PTgrNZFgZncwN2sJcXanDMF9PBSDyhPfk7ANYM",
9
+ "manifest.combobox.js": "sha384-oGZIgHsizehYUu3m7VrZkwOadrwm5MfAA4PkN0l5W3FU8LHcuSmWn7XLzmt97jfo",
10
10
  "manifest.components.js": "sha384-mzPFoM0vqL9dnTVLMN3OrmO+KCgSqGknM1fd7bM1xzYeCco5OaZi56IMR5RS5oad",
11
11
  "manifest.data.js": "sha384-bCYTYyAYNVkg5pSwGcoe07Dgf5B7JDN7GtOIQdS+BtrBStQwvjZtskiQ38Bncvrf",
12
12
  "manifest.datepicker.js": "sha384-NEb/H4vuR3CFtRcodHsm3jJjrcYW2JMpDlQKlgwTrzpMMTcDkFKYXzAYJD0gZ7Ov",
@@ -28,5 +28,5 @@
28
28
  "manifest.url.parameters.js": "sha384-FIufiClqDx1rJpU/QUc9z/D43qClQ6Qm8rBahipbJl9BDHUvhrOsUDegmTWW7Tuf",
29
29
  "manifest.utilities.js": "sha384-HWyVkjQoDRlWFKDBQw4RQOYODkBcU72NHW6l1p4bhQv1RtN0/XtnjwIb+lQK6+zv",
30
30
  "manifest.virtual.js": "sha384-J2+MpRskVH39R2DmHUib2bItuGsvSLEhPM+83iznrdfQFMZ63Ea6xwLeCsG04jOl",
31
- "manifest.js": "sha384-IsNGBIHGuUF5ABO58nkkZJQq1Ilt0wIsz+X2qR5hx4cGEhZez7GCmB8rAowJmrHS"
31
+ "manifest.js": "sha384-zPXrym9jwpYVdg4TJ1XPQVxQhz7uezdDubaO/MZDvLeiTufor+6lNJsTG75cYPXm"
32
32
  }
package/lib/manifest.js CHANGED
@@ -141,28 +141,44 @@
141
141
  }
142
142
 
143
143
  /*
144
- * Remove baked x-for/x-if clones the prerender kept for crawlers. Their
145
- * <template> is still live, so Alpine re-renders the list/conditional on
146
- * boot; dropping the baked copies first avoids a duplicate render.
147
- * data-hydrate islands keep their baked DOM.
144
+ * Reconcile baked x-for/x-if clones the prerender kept for crawlers. Their
145
+ * <template> is still live, so without intervention Alpine renders a second
146
+ * copy on boot (a duplicate). data-hydrate islands keep their baked DOM.
148
147
  *
149
- * Deliberately NOT part of hydratePrerenderedPage(): this wipe is
150
- * destructive, so it runs at the last safe moment `alpine:init`, which
151
- * Alpine dispatches after its script has arrived and executed but BEFORE
152
- * it walks the DOM and re-renders x-for/x-if from their live templates.
153
- * If Alpine never arrives (CDN failure, offline), the listener never
154
- * fires and the page keeps its complete baked content instead of losing
155
- * the clones with nothing to re-render them.
148
+ * - x-if clones are ADOPTED: we point the template's Alpine x-if state
149
+ * (`_x_currentIfEl`) at the baked element, so Alpine treats the
150
+ * condition as already-rendered and leaves the baked DOM in place. Heavy
151
+ * x-if content (e.g. an x-markdown article) would otherwise be torn down
152
+ * and re-rendered flashing raw text and re-fetching $x data — even
153
+ * though the prerendered output is already correct. A genuine
154
+ * route/condition change later still re-renders normally.
155
+ * - x-for clones are REMOVED: lists are light, often interactive, and
156
+ * Alpine re-renders them from the live template, so the baked copies are
157
+ * dropped to avoid a duplicate.
158
+ *
159
+ * Deliberately NOT part of hydratePrerenderedPage(): it runs at the last
160
+ * safe moment — `alpine:init`, which Alpine dispatches after its script has
161
+ * executed but BEFORE it walks the DOM. If Alpine never arrives (CDN
162
+ * failure, offline), the listener never fires and the page keeps its
163
+ * complete baked content.
156
164
  */
157
- function removePrerenderClones() {
165
+ function reconcilePrerenderClones() {
158
166
  if (typeof document === 'undefined' || !document.querySelectorAll) return;
159
167
  document.querySelectorAll('[data-mnfst-prerender-clone]').forEach((el) => {
160
168
  if (el.closest && el.closest('[data-hydrate]')) return;
161
- el.remove();
169
+ el.removeAttribute('data-mnfst-prerender-clone');
170
+ const tpl = el.previousElementSibling;
171
+ if (tpl && tpl.tagName === 'TEMPLATE' && tpl.hasAttribute('x-if')) {
172
+ // Adopt: Alpine's x-if `show()` returns early when _x_currentIfEl
173
+ // is set, so the baked element stays and is not re-rendered.
174
+ tpl._x_currentIfEl = el;
175
+ } else {
176
+ el.remove();
177
+ }
162
178
  });
163
179
  }
164
180
  if (typeof document !== 'undefined') {
165
- document.addEventListener('alpine:init', removePrerenderClones, { once: true });
181
+ document.addEventListener('alpine:init', reconcilePrerenderClones, { once: true });
166
182
  }
167
183
 
168
184
  // Run hydration BEFORE Alpine's deferred script executes.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnfst",
3
- "version": "0.5.150",
3
+ "version": "0.5.152",
4
4
  "private": false,
5
5
  "workspaces": [
6
6
  "templates/starter",