mnfst 0.5.259 → 0.5.261
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.appwrite.data.js +1 -1
- package/lib/manifest.charts.js +1 -1
- package/lib/manifest.data.js +1 -1
- package/lib/manifest.datepicker.js +1 -1
- package/lib/manifest.dropdowns.js +207 -147
- package/lib/manifest.integrity.json +7 -7
- package/lib/manifest.status.js +1 -1
- package/lib/manifest.sw.js +1 -1
- package/package.json +2 -1
package/lib/manifest.charts.js
CHANGED
|
@@ -350,7 +350,7 @@
|
|
|
350
350
|
// whole destroyed subtree for the life of the page (and keeps redrawing it).
|
|
351
351
|
self._effect = Alpine.effect(() => {
|
|
352
352
|
// Subscribe to the data store heartbeat so $x loads/locale reloads re-run.
|
|
353
|
-
try {
|
|
353
|
+
try { const ds = Alpine.store('data'); if (ds) void ds._dataVersion; } catch (_) { }
|
|
354
354
|
// Every effect fire re-evaluates the expression (it may read live reactive
|
|
355
355
|
// state — realtime rows, presence, staged $x hydration — that changes far
|
|
356
356
|
// more often than the resulting config). applyConfig() short-circuits a
|
package/lib/manifest.data.js
CHANGED
|
@@ -1000,7 +1000,7 @@
|
|
|
1000
1000
|
Alpine.effect(() => {
|
|
1001
1001
|
let locale = '', dir = '';
|
|
1002
1002
|
try { const ls = Alpine.store('locale'); locale = ls?.current || document.documentElement.lang || ''; dir = ls?.direction || ''; } catch (_) { }
|
|
1003
|
-
try {
|
|
1003
|
+
try { const ds = Alpine.store('data'); if (ds) void ds._dataVersion; } catch (_) { }
|
|
1004
1004
|
const ui = window.ManifestUI.resolve('date', UI_FALLBACK);
|
|
1005
1005
|
const key = locale + '|' + dir + '|' + JSON.stringify(ui);
|
|
1006
1006
|
if (state._uiKey === key) return;
|
|
@@ -38,6 +38,114 @@ function ensureContextDismiss(menu, owner) {
|
|
|
38
38
|
bindContextDismiss(menu, ctxAbort.signal);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
// A removed trigger stops being the menu's context host and dismiss owner. A menu it
|
|
42
|
+
// opened closes with it; one opened by another trigger keeps its dismiss listeners.
|
|
43
|
+
function releaseContextTrigger(menu, el) {
|
|
44
|
+
if (menu._triggerHost === el) {
|
|
45
|
+
menu._triggerHost = null;
|
|
46
|
+
menu._triggerEl = null;
|
|
47
|
+
hideIfOpen(menu);
|
|
48
|
+
}
|
|
49
|
+
if (menu.__mnfstCtxOwner === el) {
|
|
50
|
+
menu.__mnfstContextAbort?.abort();
|
|
51
|
+
menu.__mnfstContextDismissBound = false;
|
|
52
|
+
menu.__mnfstContextAbort = null;
|
|
53
|
+
menu.__mnfstCtxOwner = null;
|
|
54
|
+
const host = menu._triggerHost;
|
|
55
|
+
if (host && host.isConnected && menu.matches(':popover-open')) ensureContextDismiss(menu, host);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function hideIfOpen(menu) {
|
|
60
|
+
try { if (menu.matches(':popover-open')) menu.hidePopover(); } catch (_) { }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Menus the plugin cloned into <body> (template, component) belong to the triggers
|
|
64
|
+
// using them, and leave with the last one. Removal waits a tick: a menu item's own
|
|
65
|
+
// click may be what removed the trigger, and its dispatch must finish in the DOM.
|
|
66
|
+
function retainManagedMenu(menu, el) {
|
|
67
|
+
(menu.__mnfstManagedBy ||= new Set()).add(el);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function releaseManagedMenu(menu, el) {
|
|
71
|
+
const users = menu.__mnfstManagedBy;
|
|
72
|
+
if (!users || !users.delete(el) || users.size) return;
|
|
73
|
+
hideIfOpen(menu);
|
|
74
|
+
setTimeout(() => {
|
|
75
|
+
if (users.size) return;
|
|
76
|
+
menu.__mnfstManagedBy = null;
|
|
77
|
+
window.Alpine?.destroyTree?.(menu);
|
|
78
|
+
menu.remove();
|
|
79
|
+
}, 0);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Per-menu handlers (bound once, outliving any one trigger). Module scope for the same
|
|
83
|
+
// reason as bindContextDismiss: they must hold the menu and nothing else.
|
|
84
|
+
function bindMenuRoles(menu) {
|
|
85
|
+
const roleItems = () => menu.querySelectorAll('li').forEach((li) => {
|
|
86
|
+
if (!li.hasAttribute('role')) li.setAttribute('role', 'menuitem');
|
|
87
|
+
});
|
|
88
|
+
roleItems();
|
|
89
|
+
if (menu.__mnfstRoleOnRender) return;
|
|
90
|
+
menu.__mnfstRoleOnRender = true;
|
|
91
|
+
menu.addEventListener('manifest:defer-render', roleItems);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function bindMenuAriaToggle(menu) {
|
|
95
|
+
if (menu.__mnfstAriaToggleBound) return;
|
|
96
|
+
menu.__mnfstAriaToggleBound = true;
|
|
97
|
+
menu.addEventListener('toggle', (e) => {
|
|
98
|
+
const expanded = e.newState === 'open' ? 'true' : 'false';
|
|
99
|
+
document.querySelectorAll(`[aria-controls="${menu.id}"]`).forEach(t => t.setAttribute('aria-expanded', expanded));
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const MENU_ROW = 'li, a, button, label, [role=menuitem], [role=option]';
|
|
104
|
+
const MENU_CONTROL = 'input, select, textarea, [contenteditable]:not([contenteditable=false]), [role=switch], .combobox, [x-dropdown], [popovertarget], [keep-open]';
|
|
105
|
+
|
|
106
|
+
function bindMenuClose(menu) {
|
|
107
|
+
if (menu.__mnfstCloseBound) return;
|
|
108
|
+
menu.__mnfstCloseBound = true;
|
|
109
|
+
|
|
110
|
+
// Close the item's menu plus any menus it was nested in. The owned
|
|
111
|
+
// popover isn't always a <menu> (e.g. .dropdown-menu on a div), so
|
|
112
|
+
// fall back to it rather than relying on the ancestor lookup.
|
|
113
|
+
const closeFrom = (node) => {
|
|
114
|
+
let m = node.closest('menu[popover]') || menu;
|
|
115
|
+
while (m) {
|
|
116
|
+
if (m.matches(':popover-open')) m.hidePopover();
|
|
117
|
+
m = m.parentElement?.closest('menu[popover]');
|
|
118
|
+
}
|
|
119
|
+
if (lastInputModality === 'keyboard') {
|
|
120
|
+
const trigger = menu.__mnfstTrigger || menu._triggerHost || document.querySelector(`[aria-controls="${menu.id}"]`);
|
|
121
|
+
trigger?.focus?.();
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
menu.addEventListener('click', (e) => {
|
|
126
|
+
// Explicit opt-in wins over every exclusion below
|
|
127
|
+
const opted = e.target.closest('[close]');
|
|
128
|
+
if (opted && menu.contains(opted)) return closeFrom(opted);
|
|
129
|
+
if (!menu.__mnfstAutoClose) return;
|
|
130
|
+
|
|
131
|
+
// Outermost row between the click target and the menu
|
|
132
|
+
let row = null;
|
|
133
|
+
for (let n = e.target; n && n !== menu; n = n.parentElement) {
|
|
134
|
+
if (n.matches?.(MENU_ROW)) row = n;
|
|
135
|
+
}
|
|
136
|
+
if (!row) return;
|
|
137
|
+
|
|
138
|
+
// A control that *is* the row is a menu item; one nested
|
|
139
|
+
// inside it is embedded UI, so leave the menu open
|
|
140
|
+
const hit = e.target.closest(MENU_CONTROL);
|
|
141
|
+
if (hit && hit !== row && row.contains(hit)) return;
|
|
142
|
+
if (row.querySelector(MENU_CONTROL)) return;
|
|
143
|
+
if (row.matches('[role=switch], [x-dropdown], [popovertarget], [keep-open]')) return;
|
|
144
|
+
|
|
145
|
+
closeFrom(row);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
41
149
|
function initializeDropdownPlugin() {
|
|
42
150
|
// Ensure an Alpine context exists. Keep the scope empty — seeding properties
|
|
43
151
|
// collides with author state and the tabs plugin's `tab` property.
|
|
@@ -79,6 +187,11 @@ function initializeDropdownPlugin() {
|
|
|
79
187
|
registerDirective('dropdown', (el, { modifiers, expression }, { effect, evaluateLater, cleanup }) => {
|
|
80
188
|
let menu;
|
|
81
189
|
|
|
190
|
+
// Shared hover state for all dropdown types
|
|
191
|
+
let hoverTimeout;
|
|
192
|
+
let autoCloseTimeout;
|
|
193
|
+
let startAutoCloseTimer;
|
|
194
|
+
|
|
82
195
|
// Listeners this trigger adds to things that outlive it (its menu, document)
|
|
83
196
|
// are bound with this run's signal and aborted when Alpine tears the trigger
|
|
84
197
|
// down, or when the effect re-runs setup. Without it every re-rendered trigger
|
|
@@ -88,23 +201,17 @@ function initializeDropdownPlugin() {
|
|
|
88
201
|
cleanup(() => {
|
|
89
202
|
if (runAbort) runAbort.abort();
|
|
90
203
|
runAbort = null;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (menu && menu.__mnfstTrigger === el) menu.__mnfstTrigger = null;
|
|
98
|
-
if (menu && menu._triggerHost === el) { menu._triggerHost = null; menu._triggerEl = null; }
|
|
204
|
+
clearTimeout(hoverTimeout);
|
|
205
|
+
clearTimeout(autoCloseTimeout);
|
|
206
|
+
if (!menu) return;
|
|
207
|
+
releaseContextTrigger(menu, el);
|
|
208
|
+
if (menu.__mnfstTrigger === el) menu.__mnfstTrigger = null;
|
|
209
|
+
releaseManagedMenu(menu, el);
|
|
99
210
|
});
|
|
100
211
|
|
|
101
|
-
// Shared hover state for all dropdown types
|
|
102
|
-
let hoverTimeout;
|
|
103
|
-
let autoCloseTimeout;
|
|
104
|
-
let startAutoCloseTimer;
|
|
105
|
-
|
|
106
212
|
effect(() => {
|
|
107
213
|
if (runAbort) runAbort.abort();
|
|
214
|
+
if (menu) releaseManagedMenu(menu, el);
|
|
108
215
|
const runCtl = new AbortController();
|
|
109
216
|
runAbort = runCtl;
|
|
110
217
|
const signal = runCtl.signal;
|
|
@@ -146,6 +253,7 @@ function initializeDropdownPlugin() {
|
|
|
146
253
|
const uniqueDropdownId = `dropdown-${anchorCode}`;
|
|
147
254
|
menu.setAttribute('id', uniqueDropdownId);
|
|
148
255
|
document.body.appendChild(menu);
|
|
256
|
+
retainManagedMenu(menu, el);
|
|
149
257
|
// Appending to <body> loses the fact that the trigger sits inside a
|
|
150
258
|
// dialog, which is how manifest.dialog.css tells a dialog's own menus
|
|
151
259
|
// from the page's. An authored menu keeps its place and needs no mark.
|
|
@@ -171,6 +279,16 @@ function initializeDropdownPlugin() {
|
|
|
171
279
|
const waitForComponent = async () => {
|
|
172
280
|
const loader = window.ManifestComponentsLoader;
|
|
173
281
|
const content = await loader.loadComponent(componentName);
|
|
282
|
+
if (signal.aborted) return;
|
|
283
|
+
// Another trigger of this menu may have cloned it during the await
|
|
284
|
+
const loaded = document.getElementById(dropdownId);
|
|
285
|
+
if (loaded) {
|
|
286
|
+
menu = loaded;
|
|
287
|
+
if (menu.__mnfstManagedBy) retainManagedMenu(menu, el);
|
|
288
|
+
if (!modifiers.includes('context')) el.setAttribute('popovertarget', dropdownId);
|
|
289
|
+
setupDropdown();
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
174
292
|
if (content) {
|
|
175
293
|
// Create a temporary container to parse the component
|
|
176
294
|
const tempDiv = document.createElement('div');
|
|
@@ -182,6 +300,7 @@ function initializeDropdownPlugin() {
|
|
|
182
300
|
menu = menuElement.cloneNode(true);
|
|
183
301
|
menu.setAttribute('id', dropdownId);
|
|
184
302
|
document.body.appendChild(menu);
|
|
303
|
+
retainManagedMenu(menu, el);
|
|
185
304
|
if (el.closest('[popover]')) menu.setAttribute('data-popover-nested', '');
|
|
186
305
|
if (!modifiers.includes('context')) el.setAttribute('popovertarget', dropdownId);
|
|
187
306
|
|
|
@@ -216,6 +335,7 @@ function initializeDropdownPlugin() {
|
|
|
216
335
|
console.warn(`[Manifest] Dropdown menu with id "${dropdownId}" not found`);
|
|
217
336
|
return;
|
|
218
337
|
}
|
|
338
|
+
if (menu.__mnfstManagedBy) retainManagedMenu(menu, el);
|
|
219
339
|
if (!modifiers.includes('context')) el.setAttribute('popovertarget', dropdownId);
|
|
220
340
|
}
|
|
221
341
|
|
|
@@ -241,10 +361,9 @@ function initializeDropdownPlugin() {
|
|
|
241
361
|
const a = el.style.getPropertyValue('--trigger-anchor');
|
|
242
362
|
if (a) menu.style.setProperty('position-anchor', a);
|
|
243
363
|
};
|
|
244
|
-
if (!modifiers.includes('context')
|
|
245
|
-
el.
|
|
246
|
-
el.addEventListener('
|
|
247
|
-
el.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') pointMenuHere(); });
|
|
364
|
+
if (!modifiers.includes('context')) {
|
|
365
|
+
el.addEventListener('pointerdown', pointMenuHere, { signal });
|
|
366
|
+
el.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') pointMenuHere(); }, { signal });
|
|
248
367
|
}
|
|
249
368
|
|
|
250
369
|
// A11y wiring (WAI-ARIA Menu Button): trigger gets aria-haspopup/
|
|
@@ -258,22 +377,9 @@ function initializeDropdownPlugin() {
|
|
|
258
377
|
if (!menu.hasAttribute('role')) menu.setAttribute('role', 'menu');
|
|
259
378
|
// Deferred menus get their items later — role them off the gesture,
|
|
260
379
|
// never in the toggle task (243 role writes on a visible list ≈ 70ms)
|
|
261
|
-
|
|
262
|
-
if (!li.hasAttribute('role')) li.setAttribute('role', 'menuitem');
|
|
263
|
-
});
|
|
264
|
-
roleItems();
|
|
265
|
-
if (!menu.__mnfstRoleOnRender) {
|
|
266
|
-
menu.__mnfstRoleOnRender = true;
|
|
267
|
-
menu.addEventListener('manifest:defer-render', roleItems);
|
|
268
|
-
}
|
|
380
|
+
bindMenuRoles(menu);
|
|
269
381
|
// Keep aria-expanded in sync across every trigger of this menu.
|
|
270
|
-
|
|
271
|
-
menu.__mnfstAriaToggleBound = true;
|
|
272
|
-
menu.addEventListener('toggle', (e) => {
|
|
273
|
-
const expanded = e.newState === 'open' ? 'true' : 'false';
|
|
274
|
-
document.querySelectorAll(`[aria-controls="${menu.id}"]`).forEach(t => t.setAttribute('aria-expanded', expanded));
|
|
275
|
-
});
|
|
276
|
-
}
|
|
382
|
+
bindMenuAriaToggle(menu);
|
|
277
383
|
}
|
|
278
384
|
|
|
279
385
|
// Set up hover functionality after menu is ready
|
|
@@ -312,8 +418,8 @@ function initializeDropdownPlugin() {
|
|
|
312
418
|
}, 300); // Small delay to prevent accidental closes
|
|
313
419
|
};
|
|
314
420
|
|
|
315
|
-
el.addEventListener('mouseenter', handleShowPopover);
|
|
316
|
-
el.addEventListener('mouseleave', startAutoCloseTimer);
|
|
421
|
+
el.addEventListener('mouseenter', handleShowPopover, { signal });
|
|
422
|
+
el.addEventListener('mouseleave', startAutoCloseTimer, { signal });
|
|
317
423
|
}
|
|
318
424
|
|
|
319
425
|
// Set up context menu (right-click) functionality
|
|
@@ -359,84 +465,78 @@ function initializeDropdownPlugin() {
|
|
|
359
465
|
});
|
|
360
466
|
};
|
|
361
467
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
el.addEventListener('contextmenu', (e) => {
|
|
366
|
-
e.preventDefault();
|
|
367
|
-
e.stopPropagation();
|
|
468
|
+
el.addEventListener('contextmenu', (e) => {
|
|
469
|
+
e.preventDefault();
|
|
470
|
+
e.stopPropagation();
|
|
368
471
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
472
|
+
// Android fires a real contextmenu after a touch long-press too —
|
|
473
|
+
// our pointer path already opened the menu, so ignore the dupe.
|
|
474
|
+
if (el.__mnfstLongPressOpenedAt && Date.now() - el.__mnfstLongPressOpenedAt < LONG_PRESS_DUPE_WINDOW_MS) return;
|
|
372
475
|
|
|
373
|
-
|
|
374
|
-
|
|
476
|
+
openContextMenuAt(e.target, e.clientX, e.clientY);
|
|
477
|
+
}, { signal });
|
|
375
478
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
479
|
+
// iOS Safari never fires `contextmenu` for a touch long-press (Android
|
|
480
|
+
// does). Drive the same open from pointer events instead — touch/pen
|
|
481
|
+
// only, mouse keeps native contextmenu semantics.
|
|
482
|
+
const LONG_PRESS_MS = 500;
|
|
483
|
+
const MOVE_SLOP_PX = 10;
|
|
484
|
+
let pressTimer = null;
|
|
485
|
+
let startX = 0, startY = 0;
|
|
486
|
+
let pressTarget = null;
|
|
487
|
+
|
|
488
|
+
const suppressCallout = () => {
|
|
489
|
+
el.style.setProperty('-webkit-touch-callout', 'none');
|
|
490
|
+
el.style.setProperty('-webkit-user-select', 'none');
|
|
491
|
+
el.style.setProperty('user-select', 'none');
|
|
492
|
+
};
|
|
493
|
+
const restoreCallout = () => {
|
|
494
|
+
el.style.removeProperty('-webkit-touch-callout');
|
|
495
|
+
el.style.removeProperty('-webkit-user-select');
|
|
496
|
+
el.style.removeProperty('user-select');
|
|
497
|
+
};
|
|
498
|
+
const cancelPress = () => {
|
|
499
|
+
if (pressTimer) clearTimeout(pressTimer);
|
|
500
|
+
pressTimer = null;
|
|
501
|
+
restoreCallout();
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
el.addEventListener('pointerdown', (e) => {
|
|
505
|
+
if (e.pointerType !== 'touch' && e.pointerType !== 'pen') return;
|
|
506
|
+
cancelPress();
|
|
507
|
+
startX = e.clientX;
|
|
508
|
+
startY = e.clientY;
|
|
509
|
+
pressTarget = e.target;
|
|
510
|
+
suppressCallout();
|
|
511
|
+
pressTimer = setTimeout(() => {
|
|
397
512
|
pressTimer = null;
|
|
513
|
+
if (signal.aborted) return;
|
|
398
514
|
restoreCallout();
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
el.addEventListener('pointerup', cancelPress);
|
|
422
|
-
el.addEventListener('pointercancel', cancelPress);
|
|
423
|
-
el.addEventListener('scroll', cancelPress, true);
|
|
424
|
-
|
|
425
|
-
// Suppress the synthetic click that follows the touch sequence which
|
|
426
|
-
// opened the menu, so it doesn't also activate/navigate the trigger.
|
|
427
|
-
el.addEventListener('click', (e) => {
|
|
428
|
-
if (el.__mnfstLongPressOpenedAt && Date.now() - el.__mnfstLongPressOpenedAt < LONG_PRESS_DUPE_WINDOW_MS) {
|
|
429
|
-
e.preventDefault();
|
|
430
|
-
e.stopPropagation();
|
|
431
|
-
}
|
|
432
|
-
}, true);
|
|
433
|
-
}
|
|
515
|
+
el.__mnfstLongPressOpenedAt = Date.now();
|
|
516
|
+
// No contextmenu event on iOS: announce the press so composing plugins (x-edit) can track the target.
|
|
517
|
+
pressTarget.dispatchEvent(new CustomEvent('dropdown:context', { bubbles: true, detail: { x: startX, y: startY } }));
|
|
518
|
+
openContextMenuAt(pressTarget, startX, startY);
|
|
519
|
+
}, LONG_PRESS_MS);
|
|
520
|
+
}, { signal });
|
|
521
|
+
el.addEventListener('pointermove', (e) => {
|
|
522
|
+
if (!pressTimer) return;
|
|
523
|
+
if (Math.abs(e.clientX - startX) > MOVE_SLOP_PX || Math.abs(e.clientY - startY) > MOVE_SLOP_PX) cancelPress();
|
|
524
|
+
}, { signal });
|
|
525
|
+
el.addEventListener('pointerup', cancelPress, { signal });
|
|
526
|
+
el.addEventListener('pointercancel', cancelPress, { signal });
|
|
527
|
+
el.addEventListener('scroll', cancelPress, { capture: true, signal });
|
|
528
|
+
|
|
529
|
+
// Suppress the synthetic click that follows the touch sequence which
|
|
530
|
+
// opened the menu, so it doesn't also activate/navigate the trigger.
|
|
531
|
+
el.addEventListener('click', (e) => {
|
|
532
|
+
if (el.__mnfstLongPressOpenedAt && Date.now() - el.__mnfstLongPressOpenedAt < LONG_PRESS_DUPE_WINDOW_MS) {
|
|
533
|
+
e.preventDefault();
|
|
534
|
+
e.stopPropagation();
|
|
535
|
+
}
|
|
536
|
+
}, { capture: true, signal });
|
|
434
537
|
|
|
435
|
-
//
|
|
436
|
-
//
|
|
437
|
-
// Bound once per menu, owned by one trigger and released in that
|
|
438
|
-
// trigger's cleanup (see top). Any other trigger of the same menu
|
|
439
|
-
// re-binds on its next open, so a shared menu never loses dismiss.
|
|
538
|
+
// Owned by one trigger, released in its cleanup. Any other trigger of
|
|
539
|
+
// the same menu re-binds on its next open.
|
|
440
540
|
ensureContextDismiss(menu, el);
|
|
441
541
|
}
|
|
442
542
|
|
|
@@ -445,47 +545,7 @@ function initializeDropdownPlugin() {
|
|
|
445
545
|
// Context menus auto-close by nature, so .close is implied.
|
|
446
546
|
if (modifiers.includes('close') || modifiers.includes('context')) menu.__mnfstAutoClose = true;
|
|
447
547
|
|
|
448
|
-
|
|
449
|
-
menu.__mnfstCloseBound = true;
|
|
450
|
-
|
|
451
|
-
const ROW = 'li, a, button, label, [role=menuitem], [role=option]';
|
|
452
|
-
const CONTROL = 'input, select, textarea, [contenteditable]:not([contenteditable=false]), [role=switch], .combobox, [x-dropdown], [popovertarget], [keep-open]';
|
|
453
|
-
|
|
454
|
-
// Close the item's menu plus any menus it was nested in. The owned
|
|
455
|
-
// popover isn't always a <menu> (e.g. .dropdown-menu on a div), so
|
|
456
|
-
// fall back to it rather than relying on the ancestor lookup.
|
|
457
|
-
const closeFrom = (node) => {
|
|
458
|
-
let m = node.closest('menu[popover]') || menu;
|
|
459
|
-
while (m) {
|
|
460
|
-
if (m.matches(':popover-open')) m.hidePopover();
|
|
461
|
-
m = m.parentElement?.closest('menu[popover]');
|
|
462
|
-
}
|
|
463
|
-
if (lastInputModality === 'keyboard') (menu.__mnfstTrigger || el).focus?.();
|
|
464
|
-
};
|
|
465
|
-
|
|
466
|
-
menu.addEventListener('click', (e) => {
|
|
467
|
-
// Explicit opt-in wins over every exclusion below
|
|
468
|
-
const opted = e.target.closest('[close]');
|
|
469
|
-
if (opted && menu.contains(opted)) return closeFrom(opted);
|
|
470
|
-
if (!menu.__mnfstAutoClose) return;
|
|
471
|
-
|
|
472
|
-
// Outermost row between the click target and the menu
|
|
473
|
-
let row = null;
|
|
474
|
-
for (let n = e.target; n && n !== menu; n = n.parentElement) {
|
|
475
|
-
if (n.matches?.(ROW)) row = n;
|
|
476
|
-
}
|
|
477
|
-
if (!row) return;
|
|
478
|
-
|
|
479
|
-
// A control that *is* the row is a menu item; one nested
|
|
480
|
-
// inside it is embedded UI, so leave the menu open
|
|
481
|
-
const hit = e.target.closest(CONTROL);
|
|
482
|
-
if (hit && hit !== row && row.contains(hit)) return;
|
|
483
|
-
if (row.querySelector(CONTROL)) return;
|
|
484
|
-
if (row.matches('[role=switch], [x-dropdown], [popovertarget], [keep-open]')) return;
|
|
485
|
-
|
|
486
|
-
closeFrom(row);
|
|
487
|
-
});
|
|
488
|
-
}
|
|
548
|
+
bindMenuClose(menu);
|
|
489
549
|
|
|
490
550
|
// Add keyboard navigation handling
|
|
491
551
|
menu.addEventListener('keydown', (e) => {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest.appwrite.auth.js": "sha384-9WNhIeSEzxxJm+yP7cV41kV2xyzyXgFCNpThqiREcNbb6tnIW6O7VRZPL2jwy9Nk",
|
|
3
|
-
"manifest.appwrite.data.js": "sha384-
|
|
3
|
+
"manifest.appwrite.data.js": "sha384-o/I02A1W20/hRbSQ4ihAlTVdMj03/v05BPIo24L93vCh1oDmJCy/mHtWXyO9P7z1",
|
|
4
4
|
"manifest.appwrite.presences.js": "sha384-/CagPsgWhO1sWEevCNGJmZ/ezRK9YCjLjvF7hxNSM4gVlJmEVtfSLfF7wnsRq441",
|
|
5
5
|
"manifest.bindings.js": "sha384-lPtMf8Ykaic9gNVZKq8Scia1881ZeMAyxer3WCIkUNwwrgzGBWto1OXADUwjvORF",
|
|
6
|
-
"manifest.charts.js": "sha384-
|
|
6
|
+
"manifest.charts.js": "sha384-lzcoovnPCKhcwigrwYvc5rPAZuZNHwWmNNuu7hxPsfJuKAu8ejla5F7agHZ2Xft9",
|
|
7
7
|
"manifest.chat.js": "sha384-88DfMfJLoi2oxclVD2iIMDkIGAExMXq5k47mekUredlicLaP3oZY5bwtwqs+TqDF",
|
|
8
8
|
"manifest.code.js": "sha384-WGPlZE5Sp9GygUzJSmCdpLyYQTgvmEKc9BQS9Hmq1VTQwkqYJzPSWibHvPekcres",
|
|
9
9
|
"manifest.color.js": "sha384-6Rv3LxyTcZNjrhtayQfqRdCx0uSZ4BiEbgEI98I62eTvp8Aw7LBIoNJ0Je1oktwL",
|
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
"manifest.combobox.js": "sha384-MILTkrgsTzsYUjGo4ZuADqV85Xs4GmfB4xexfe9vbJMIoQTZ42W4zs+DTaVSPEsx",
|
|
12
12
|
"manifest.components.js": "sha384-BUFA8h+G5Plr82sV7bKzO42pPoiRZwRQcKj1lt2ROFiKjjlkayU/vmkAFqEsdBA+",
|
|
13
13
|
"manifest.computed.js": "sha384-GYML/e81gQfN+iDbqhPUgny8bINCyL+SrbTofoGk4hlCnLLjv51mCv42JMqq+ku5",
|
|
14
|
-
"manifest.data.js": "sha384-
|
|
15
|
-
"manifest.datepicker.js": "sha384-
|
|
14
|
+
"manifest.data.js": "sha384-oOf9cFcoI3VFsfqg4eZyDVq7gdeF5DoIB92shYZI0N4ajWqhFx7B6Byy9H1g5HNE",
|
|
15
|
+
"manifest.datepicker.js": "sha384-JeDdzdwXdXmnvgv1N1N+7WIQlezew9YJcvui3uVEvqAOX0WEaJO7ofLRRuc4hFBS",
|
|
16
16
|
"manifest.defer.js": "sha384-GK9qJkYtchtyzSQ+3zmDQUJE3+au4qthT2M8JnFjXxgfwCH5IVKc2r3tackgMryU",
|
|
17
17
|
"manifest.device.js": "sha384-YeQaS56L8IILqrQNBSeAOLwIh5DuAOrE60cYLK1+46qJtUrXlAvqGe2j5mbV4Vq7",
|
|
18
|
-
"manifest.dropdowns.js": "sha384-
|
|
18
|
+
"manifest.dropdowns.js": "sha384-H1IH7pU4zqjKNZtypXzwvUfnKkvNH96hM5za5xbb6SQlAucqkjKtlAepJW39nn/u",
|
|
19
19
|
"manifest.edit.js": "sha384-MOgQNdNLeHijhR0u2W6ntoOulKFglsATCFDqkinesOU15tHoILk/JNzHKkGi5dUG",
|
|
20
20
|
"manifest.export.js": "sha384-Rx6XvqBZc4apzP/FwcDr6yIln4/zNa08Q4ZqBZcEqUz1aNZQfl4h0+Ae7mqCQyuG",
|
|
21
21
|
"manifest.icons.js": "sha384-kKOINpX70YzNO3FvkV6umrmO3f0KIu0DNbwB+ej55a7y/lhU7v/myQtXL0bwCTbt",
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
"manifest.rich.text.js": "sha384-ZBV9jXmu2nmprEHicddRE/m7nR4RPZtVc969Jte0+EJYBcVph7itkjcgN/61PyeC",
|
|
27
27
|
"manifest.router.js": "sha384-9LMIkwrgnxmmFDv70TND2MRJF1z6Nl4yqG7fH5DbJ8oUIhdu/u4yUPSime1/EYhP",
|
|
28
28
|
"manifest.slides.js": "sha384-3uRTkyK9XPLmnxI2+igZlpi4EyPlU/7IHj5j3BZJJ2KN455vXyk99fiXV3feO/XY",
|
|
29
|
-
"manifest.status.js": "sha384-
|
|
29
|
+
"manifest.status.js": "sha384-Zf07q/5tRS2F0o128sINYrce8xP035FTV0Ot7V7/hh+cwFhVZg+EWkHZN5b42aBn",
|
|
30
30
|
"manifest.svg.js": "sha384-qr/8H9+2cIhSXNbRWRtIdrDlTOxKlNJqwEefHvCYoiBNW/kKhPTFa109Orv/EBnb",
|
|
31
|
-
"manifest.sw.js": "sha384-
|
|
31
|
+
"manifest.sw.js": "sha384-Bm4NWZDrWtm0kekg7m9SnOve6wsB0nOjHzQre3TsvDaIlWK5vMlJAJ1qyV9cQGbi",
|
|
32
32
|
"manifest.tabs.js": "sha384-otU09H7jnawVKQdLAYpMCd3jx00tgyPVr42D2hLIBzN5dG4se505YesavdnMqEi1",
|
|
33
33
|
"manifest.tailwind.js": "sha384-r/Jvqb+Piz21jUts10BbVAJoKkShOdJRVFpywyi4HPIi/OfOJq5CtPyWAPbdkMgt",
|
|
34
34
|
"manifest.toasts.js": "sha384-3zkWxzw693pxg3QDFsoYCA3CqYaLKmBaMArULXODD66Utolpewl0OAeTB92aWUVv",
|
package/lib/manifest.status.js
CHANGED
|
@@ -543,7 +543,7 @@
|
|
|
543
543
|
// Human label for a state, localized through `_ui`, else title-cased.
|
|
544
544
|
function formatStateLabel(state) {
|
|
545
545
|
// Dep on the data store version so labels re-resolve when a `_ui` source loads late.
|
|
546
|
-
try {
|
|
546
|
+
try { const ds = window.Alpine && Alpine.store('data'); if (ds) void ds._dataVersion; } catch (_) { }
|
|
547
547
|
const ui = window.ManifestUI ? window.ManifestUI.resolve('status', UI_FALLBACK) : UI_FALLBACK;
|
|
548
548
|
const labels = (ui && ui.label) || UI_FALLBACK.label;
|
|
549
549
|
if (labels[state]) return _resolveRefString(labels[state]);
|
package/lib/manifest.sw.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
// Build stamps the published version here; unstamped (dev) falls back to the
|
|
18
18
|
// registration URL's `v`, then 'dev'.
|
|
19
|
-
const BUILD_VERSION = '0.5.
|
|
19
|
+
const BUILD_VERSION = '0.5.261';
|
|
20
20
|
const CACHE_PREFIX = 'mnfst-sw:';
|
|
21
21
|
const META_CACHE = CACHE_PREFIX + 'meta';
|
|
22
22
|
// Sized to hold a full precache.json warm-up (PRECACHE_MAX_ENTRIES server-side)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mnfst",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.261",
|
|
4
4
|
"private": false,
|
|
5
5
|
"workspaces": [
|
|
6
6
|
"templates/starter",
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
"puppeteer": "^24.15.0",
|
|
66
66
|
"rimraf": "^5.0.0",
|
|
67
67
|
"rollup": "^4.24.2",
|
|
68
|
+
"terser": "^5.47.1",
|
|
68
69
|
"vitest": "^4.1.2"
|
|
69
70
|
},
|
|
70
71
|
"keywords": [
|