mnfst 0.5.214 → 0.5.215

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.
@@ -1,4 +1,5 @@
1
- /* Manifest Export — x-export / $export download of pages, regions, or data (pdf/png/jpeg/webp/csv/json) */
1
+ /* Manifest Export — x-export / $export download of pages, regions, or data (pdf/png/jpeg/webp/csv/json),
2
+ and its mirror x-import / $import: pick a local file and parse it into data (json/csv) */
2
3
 
3
4
  (function () {
4
5
 
@@ -523,4 +524,251 @@ if (window.Alpine && typeof window.Alpine.directive === 'function') {
523
524
  }
524
525
 
525
526
 
527
+ })();
528
+
529
+ // ----- Import (x-import / $import) ------------------------------------------
530
+
531
+ (function () {
532
+
533
+
534
+ function initializeImportPlugin() {
535
+
536
+ Alpine.directive('import', (el, { modifiers, expression }, { evaluate, cleanup }) => {
537
+
538
+ const onClick = async (e) => {
539
+ if (e && typeof e.preventDefault === 'function') e.preventDefault();
540
+ const opts = resolveOptions(expression, modifiers, evaluate);
541
+ try {
542
+ await runImport(el, opts);
543
+ } catch (err) {
544
+ if (!err || !err._mnfstReported) emitError(el, opts.format, err);
545
+ }
546
+ };
547
+ el.addEventListener('click', onClick);
548
+ cleanup(() => el.removeEventListener('click', onClick));
549
+ });
550
+
551
+ // ----- $import magic — programmatic file pick, resolves parsed data -----
552
+ Alpine.magic('import', (el) => async (opts = {}) => {
553
+ return runImport(el || null, { ...opts });
554
+ });
555
+
556
+ // ------- Options + format helpers -----------------------------------
557
+
558
+ function resolveOptions(expression, modifiers, evaluate) {
559
+ let opts = {};
560
+ if (expression && expression.trim()) {
561
+ try {
562
+ const v = evaluate(expression);
563
+ if (v && typeof v === 'object') opts = { ...v };
564
+ else if (typeof v === 'string') opts = isKnownFormat(v.toLowerCase()) ? { format: v.toLowerCase() } : { source: v };
565
+ } catch (err) {
566
+ console.warn('[x-import] could not evaluate options expression:', err.message);
567
+ }
568
+ }
569
+ // Modifier shorthand for format only (.json, .csv).
570
+ if (!opts.format && Array.isArray(modifiers) && modifiers.length) {
571
+ const found = modifiers.find((m) => isKnownFormat(String(m).toLowerCase()));
572
+ if (found) opts.format = String(found).toLowerCase();
573
+ }
574
+ return opts;
575
+ }
576
+
577
+ function isKnownFormat(f) {
578
+ return f === 'json' || f === 'csv';
579
+ }
580
+
581
+ function acceptFor(format) {
582
+ if (format === 'json') return '.json,application/json';
583
+ if (format === 'csv') return '.csv,text/csv,.tsv';
584
+ return '.json,application/json,.csv,text/csv,.tsv';
585
+ }
586
+
587
+ function formatOf(file, declared) {
588
+ if (declared && isKnownFormat(declared)) return declared;
589
+ const name = (file && file.name || '').toLowerCase();
590
+ if (name.endsWith('.csv') || name.endsWith('.tsv')) return 'csv';
591
+ return 'json';
592
+ }
593
+
594
+ // ------- File picking ------------------------------------------------
595
+
596
+ // Hidden input per pick; resolves the chosen File, or null when the dialog
597
+ // is dismissed (the input 'cancel' event, where supported).
598
+ function pickFile(accept) {
599
+ return new Promise((resolve) => {
600
+ const input = document.createElement('input');
601
+ input.type = 'file';
602
+ if (accept) input.accept = accept;
603
+ input.style.display = 'none';
604
+ const done = (file) => { input.remove(); resolve(file); };
605
+ input.addEventListener('change', () => done(input.files && input.files[0] || null), { once: true });
606
+ input.addEventListener('cancel', () => done(null), { once: true });
607
+ document.body.appendChild(input);
608
+ input.click();
609
+ });
610
+ }
611
+
612
+ function readAsText(file) {
613
+ if (typeof file.text === 'function') return file.text();
614
+ return new Promise((resolve, reject) => {
615
+ const reader = new FileReader();
616
+ reader.onload = () => resolve(String(reader.result || ''));
617
+ reader.onerror = () => reject(reader.error || new Error('could not read file'));
618
+ reader.readAsText(file);
619
+ });
620
+ }
621
+
622
+ // ------- Parsing -------------------------------------------------------
623
+
624
+ function parseContent(text, format) {
625
+ if (format === 'json') {
626
+ return JSON.parse(text);
627
+ }
628
+ return parseCsv(text);
629
+ }
630
+
631
+ // RFC 4180-style CSV: quoted fields, embedded commas/newlines/quotes.
632
+ // Header row → array of objects. Delimiter sniffed from the header line.
633
+ function parseCsv(text) {
634
+ const src = String(text).replace(/^/, '');
635
+ if (!src.trim()) return [];
636
+ const delimiter = sniffDelimiter(src);
637
+ const rows = [];
638
+ let row = [];
639
+ let cell = '';
640
+ let inQuotes = false;
641
+ for (let i = 0; i < src.length; i++) {
642
+ const ch = src[i];
643
+ if (inQuotes) {
644
+ if (ch === '"') {
645
+ if (src[i + 1] === '"') { cell += '"'; i++; }
646
+ else inQuotes = false;
647
+ } else cell += ch;
648
+ } else if (ch === '"') {
649
+ inQuotes = true;
650
+ } else if (ch === delimiter) {
651
+ row.push(cell); cell = '';
652
+ } else if (ch === '\n' || ch === '\r') {
653
+ if (ch === '\r' && src[i + 1] === '\n') i++;
654
+ row.push(cell); cell = '';
655
+ rows.push(row); row = [];
656
+ } else {
657
+ cell += ch;
658
+ }
659
+ }
660
+ if (cell !== '' || row.length) { row.push(cell); rows.push(row); }
661
+ while (rows.length && rows[rows.length - 1].every((c) => c === '')) rows.pop();
662
+ if (!rows.length) return [];
663
+ const headers = rows[0].map((h) => String(h).trim());
664
+ return rows.slice(1).map((cells) => {
665
+ const out = {};
666
+ headers.forEach((h, i) => { if (h) out[h] = coerceCell(cells[i]); });
667
+ return out;
668
+ });
669
+ }
670
+
671
+ function sniffDelimiter(src) {
672
+ const header = src.slice(0, src.indexOf('\n') < 0 ? src.length : src.indexOf('\n'));
673
+ let best = ',', bestCount = 0;
674
+ for (const d of [',', ';', '\t']) {
675
+ const count = header.split(d).length - 1;
676
+ if (count > bestCount) { best = d; bestCount = count; }
677
+ }
678
+ return best;
679
+ }
680
+
681
+ // Undo x-export's CSV serialization: JSON-looking cells parse back to
682
+ // objects/arrays; numbers, booleans and null coerce; everything else stays a string.
683
+ function coerceCell(v) {
684
+ if (v == null) return '';
685
+ const s = String(v);
686
+ const t = s.trim();
687
+ if (t === '') return '';
688
+ if (t === 'true') return true;
689
+ if (t === 'false') return false;
690
+ if (t === 'null') return null;
691
+ if (/^-?(\d+\.?\d*|\.\d+)(e[+-]?\d+)?$/i.test(t)) {
692
+ const n = Number(t);
693
+ if (Number.isFinite(n)) return n;
694
+ }
695
+ if (t[0] === '{' || t[0] === '[') {
696
+ try { return JSON.parse(t); } catch { /* keep string */ }
697
+ }
698
+ return s;
699
+ }
700
+
701
+ // ------- Delivery ------------------------------------------------------
702
+
703
+ function applyToSource(source, data) {
704
+ const store = window.ManifestDataStore;
705
+ if (!store || typeof store.updateStore !== 'function') {
706
+ throw new Error(`cannot write to source "${source}" — the data plugin is not loaded`);
707
+ }
708
+ store.updateStore(source, data, { loading: false, error: null, ready: true, fresh: true, allowDuringInit: true });
709
+ }
710
+
711
+ async function runImport(el, opts) {
712
+ const accept = opts.accept || acceptFor(opts.format);
713
+ const file = await pickFile(accept);
714
+ if (!file) return null;
715
+ const format = formatOf(file, opts.format);
716
+ let data;
717
+ try {
718
+ data = parseContent(await readAsText(file), format);
719
+ } catch (err) {
720
+ const e = new Error(`could not parse ${file.name} as ${format}: ${err.message}`);
721
+ emitError(el, format, e);
722
+ e._mnfstReported = true;
723
+ throw e;
724
+ }
725
+ if (opts.source) applyToSource(String(opts.source), data);
726
+ emit(el, 'manifest:import', {
727
+ data,
728
+ format,
729
+ source: opts.source || null,
730
+ file: { name: file.name, size: file.size, type: file.type }
731
+ });
732
+ return data;
733
+ }
734
+
735
+ function emit(el, name, detail) {
736
+ try {
737
+ const event = new CustomEvent(name, { detail, bubbles: true, composed: true });
738
+ (el && el.dispatchEvent ? el : window).dispatchEvent(event);
739
+ } catch { /* ignore */ }
740
+ }
741
+
742
+ function emitError(el, format, err) {
743
+ console.error('[x-import] import failed:', err && err.message || err);
744
+ emit(el, 'manifest:import-error', { format: format || null, error: err && err.message || String(err) });
745
+ }
746
+ }
747
+
748
+ // Standard plugin init lifecycle.
749
+ let importPluginInitialized = false;
750
+ function ensureImportPluginInitialized() {
751
+ if (importPluginInitialized) return;
752
+ if (!window.Alpine || typeof window.Alpine.directive !== 'function') return;
753
+ importPluginInitialized = true;
754
+ initializeImportPlugin();
755
+ }
756
+ window.ensureImportPluginInitialized = ensureImportPluginInitialized;
757
+ if (document.readyState === 'loading') {
758
+ document.addEventListener('DOMContentLoaded', ensureImportPluginInitialized);
759
+ }
760
+ document.addEventListener('alpine:init', ensureImportPluginInitialized);
761
+ if (window.Alpine && typeof window.Alpine.directive === 'function') {
762
+ setTimeout(ensureImportPluginInitialized, 0);
763
+ } else {
764
+ const checkAlpine = setInterval(() => {
765
+ if (window.Alpine && typeof window.Alpine.directive === 'function') {
766
+ clearInterval(checkAlpine);
767
+ ensureImportPluginInitialized();
768
+ }
769
+ }, 10);
770
+ setTimeout(() => clearInterval(checkAlpine), 5000);
771
+ }
772
+
773
+
526
774
  })();
