mnfst 0.5.163 → 0.5.164

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.
@@ -66,6 +66,8 @@ window.ManifestComponentsRegistry = {
66
66
  });
67
67
  if (res.ok) {
68
68
  manifest = await res.json();
69
+ // No-loader path: resolve ${VAR} placeholders the dynamic loader would have.
70
+ window.ManifestDataConfig?.interpolateManifest?.(manifest);
69
71
  } else {
70
72
  console.warn('[Manifest] Failed to load manifest.json (HTTP', res.status + ')');
71
73
  }
@@ -151,16 +153,9 @@ window.ManifestComponentsLoader = {
151
153
 
152
154
  // Components processor
153
155
 
154
- // Escape a string so it's safe to interpolate inside a single-quoted JS
155
- // string AND inside backtick template literals. The original escape covered
156
- // the single-quote + whitespace cases but missed three vectors that matter
157
- // when component templates use backtick literals (e.g. x-text="`Hi $modify('n')`"):
158
- // - `\` → a trailing backslash would escape the closing quote
159
- // - `` ` `` → terminates a backtick template literal
160
- // - `${` → opens an interpolation that Alpine evaluates as JS
161
- // Without escaping those, a bound value like `${alert(1)}` from a data
162
- // source becomes code execution inside the wrapping template literal.
163
- // Backslash must be escaped FIRST so the other replacements don't compound.
156
+ // Escape a value for safe interpolation in single-quoted JS strings AND
157
+ // backtick template literals. Backslash, backtick, and ${ must be escaped or a
158
+ // bound value like `${alert(1)}` becomes code execution; escape backslash FIRST.
164
159
  function escapeForSingleQuotedJsString(s) {
165
160
  return String(s)
166
161
  .replace(/\\/g, '\\\\')
@@ -184,8 +179,7 @@ window.ManifestComponentsProcessor = {
184
179
  return;
185
180
  }
186
181
  if (element.hasAttribute('data-pre-rendered') || element.hasAttribute('data-processed')) {
187
- // Pre-rendered components skip re-fetching, but hydrate-marked content
188
- // still needs Alpine initialization (x-data, @click, :class, x-color etc.).
182
+ // Pre-rendered content skips re-fetch but still needs Alpine init.
189
183
  if (element.hasAttribute('data-pre-rendered') && window.Alpine && typeof window.Alpine.initTree === 'function') {
190
184
  try { window.Alpine.initTree(element); } catch (e) { /* graceful */ }
191
185
  }
@@ -226,10 +220,10 @@ window.ManifestComponentsProcessor = {
226
220
  const props = {};
227
221
  Array.from(element.attributes).forEach(attr => {
228
222
  if (attr.name !== name && attr.name !== 'class' && !attr.name.startsWith('data-')) {
229
- // Store both original case and lowercase for flexibility
223
+ // Store original case and lowercase
230
224
  props[attr.name] = attr.value;
231
225
  props[attr.name.toLowerCase()] = attr.value;
232
- // For Alpine bindings (starting with :), also store without the : prefix
226
+ // Alpine bindings (:foo): also store without the colon
233
227
  if (attr.name.startsWith(':')) {
234
228
  const keyWithoutColon = attr.name.substring(1);
235
229
  props[keyWithoutColon] = attr.value;
@@ -292,32 +286,25 @@ window.ManifestComponentsProcessor = {
292
286
  if (!val || val.trim() === '' || /^[\r\n\t\s]+$/.test(val)) {
293
287
  return value.includes('||') ? 'null' : "''";
294
288
  }
295
- // If value starts with $, it's an Alpine expression - don't quote
289
+ // $-prefixed values are Alpine expressions don't quote.
296
290
  if (val.startsWith('$')) {
297
- // Special handling for x-for, x-if, and x-show with $x data source expressions
298
- // Add safe fallbacks to prevent errors during initial render when data source hasn't loaded yet
291
+ // Guard $x data-source expressions on x-for/if/show against
292
+ // errors before the source loads: optional-chain + fallback.
299
293
  if ((attr.name === 'x-for' || attr.name === 'x-if' || attr.name === 'x-show') && val.startsWith('$x') && !val.includes('??')) {
300
- // Convert regular property access dots to optional chaining for safe navigation
301
294
  let safeVal = val.replace(/\./g, '?.');
302
- // Add fallback based on directive type (only if user hasn't already provided one)
303
295
  if (attr.name === 'x-for') {
304
- // x-for needs an iterable, so fallback to empty array
305
296
  return `${safeVal} ?? []`;
306
297
  } else {
307
- // x-if and x-show evaluate to boolean, fallback to false
308
298
  return `${safeVal} ?? false`;
309
299
  }
310
300
  }
311
301
  return val;
312
302
  }
313
- // Special handling for x-for, x-if, and x-show - these can contain expressions
314
- // that reference data sources or other dynamic content
303
+ // x-for/if/show can hold expressions (e.g. "card in $x.data.items") — preserve as-is.
315
304
  if (attr.name === 'x-for' || attr.name === 'x-if' || attr.name === 'x-show') {
316
- // For these directives, preserve the value as-is to allow Alpine to evaluate it
317
- // This is critical for x-for expressions like "card in $x.data.items"
318
305
  return val;
319
306
  }
320
- // Always quote string values to ensure they're treated as strings, not variables
307
+ // Quote everything else so it's treated as a string.
321
308
  return `'${escapeForSingleQuotedJsString(val)}'`;
322
309
  }
323
310
  );
@@ -369,19 +356,16 @@ window.ManifestComponentsProcessor = {
369
356
  rootElement.setAttribute('data-component', instanceId);
370
357
  }
371
358
  });
372
- // After rendering, copy all attributes from the original placeholder to the first top-level element
373
- // Note: This block ensures the first element has all attributes, including those that might have been
374
- // skipped by the first loop due to conditions. Classes are already handled in the first loop, so we skip them here.
359
+ // Copy any placeholder attributes the first loop skipped onto the first
360
+ // root element (classes already handled there, so skip them).
375
361
  if (topLevelElements.length > 0) {
376
362
  const firstRoot = topLevelElements[0];
377
363
  Array.from(element.attributes).forEach(attr => {
378
- // Skip attributes that were already handled in the first loop
379
- // Classes are always handled in the first loop, so skip them here to avoid duplication
380
364
  if (attr.name === 'class') {
381
- return; // Skip - already handled in first loop
365
+ return;
382
366
  }
383
367
 
384
- // Preserve important attributes including data-order, x-route, and other routing/data attributes
368
+ // Routing/data attributes to preserve
385
369
  const preserveAttributes = [
386
370
  'data-order', 'x-route', 'data-component', 'data-head',
387
371
  'x-route-*', 'data-route-*', 'x-tabpanel'
@@ -396,32 +380,25 @@ window.ManifestComponentsProcessor = {
396
380
  (attr.name !== name && !attr.name.startsWith('data-')) ||
397
381
  attr.name === 'data-order' || attr.name === 'x-route' || attr.name === 'data-head';
398
382
 
399
- // Only apply if: (1) it wasn't handled in first loop, OR (2) it should be preserved, AND (3) it's not in the skip list
383
+ // Apply if unhandled or preserved, and not in the skip list.
400
384
  if ((!alreadyHandledInFirstLoop || shouldPreserve) &&
401
385
  !['data-original-placeholder', 'data-pre-rendered', 'data-processed'].includes(attr.name)) {
402
386
  if (attr.name.startsWith('x-') || attr.name.startsWith(':') || attr.name.startsWith('@')) {
403
- // For Alpine directives, merge if they already exist (for x-data, combine objects)
387
+ // x-data: merge two object literals; otherwise replace.
404
388
  if (attr.name === 'x-data' && firstRoot.hasAttribute('x-data')) {
405
- // For x-data, we need to merge the objects - this is complex, so for now we'll append
406
- // The user should structure their x-data to avoid conflicts
407
389
  const existing = firstRoot.getAttribute('x-data');
408
- // If both are objects, try to merge them
409
390
  if (existing.trim().startsWith('{') && attr.value.trim().startsWith('{')) {
410
- // Remove outer braces and merge
411
391
  const existingContent = existing.trim().slice(1, -1).trim();
412
392
  const newContent = attr.value.trim().slice(1, -1).trim();
413
393
  const merged = `{ ${existingContent}${existingContent && newContent ? ', ' : ''}${newContent} }`;
414
394
  firstRoot.setAttribute('x-data', merged);
415
395
  } else {
416
- // If not both objects, replace (user should handle this case)
417
396
  firstRoot.setAttribute(attr.name, attr.value);
418
397
  }
419
398
  } else {
420
- // For other Alpine directives, replace if they exist
421
399
  firstRoot.setAttribute(attr.name, attr.value);
422
400
  }
423
401
  } else {
424
- // For other attributes, replace if they exist
425
402
  firstRoot.setAttribute(attr.name, attr.value);
426
403
  }
427
404
  }
@@ -435,18 +412,14 @@ window.ManifestComponentsProcessor = {
435
412
  const fragment = document.createDocumentFragment();
436
413
  topLevelElements.forEach(el => fragment.appendChild(el));
437
414
 
438
- // Replace the placeholder element with the component content
439
- // Alpine will auto-initialize on DOM insertion, but we need to ensure
440
- // magic methods are ready first. If data plugin is ready, give it a tick
441
- // to ensure Alpine has processed the magic method registration.
442
415
  parent.replaceChild(fragment, element);
443
416
 
444
- // Manually initialize Alpine on the swapped-in elements after ensuring
445
- // magic methods are available. This prevents "i is not a function" errors.
417
+ // Manually init Alpine on the swapped-in elements once magic methods are
418
+ // ready prevents "i is not a function" errors.
446
419
  if (window.Alpine && typeof window.Alpine.initTree === 'function') {
447
420
  const initAlpine = () => {
448
- // CRITICAL: Ensure auth convenience methods are initialized before Alpine evaluates expressions
449
- // This prevents "$auth.isCreatingTeam is not a function" errors after idle/reinitialization
421
+ // Init auth convenience methods before Alpine evaluates expressions,
422
+ // else "$auth.isCreatingTeam is not a function" after reinit.
450
423
  if (window.ManifestAppwriteAuthTeamsConvenience && window.ManifestAppwriteAuthTeamsConvenience.initialize) {
451
424
  try {
452
425
  const authStore = window.Alpine.store('auth');
@@ -458,10 +431,8 @@ window.ManifestComponentsProcessor = {
458
431
  }
459
432
  }
460
433
 
461
- // Re-initialize Alpine on the swapped elements
462
- // This ensures magic methods are available when expressions are evaluated
463
434
  topLevelElements.forEach(el => {
464
- if (!el.__x) { // Only init if not already initialized
435
+ if (!el.__x) {
465
436
  try {
466
437
  window.Alpine.initTree(el);
467
438
  } catch (e) {
@@ -471,7 +442,7 @@ window.ManifestComponentsProcessor = {
471
442
  });
472
443
  };
473
444
 
474
- // If data plugin is ready, wait a tick to ensure magic method is processed
445
+ // If the data plugin is ready, wait a tick for its magic method.
475
446
  if (window.__manifestDataMagicRegistered) {
476
447
  if (window.Alpine.nextTick) {
477
448
  window.Alpine.nextTick(initAlpine);
@@ -479,18 +450,16 @@ window.ManifestComponentsProcessor = {
479
450
  setTimeout(initAlpine, 0);
480
451
  }
481
452
  } else {
482
- // Data plugin not ready, initialize immediately (will fail gracefully)
483
453
  initAlpine();
484
454
  }
485
455
  }
486
456
 
487
- // Execute scripts after component is rendered
457
+ // Execute component scripts after render (small delay for the DOM swap)
488
458
  if (scripts.length > 0) {
489
- // Use a small delay to ensure DOM is updated
490
459
  setTimeout(() => {
491
460
  scripts.forEach(script => {
492
461
  if (script.src) {
493
- // External script - create and append to head
462
+ // External script append to head
494
463
  const scriptEl = document.createElement('script');
495
464
  scriptEl.src = script.src;
496
465
  scriptEl.type = script.type;
@@ -498,9 +467,8 @@ window.ManifestComponentsProcessor = {
498
467
  if (script.defer) scriptEl.defer = true;
499
468
  document.head.appendChild(scriptEl);
500
469
  } else if (script.content) {
501
- // Inline script - execute directly
470
+ // Inline script run in global scope
502
471
  try {
503
- // Create a function to execute the script in the global scope
504
472
  const executeScript = new Function(script.content);
505
473
  executeScript();
506
474
  } catch (error) {
@@ -799,25 +767,7 @@ window.ManifestComponentsMutation = {
799
767
  }
800
768
  };
801
769
 
802
- // Components — route-level prefetch.
803
- //
804
- // Two enhancements that run on top of the existing on-encounter loader:
805
- //
806
- // 1. Parallel batch on route change. When manifest:route-change fires,
807
- // scan the [x-route] subtrees that match the new route and call
808
- // loadComponent() on every <x-*> tag inside them. The loader
809
- // deduplicates fetches, so calling it for components that the
810
- // regular swapping logic is already mounting is harmless — but
811
- // pre-issuing in parallel saves 50–200 ms vs. one-by-one fetches.
812
- //
813
- // 2. Prefetch on hover. When the pointer enters an internal <a href>,
814
- // derive the target pathname, find the [x-route] subtree(s) that
815
- // would match it, and prefetch their components. By the time the
816
- // user clicks the link, the components are warm in the loader's
817
- // cache and navigation feels instant.
818
- //
819
- // Both phases require zero author configuration. Manifest auto-discovers
820
- // what to prefetch from the existing [x-route] DOM structure.
770
+ /* Manifest Components — route-level prefetch (batch on route change + on hover) */
821
771
 
822
772
  (function () {
823
773
  'use strict';
@@ -825,20 +775,15 @@ window.ManifestComponentsMutation = {
825
775
  // <x-*> tag pattern — lowercase, hyphenated.
826
776
  const TAG_RE = /^x-[a-z][a-z0-9-]*$/;
827
777
 
828
- // Framework-provided web components (registered by Manifest plugins
829
- // themselves, not as project components in manifest.json). Skip these
830
- // when scanning for project components to prefetch.
778
+ // Framework web components (not project components) — skip when scanning.
831
779
  const FRAMEWORK_TAGS = new Set(['code', 'code-group']);
832
780
 
833
- // Anchors we've already issued a hover-prefetch for. WeakSet so DOM
834
- // garbage-collects naturally as elements leave the tree.
781
+ // Anchors already hover-prefetched. WeakSet so detached nodes GC naturally.
835
782
  const prefetchedAnchors = new WeakSet();
836
783
 
837
784
  function loader() { return window.ManifestComponentsLoader; }
838
785
 
839
- // Match a single route pattern against a normalized pathname (no
840
- // leading/trailing slashes, '/' represented as '/'). Mirrors the
841
- // router visibility logic so prefetch targets the same subtrees.
786
+ // Match a route pattern against a normalized pathname. Mirrors router visibility.
842
787
  function routeMatches(routeValue, pathname) {
843
788
  const pieces = String(routeValue || '').split(',').map((s) => s.trim()).filter(Boolean);
844
789
  let matched = false;
@@ -877,10 +822,7 @@ window.ManifestComponentsMutation = {
877
822
  function discoverComponentNames(root) {
878
823
  const names = new Set();
879
824
  if (!root || !root.querySelectorAll) return names;
880
- // querySelectorAll('*') is the fastest path for "every descendant".
881
- // We filter by tag name in JS — there's no CSS selector for "tag
882
- // name starts with x-". A page typically has a few thousand nodes,
883
- // which scans in well under a millisecond.
825
+ // No CSS selector for "tag starts with x-", so scan all and filter in JS.
884
826
  root.querySelectorAll('*').forEach((el) => {
885
827
  const tag = el.tagName.toLowerCase();
886
828
  if (!tag.startsWith('x-') || !TAG_RE.test(tag)) return;
@@ -925,10 +867,7 @@ window.ManifestComponentsMutation = {
925
867
  prefetchForRoute(pathname);
926
868
  });
927
869
 
928
- // 2) Hover prefetch. Use pointerover (bubbles) and check the closest
929
- // anchor on each event so we get a single trigger per anchor entry
930
- // without needing pointerenter (which doesn't bubble). Dedup via
931
- // a WeakSet so repeat moves within the anchor don't re-scan.
870
+ // 2) Hover prefetch. pointerover bubbles (pointerenter doesn't); WeakSet dedups.
932
871
  document.addEventListener('pointerover', (e) => {
933
872
  if (!e.target || !e.target.closest) return;
934
873
  const a = e.target.closest('a[href]');
package/lib/manifest.css CHANGED
@@ -3233,138 +3233,6 @@
3233
3233
  }
3234
3234
  }
3235
3235
 
3236
- /* Manifest Presence - Visual Indicators */
3237
-
3238
- /* CSS Variables - move to manifest.theme.css after testing completes */
3239
- :root {
3240
- /* Focus indicator */
3241
- --presence-focus-outline-width: 2px;
3242
- --presence-focus-outline-style: dashed;
3243
- --presence-focus-outline-offset: 2px;
3244
- --presence-focus-opacity: 0.6;
3245
- --presence-focus-color: #3b82f6;
3246
-
3247
- /* Text caret indicator */
3248
- --presence-caret-width: 2px;
3249
- --presence-caret-height: 1.2em;
3250
- --presence-caret-color: #3b82f6;
3251
- --presence-caret-z-index: 1000;
3252
- --presence-caret-blink-duration: 1s;
3253
- --presence-caret-opacity-high: 1;
3254
- --presence-caret-opacity-low: 0.3;
3255
-
3256
- /* Text selection highlight */
3257
- --presence-selection-color: #3b82f6;
3258
- --presence-selection-opacity: 0.2;
3259
- --presence-selection-z-index: 999;
3260
-
3261
- /* Cursor indicator (if rendered) */
3262
- --presence-cursor-size: 8px;
3263
- --presence-cursor-border-width: 2px;
3264
- --presence-cursor-z-index: 1001;
3265
-
3266
- /* Timing and threshold configuration (read by JavaScript) */
3267
- --presence-throttle: 300ms;
3268
- --presence-cleanup-interval: 30000ms;
3269
- --presence-min-change-threshold: 5px;
3270
- --presence-idle-threshold: 5000ms;
3271
- }
3272
-
3273
- @layer elements {
3274
-
3275
- /* Focus indicator - applied to elements focused by other users */
3276
- :where(.presence-focused) {
3277
- outline: var(--presence-focus-outline-width) var(--presence-focus-outline-style) var(--presence-focus-color);
3278
- outline-offset: var(--presence-focus-outline-offset);
3279
- opacity: var(--presence-focus-opacity);
3280
- }
3281
-
3282
- /* Dynamic focus color via data attribute */
3283
- :where(.presence-focused[data-presence-focus-color]) {
3284
- --presence-focus-color: attr(data-presence-focus-color);
3285
- outline-color: var(--presence-focus-color);
3286
- }
3287
-
3288
- /* Caret indicator for other users' text cursors */
3289
- :where(.presence-caret) {
3290
- position: absolute;
3291
- width: var(--presence-caret-width);
3292
- height: var(--presence-caret-height);
3293
- background-color: var(--presence-caret-color);
3294
- pointer-events: none;
3295
- z-index: var(--presence-caret-z-index);
3296
- animation: presence-caret-blink var(--presence-caret-blink-duration) infinite;
3297
- }
3298
-
3299
- /* Dynamic caret color via data attribute */
3300
- :where(.presence-caret[data-presence-caret-color]),
3301
- [data-presence-caret-color] .presence-caret {
3302
- --presence-caret-color: attr(data-presence-caret-color);
3303
- }
3304
-
3305
- /* Caret blink animation */
3306
- @keyframes presence-caret-blink {
3307
-
3308
- 0%,
3309
- 50% {
3310
- opacity: var(--presence-caret-opacity-high);
3311
- }
3312
-
3313
- 51%,
3314
- 100% {
3315
- opacity: var(--presence-caret-opacity-low);
3316
- }
3317
- }
3318
-
3319
- /* Selection highlight for other users' text selections */
3320
- :where(.presence-selection) {
3321
- position: absolute;
3322
- background-color: var(--presence-selection-color);
3323
- opacity: var(--presence-selection-opacity);
3324
- pointer-events: none;
3325
- z-index: var(--presence-selection-z-index);
3326
- }
3327
-
3328
- /* Dynamic selection color - plugin sets CSS variable via setProperty */
3329
- :where(.presence-selection) {
3330
- background-color: var(--presence-selection-color);
3331
- }
3332
-
3333
- /* Cursor indicator (for mouse/touch cursors) */
3334
- :where(.presence-cursor) {
3335
- position: absolute;
3336
- width: var(--presence-cursor-size);
3337
- height: var(--presence-cursor-size);
3338
- border: var(--presence-cursor-border-width) solid var(--presence-cursor-color, var(--presence-focus-color));
3339
- border-radius: 50%;
3340
- background-color: var(--presence-cursor-color, var(--presence-focus-color));
3341
- pointer-events: none;
3342
- z-index: var(--presence-cursor-z-index);
3343
- transform: translate(-50%, -50%);
3344
- }
3345
-
3346
- /* Cursor label (user name) */
3347
- :where(.presence-cursor-label) {
3348
- position: absolute;
3349
- top: calc(var(--presence-cursor-size) + 4px);
3350
- left: 50%;
3351
- transform: translateX(-50%);
3352
- background-color: var(--presence-cursor-color, var(--presence-focus-color));
3353
- color: oklch(98.5% 0 0);
3354
- padding: 2px 6px;
3355
- border-radius: 4px;
3356
- font-size: 12px;
3357
- white-space: nowrap;
3358
- pointer-events: none;
3359
- z-index: calc(var(--presence-cursor-z-index) + 1);
3360
- }
3361
-
3362
- /* Element that contains presence indicators */
3363
- :where([data-presence-caret-user], [data-presence-selection-user], [data-presence-focus-user]) {
3364
- position: relative;
3365
- }
3366
- }
3367
-
3368
3236
  /* Manifest Radios */
3369
3237
 
3370
3238
  @layer components {