mnfst 0.5.52 → 0.5.54

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.
@@ -43,6 +43,18 @@ function getTooltipHoverDelay(element) {
43
43
  }
44
44
  }
45
45
 
46
+ /** Keep anchor-name on the trigger long after hide so position-anchor stays valid through any close transition/layout. */
47
+ const ANCHOR_RESTORE_DELAY_MS = 2000;
48
+
49
+ /**
50
+ * DOM parent for the tooltip popover. Must be the same popover subtree as the trigger when the trigger
51
+ * lives inside menu/dialog/etc.; otherwise CSS anchor positioning cannot resolve (tooltip in body + anchor
52
+ * inside top-layer popover → invalid position-anchor, jump to origin).
53
+ */
54
+ function getTooltipHostForTrigger(triggerEl) {
55
+ return triggerEl.closest('[popover]') || document.body;
56
+ }
57
+
46
58
  function initializeTooltipPlugin() {
47
59
 
48
60
  Alpine.directive('tooltip', (el, { modifiers, expression }, { effect, evaluateLater }) => {
@@ -92,9 +104,9 @@ function initializeTooltipPlugin() {
92
104
  originalTarget = el.getAttribute('x-dropdown');
93
105
  }
94
106
 
95
- // Create the tooltip element
107
+ // Create the tooltip element (hint popovers coexist with auto menus/dialogs)
96
108
  const tooltip = document.createElement('div');
97
- tooltip.setAttribute('popover', '');
109
+ tooltip.setAttribute('popover', 'hint');
98
110
  tooltip.setAttribute('id', tooltipId);
99
111
  tooltip.setAttribute('class', 'tooltip');
100
112
 
@@ -117,15 +129,55 @@ function initializeTooltipPlugin() {
117
129
  tooltip.classList.add(positionClass);
118
130
  }
119
131
 
120
- // Add the tooltip to the document
121
- document.body.appendChild(tooltip);
132
+ // Mount under the same popover as the trigger when applicable (see getTooltipHostForTrigger)
133
+ getTooltipHostForTrigger(el).appendChild(tooltip);
122
134
 
123
135
  // State variables for managing tooltip behavior
124
136
  let showTimeout;
137
+ let restoreAnchorTimeout = null;
138
+ let anchorRestoreGeneration = 0;
125
139
  let isMouseDown = false;
126
140
  let isDynamic = expression.includes('+') || expression.includes('`') || expression.includes('${') || expression.startsWith('$x.');
127
141
  let isUpdatingContent = false;
128
142
 
143
+ function restoreOriginalAnchor() {
144
+ if (el._originalAnchorName) {
145
+ el.style.setProperty('anchor-name', el._originalAnchorName);
146
+ } else {
147
+ el.style.removeProperty('anchor-name');
148
+ }
149
+ }
150
+
151
+ function cancelScheduledAnchorRestore() {
152
+ anchorRestoreGeneration += 1;
153
+ if (restoreAnchorTimeout !== null) {
154
+ clearTimeout(restoreAnchorTimeout);
155
+ restoreAnchorTimeout = null;
156
+ }
157
+ }
158
+
159
+ function scheduleAnchorRestoreAfterTooltipDismissal(restoreFn) {
160
+ cancelScheduledAnchorRestore();
161
+ const gen = anchorRestoreGeneration;
162
+ const run = () => {
163
+ if (gen !== anchorRestoreGeneration) return;
164
+ restoreAnchorTimeout = null;
165
+ restoreFn();
166
+ };
167
+ restoreAnchorTimeout = setTimeout(run, ANCHOR_RESTORE_DELAY_MS);
168
+ }
169
+
170
+ function scheduleRestoreAnchorAfterClose() {
171
+ scheduleAnchorRestoreAfterTooltipDismissal(() => restoreOriginalAnchor());
172
+ }
173
+
174
+ function ensureTooltipHostMatchesTrigger() {
175
+ const host = getTooltipHostForTrigger(el);
176
+ if (tooltip.parentNode !== host) {
177
+ host.appendChild(tooltip);
178
+ }
179
+ }
180
+
129
181
  // Function to update tooltip content - prevents double updates
130
182
  const updateTooltipContent = () => {
131
183
  // Prevent concurrent updates that cause flicker
@@ -148,6 +200,8 @@ function initializeTooltipPlugin() {
148
200
  }
149
201
 
150
202
  el.addEventListener('mouseenter', () => {
203
+ cancelScheduledAnchorRestore();
204
+ clearTimeout(showTimeout);
151
205
  if (!isMouseDown) {
152
206
  const hoverDelay = getTooltipHoverDelay(el);
153
207
  showTimeout = setTimeout(() => {
@@ -161,6 +215,8 @@ function initializeTooltipPlugin() {
161
215
  updateTooltipContent();
162
216
  }
163
217
 
218
+ ensureTooltipHostMatchesTrigger();
219
+
164
220
  // Only manage anchor-name if element has other popover functionality
165
221
  if (originalTarget) {
166
222
  // Store current anchor name (dropdown may have set it by now)
@@ -190,10 +246,7 @@ function initializeTooltipPlugin() {
190
246
  clearTimeout(showTimeout);
191
247
  if (tooltip.matches(':popover-open')) {
192
248
  tooltip.hidePopover();
193
- // Only restore anchor name if element has other popover functionality
194
- if (originalTarget) {
195
- restoreOriginalAnchor();
196
- }
249
+ scheduleRestoreAnchorAfterClose();
197
250
  }
198
251
  });
199
252
 
@@ -203,10 +256,7 @@ function initializeTooltipPlugin() {
203
256
  if (tooltip.matches(':popover-open')) {
204
257
  tooltip.hidePopover();
205
258
  }
206
- // Only restore anchor name if element has other popover functionality
207
- if (originalTarget) {
208
- restoreOriginalAnchor();
209
- }
259
+ scheduleRestoreAnchorAfterClose();
210
260
  });
211
261
 
212
262
  el.addEventListener('mouseup', () => {
@@ -222,43 +272,26 @@ function initializeTooltipPlugin() {
222
272
  tooltip.hidePopover();
223
273
  }
224
274
 
225
- // Don't restore anchor immediately - let other click handlers run first
226
- // This allows dropdown plugin to set its own anchor-name
227
- setTimeout(() => {
228
- // Only restore anchor if no popover opened from this click
275
+ // After computed transition time: restore anchor only if no popover opened from this click
276
+ scheduleAnchorRestoreAfterTooltipDismissal(() => {
229
277
  if (originalTarget) {
230
278
  const targetPopover = document.getElementById(originalTarget);
231
279
  const isPopoverOpen = targetPopover?.matches(':popover-open');
232
280
  if (!targetPopover || !isPopoverOpen) {
233
281
  restoreOriginalAnchor();
234
282
  }
235
- // If popover is open, keep current anchor (don't restore)
236
283
  } else {
237
284
  restoreOriginalAnchor();
238
285
  }
239
- }, 100); // Give other plugins time to set their anchors
286
+ });
240
287
  });
241
288
 
242
- // Helper function to restore original anchor
243
- function restoreOriginalAnchor() {
244
- if (el._originalAnchorName) {
245
- // Restore the original anchor name
246
- el.style.setProperty('anchor-name', el._originalAnchorName);
247
- } else {
248
- // Remove the tooltip anchor name so other plugins can set their own
249
- el.style.removeProperty('anchor-name');
250
- }
251
- }
252
-
253
289
  // Listen for other popovers opening and close tooltip if needed
254
290
  const handlePopoverOpen = (event) => {
255
291
  // If another popover opens and it's not our tooltip, close our tooltip
256
292
  if (event.target !== tooltip && tooltip.matches(':popover-open')) {
257
293
  tooltip.hidePopover();
258
- // Only restore anchor name if element has other popover functionality
259
- if (originalTarget) {
260
- restoreOriginalAnchor();
261
- }
294
+ scheduleRestoreAnchorAfterClose();
262
295
  }
263
296
  };
264
297
 
@@ -270,6 +303,7 @@ function initializeTooltipPlugin() {
270
303
 
271
304
  // Cleanup function for when element is removed
272
305
  const cleanup = () => {
306
+ cancelScheduledAnchorRestore();
273
307
  if (el._tooltipPopoverListener) {
274
308
  document.removeEventListener('toggle', el._tooltipPopoverListener);
275
309
  delete el._tooltipPopoverListener;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnfst",
3
- "version": "0.5.52",
3
+ "version": "0.5.54",
4
4
  "private": false,
5
5
  "workspaces": [
6
6
  "templates/starter",
@@ -17,15 +17,16 @@
17
17
  "clean": "rimraf src/scripts/manifest.js src/scripts/manifest.render.mjs src/styles/manifest.css src/styles/manifest.min.css src/styles/manifest.code.min.css lib",
18
18
  "build": "cd src && node scripts/build.mjs",
19
19
  "build:docs": "echo 'Docs is a static website - no build needed'",
20
- "start:src": "cd src && browser-sync start --config bs-config.js",
21
- "start:docs": "cd docs && browser-sync start --config bs-config.js",
22
- "start:starter": "cd templates/starter && browser-sync start --config bs-config.js --port 3001",
23
- "start:dist": "cd src && browser-sync start --config dist-bs-config.js --port 5003",
20
+ "start:src": "node packages/run/serve.mjs src",
21
+ "start:docs": "node packages/run/serve.mjs docs",
22
+ "start:starter": "node packages/run/serve.mjs templates/starter --port 3001",
23
+ "start:dist": "node packages/run/serve.mjs src/test-prerender --port 5003",
24
24
  "prerender": "node src/scripts/manifest.render.mjs --root src",
25
25
  "prerender:docs": "node src/scripts/manifest.render.mjs --root docs",
26
26
  "prerender:starter": "node src/scripts/manifest.render.mjs --root templates/starter",
27
27
  "render": "node src/scripts/manifest.render.mjs --root src",
28
28
  "publish:starter": "cd packages/create-starter && npm publish --auth-type=web",
29
+ "publish:run": "cd packages/run && npm publish --auth-type=web",
29
30
  "publish:render": "cd packages/render && npm publish --auth-type=web",
30
31
  "prepublishOnly": "npm run build",
31
32
  "test": "vitest run",
@@ -35,7 +36,6 @@
35
36
  "@rollup/plugin-commonjs": "^28.0.1",
36
37
  "@rollup/plugin-node-resolve": "^15.3.0",
37
38
  "@rollup/plugin-terser": "^0.4.4",
38
- "browser-sync": "^3.0.3",
39
39
  "cssnano": "^7.1.1",
40
40
  "glob": "^11.0.2",
41
41
  "puppeteer": "^24.15.0",