@@ -25,7 +25,12 @@
25
25
  }
26
26
 
27
27
  & input,
28
- & .combobox,
28
+ & .combobox {
29
+ width: fit-content
30
+ }
31
+
32
+ /* Own rule: a selector list is not forgiving, so a browser that can't
33
+ parse the :has() would drop the two plain selectors above with it. */
29
34
  & :where(label, .label):has(input:not([type=checkbox], [type=radio]), select, textarea) {
30
35
  width: fit-content
31
36
  }
@@ -1,23 +1,23 @@
1
1
  {
2
- "manifest.appwrite.auth.js": "sha384-HMJmRXnvdDBWyuteXL+DTxWM2llSCdjl4EDamEI7iHNAVlqeOCE4GDTm8ZHipPRj",
3
- "manifest.appwrite.data.js": "sha384-toiWoD71kvFPTWmSPNs+ip2iy3TI8lWE1oNQGeUxJFWDaoOuBY47dCrovzNBsUZH",
2
+ "manifest.appwrite.auth.js": "sha384-X30RFvbwT9qEIaMsMUumnW54ZzcnY5aY9QQeXPG8/0sI6VAbAw3+FZ6iuZb6b4cu",
3
+ "manifest.appwrite.data.js": "sha384-cfOicas38L58E3CLRnXIdHR5FFFBPuzesQHl5HgqXARZq29XMBlqOmITteI35d5a",
4
4
  "manifest.appwrite.presences.js": "sha384-JyVDWFT69b7Svq8J8p9tNWe2v9PZNQBY5UyhtOl1vnuHFAX0CRQ3MA9SiKq5wzNh",
5
5
  "manifest.bindings.js": "sha384-lPtMf8Ykaic9gNVZKq8Scia1881ZeMAyxer3WCIkUNwwrgzGBWto1OXADUwjvORF",
6
- "manifest.charts.js": "sha384-WE0pnHK4LBAkFyTw4+R6gheB9NFVJg81ZAG9GwMnbKI5gY3zlJIKM5neMlBUhTso",
6
+ "manifest.charts.js": "sha384-gEAv/98+29RbhvNkiNWTNyBPbLTFr2Ld15wLu29FLCL57j1cwGChRrL9Pfk7dBtT",
7
7
  "manifest.chat.js": "sha384-88DfMfJLoi2oxclVD2iIMDkIGAExMXq5k47mekUredlicLaP3oZY5bwtwqs+TqDF",
8
8
  "manifest.code.js": "sha384-aoQ4E26vrhNe/+tL7K0h90c8/0ies7e8oIR6Ay92he2X3dnWZ+OPcn2Dna8fmc+c",
9
9
  "manifest.color.js": "sha384-6Rv3LxyTcZNjrhtayQfqRdCx0uSZ4BiEbgEI98I62eTvp8Aw7LBIoNJ0Je1oktwL",
10
- "manifest.colorpicker.js": "sha384-1+ZV5Hhjf7WnGP7b71Cqa5h3BKW8UgGT5V14i8Q+aF5zH8Xxwowt/ibSUVsiIHSm",
11
- "manifest.combobox.js": "sha384-5DaKDWkWF7zxS80h7G1AcZG9hr+uGqJ1+B09aaVIVF+z0Qn4j8+9KEg7gys/ABQG",
12
- "manifest.components.js": "sha384-458gCDrUIZts3WE1WZ/UA8Y1XcV6PaF+5k3Wf/Jr2iIY5EM/FisjwbLzpON8I25s",
10
+ "manifest.colorpicker.js": "sha384-BhcCe8cGcd0DTJNwzXWWY+FQNRfSFFl6XqfHIFP8yHg/aXFks1NIC6lVx0Sr1VkM",
11
+ "manifest.combobox.js": "sha384-MILTkrgsTzsYUjGo4ZuADqV85Xs4GmfB4xexfe9vbJMIoQTZ42W4zs+DTaVSPEsx",
12
+ "manifest.components.js": "sha384-27RMMNmXRgFLysdvJgZjDxfXvpIGlQUoWzmwaYCwdfNA+78LARTbOt3nKAPfXwvA",
13
13
  "manifest.computed.js": "sha384-GYML/e81gQfN+iDbqhPUgny8bINCyL+SrbTofoGk4hlCnLLjv51mCv42JMqq+ku5",
14
- "manifest.data.js": "sha384-La5wZy/YDyHgA/Hr/PS6rZLI9dUSIpyF9nNWFkter6on5GBxx9j+SfkrflCZLVgs",
15
- "manifest.datepicker.js": "sha384-8vTJYkLqqvgXgm1w1becuaRR/jUEpiLBjBJ6RsRbUr+ZkypvspD6EsssuDrX4SQq",
14
+ "manifest.data.js": "sha384-rZO5eUSHAFhSWqcGTqgQibrThpKZcTTgvaI+x1HYa2oepLjDGwhbjgNeYCWqZNM+",
15
+ "manifest.datepicker.js": "sha384-XnbeURni2X91ozVzatQcSjYYIS4MoW12dBRJnYAGBr0nCQ624heH5s+xtQSpLZX6",
16
16
  "manifest.defer.js": "sha384-wXa60M2OHigi5rBurXQ7Oa8eyr5WTKs7EUn2GOru62KMdfs/LDyNegxCc9MNJFSe",
17
17
  "manifest.device.js": "sha384-g+txmrEszYtuOvRelTp/c1wSNR2AtllVI9rx5NPPD7DzhVUnMNughonZykv2xlBW",
18
18
  "manifest.dropdowns.js": "sha384-0ilj9AvRph0LacIBJSqwpu1fvPRn5gcWbhIG1SOpaoM/Q8idMbKBLupwM+5O0UaX",
19
19
  "manifest.edit.js": "sha384-oLsrH7/UkJ/KEl3UO77hexcf2rYCGgzXZd0fe2EMq3hKRE7aUqLiEnLJhSV7zTaY",
20
- "manifest.export.js": "sha384-HCy3n6ifrcAGIyzTRgZ5JIRvSX9q9/8ykMKRSbt1QTnhXz6PgYlV0AE2N2Yi2re+",
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-fzyRaD7LwPOYcUeJZCMh6aWgk1ChxIVgy3eUTEjSwZ3DvAHrsl5EMvfxlvOKXD5U",
@@ -26,9 +26,9 @@
26
26
  "manifest.resize.js": "sha384-+0IsyrGP/4gZmNbD6oU8dCgEyhpBsXMm74NIenYeUGuGRWyJDurcG27xE/CA0/aA",
27
27
  "manifest.router.js": "sha384-4qmGEkRpgAo9xoQkW6+hHIhowvcyAggKB2J3Wf8tJtXp+L0hufoVAqzfhnqw4cNR",
28
28
  "manifest.slides.js": "sha384-3uRTkyK9XPLmnxI2+igZlpi4EyPlU/7IHj5j3BZJJ2KN455vXyk99fiXV3feO/XY",
29
- "manifest.status.js": "sha384-nwvUSbT+gpRMqhYx6MeBYswUMH72rY92mjgxv0vrSG7Ht7l83XYpEB6fCTbXjjfK",
29
+ "manifest.status.js": "sha384-MBO345ab7H6Uq1X2dZLt4Ge1Gcra+Z2qYApvlAeIV8CJhDDJrz2FPXpp+Kcp8zcM",
30
30
  "manifest.svg.js": "sha384-qr/8H9+2cIhSXNbRWRtIdrDlTOxKlNJqwEefHvCYoiBNW/kKhPTFa109Orv/EBnb",
31
- "manifest.sw.js": "sha384-7CAISs703hbP8I8hw46xibMiT3RG6bUGaUiASk0eQvHktozHGqKXcMhwNCdcIKVY",
31
+ "manifest.sw.js": "sha384-Kh0gcISFTnkgXmmn2ykDjYsGw7NFDFaorDt5ZYVfg5Xs1Uy8H0ll0E98MMPqdBPy",
32
32
  "manifest.tabs.js": "sha384-OIIcyaQnGS0eNYk4zgAFEDboNYect7THvUd9D0V6nqCeDOxQcoiqLrBEmmWVTTet",
33
33
  "manifest.tailwind.js": "sha384-r/Jvqb+Piz21jUts10BbVAJoKkShOdJRVFpywyi4HPIi/OfOJq5CtPyWAPbdkMgt",
34
34
  "manifest.text.edit.js": "sha384-pfhc4IPKNJokj6ldlgD+KwGLN4UoM21CNeVf2sVq/34X1Un2PUhEuyTg5itmtsXS",
@@ -37,5 +37,5 @@
37
37
  "manifest.url.parameters.js": "sha384-J2aSVBOPsoqsbafHndrWLHEDpKb4AKJAoQnVmKA4gZh5P/8HHjFWHO/lkusmG84l",
38
38
  "manifest.utilities.js": "sha384-pIKcTvk9lrlXZK0OXld6SA6wrmlaFTCmTpLn67nOCE+XhNw4fuGSDX4Z3C2eR6CJ",
39
39
  "manifest.virtual.js": "sha384-YwwmAsZNddajnAkLhBWtrK8edHMJOhD7cJkQi3qmm5o1Thg0wbtDU0dsfrCHKTIQ",
40
- "manifest.js": "sha384-fyktZSCaKJ267Hj5ORpbS/Dk11vtT+J8BN1XkA8zG+kXG1wPDwcA1hmB/40fY5Pq"
40
+ "manifest.js": "sha384-OMEyd7TUB5EfAhabW58OgEn6AkZgOuH5IQ85dHZf7ciI0jmXXrsCp2Eh74iVnJ62"
41
41
  }
