mnfst 0.5.180 → 0.5.181
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.combobox.js +78 -16
- package/lib/manifest.integrity.json +2 -2
- package/lib/manifest.js +81 -10
- package/package.json +1 -1
package/lib/manifest.combobox.js
CHANGED
|
@@ -70,25 +70,34 @@ function initializeComboboxPlugin() {
|
|
|
70
70
|
// Alpine binding attributes — never carried onto combobox-owned clones
|
|
71
71
|
const isBinding = (n) => n[0] === ':' || n[0] === '@' || n.indexOf('x-') === 0;
|
|
72
72
|
|
|
73
|
+
// Rendered-snapshot clone: strip Alpine bindings (x-/:/@) from the whole subtree —
|
|
74
|
+
// they reference the source's x-for scope and would throw when Alpine walks the
|
|
75
|
+
// combobox-owned copy. Computed results (style, class, text) are baked in and survive.
|
|
76
|
+
function stripClone(root) {
|
|
77
|
+
Array.from(root.attributes).forEach(a => { if (isBinding(a.name)) root.removeAttribute(a.name); });
|
|
78
|
+
root.querySelectorAll('*').forEach(n => {
|
|
79
|
+
Array.from(n.attributes).forEach(a => { if (isBinding(a.name)) n.removeAttribute(a.name); });
|
|
80
|
+
});
|
|
81
|
+
return root;
|
|
82
|
+
}
|
|
83
|
+
|
|
73
84
|
// Read options from a source element (datalist/select → option, menu → li)
|
|
74
85
|
function readOptions(src) {
|
|
75
86
|
if (!src) return [];
|
|
76
87
|
if (src.tagName === 'MENU') {
|
|
77
88
|
return Array.from(src.querySelectorAll('li')).map(li => {
|
|
78
|
-
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
const
|
|
83
|
-
copy.querySelectorAll('*').forEach(n => {
|
|
84
|
-
Array.from(n.attributes).forEach(a => { if (isBinding(a.name)) n.removeAttribute(a.name); });
|
|
85
|
-
});
|
|
89
|
+
const copy = stripClone(li.cloneNode(true));
|
|
90
|
+
// A [data-chip] element inside the row is the chip's rich content: the chip
|
|
91
|
+
// shows its rendered clone, and clicks are mirrored back to this live source
|
|
92
|
+
// fragment so author @click handlers run in their own x-for scope.
|
|
93
|
+
const chipSrc = li.querySelector('[data-chip]');
|
|
86
94
|
return {
|
|
87
95
|
value: li.dataset.value != null ? li.dataset.value : li.textContent.trim(),
|
|
88
96
|
label: li.dataset.label || li.textContent.trim(),
|
|
89
97
|
pattern: li.dataset.pattern || null,
|
|
90
98
|
locked: li.hasAttribute('data-locked'),
|
|
91
99
|
html: copy.innerHTML,
|
|
100
|
+
chip: chipSrc ? (n => ({ node: n, src: chipSrc, sig: n.outerHTML }))(stripClone(chipSrc.cloneNode(true))) : null,
|
|
92
101
|
// Carry the row's own attributes into the option, minus combobox-managed ones.
|
|
93
102
|
attrs: Array.from(li.attributes)
|
|
94
103
|
.filter(a => { const n = a.name; return n !== 'role' && n !== 'id' && n !== 'aria-selected' && !isBinding(n); })
|
|
@@ -98,7 +107,9 @@ function initializeComboboxPlugin() {
|
|
|
98
107
|
}
|
|
99
108
|
return Array.from(src.querySelectorAll('option')).map(o => ({
|
|
100
109
|
value: o.value || o.textContent.trim(),
|
|
101
|
-
|
|
110
|
+
// o.label is the native `label` IDL prop: the `label` attribute when set, else
|
|
111
|
+
// the option's text — so <option value="SE" label="Sweden"> works natively.
|
|
112
|
+
label: (o.label || '').trim() || o.value,
|
|
102
113
|
pattern: o.getAttribute('data-pattern') || null,
|
|
103
114
|
locked: o.hasAttribute('data-locked'),
|
|
104
115
|
html: null
|
|
@@ -246,6 +257,17 @@ function initializeComboboxPlugin() {
|
|
|
246
257
|
if (!editorNone) el.removeAttribute('placeholder');
|
|
247
258
|
if (name) el.removeAttribute('name'); // hidden inputs carry the value(s)
|
|
248
259
|
|
|
260
|
+
// A wrapping <label> with no `for` targets its FIRST labelable descendant —
|
|
261
|
+
// that's the first chip's remove button, not the editor. Hovering the label then
|
|
262
|
+
// hover-lights that ×, and clicking the label (or a chip's text) activates it,
|
|
263
|
+
// deleting chips. Bind the label to the editor explicitly; clicks on interactive
|
|
264
|
+
// chip content (the ×, or any links/handlers) still land on it, not the label.
|
|
265
|
+
const ownerLabel = wrap.closest('label');
|
|
266
|
+
if (ownerLabel && !ownerLabel.hasAttribute('for')) {
|
|
267
|
+
if (!el.id) el.id = 'combobox-editor-' + rand();
|
|
268
|
+
ownerLabel.setAttribute('for', el.id);
|
|
269
|
+
}
|
|
270
|
+
|
|
249
271
|
// Live region for add/remove announcements
|
|
250
272
|
const live = document.createElement('span');
|
|
251
273
|
live.setAttribute('role', 'status');
|
|
@@ -572,11 +594,47 @@ function initializeComboboxPlugin() {
|
|
|
572
594
|
chip.className = 'combobox-chip';
|
|
573
595
|
chip.dataset.value = s.value;
|
|
574
596
|
const label = document.createElement('span');
|
|
575
|
-
label
|
|
597
|
+
setChipContent(label, s);
|
|
576
598
|
chip.appendChild(label);
|
|
577
599
|
applyLock(chip, s.value, s.label);
|
|
578
600
|
return chip;
|
|
579
601
|
}
|
|
602
|
+
// Chip content: the option's [data-chip] fragment when present (rendered clone,
|
|
603
|
+
// refreshed in place on reread — updating innerHTML never re-inserts the chip
|
|
604
|
+
// node, so the WebKit focused-insert collapse can't trigger), else the text
|
|
605
|
+
// label. Links in the clone navigate natively (computed href carries); other
|
|
606
|
+
// clicks are mirrored onto the same element in the LIVE source fragment, where
|
|
607
|
+
// the author's @click runs in its own x-for scope.
|
|
608
|
+
function setChipContent(span, s) {
|
|
609
|
+
const o = options.find(o => String(o.value).toLowerCase() === String(s.value).toLowerCase());
|
|
610
|
+
const rich = o && o.chip;
|
|
611
|
+
const sig = rich ? o.chip.sig : 't:' + s.label;
|
|
612
|
+
if (span.__cbSig === sig) return;
|
|
613
|
+
span.__cbSig = sig;
|
|
614
|
+
if (!rich) { span.textContent = s.label; span.__cbSrc = null; return; }
|
|
615
|
+
span.innerHTML = '';
|
|
616
|
+
span.appendChild(o.chip.node.cloneNode(true));
|
|
617
|
+
span.__cbSrc = o.chip.src;
|
|
618
|
+
if (span.__cbDelegated) return;
|
|
619
|
+
span.__cbDelegated = true;
|
|
620
|
+
span.addEventListener('click', (e) => {
|
|
621
|
+
const srcRoot = span.__cbSrc;
|
|
622
|
+
if (!srcRoot || !srcRoot.isConnected) return;
|
|
623
|
+
if (e.target.closest && e.target.closest('a[href]')) return; // native nav owns it
|
|
624
|
+
const cloneRoot = span.firstElementChild;
|
|
625
|
+
let n = e.target === span ? cloneRoot : e.target;
|
|
626
|
+
const path = [];
|
|
627
|
+
while (n && n !== cloneRoot) {
|
|
628
|
+
const p = n.parentElement;
|
|
629
|
+
if (!p) return;
|
|
630
|
+
path.unshift(Array.prototype.indexOf.call(p.children, n));
|
|
631
|
+
n = p;
|
|
632
|
+
}
|
|
633
|
+
let t = srcRoot;
|
|
634
|
+
for (const i of path) { t = t.children[i]; if (!t) return; }
|
|
635
|
+
t.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
|
|
636
|
+
});
|
|
637
|
+
}
|
|
580
638
|
// Add/drop the × to match locked state. Re-run from render() so a reactive
|
|
581
639
|
// `locked` change toggles existing chips too.
|
|
582
640
|
function applyLock(chip, value, label) {
|
|
@@ -615,7 +673,11 @@ function initializeComboboxPlugin() {
|
|
|
615
673
|
const key = norm(s.value);
|
|
616
674
|
const node = have.get(key);
|
|
617
675
|
if (!node) { const n = makeChip(s); have.set(key, n); wrap.insertBefore(n, el); }
|
|
618
|
-
else
|
|
676
|
+
else {
|
|
677
|
+
applyLock(node, s.value, s.label);
|
|
678
|
+
const sp = node.querySelector(':scope > span');
|
|
679
|
+
if (sp) setChipContent(sp, s); // in-place refresh; node identity kept
|
|
680
|
+
}
|
|
619
681
|
});
|
|
620
682
|
// Reorder only when order differs — needless re-insertion collapses chips
|
|
621
683
|
// in WebKit; the common append path (order matches) skips this.
|
|
@@ -802,11 +864,11 @@ function initializeComboboxPlugin() {
|
|
|
802
864
|
if (menu && menu.matches(':popover-open')) filter();
|
|
803
865
|
};
|
|
804
866
|
const mo = new MutationObserver(reread);
|
|
805
|
-
// childList/characterData: x-for row edits. attributes: bound
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
});
|
|
867
|
+
// childList/characterData: x-for row edits. attributes unfiltered: ANY bound
|
|
868
|
+
// attribute inside a row (style, class, data-*) changes the rendered snapshot
|
|
869
|
+
// the menu and rich chips clone, so all must re-trigger. Mutations coalesce
|
|
870
|
+
// per microtask, so a state flush costs one reread.
|
|
871
|
+
mo.observe(src, { childList: true, subtree: true, characterData: true, attributes: true });
|
|
810
872
|
if (cleanup) cleanup(() => mo.disconnect());
|
|
811
873
|
}
|
|
812
874
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"manifest.code.js": "sha384-G3MNfvQRWWY+gm4SGGrSsgQbDeil9MP+ON2DTk6Rcq9Nsex8woHMDt5EJHrFH0Nj",
|
|
8
8
|
"manifest.color.js": "sha384-6Rv3LxyTcZNjrhtayQfqRdCx0uSZ4BiEbgEI98I62eTvp8Aw7LBIoNJ0Je1oktwL",
|
|
9
9
|
"manifest.colorpicker.js": "sha384-N5ezEwgKrDodV4YdURxhdyZKmg1n/QhqvmAC4SEG03mw1pEAZAA8Gphp+NLAOxts",
|
|
10
|
-
"manifest.combobox.js": "sha384-
|
|
10
|
+
"manifest.combobox.js": "sha384-J1zXXanlzsfoYJsRdz41DJD168S+20oT+lIEpUJ4UOjwjMcy/t+2GwwVjfSSNOlD",
|
|
11
11
|
"manifest.components.js": "sha384-eZDib+RFdwd9mOUdi6o53eNS0LBlEEkZPDegxtkAcE+GrGIFxsuB4O0ymfixGNfM",
|
|
12
12
|
"manifest.data.js": "sha384-3lLvGnqcQr4Ium80MdaQTr8RytjVbs5rnxd0q41BF3ppcoQccMFAcvgFSyZP/0sl",
|
|
13
13
|
"manifest.datepicker.js": "sha384-52BBZdlLN+EATiUorwTEQOWGJZiSNu0voy9q+WQxxWpUeYsGGkuS0QPPXGx2akxG",
|
|
@@ -30,5 +30,5 @@
|
|
|
30
30
|
"manifest.url.parameters.js": "sha384-+ZOsgPgbFmKOZsReC1EiH8PsZwvr2nC/duhzIzFf70xrbRrLsr926sLFW0M+BXtN",
|
|
31
31
|
"manifest.utilities.js": "sha384-yLEqEbd1FncqswrtrcTO7KrIEK4iP0ZvqCi64XLI7D77/L0YiJbjLdLGGg6l7rgN",
|
|
32
32
|
"manifest.virtual.js": "sha384-c194pXD0Ld48QJ+HPVNibvbn54/ETJRuYhUOWesEBjtOIQcQMIKo0DnCyQMvDlLg",
|
|
33
|
-
"manifest.js": "sha384-
|
|
33
|
+
"manifest.js": "sha384-2XTZDu5M3icGSRvAVLeLQfQqwJQvzeOfW0yMRDqVa8kxhTqfBwA17RBBYOUY3d9a"
|
|
34
34
|
}
|
package/lib/manifest.js
CHANGED
|
@@ -466,7 +466,7 @@
|
|
|
466
466
|
))) {
|
|
467
467
|
plugins.push('appwrite-data');
|
|
468
468
|
}
|
|
469
|
-
if (manifest.appwrite?.presence) {
|
|
469
|
+
if (manifest.appwrite?.presence || manifest.appwrite?.presences) {
|
|
470
470
|
plugins.push('appwrite-presences');
|
|
471
471
|
}
|
|
472
472
|
return plugins;
|
|
@@ -481,14 +481,54 @@
|
|
|
481
481
|
}
|
|
482
482
|
|
|
483
483
|
// Detect the chat plugin from manifest.json content.
|
|
484
|
-
// Opt-in / auto-loaded when an `ai` (or `chat`)
|
|
484
|
+
// Opt-in / auto-loaded when an `ai` (or `chat`) entry is present — an
|
|
485
|
+
// object block or bare `true` (custom-adapter projects have no config).
|
|
485
486
|
function detectChatPlugins(manifest) {
|
|
486
487
|
if (!manifest || typeof manifest !== 'object') return [];
|
|
487
|
-
if (
|
|
488
|
-
(manifest.chat && typeof manifest.chat === 'object')) return ['chat'];
|
|
488
|
+
if (manifest.ai || manifest.chat) return ['chat'];
|
|
489
489
|
return [];
|
|
490
490
|
}
|
|
491
491
|
|
|
492
|
+
// Detect plugins from markup usage — magics written in Alpine expressions
|
|
493
|
+
// with no declarative trigger (e.g. $chat driven by custom adapters).
|
|
494
|
+
// Runs at DCL, before Alpine boots: scans element attributes (recursing
|
|
495
|
+
// into template content, which the parser detaches) and inline scripts.
|
|
496
|
+
// Rendered text is deliberately not scanned — code samples on a page
|
|
497
|
+
// must not pull plugins in.
|
|
498
|
+
const USAGE_PLUGINS = [
|
|
499
|
+
{ magic: '$chat', plugin: 'chat' },
|
|
500
|
+
{ magic: '$presence', plugin: 'appwrite-presences' }
|
|
501
|
+
];
|
|
502
|
+
function detectUsagePlugins(alreadyLoaded) {
|
|
503
|
+
const wanted = USAGE_PLUGINS.filter(u => !alreadyLoaded.includes(u.plugin));
|
|
504
|
+
if (wanted.length === 0 || typeof document === 'undefined') return [];
|
|
505
|
+
const found = new Set();
|
|
506
|
+
const done = () => found.size === wanted.length;
|
|
507
|
+
|
|
508
|
+
for (const s of document.querySelectorAll('script:not([src])')) {
|
|
509
|
+
const t = s.textContent || '';
|
|
510
|
+
for (const u of wanted) if (!found.has(u.plugin) && t.includes(u.magic)) found.add(u.plugin);
|
|
511
|
+
if (done()) return [...found];
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const scan = (root) => {
|
|
515
|
+
for (const el of root.querySelectorAll('*')) {
|
|
516
|
+
for (const attr of el.attributes || []) {
|
|
517
|
+
const v = attr.value;
|
|
518
|
+
if (!v || v.indexOf('$') === -1) continue;
|
|
519
|
+
for (const u of wanted) if (!found.has(u.plugin) && v.includes(u.magic)) found.add(u.plugin);
|
|
520
|
+
if (done()) return;
|
|
521
|
+
}
|
|
522
|
+
if (el.tagName === 'TEMPLATE' && el.content) {
|
|
523
|
+
scan(el.content);
|
|
524
|
+
if (done()) return;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
scan(document);
|
|
529
|
+
return [...found];
|
|
530
|
+
}
|
|
531
|
+
|
|
492
532
|
// Detect the native umbrella plugin. Auto-loaded inside a Capacitor container
|
|
493
533
|
// (window.Capacitor present) or when a `native` config block opts in on the web.
|
|
494
534
|
function detectNativePlugins(manifest) {
|
|
@@ -520,12 +560,25 @@
|
|
|
520
560
|
// Override: custom CDN fallback chain (comma-separated origins).
|
|
521
561
|
const cdn = script.getAttribute('data-cdn');
|
|
522
562
|
|
|
563
|
+
// `data-plugins="a,b"` replaces the default set; a `+` prefix is additive
|
|
564
|
+
// (`data-plugins="+chat"` = defaults plus chat) — needed for plugins with
|
|
565
|
+
// no manifest.json trigger, e.g. chat driven by custom adapters only.
|
|
523
566
|
let pluginList = [];
|
|
524
|
-
|
|
567
|
+
let extraPlugins = [];
|
|
568
|
+
let deriveFromManifest = !plugins;
|
|
525
569
|
|
|
526
570
|
if (plugins) {
|
|
527
|
-
|
|
528
|
-
|
|
571
|
+
const entries = plugins.split(',').map(p => p.trim()).filter(p => p);
|
|
572
|
+
extraPlugins = entries.filter(p => p.startsWith('+')).map(p => p.slice(1));
|
|
573
|
+
const explicit = entries.filter(p => !p.startsWith('+'));
|
|
574
|
+
if (explicit.length > 0) {
|
|
575
|
+
pluginList = [...explicit, ...extraPlugins];
|
|
576
|
+
} else {
|
|
577
|
+
// Only additive entries: keep the default derive-from-manifest
|
|
578
|
+
// behavior and append the extras once the manifest is inspected.
|
|
579
|
+
deriveFromManifest = true;
|
|
580
|
+
pluginList = AVAILABLE_PLUGINS.slice();
|
|
581
|
+
}
|
|
529
582
|
} else {
|
|
530
583
|
// Default: start with all core plugins; loader will trim by manifest when manifest is available
|
|
531
584
|
pluginList = AVAILABLE_PLUGINS.slice();
|
|
@@ -542,6 +595,7 @@
|
|
|
542
595
|
|
|
543
596
|
return {
|
|
544
597
|
plugins: pluginList,
|
|
598
|
+
extraPlugins,
|
|
545
599
|
deriveFromManifest,
|
|
546
600
|
tailwind,
|
|
547
601
|
version,
|
|
@@ -598,7 +652,7 @@
|
|
|
598
652
|
|
|
599
653
|
const MANIFEST_DEPENDENT_PLUGINS = [
|
|
600
654
|
'data', 'localization', 'components',
|
|
601
|
-
'appwrite-auth', 'appwrite-data', 'appwrite-presences', 'payments'
|
|
655
|
+
'appwrite-auth', 'appwrite-data', 'appwrite-presences', 'payments', 'chat'
|
|
602
656
|
];
|
|
603
657
|
const manifestUrl = (document.querySelector('link[rel="manifest"]')?.getAttribute('href')) || '/manifest.json';
|
|
604
658
|
|
|
@@ -664,7 +718,7 @@
|
|
|
664
718
|
const paymentsPlugins = detectPaymentsPlugins(manifest);
|
|
665
719
|
const chatPlugins = detectChatPlugins(manifest);
|
|
666
720
|
const nativePlugins = detectNativePlugins(manifest);
|
|
667
|
-
pluginsToLoad = resolveDependencies([...corePlugins, ...appwritePlugins, ...paymentsPlugins, ...chatPlugins, ...nativePlugins]);
|
|
721
|
+
pluginsToLoad = resolveDependencies([...corePlugins, ...appwritePlugins, ...paymentsPlugins, ...chatPlugins, ...nativePlugins, ...(config.extraPlugins || [])]);
|
|
668
722
|
} else {
|
|
669
723
|
const needsManifest = config.plugins.some(p => MANIFEST_DEPENDENT_PLUGINS.includes(p));
|
|
670
724
|
if (needsManifest) {
|
|
@@ -698,7 +752,24 @@
|
|
|
698
752
|
window.ManifestComponentsRegistry.manifest = manifest;
|
|
699
753
|
}
|
|
700
754
|
}
|
|
701
|
-
|
|
755
|
+
// Usage sniff: Alpine is DCL-gated anyway, so the parsed document can
|
|
756
|
+
// be checked for magics with no declarative trigger and the missing
|
|
757
|
+
// plugins fetched before Alpine boots. Derive path only — an explicit
|
|
758
|
+
// data-plugins list is an intentional constraint, not a default.
|
|
759
|
+
whenDomReady(async () => {
|
|
760
|
+
if (config.deriveFromManifest) {
|
|
761
|
+
try {
|
|
762
|
+
const used = detectUsagePlugins(pluginsToLoad);
|
|
763
|
+
if (used.length > 0) {
|
|
764
|
+
const late = resolveDependencies(used).filter(p => !pluginsToLoad.includes(p));
|
|
765
|
+
await Promise.all(late.map(p => addScript(p, config.version).catch(error => {
|
|
766
|
+
console.warn(`[Manifest Loader] Failed to load plugin ${p}:`, error);
|
|
767
|
+
})));
|
|
768
|
+
}
|
|
769
|
+
} catch (_) { /* sniffing must never block Alpine */ }
|
|
770
|
+
}
|
|
771
|
+
loadAlpine(alpineUrlCandidates(config.alpine));
|
|
772
|
+
});
|
|
702
773
|
};
|
|
703
774
|
|
|
704
775
|
loadPlugins();
|