mnfst 0.5.171 → 0.5.173

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.
@@ -649,25 +649,31 @@
649
649
  const thickness = Math.max(8, r * 0.22);
650
650
  const cx = width / 2, cy = Math.max(PAD, (height - (r + labelBand)) / 2) + r;
651
651
  const g = svg('g', { transform: `translate(${cx},${cy})` }, root);
652
- const arc = d3.arc().innerRadius(r - thickness).outerRadius(r).cornerRadius(cssRadius(state.el));
652
+ // Bands are drawn with square (butt) ends so zone/value joins stay flush;
653
+ // the whole dial is then clipped to a single rounded full-sweep arc, so
654
+ // only its two outer corners are rounded — not each interior segment.
655
+ const arc = d3.arc().innerRadius(r - thickness).outerRadius(r);
656
+ const clipId = 'mnfst-gauge-' + (++_uid);
657
+ svg('path', { d: d3.arc().innerRadius(r - thickness).outerRadius(r).cornerRadius(cssRadius(state.el))({ startAngle: START, endAngle: END }) }, svg('clipPath', { id: clipId }, svg('defs', null, root)));
658
+ const ring = svg('g', { 'clip-path': `url(#${clipId})` }, g);
653
659
 
654
660
  // Track, or threshold zone bands when `zones` is given.
655
661
  if (Array.isArray(cfg.zones) && cfg.zones.length) {
656
662
  let from = min;
657
663
  cfg.zones.forEach((z, i) => {
658
664
  const to = num(z.to, max);
659
- svg('path', { class: 'gauge-track', d: arc({ startAngle: scale(from), endAngle: scale(to) }), style: `--color-chart-color:${z.color || seriesColorVar(i)}`, opacity: 0.35 }, g);
665
+ svg('path', { class: 'gauge-track', d: arc({ startAngle: scale(from), endAngle: scale(to) }), style: `--color-chart-color:${z.color || seriesColorVar(i)}`, opacity: 0.35 }, ring);
660
666
  from = to;
661
667
  });
662
668
  } else {
663
- svg('path', { class: 'gauge-track', d: arc({ startAngle: START, endAngle: END }) }, g);
669
+ svg('path', { class: 'gauge-track', d: arc({ startAngle: START, endAngle: END }) }, ring);
664
670
  }
665
671
 
666
672
  // Value arc — final geometry set unconditionally, so a background tab that
667
673
  // skips the fade reveal still renders correctly.
668
674
  const color = (cfg.series[0] && cfg.series[0].color) || 'var(--color-chart-1)';
669
675
  const valueAngle = scale(value);
670
- const vArc = svg('path', { class: 'gauge-value', d: arc({ startAngle: START, endAngle: valueAngle }), style: `--color-chart-color:${color}` }, g);
676
+ const vArc = svg('path', { class: 'gauge-value', d: arc({ startAngle: START, endAngle: valueAngle }), style: `--color-chart-color:${color}` }, ring);
671
677
  applyTip(vArc, (cfg.title ? cfg.title + ': ' : '') + value + unit, cfg);
672
678
  animate(vArc, [{ opacity: 0 }, { opacity: 1 }], { duration: 500 });
673
679
 