package/lib/manifest.js CHANGED
@@ -461,7 +461,7 @@
461
461
  const resolved = [];
462
462
  const added = new Set();
463
463
 
464
- const PLUGIN_ALIASES = { native: 'device' }; // renamed 0.5.199; the old name keeps working
464
+ const PLUGIN_ALIASES = { native: 'device', import: 'export' }; // native renamed 0.5.199; import ships inside export
465
465
  function addPluginWithDeps(plugin) {
466
466
  plugin = PLUGIN_ALIASES[plugin] || plugin;
467
467
  if (added.has(plugin)) return;
@@ -540,11 +540,16 @@
540
540
  { magic: '$presence', plugin: 'appwrite-presences' },
541
541
  ...['$share', '$secure', '$links', '$push', '$app', '$haptics', '$biometric', '$camera'].map(magic => ({ magic, plugin: 'device' }))
542
542
  ];
543
+ // Framework web components — <x-*> tags that are NOT project components.
544
+ const FRAMEWORK_TAG_RE = /^x-(code|code-group)$/;
543
545
  function detectUsagePlugins(alreadyLoaded) {
544
546
  const wanted = USAGE_PLUGINS.filter(u => !alreadyLoaded.includes(u.plugin));
545
- if (wanted.length === 0 || typeof document === 'undefined') return [];
547
+ // <x-*> tags load the components plugin even with nothing registered in
548
+ // manifest.json (the loader's components/<name>.html convention).
549
+ const wantComponents = !alreadyLoaded.includes('components');
550
+ if ((wanted.length === 0 && !wantComponents) || typeof document === 'undefined') return [];
546
551
  const found = new Set();
547
- const done = () => found.size === wanted.length;
552
+ const done = () => found.size === wanted.length + (wantComponents ? 1 : 0);
548
553
 
549
554
  for (const s of document.querySelectorAll('script:not([src])')) {
550
555
  const t = s.textContent || '';
@@ -554,6 +559,10 @@
554
559
 
555
560
  const scan = (root) => {
556
561
  for (const el of root.querySelectorAll('*')) {
562
+ if (wantComponents && !found.has('components')) {
563
+ const tag = el.tagName.toLowerCase();
564
+ if (tag.startsWith('x-') && !FRAMEWORK_TAG_RE.test(tag)) found.add('components');
565
+ }
557
566
  for (const attr of el.attributes || []) {
558
567
  const v = attr.value;
559
568
  if (!v || v.indexOf('$') === -1) continue;
@@ -690,7 +699,7 @@
690
699
  // browser engine (lib/manifest.tailwind.js) — but the utilities plugin's
691
700
  // uncovered-class watcher lazily loads it this way (see
692
701
  // manifest.utilities.static.js setupUncoveredClassWatcher).
693
- const allPlugins = [...AVAILABLE_PLUGINS, ...APPWRITE_PLUGINS, ...AUTHORING_PLUGINS, 'payments', 'chat', 'device', 'tailwind'];
702
+ const allPlugins = [...AVAILABLE_PLUGINS, ...APPWRITE_PLUGINS, ...AUTHORING_PLUGINS, 'payments', 'chat', 'device', 'native', 'import', 'tailwind'];
694
703
  if (!allPlugins.includes(pluginName)) {
695
704
  console.warn(`[Manifest Loader] Unknown plugin: ${pluginName}`);
696
705
  return Promise.reject(new Error(`Unknown plugin: ${pluginName}`));