@@ -67,22 +67,34 @@ function initializeComboboxPlugin() {
67
67
  const rand = () => Math.random().toString(36).slice(2, 9);
68
68
  const ui = () => window.ManifestUI ? window.ManifestUI.resolve('combobox', UI_FALLBACK) : UI_FALLBACK;
69
69
 
70
+ // Alpine binding attributes — never carried onto combobox-owned clones
71
+ const isBinding = (n) => n[0] === ':' || n[0] === '@' || n.indexOf('x-') === 0;
72
+
70
73
  // Read options from a source element (datalist/select → option, menu → li)
71
74
  function readOptions(src) {
72
75
  if (!src) return [];
73
76
  if (src.tagName === 'MENU') {
74
- return Array.from(src.querySelectorAll('li')).map(li => ({
75
- value: li.dataset.value != null ? li.dataset.value : li.textContent.trim(),
76
- label: li.dataset.label || li.textContent.trim(),
77
- pattern: li.dataset.pattern || null,
78
- locked: li.hasAttribute('data-locked'),
79
- html: li.innerHTML,
80
- // Carry the row's own attributes into the option, minus combobox-managed
81
- // ones and Alpine bindings (x-/:/@ reference the source's scope, not the clone).
82
- attrs: Array.from(li.attributes)
83
- .filter(a => { const n = a.name; return n !== 'role' && n !== 'id' && n !== 'aria-selected' && n[0] !== ':' && n[0] !== '@' && n.indexOf('x-') !== 0; })
84
- .map(a => [a.name, a.value])
85
- }));
77
+ return Array.from(src.querySelectorAll('li')).map(li => {
78
+ // The clone is a rendered snapshot: strip Alpine bindings (x-/:/@) from the
79
+ // WHOLE subtree, not just the row — they reference the source's x-for scope
80
+ // and would throw when Alpine walks the combobox-owned copy. The computed
81
+ // results (style, class, text) are already baked in and survive.
82
+ const copy = li.cloneNode(true);
83
+ copy.querySelectorAll('*').forEach(n => {
84
+ Array.from(n.attributes).forEach(a => { if (isBinding(a.name)) n.removeAttribute(a.name); });
85
+ });
86
+ return {
87
+ value: li.dataset.value != null ? li.dataset.value : li.textContent.trim(),
88
+ label: li.dataset.label || li.textContent.trim(),
89
+ pattern: li.dataset.pattern || null,
90
+ locked: li.hasAttribute('data-locked'),
91
+ html: copy.innerHTML,
92
+ // Carry the row's own attributes into the option, minus combobox-managed ones.
93
+ attrs: Array.from(li.attributes)
94
+ .filter(a => { const n = a.name; return n !== 'role' && n !== 'id' && n !== 'aria-selected' && !isBinding(n); })
95
+ .map(a => [a.name, a.value])
96
+ };
97
+ });
86
98
  }
87
99
  return Array.from(src.querySelectorAll('option')).map(o => ({
88
100
  value: o.value || o.textContent.trim(),
@@ -308,7 +320,10 @@ function initializeComboboxPlugin() {
308
320
  // stays a data layer x-for/$x can own (reread below mirrors it), the combobox
309
321
  // owns the listbox. readOptions keeps each row's innerHTML, preserving markup.
310
322
  menu = document.createElement('menu');
311
- document.body.appendChild(menu); // anchor positioning needs it out of overflow contexts
323
+ menu.setAttribute('x-ignore', ''); // plugin-owned rows Alpine must never walk them
324
+ // Nearest popover host, so an outer auto popover isn't light-dismissed
325
+ // by listbox clicks; body otherwise (out of overflow contexts either way).
326
+ (el.closest('[popover]') || document.body).appendChild(menu);
312
327
  generatedMenu = menu; // tracked so cleanup() can remove it on re-render
313
328
  if (src) src.style.setProperty('display', 'none', 'important');
314
329
  menu.setAttribute('popover', 'manual');
package/lib/manifest.css CHANGED
@@ -28,7 +28,13 @@
28
28
  overflow-wrap: break-word;
29
29
  -moz-tab-size: 4;
30
30
  -o-tab-size: 4;
31
- tab-size: 4
31
+ tab-size: 4;
32
+
33
+ /* Device safe-area insets (0 unless viewport-fit=cover on a notched screen) */
34
+ --safe-top: env(safe-area-inset-top, 0px);
35
+ --safe-right: env(safe-area-inset-right, 0px);
36
+ --safe-bottom: env(safe-area-inset-bottom, 0px);
37
+ --safe-left: env(safe-area-inset-left, 0px)
32
38
  }
33
39
 
34
40
  :where(html),
@@ -2326,28 +2332,45 @@
2326
2332
  justify-content: space-between;
2327
2333
  gap: 0.25rem;
2328
2334
  width: 100%;
2335
+ contain: inline-size;
2329
2336
  padding: 0.5rem 0;
2330
2337
  border-top: 1px solid var(--color-line, color-mix(oklch(20.5% 0 0) 10%, transparent));
2331
2338
 
2332
2339
  /* Today | Clear buttons */
2333
- & button {
2334
- width: fit-content
2340
+ &>button {
2341
+ width: fit-content;
2342
+ background: transparent;
2343
+ color: var(--color-content-neutral, oklch(43.9% 0 0));
2344
+
2345
+ &:hover {
2346
+ background: var(--color-field-surface, color-mix(in oklch, oklch(20.5% 0 0) 10%, transparent));
2347
+ color: var(--color-content-stark, oklch(20.5% 0 0))
2348
+ }
2335
2349
  }
2336
2350
 
2337
2351
  /* Preset chips */
2338
2352
  &>div {
2339
2353
  flex: 1;
2354
+ min-width: 0;
2340
2355
  display: flex;
2341
- flex-flow: row wrap;
2356
+ flex-wrap: nowrap;
2342
2357
  align-items: center;
2343
- justify-content: center;
2358
+ justify-content: safe center;
2344
2359
  gap: 0.25rem;
2360
+ overflow-x: auto;
2361
+ scrollbar-width: none;
2362
+
2363
+ &::-webkit-scrollbar {
2364
+ display: none
2365
+ }
2345
2366
 
2346
2367
  & button {
2368
+ flex: 0 0 auto;
2347
2369
  height: auto;
2348
2370
  min-height: 0;
2349
2371
  padding: 0.25rem 0.625rem;
2350
2372
  font-size: 0.75rem;
2373
+ white-space: nowrap;
2351
2374
  background: var(--color-field-surface, color-mix(oklch(20.5% 0 0) 10%, transparent));
2352
2375
  color: var(--color-content-stark, oklch(20.5% 0 0));
2353
2376
  border: none;
@@ -2640,6 +2663,63 @@
2640
2663
  }
2641
2664
  }
2642
2665
 
2666
+ /* Manifest Dock */
2667
+
2668
+ @layer components {
2669
+
2670
+ /* Fixed bottom navigation bar. Best practice: <nav dock aria-label="Primary">.
2671
+ Physics (fixed + safe-area + item layout) baked in; skin is themeable via
2672
+ the --dock-* variables. Edge-to-edge by default; set --dock-inset (+ radius)
2673
+ to float it. */
2674
+ :where([dock]) {
2675
+ position: fixed;
2676
+ z-index: 40;
2677
+ inset-inline: var(--dock-inset, 0px);
2678
+ bottom: var(--dock-inset, 0px);
2679
+ display: flex;
2680
+ align-items: stretch;
2681
+ justify-content: space-around;
2682
+ gap: var(--dock-gap, 0px);
2683
+ /* Clear the home indicator in both modes: total bottom clearance == --safe-bottom */
2684
+ padding-bottom: max(calc(var(--safe-bottom, 0px) - var(--dock-inset, 0px)), 0px);
2685
+ background: var(--dock-surface, var(--color-surface-1, oklch(96.7% 0 0)));
2686
+ border-top: var(--dock-border-top, 1px solid var(--color-line, color-mix(in oklch, currentColor 11%, transparent)));
2687
+ border-radius: var(--dock-radius, 0px);
2688
+ box-shadow: var(--dock-shadow, none)
2689
+ }
2690
+
2691
+ /* Dock items */
2692
+ :where([dock] > a, [dock] > button) {
2693
+ flex: 1 1 0;
2694
+ display: flex;
2695
+ flex-direction: column;
2696
+ align-items: center;
2697
+ justify-content: center;
2698
+ gap: 0.125rem;
2699
+ min-height: 3.25rem;
2700
+ padding: 0.375rem 0.25rem;
2701
+ background: transparent;
2702
+ color: var(--color-content-subtle, oklch(55.6% 0 0));
2703
+ font-size: 0.6875rem;
2704
+ line-height: 1.1;
2705
+ text-decoration: none
2706
+ }
2707
+
2708
+ /* Dock icons */
2709
+ :where([dock] :is([x-icon], svg, img)) {
2710
+ width: 1.375rem;
2711
+ height: 1.375rem;
2712
+ font-size: 1.375rem
2713
+ }
2714
+
2715
+ /* Active item — a hook, not a look (bind :aria-current="$route === '/x' ? 'page' : null') */
2716
+ [dock] > a[aria-current="page"],
2717
+ [dock] > button[aria-current="page"],
2718
+ [dock] > .selected {
2719
+ color: var(--color-content-stark, oklch(14.5% 0 0))
2720
+ }
2721
+ }
2722
+
2643
2723
  /* Manifest Dropdowns */
2644
2724
  [popover]:not(.unstyle):not(:popover-open) {
2645
2725
  display: none;
@@ -2669,8 +2749,8 @@
2669
2749
  transform-origin: top center;
2670
2750
  overflow-x: hidden;
2671
2751
 
2672
- /* Options */
2673
- & :where(li, a, button, [role=button], label):not(.unstyle) {
2752
+ /* Options (labels only when flat items — field wrappers keep form layout) */
2753
+ & :where(li, a, button, [role=button], label:not(:has(input:not([type=checkbox], [type=radio], [type=hidden]), select, textarea, .combobox, .label, label))):not(.unstyle) {
2674
2754
  display: inline-flex;
2675
2755
  justify-content: start;
2676
2756
  align-items: center;
@@ -2730,13 +2810,14 @@
2730
2810
  background-color: var(--color-line, color-mix(oklch(20.5% 0 0) 10%, transparent))
2731
2811
  }
2732
2812
 
2733
- /* Labels */
2734
- & label {
2813
+ /* Labels (not inner field-surface wrappers) */
2814
+ & label:not(.unstyle):not(:has(> input[type=search], > input[type=file], > button[command="--copy"])) {
2735
2815
  padding-inline-start: 0.5rem;
2736
2816
  padding-inline-end: 0.5rem;
2737
2817
  cursor: default;
2738
2818
 
2739
- &:hover {
2819
+ &:hover,
2820
+ &:active {
2740
2821
  background-color: transparent
2741
2822
  }
2742
2823
 
@@ -2745,8 +2826,9 @@
2745
2826
  }
2746
2827
  }
2747
2828
 
2748
- /* Inputs and textareas */
2749
- & :where(input, textarea) {
2829
+ /* Inputs and textareas placed as direct items */
2830
+ &>:where(input, textarea),
2831
+ & :where(li)>:where(input, textarea) {
2750
2832
  flex-shrink: 0;
2751
2833
 
2752
2834
  &:not(:first-child) {
@@ -2774,12 +2856,12 @@
2774
2856
  }
2775
2857
 
2776
2858
  /* Dark theme */
2777
- :where(.dark menu[popover]) :where(li, a, button, label):hover {
2859
+ :where(.dark menu[popover]) :where(li, a, button, label:not(:has(input:not([type=checkbox], [type=radio], [type=hidden]), select, textarea, .combobox, .label, label))):hover {
2778
2860
  background-color: var(--color-field-surface-hover, color-mix(oklch(20.5% 0 0) 15%, transparent))
2779
2861
  }
2780
2862
 
2781
- /* Nested menu alignment */
2782
- :where(menu menu):where(:not(.unstyle)) {
2863
+ /* Nested menu alignment (not combobox listboxes, which anchor to their field) */
2864
+ :where(menu menu:not([role=listbox])):where(:not(.unstyle)) {
2783
2865
  position-area: span-end end;
2784
2866
  margin: 0 var(--spacing-popover-offset, 0.5rem)
2785
2867
  }
@@ -3099,7 +3181,7 @@
3099
3181
  z-index: 1
3100
3182
  }
3101
3183
 
3102
- /* Equal-width children (give the wrapper a width if children need more room) */
3184
+ /* Equal-width children */
3103
3185
  &.even>* {
3104
3186
  flex-shrink: initial;
3105
3187
  width: 100%
@@ -5649,4 +5731,31 @@
5649
5731
  html[data-os="ios"] .no-ios { display: none !important }
5650
5732
  html[data-os="android"] .no-android { display: none !important }
5651
5733
  html[data-os="macos"] .no-apple, html[data-os="ios"] .no-apple { display: none !important }
5734
+
5735
+ /* Visibility — connectivity & container (html[data-*] set by the $device signal) */
5736
+ html:not([data-online="false"]) .offline-only { display: none !important }
5737
+ html[data-online="false"] .online-only { display: none !important }
5738
+ html:not([data-standalone]) .standalone-only { display: none !important }
5739
+ html:not([data-native]) .native-only { display: none !important }
5740
+ html[data-native] .web-only { display: none !important }
5741
+
5742
+ /* Safe-area padding (var --safe-* is 0 unless viewport-fit=cover on a notched screen) */
5743
+ :where(.pt-safe) { padding-top: var(--safe-top) }
5744
+ :where(.pr-safe) { padding-right: var(--safe-right) }
5745
+ :where(.pb-safe) { padding-bottom: var(--safe-bottom) }
5746
+ :where(.pl-safe) { padding-left: var(--safe-left) }
5747
+ :where(.px-safe) { padding-left: var(--safe-left); padding-right: var(--safe-right) }
5748
+ :where(.py-safe) { padding-top: var(--safe-top); padding-bottom: var(--safe-bottom) }
5749
+ :where(.p-safe) { padding: var(--safe-top) var(--safe-right) var(--safe-bottom) var(--safe-left) }
5750
+
5751
+ /* Dynamic viewport height (avoids the mobile 100vh browser-chrome jump) */
5752
+ :where(.min-h-dvh) { min-height: 100dvh }
5753
+ :where(.h-dvh) { height: 100dvh }
5754
+
5755
+ /* Native — suppress web tells (opt-in) */
5756
+ :where(.no-callout) { -webkit-touch-callout: none }
5757
+ :where(.no-select) { -webkit-user-select: none; user-select: none }
5758
+ :where(.no-tap-zoom) { touch-action: manipulation }
5759
+ :where(.no-overscroll) { overscroll-behavior: none }
5760
+ :where(.no-overscroll-y) { overscroll-behavior-y: none }
5652
5761
  }
@@ -373,28 +373,45 @@
373
373
  justify-content: space-between;
374
374
  gap: 0.25rem;
375
375
  width: 100%;
376
+ contain: inline-size;
376
377
  padding: 0.5rem 0;
377
378
  border-top: 1px solid var(--color-line, color-mix(oklch(20.5% 0 0) 10%, transparent));
378
379
 
379
380
  /* Today | Clear buttons */
380
- & button {
381
- width: fit-content
381
+ &>button {
382
+ width: fit-content;
383
+ background: transparent;
384
+ color: var(--color-content-neutral, oklch(43.9% 0 0));
385
+
386
+ &:hover {
387
+ background: var(--color-field-surface, color-mix(in oklch, oklch(20.5% 0 0) 10%, transparent));
388
+ color: var(--color-content-stark, oklch(20.5% 0 0))
389
+ }
382
390
  }
383
391
 
384
392
  /* Preset chips */
385
393
  &>div {
386
394
  flex: 1;
395
+ min-width: 0;
387
396
  display: flex;
388
- flex-flow: row wrap;
397
+ flex-wrap: nowrap;
389
398
  align-items: center;
390
- justify-content: center;
399
+ justify-content: safe center;
391
400
  gap: 0.25rem;
401
+ overflow-x: auto;
402
+ scrollbar-width: none;
403
+
404
+ &::-webkit-scrollbar {
405
+ display: none
406
+ }
392
407
 
393
408
  & button {
409
+ flex: 0 0 auto;
394
410
  height: auto;
395
411
  min-height: 0;
396
412
  padding: 0.25rem 0.625rem;
397
413
  font-size: 0.75rem;
414
+ white-space: nowrap;
398
415
  background: var(--color-field-surface, color-mix(oklch(20.5% 0 0) 10%, transparent));
399
416
  color: var(--color-content-stark, oklch(20.5% 0 0));
400
417
  border: none;
@@ -45,8 +45,8 @@
45
45
  transform-origin: top center;
46
46
  overflow-x: hidden;
47
47
 
48
- /* Options */
49
- & :where(li, a, button, [role=button], label):not(.unstyle) {
48
+ /* Options (labels only when flat items — field wrappers keep form layout) */
49
+ & :where(li, a, button, [role=button], label:not(:has(input:not([type=checkbox], [type=radio], [type=hidden]), select, textarea, .combobox, .label, label))):not(.unstyle) {
50
50
  display: inline-flex;
51
51
  justify-content: start;
52
52
  align-items: center;
@@ -106,13 +106,14 @@
106
106
  background-color: var(--color-line, color-mix(oklch(20.5% 0 0) 10%, transparent))
107
107
  }
108
108
 
109
- /* Labels */
110
- & label {
109
+ /* Labels (not inner field-surface wrappers) */
110
+ & label:not(.unstyle):not(:has(> input[type=search], > input[type=file], > button[command="--copy"])) {
111
111
  padding-inline-start: 0.5rem;
112
112
  padding-inline-end: 0.5rem;
113
113
  cursor: default;
114
114
 
115
- &:hover {
115
+ &:hover,
116
+ &:active {
116
117
  background-color: transparent
117
118
  }
118
119
 
@@ -121,8 +122,9 @@
121
122
  }
122
123
  }
123
124
 
124
- /* Inputs and textareas */
125
- & :where(input, textarea) {
125
+ /* Inputs and textareas placed as direct items */
126
+ &>:where(input, textarea),
127
+ & :where(li)>:where(input, textarea) {
126
128
  flex-shrink: 0;
127
129
 
128
130
  &:not(:first-child) {
@@ -150,12 +152,12 @@
150
152
  }
151
153
 
152
154
  /* Dark theme */
153
- :where(.dark menu[popover]) :where(li, a, button, label):hover {
155
+ :where(.dark menu[popover]) :where(li, a, button, label:not(:has(input:not([type=checkbox], [type=radio], [type=hidden]), select, textarea, .combobox, .label, label))):hover {
154
156
  background-color: var(--color-field-surface-hover, color-mix(oklch(20.5% 0 0) 15%, transparent))
155
157
  }
156
158
 
157
- /* Nested menu alignment */
158
- :where(menu menu):where(:not(.unstyle)) {
159
+ /* Nested menu alignment (not combobox listboxes, which anchor to their field) */
160
+ :where(menu menu:not([role=listbox])):where(:not(.unstyle)) {
159
161
  position-area: span-end end;
160
162
  margin: 0 var(--spacing-popover-offset, 0.5rem)
161
163
  }
@@ -16,7 +16,7 @@
16
16
  z-index: 1
17
17
  }
18
18
 
19
- /* Equal-width children (give the wrapper a width if children need more room) */
19
+ /* Equal-width children */
20
20
  &.even>* {
21
21
  flex-shrink: initial;
22
22
  width: 100%
@@ -2,12 +2,12 @@
2
2
  "manifest.appwrite.auth.js": "sha384-mHMJ2sVgZ82AdmLXK6ZyUpkWyX6KOtmIf5P/ribCUb2ejtvu/lwMhB68IgAcy9po",
3
3
  "manifest.appwrite.data.js": "sha384-KFKVo5TQESLJB7wgk+C8Zb/sAl43QAMdioWW3tQRWYq5umfdpGCZ5Hld9y9f6/Np",
4
4
  "manifest.appwrite.presences.js": "sha384-JyVDWFT69b7Svq8J8p9tNWe2v9PZNQBY5UyhtOl1vnuHFAX0CRQ3MA9SiKq5wzNh",
5
- "manifest.charts.js": "sha384-wTQlB3hJ9V9lmisCgm8l6YkpJV+nA4+Vci3dI0x1nBtWnS9TFr+eXaJelP7AusyD",
5
+ "manifest.charts.js": "sha384-qQrjhWkIDi4oU5uQrpy8DdlMjY+gfiivBuX6mssNiFdkawgkVBvKneGgp+nd3PYJ",
6
6
  "manifest.chat.js": "sha384-3+eva9YYz5DrdS3DeRGw9oXH/CreuYX+ZHHtYP25vmnYDHiEpFf9nEGLvwSHBXjz",
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-ZPOkx4hKGlcrOqeb3MmWLyBfjZXlnkP47Yc9VLE7yHds6dXRqMeB5DPFeJl3NZS/",
10
+ "manifest.combobox.js": "sha384-iJpm6Ufqrmm+zdl1YjPUeBkv60gMUxxe/P4Da9rNBuLyo04wkijU7KHxS/W7IG3Q",
11
11
  "manifest.components.js": "sha384-eZDib+RFdwd9mOUdi6o53eNS0LBlEEkZPDegxtkAcE+GrGIFxsuB4O0ymfixGNfM",
12
12
  "manifest.data.js": "sha384-3lLvGnqcQr4Ium80MdaQTr8RytjVbs5rnxd0q41BF3ppcoQccMFAcvgFSyZP/0sl",
13
13
  "manifest.datepicker.js": "sha384-e6rJI0BxuE+AyI96/u6XoeOstsxLuQTO6+wYSr6rm2aKhOP2APM2xJqTz//0v4Pw",
@@ -16,6 +16,7 @@
16
16
  "manifest.icons.js": "sha384-kKOINpX70YzNO3FvkV6umrmO3f0KIu0DNbwB+ej55a7y/lhU7v/myQtXL0bwCTbt",
17
17
  "manifest.localization.js": "sha384-MUetuW8PllIM+9JDMw/tx+qGem8t+ZUN5WZpycsTDKtz+LaX4DJehsfh/YZt9J7L",
18
18
  "manifest.markdown.js": "sha384-qlhYOzX0nl8HzuaiMVTkIhsma9mv3PlEvHO2S0AzshgmIcdZFvth6/sbHPCe5Zwr",
19
+ "manifest.native.js": "sha384-4WfuciRjqrfDLpHXp91AKQUTt8FDHaupSuVErRgdeaeoHf5d1CfHWqBd/aCOduDk",
19
20
  "manifest.payments.js": "sha384-vhG1ZL3wGzb0vt0ygJJQF3jVQumMuvc2smGjYezuyQ+auN+MN9HY2k5NW2bdClxl",
20
21
  "manifest.resize.js": "sha384-S3v4RAJoA77LUH6MddYG9bx//dZX//up7XWeH69Ql2ge3bVHgC6mR/CJBe6y4nQI",
21
22
  "manifest.router.js": "sha384-oV0lwFkeevEWjRCC/VV2OzkjNdE70owBz+pwKqZzx5bqbx5/1PORW+ndCClEqSgS",
@@ -27,7 +28,7 @@
27
28
  "manifest.toasts.js": "sha384-ytd5rDbax/Ou9z23uedFXPZbxDPsk2E/pxCTq4WLvfv+os1qTI6kELp0kPp07g24",
28
29
  "manifest.tooltips.js": "sha384-HeJ0BGK3ebut1t8lm+MGeUUxd1pOZDyX8e6HFLsa4kAiRL+LwGDr67ckkRqv7to1",
29
30
  "manifest.url.parameters.js": "sha384-FIufiClqDx1rJpU/QUc9z/D43qClQ6Qm8rBahipbJl9BDHUvhrOsUDegmTWW7Tuf",
30
- "manifest.utilities.js": "sha384-MtzNaVOrgyepjlY5nz38pAJk67yiFPU1H5WoPNfcNbUZTXy+oin1lrtKCIcobEOJ",
31
+ "manifest.utilities.js": "sha384-yLEqEbd1FncqswrtrcTO7KrIEK4iP0ZvqCi64XLI7D77/L0YiJbjLdLGGg6l7rgN",
31
32
  "manifest.virtual.js": "sha384-c194pXD0Ld48QJ+HPVNibvbn54/ETJRuYhUOWesEBjtOIQcQMIKo0DnCyQMvDlLg",
32
- "manifest.js": "sha384-RusFaByZu31LkLZCAhjgtJC8iWksvmjTKS8D5Xj9/4N7B/Xr4DDyFGmiL23kajgK"
33
+ "manifest.js": "sha384-FLz68HEE8hEVLlAsoAX2+5cWBrhS5+B24rdJy5obXtP5U5bJKCswjbIQL8MpDUFZ"
33
34
  }
package/lib/manifest.js CHANGED
@@ -265,7 +265,8 @@
265
265
  // Plugin dependencies: plugins that require other plugins to be loaded first
266
266
  const PLUGIN_DEPENDENCIES = {
267
267
  'appwrite-data': ['data'],
268
- 'appwrite-presences': ['data', 'appwrite-auth']
268
+ 'appwrite-presences': ['data', 'appwrite-auth'],
269
+ 'native': ['utilities']
269
270
  };
270
271
 
271
272
  // Derive default plugin list from manifest (only load data/localization/components when manifest needs them)
@@ -488,6 +489,14 @@
488
489
  return [];
489
490
  }
490
491
 
492
+ // Detect the native umbrella plugin. Auto-loaded inside a Capacitor container
493
+ // (window.Capacitor present) or when a `native` config block opts in on the web.
494
+ function detectNativePlugins(manifest) {
495
+ if (typeof window !== 'undefined' && window.Capacitor) return ['native'];
496
+ if (manifest && typeof manifest === 'object' && manifest.native && typeof manifest.native === 'object') return ['native'];
497
+ return [];
498
+ }
499
+
491
500
  // Parse data attributes
492
501
  function parseDataAttributes() {
493
502
  // Try to get current script first, then fall back to querySelector
@@ -560,7 +569,7 @@
560
569
  // Expose API
561
570
  window.Manifest = {
562
571
  loadPlugin: function (pluginName, version = DEFAULT_VERSION) {
563
- const allPlugins = [...AVAILABLE_PLUGINS, ...APPWRITE_PLUGINS, 'payments', 'chat'];
572
+ const allPlugins = [...AVAILABLE_PLUGINS, ...APPWRITE_PLUGINS, 'payments', 'chat', 'native'];
564
573
  if (!allPlugins.includes(pluginName)) {
565
574
  console.warn(`[Manifest Loader] Unknown plugin: ${pluginName}`);
566
575
  return Promise.reject(new Error(`Unknown plugin: ${pluginName}`));
@@ -654,12 +663,18 @@
654
663
  const appwritePlugins = detectAppwritePlugins(manifest);
655
664
  const paymentsPlugins = detectPaymentsPlugins(manifest);
656
665
  const chatPlugins = detectChatPlugins(manifest);
657
- pluginsToLoad = resolveDependencies([...corePlugins, ...appwritePlugins, ...paymentsPlugins, ...chatPlugins]);
666
+ const nativePlugins = detectNativePlugins(manifest);
667
+ pluginsToLoad = resolveDependencies([...corePlugins, ...appwritePlugins, ...paymentsPlugins, ...chatPlugins, ...nativePlugins]);
658
668
  } else {
659
669
  const needsManifest = config.plugins.some(p => MANIFEST_DEPENDENT_PLUGINS.includes(p));
660
670
  if (needsManifest) {
661
671
  manifestPromise = fetch(manifestUrl).then(r => r.ok ? r.json() : null).catch(() => null);
662
672
  }
673
+ // Inside a Capacitor container, ensure the native umbrella loads even on
674
+ // the explicit data-plugins path (matches the derive-path auto-inject).
675
+ if (typeof window !== 'undefined' && window.Capacitor && !pluginsToLoad.includes('native')) {
676
+ pluginsToLoad = resolveDependencies([...pluginsToLoad, 'native']);
677
+ }
663
678
  }
664
679
 
665
680
  const pluginPromises = pluginsToLoad.map(pluginName => {