mnfst 0.5.14

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.
Files changed (47) hide show
  1. package/LICENSE +11 -0
  2. package/README.md +58 -0
  3. package/dist/manifest.accordion.css +81 -0
  4. package/dist/manifest.appwrite.auth.js +6247 -0
  5. package/dist/manifest.appwrite.data.js +1586 -0
  6. package/dist/manifest.appwrite.presence.js +1845 -0
  7. package/dist/manifest.avatar.css +113 -0
  8. package/dist/manifest.button.css +79 -0
  9. package/dist/manifest.checkbox.css +58 -0
  10. package/dist/manifest.code.css +453 -0
  11. package/dist/manifest.code.js +958 -0
  12. package/dist/manifest.code.min.css +1 -0
  13. package/dist/manifest.components.js +737 -0
  14. package/dist/manifest.css +3124 -0
  15. package/dist/manifest.data.js +11413 -0
  16. package/dist/manifest.dialog.css +130 -0
  17. package/dist/manifest.divider.css +77 -0
  18. package/dist/manifest.dropdown.css +278 -0
  19. package/dist/manifest.dropdowns.js +378 -0
  20. package/dist/manifest.form.css +169 -0
  21. package/dist/manifest.icons.js +161 -0
  22. package/dist/manifest.input.css +129 -0
  23. package/dist/manifest.js +302 -0
  24. package/dist/manifest.localization.js +571 -0
  25. package/dist/manifest.markdown.js +738 -0
  26. package/dist/manifest.min.css +1 -0
  27. package/dist/manifest.radio.css +38 -0
  28. package/dist/manifest.resize.css +233 -0
  29. package/dist/manifest.resize.js +442 -0
  30. package/dist/manifest.router.js +1207 -0
  31. package/dist/manifest.sidebar.css +102 -0
  32. package/dist/manifest.slides.css +80 -0
  33. package/dist/manifest.slides.js +173 -0
  34. package/dist/manifest.switch.css +44 -0
  35. package/dist/manifest.table.css +74 -0
  36. package/dist/manifest.tabs.js +273 -0
  37. package/dist/manifest.tailwind.js +578 -0
  38. package/dist/manifest.theme.css +119 -0
  39. package/dist/manifest.themes.js +109 -0
  40. package/dist/manifest.toast.css +92 -0
  41. package/dist/manifest.toasts.js +285 -0
  42. package/dist/manifest.tooltip.css +156 -0
  43. package/dist/manifest.tooltips.js +331 -0
  44. package/dist/manifest.typography.css +341 -0
  45. package/dist/manifest.utilities.css +399 -0
  46. package/dist/manifest.utilities.js +3197 -0
  47. package/package.json +63 -0
@@ -0,0 +1,737 @@
1
+ /* Manifest Components */
2
+
3
+ // Components registry
4
+ window.ManifestComponentsRegistry = {
5
+ manifest: null,
6
+ registered: new Set(),
7
+ preloaded: [],
8
+ initialize() {
9
+ // Load manifest.json synchronously
10
+ try {
11
+ const req = new XMLHttpRequest();
12
+ req.open('GET', '/manifest.json?t=' + Date.now(), false);
13
+ req.setRequestHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
14
+ req.setRequestHeader('Pragma', 'no-cache');
15
+ req.setRequestHeader('Expires', '0');
16
+ req.send(null);
17
+ if (req.status === 200) {
18
+ this.manifest = JSON.parse(req.responseText);
19
+ // Register all components from manifest
20
+ const allComponents = [
21
+ ...(this.manifest?.preloadedComponents || []),
22
+ ...(this.manifest?.components || [])
23
+ ];
24
+ allComponents.forEach(path => {
25
+ const name = path.split('/').pop().replace('.html', '');
26
+ this.registered.add(name);
27
+ });
28
+ this.preloaded = (this.manifest?.preloadedComponents || []).map(path => path.split('/').pop().replace('.html', ''));
29
+ } else {
30
+ console.warn('[Manifest] Failed to load manifest.json (HTTP', req.status + ')');
31
+ }
32
+ } catch (e) {
33
+ console.warn('[Manifest] Failed to load manifest.json:', e.message);
34
+ }
35
+ }
36
+ };
37
+
38
+ // Components loader
39
+ window.ManifestComponentsLoader = {
40
+ cache: {},
41
+ initialize() {
42
+ this.cache = {};
43
+ // Preload components listed in registry.preloaded
44
+ const registry = window.ManifestComponentsRegistry;
45
+ if (registry && Array.isArray(registry.preloaded)) {
46
+ registry.preloaded.forEach(name => {
47
+ this.loadComponent(name).then(() => {
48
+ // Preloaded component
49
+ });
50
+ });
51
+ }
52
+ },
53
+ async loadComponent(name) {
54
+ if (this.cache[name]) {
55
+ return this.cache[name];
56
+ }
57
+ const registry = window.ManifestComponentsRegistry;
58
+ if (!registry || !registry.manifest) {
59
+ console.warn('[Manifest] Manifest not loaded, cannot load component:', name);
60
+ return null;
61
+ }
62
+ const path = (registry.manifest.preloadedComponents || []).concat(registry.manifest.components || [])
63
+ .find(p => p.split('/').pop().replace('.html', '') === name);
64
+ if (!path) {
65
+ console.warn('[Manifest] Component', name, 'not found in manifest.');
66
+ return null;
67
+ }
68
+ try {
69
+ const response = await fetch('/' + path);
70
+ if (!response.ok) {
71
+ console.warn('[Manifest] HTML file not found for component', name, 'at path:', path, '(HTTP', response.status + ')');
72
+ return null;
73
+ }
74
+ const content = await response.text();
75
+ this.cache[name] = content;
76
+ return content;
77
+ } catch (error) {
78
+ console.warn('[Manifest] Failed to load component', name, 'from', path + ':', error.message);
79
+ return null;
80
+ }
81
+ }
82
+ };
83
+
84
+ // Components processor
85
+ window.ManifestComponentsProcessor = {
86
+ async processComponent(element, instanceId) {
87
+ const name = element.tagName.toLowerCase().replace('x-', '');
88
+ const registry = window.ManifestComponentsRegistry;
89
+ const loader = window.ManifestComponentsLoader;
90
+ if (!registry || !loader) {
91
+ return;
92
+ }
93
+ if (!registry.registered.has(name)) {
94
+ return;
95
+ }
96
+ if (element.hasAttribute('data-pre-rendered') || element.hasAttribute('data-processed')) {
97
+ return;
98
+ }
99
+ const content = await loader.loadComponent(name);
100
+ if (!content) {
101
+ element.replaceWith(document.createComment(` Failed to load component: ${name} `));
102
+ return;
103
+ }
104
+ const container = document.createElement('div');
105
+ container.innerHTML = content.trim();
106
+ const topLevelElements = Array.from(container.children);
107
+ if (topLevelElements.length === 0) {
108
+ element.replaceWith(document.createComment(` Empty component: ${name} `));
109
+ return;
110
+ }
111
+
112
+ // Extract and prepare scripts for execution
113
+ const scripts = [];
114
+ const processScripts = (el) => {
115
+ if (el.tagName.toLowerCase() === 'script') {
116
+ scripts.push({
117
+ content: el.textContent,
118
+ type: el.getAttribute('type') || 'text/javascript',
119
+ src: el.getAttribute('src'),
120
+ async: el.hasAttribute('async'),
121
+ defer: el.hasAttribute('defer')
122
+ });
123
+ // Remove script from DOM to avoid duplication
124
+ el.remove();
125
+ } else {
126
+ Array.from(el.children).forEach(processScripts);
127
+ }
128
+ };
129
+ topLevelElements.forEach(processScripts);
130
+ // Collect properties from placeholder attributes
131
+ const props = {};
132
+ Array.from(element.attributes).forEach(attr => {
133
+ if (attr.name !== name && attr.name !== 'class' && !attr.name.startsWith('data-')) {
134
+ // Store both original case and lowercase for flexibility
135
+ props[attr.name] = attr.value;
136
+ props[attr.name.toLowerCase()] = attr.value;
137
+ // For Alpine bindings (starting with :), also store without the : prefix
138
+ if (attr.name.startsWith(':')) {
139
+ const keyWithoutColon = attr.name.substring(1);
140
+ props[keyWithoutColon] = attr.value;
141
+ props[keyWithoutColon.toLowerCase()] = attr.value;
142
+ }
143
+ }
144
+ });
145
+ // Process $modify usage in all elements
146
+ const processElementProps = (el) => {
147
+ Array.from(el.attributes).forEach(attr => {
148
+ const value = attr.value.trim();
149
+ if (value.includes('$modify(')) {
150
+ const propMatch = value.match(/\$modify\(['"]([^'"]+)['"]\)/);
151
+ if (propMatch) {
152
+ const propName = propMatch[1].toLowerCase();
153
+ const propValue = props[propName] || '';
154
+ if (attr.name === 'class') {
155
+ const existingClasses = el.getAttribute('class') || '';
156
+ const newClasses = existingClasses
157
+ .replace(new RegExp(`\$modify\(['"]${propName}['"]\)`, 'i'), propValue)
158
+ .split(' ')
159
+ .filter(Boolean)
160
+ .join(' ');
161
+ el.setAttribute('class', newClasses);
162
+ } else if (attr.name === 'x-icon') {
163
+ // x-icon should get the raw value, not wrapped for Alpine evaluation
164
+ el.setAttribute(attr.name, propValue);
165
+ } else if (attr.name === 'x-show' || attr.name === 'x-if') {
166
+ // x-show and x-if expect boolean expressions, convert string to boolean check
167
+ if (value !== `$modify('${propName}')`) {
168
+ const newValue = value.replace(
169
+ /\$modify\(['"]([^'"]+)['"]\)/g,
170
+ (_, name) => {
171
+ const val = props[name.toLowerCase()] || '';
172
+ // Convert to boolean check - true if value exists and is not empty
173
+ return val ? 'true' : 'false';
174
+ }
175
+ );
176
+ el.setAttribute(attr.name, newValue);
177
+ } else {
178
+ // Simple replacement - check if prop exists and is not empty
179
+ const booleanValue = propValue && propValue.trim() !== '' ? 'true' : 'false';
180
+ el.setAttribute(attr.name, booleanValue);
181
+ }
182
+ } else if (
183
+ attr.name.startsWith('x-') ||
184
+ attr.name.startsWith(':') ||
185
+ attr.name.startsWith('@') ||
186
+ attr.name.startsWith('x-bind:') ||
187
+ attr.name.startsWith('x-on:')
188
+ ) {
189
+ // For Alpine directives, properly quote string values
190
+ if (value !== `$modify('${propName}')`) {
191
+ // Handle mixed content with multiple $modify() calls
192
+ const newValue = value.replace(
193
+ /\$modify\(['"]([^'"]+)['"]\)/g,
194
+ (_, name) => {
195
+ const val = props[name.toLowerCase()] || '';
196
+ // For expressions with fallbacks (||), use null for empty/whitespace values
197
+ if (!val || val.trim() === '' || /^[\r\n\t\s]+$/.test(val)) {
198
+ return value.includes('||') ? 'null' : "''";
199
+ }
200
+ // If value starts with $, it's an Alpine expression - don't quote
201
+ if (val.startsWith('$')) {
202
+ // Special handling for x-for, x-if, and x-show with $x data source expressions
203
+ // Add safe fallbacks to prevent errors during initial render when data source hasn't loaded yet
204
+ if ((attr.name === 'x-for' || attr.name === 'x-if' || attr.name === 'x-show') && val.startsWith('$x') && !val.includes('??')) {
205
+ // Convert regular property access dots to optional chaining for safe navigation
206
+ let safeVal = val.replace(/\./g, '?.');
207
+ // Add fallback based on directive type (only if user hasn't already provided one)
208
+ if (attr.name === 'x-for') {
209
+ // x-for needs an iterable, so fallback to empty array
210
+ return `${safeVal} ?? []`;
211
+ } else {
212
+ // x-if and x-show evaluate to boolean, fallback to false
213
+ return `${safeVal} ?? false`;
214
+ }
215
+ }
216
+ return val;
217
+ }
218
+ // Special handling for x-for, x-if, and x-show - these can contain expressions
219
+ // that reference data sources or other dynamic content
220
+ if (attr.name === 'x-for' || attr.name === 'x-if' || attr.name === 'x-show') {
221
+ // For these directives, preserve the value as-is to allow Alpine to evaluate it
222
+ // This is critical for x-for expressions like "card in $x.data.items"
223
+ return val;
224
+ }
225
+ // Always quote string values to ensure they're treated as strings, not variables
226
+ return `'${val.replace(/'/g, "\\'").replace(/\r/g, '\\r').replace(/\n/g, '\\n').replace(/\t/g, '\\t')}'`;
227
+ }
228
+ );
229
+ el.setAttribute(attr.name, newValue);
230
+ } else {
231
+ // Simple $modify() replacement
232
+ if (!propValue || propValue.trim() === '' || /^[\r\n\t\s]+$/.test(propValue)) {
233
+ // For empty/whitespace values, remove the attribute
234
+ el.removeAttribute(attr.name);
235
+ } else {
236
+ // If value starts with $, it's an Alpine expression - don't quote
237
+ if (propValue.startsWith('$')) {
238
+ el.setAttribute(attr.name, propValue);
239
+ } else {
240
+ // Always quote string values and escape special characters
241
+ const quotedValue = `'${propValue.replace(/'/g, "\\'").replace(/\r/g, '\\r').replace(/\n/g, '\\n').replace(/\t/g, '\\t')}'`;
242
+ el.setAttribute(attr.name, quotedValue);
243
+ }
244
+ }
245
+ }
246
+ } else {
247
+ el.setAttribute(attr.name, propValue);
248
+ }
249
+ }
250
+ }
251
+ });
252
+ Array.from(el.children).forEach(processElementProps);
253
+ };
254
+ topLevelElements.forEach(processElementProps);
255
+ // Apply attributes from placeholder to root elements
256
+ topLevelElements.forEach(rootElement => {
257
+ Array.from(element.attributes).forEach(attr => {
258
+ if (attr.name === 'class') {
259
+ const existingClass = rootElement.getAttribute('class') || '';
260
+ const newClasses = `${existingClass} ${attr.value}`.trim();
261
+ rootElement.setAttribute('class', newClasses);
262
+ } else if (attr.name.startsWith('x-') || attr.name.startsWith(':') || attr.name.startsWith('@')) {
263
+ rootElement.setAttribute(attr.name, attr.value);
264
+ } else if (attr.name !== name && !attr.name.startsWith('data-')) {
265
+ rootElement.setAttribute(attr.name, attr.value);
266
+ }
267
+ // Preserve important data attributes including data-order
268
+ else if (attr.name === 'data-order' || attr.name === 'x-route' || attr.name === 'data-head') {
269
+ rootElement.setAttribute(attr.name, attr.value);
270
+ }
271
+ });
272
+ // Set data-component=instanceId if provided
273
+ if (instanceId) {
274
+ rootElement.setAttribute('data-component', instanceId);
275
+ }
276
+ });
277
+ // After rendering, copy all attributes from the original placeholder to the first top-level element
278
+ // Note: This block ensures the first element has all attributes, including those that might have been
279
+ // skipped by the first loop due to conditions. Classes are already handled in the first loop, so we skip them here.
280
+ if (topLevelElements.length > 0) {
281
+ const firstRoot = topLevelElements[0];
282
+ Array.from(element.attributes).forEach(attr => {
283
+ // Skip attributes that were already handled in the first loop
284
+ // Classes are always handled in the first loop, so skip them here to avoid duplication
285
+ if (attr.name === 'class') {
286
+ return; // Skip - already handled in first loop
287
+ }
288
+
289
+ // Preserve important attributes including data-order, x-route, and other routing/data attributes
290
+ const preserveAttributes = [
291
+ 'data-order', 'x-route', 'data-component', 'data-head',
292
+ 'x-route-*', 'data-route-*', 'x-tabpanel'
293
+ ];
294
+ const shouldPreserve = preserveAttributes.some(preserveAttr =>
295
+ attr.name === preserveAttr || attr.name.startsWith(preserveAttr.replace('*', ''))
296
+ );
297
+
298
+ // Check if this attribute was already handled in the first loop
299
+ const alreadyHandledInFirstLoop =
300
+ attr.name.startsWith('x-') || attr.name.startsWith(':') || attr.name.startsWith('@') ||
301
+ (attr.name !== name && !attr.name.startsWith('data-')) ||
302
+ attr.name === 'data-order' || attr.name === 'x-route' || attr.name === 'data-head';
303
+
304
+ // 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
305
+ if ((!alreadyHandledInFirstLoop || shouldPreserve) &&
306
+ !['data-original-placeholder', 'data-pre-rendered', 'data-processed'].includes(attr.name)) {
307
+ if (attr.name.startsWith('x-') || attr.name.startsWith(':') || attr.name.startsWith('@')) {
308
+ // For Alpine directives, merge if they already exist (for x-data, combine objects)
309
+ if (attr.name === 'x-data' && firstRoot.hasAttribute('x-data')) {
310
+ // For x-data, we need to merge the objects - this is complex, so for now we'll append
311
+ // The user should structure their x-data to avoid conflicts
312
+ const existing = firstRoot.getAttribute('x-data');
313
+ // If both are objects, try to merge them
314
+ if (existing.trim().startsWith('{') && attr.value.trim().startsWith('{')) {
315
+ // Remove outer braces and merge
316
+ const existingContent = existing.trim().slice(1, -1).trim();
317
+ const newContent = attr.value.trim().slice(1, -1).trim();
318
+ const merged = `{ ${existingContent}${existingContent && newContent ? ', ' : ''}${newContent} }`;
319
+ firstRoot.setAttribute('x-data', merged);
320
+ } else {
321
+ // If not both objects, replace (user should handle this case)
322
+ firstRoot.setAttribute(attr.name, attr.value);
323
+ }
324
+ } else {
325
+ // For other Alpine directives, replace if they exist
326
+ firstRoot.setAttribute(attr.name, attr.value);
327
+ }
328
+ } else {
329
+ // For other attributes, replace if they exist
330
+ firstRoot.setAttribute(attr.name, attr.value);
331
+ }
332
+ }
333
+ });
334
+ }
335
+ const parent = element.parentElement;
336
+ if (!parent || !document.contains(element)) {
337
+ return;
338
+ }
339
+ // Replace the placeholder element with the component content
340
+ const fragment = document.createDocumentFragment();
341
+ topLevelElements.forEach(el => fragment.appendChild(el));
342
+
343
+ // Replace the placeholder element with the component content
344
+ // Alpine will auto-initialize on DOM insertion, but we need to ensure
345
+ // magic methods are ready first. If data plugin is ready, give it a tick
346
+ // to ensure Alpine has processed the magic method registration.
347
+ parent.replaceChild(fragment, element);
348
+
349
+ // Manually initialize Alpine on the swapped-in elements after ensuring
350
+ // magic methods are available. This prevents "i is not a function" errors.
351
+ if (window.Alpine && typeof window.Alpine.initTree === 'function') {
352
+ const initAlpine = () => {
353
+ // Re-initialize Alpine on the swapped elements
354
+ // This ensures magic methods are available when expressions are evaluated
355
+ topLevelElements.forEach(el => {
356
+ if (!el.__x) { // Only init if not already initialized
357
+ try {
358
+ window.Alpine.initTree(el);
359
+ } catch (e) {
360
+ console.error(`[Manifest Components] Error initializing Alpine for component "${name}":`, e);
361
+ }
362
+ }
363
+ });
364
+ };
365
+
366
+ // If data plugin is ready, wait a tick to ensure magic method is processed
367
+ if (window.__induxDataMagicRegistered) {
368
+ if (window.Alpine.nextTick) {
369
+ window.Alpine.nextTick(initAlpine);
370
+ } else {
371
+ setTimeout(initAlpine, 0);
372
+ }
373
+ } else {
374
+ // Data plugin not ready, initialize immediately (will fail gracefully)
375
+ initAlpine();
376
+ }
377
+ }
378
+
379
+ // Execute scripts after component is rendered
380
+ if (scripts.length > 0) {
381
+ // Use a small delay to ensure DOM is updated
382
+ setTimeout(() => {
383
+ scripts.forEach(script => {
384
+ if (script.src) {
385
+ // External script - create and append to head
386
+ const scriptEl = document.createElement('script');
387
+ scriptEl.src = script.src;
388
+ scriptEl.type = script.type;
389
+ if (script.async) scriptEl.async = true;
390
+ if (script.defer) scriptEl.defer = true;
391
+ document.head.appendChild(scriptEl);
392
+ } else if (script.content) {
393
+ // Inline script - execute directly
394
+ try {
395
+ // Create a function to execute the script in the global scope
396
+ const executeScript = new Function(script.content);
397
+ executeScript();
398
+ } catch (error) {
399
+ console.error(`[Manifest] Error executing script in component ${name}:`, error);
400
+ }
401
+ }
402
+ });
403
+ }, 0);
404
+ }
405
+ },
406
+ initialize() {
407
+ }
408
+ };
409
+
410
+ // Components swapping
411
+ (function () {
412
+ let componentInstanceCounters = {};
413
+ const swappedInstances = new Set();
414
+ const instanceRouteMap = new Map();
415
+ const placeholderMap = new Map();
416
+
417
+ function getComponentInstanceId(name) {
418
+ if (!componentInstanceCounters[name]) componentInstanceCounters[name] = 1;
419
+ else componentInstanceCounters[name]++;
420
+ return `${name}-${componentInstanceCounters[name]}`;
421
+ }
422
+
423
+ function logSiblings(parent, context) {
424
+ if (!parent) return;
425
+ const siblings = Array.from(parent.children).map(el => `${el.tagName}[data-component=${el.getAttribute('data-component') || ''}]`).join(', ');
426
+ }
427
+
428
+ window.ManifestComponentsSwapping = {
429
+ // Swap in source code for a placeholder
430
+ async swapIn(placeholder) {
431
+ if (placeholder.hasAttribute('data-swapped')) return;
432
+ const processor = window.ManifestComponentsProcessor;
433
+ if (!processor) return;
434
+ const name = placeholder.tagName.toLowerCase().replace('x-', '');
435
+ let instanceId = placeholder.getAttribute('data-component');
436
+ if (!instanceId) {
437
+ instanceId = getComponentInstanceId(name);
438
+ placeholder.setAttribute('data-component', instanceId);
439
+ }
440
+ // Save placeholder for reversion in the map
441
+ if (!placeholderMap.has(instanceId)) {
442
+ const clone = placeholder.cloneNode(true);
443
+ clone.setAttribute('data-original-placeholder', '');
444
+ clone.setAttribute('data-component', instanceId);
445
+ placeholderMap.set(instanceId, clone);
446
+ }
447
+ // Log before swap
448
+ logSiblings(placeholder.parentNode, `Before swapIn for ${instanceId}`);
449
+ // Process and swap in source code, passing instanceId
450
+ await processor.processComponent(placeholder, instanceId);
451
+ swappedInstances.add(instanceId);
452
+ // Track the route for this instance
453
+ const xRoute = placeholder.getAttribute('x-route');
454
+ instanceRouteMap.set(instanceId, xRoute);
455
+ // Log after swap
456
+ logSiblings(placeholder.parentNode || document.body, `After swapIn for ${instanceId}`);
457
+ },
458
+ // Revert to placeholder
459
+ revert(instanceId) {
460
+ if (!swappedInstances.has(instanceId)) return;
461
+ // Remove all elements with data-component=instanceId
462
+ const rendered = Array.from(document.querySelectorAll(`[data-component="${instanceId}"]`));
463
+ if (rendered.length === 0) return;
464
+ const first = rendered[0];
465
+ const parent = first.parentNode;
466
+ // Retrieve the original placeholder from the map
467
+ const placeholder = placeholderMap.get(instanceId);
468
+ // Log before revert
469
+ logSiblings(parent, `Before revert for ${instanceId}`);
470
+ // Remove all rendered elements
471
+ rendered.forEach(el => {
472
+ el.remove();
473
+ });
474
+ // Restore the placeholder at the correct position if not present
475
+ if (placeholder && parent && !parent.contains(placeholder)) {
476
+ const targetPosition = parseInt(placeholder.getAttribute('data-order')) || 0;
477
+ let inserted = false;
478
+
479
+ // Find the correct position based on data-order
480
+ for (let i = 0; i < parent.children.length; i++) {
481
+ const child = parent.children[i];
482
+ const childPosition = parseInt(child.getAttribute('data-order')) || 0;
483
+
484
+ if (targetPosition < childPosition) {
485
+ parent.insertBefore(placeholder, child);
486
+ inserted = true;
487
+ break;
488
+ }
489
+ }
490
+
491
+ // If not inserted (should be at the end), append to parent
492
+ if (!inserted) {
493
+ parent.appendChild(placeholder);
494
+ }
495
+
496
+ }
497
+ swappedInstances.delete(instanceId);
498
+ instanceRouteMap.delete(instanceId);
499
+ placeholderMap.delete(instanceId);
500
+ // Log after revert
501
+ logSiblings(parent, `After revert for ${instanceId}`);
502
+ },
503
+ // Main swapping logic
504
+ async processAll(normalizedPathFromEvent = null) {
505
+ componentInstanceCounters = {};
506
+ const registry = window.ManifestComponentsRegistry;
507
+ if (!registry) return;
508
+ const routing = window.ManifestRouting;
509
+
510
+ // Use normalized path from event if provided, otherwise compute from window.location
511
+ let normalizedPath;
512
+ if (normalizedPathFromEvent !== null) {
513
+ normalizedPath = normalizedPathFromEvent;
514
+ } else {
515
+ const currentPath = window.location.pathname;
516
+ normalizedPath = currentPath === '/' ? '/' : currentPath.replace(/^\/|\/$/g, '');
517
+ }
518
+
519
+ const placeholders = Array.from(document.querySelectorAll('*')).filter(el =>
520
+ el.tagName.toLowerCase().startsWith('x-') &&
521
+ !el.hasAttribute('data-pre-rendered') &&
522
+ !el.hasAttribute('data-processed')
523
+ );
524
+ // First pass: revert any swapped-in instances that no longer match
525
+ if (routing) {
526
+ for (const instanceId of Array.from(swappedInstances)) {
527
+ const xRoute = instanceRouteMap.get(instanceId);
528
+ if (!xRoute) {
529
+ // No route condition means always visible, don't revert
530
+ continue;
531
+ }
532
+ // Parse route conditions the same way as route visibility
533
+ const conditions = xRoute.split(',').map(cond => cond.trim());
534
+ const positiveConditions = conditions.filter(cond => !cond.startsWith('!'));
535
+ const negativeConditions = conditions
536
+ .filter(cond => cond.startsWith('!'))
537
+ .map(cond => cond.slice(1));
538
+
539
+ const hasNegativeMatch = negativeConditions.some(cond =>
540
+ window.ManifestRouting.matchesCondition(normalizedPath, cond)
541
+ );
542
+ const hasPositiveMatch = positiveConditions.length === 0 || positiveConditions.some(cond =>
543
+ window.ManifestRouting.matchesCondition(normalizedPath, cond)
544
+ );
545
+
546
+ const matches = hasPositiveMatch && !hasNegativeMatch;
547
+ if (!matches) {
548
+ this.revert(instanceId);
549
+ }
550
+ }
551
+ }
552
+ // Second pass: swap in any placeholders that match
553
+ for (const placeholder of placeholders) {
554
+ const name = placeholder.tagName.toLowerCase().replace('x-', '');
555
+ let instanceId = placeholder.getAttribute('data-component');
556
+ if (!instanceId) {
557
+ instanceId = getComponentInstanceId(name);
558
+ placeholder.setAttribute('data-component', instanceId);
559
+ }
560
+ const xRoute = placeholder.getAttribute('x-route');
561
+ if (!routing) {
562
+ // No routing: always swap in
563
+ await this.swapIn(placeholder);
564
+ } else {
565
+ // Routing present: check route using same logic as route visibility
566
+ // Handle comma-separated route conditions (e.g., "/,page-1,page-2")
567
+ let matches = !xRoute;
568
+ if (xRoute) {
569
+ const conditions = xRoute.split(',').map(cond => cond.trim());
570
+ const positiveConditions = conditions.filter(cond => !cond.startsWith('!'));
571
+ const negativeConditions = conditions
572
+ .filter(cond => cond.startsWith('!'))
573
+ .map(cond => cond.slice(1));
574
+
575
+ // Check negative conditions first
576
+ const hasNegativeMatch = negativeConditions.some(cond =>
577
+ window.ManifestRouting.matchesCondition(normalizedPath, cond)
578
+ );
579
+
580
+ // Check positive conditions
581
+ const hasPositiveMatch = positiveConditions.length === 0 || positiveConditions.some(cond =>
582
+ window.ManifestRouting.matchesCondition(normalizedPath, cond)
583
+ );
584
+
585
+ matches = hasPositiveMatch && !hasNegativeMatch;
586
+ }
587
+
588
+ if (matches) {
589
+ await this.swapIn(placeholder);
590
+ }
591
+ }
592
+ }
593
+ },
594
+ initialize() {
595
+ // On init, process all
596
+ this.processAll().then(() => {
597
+ // Dispatch event when components are fully processed
598
+ window.dispatchEvent(new CustomEvent('indux:components-processed'));
599
+ });
600
+ // If routing is present, listen for route changes
601
+ if (window.ManifestRouting) {
602
+ window.addEventListener('indux:route-change', (event) => {
603
+ // Use normalized path from event detail if available
604
+ const normalizedPath = event.detail?.normalizedPath || null;
605
+ this.processAll(normalizedPath).then(() => {
606
+ // Dispatch event when components are fully processed after route change
607
+ window.dispatchEvent(new CustomEvent('indux:components-processed'));
608
+ });
609
+ });
610
+ }
611
+ }
612
+ };
613
+ })();
614
+
615
+ // Components mutation observer
616
+ window.ManifestComponentsMutation = {
617
+ async processAllPlaceholders() {
618
+ const processor = window.ManifestComponentsProcessor;
619
+ const routing = window.ManifestRouting;
620
+ if (!processor) return;
621
+ const placeholders = Array.from(document.querySelectorAll('*')).filter(el =>
622
+ el.tagName.toLowerCase().startsWith('x-') &&
623
+ !el.hasAttribute('data-pre-rendered') &&
624
+ !el.hasAttribute('data-processed')
625
+ );
626
+ for (const el of placeholders) {
627
+ if (routing) {
628
+ // Only process if route matches
629
+ const xRoute = el.getAttribute('x-route');
630
+ const currentPath = window.location.pathname;
631
+ const normalizedPath = currentPath === '/' ? '/' : currentPath.replace(/^\/+|\/+$/g, '');
632
+ const matches = !xRoute || window.ManifestRouting.matchesCondition(normalizedPath, xRoute);
633
+ if (!matches) continue;
634
+ }
635
+ await processor.processComponent(el);
636
+ }
637
+ },
638
+ initialize() {
639
+ const processor = window.ManifestComponentsProcessor;
640
+ const routing = window.ManifestRouting;
641
+ if (!processor) return;
642
+ // Initial scan
643
+ this.processAllPlaceholders();
644
+ // Mutation observer for new placeholders
645
+ const observer = new MutationObserver(async mutations => {
646
+ for (const mutation of mutations) {
647
+ for (const node of mutation.addedNodes) {
648
+ if (node.nodeType === 1 && node.tagName.toLowerCase().startsWith('x-')) {
649
+ if (!node.hasAttribute('data-pre-rendered') && !node.hasAttribute('data-processed')) {
650
+ if (routing) {
651
+ const xRoute = node.getAttribute('x-route');
652
+ const currentPath = window.location.pathname;
653
+ const normalizedPath = currentPath === '/' ? '/' : currentPath.replace(/^\/+|\/+$/g, '');
654
+ const matches = !xRoute || window.ManifestRouting.matchesCondition(normalizedPath, xRoute);
655
+ if (!matches) continue;
656
+ }
657
+ await processor.processComponent(node);
658
+ }
659
+ }
660
+ // Also check for any <x-*> descendants
661
+ if (node.nodeType === 1) {
662
+ const descendants = Array.from(node.querySelectorAll('*')).filter(el =>
663
+ el.tagName.toLowerCase().startsWith('x-') &&
664
+ !el.hasAttribute('data-pre-rendered') &&
665
+ !el.hasAttribute('data-processed')
666
+ );
667
+ for (const el of descendants) {
668
+ if (routing) {
669
+ const xRoute = el.getAttribute('x-route');
670
+ const currentPath = window.location.pathname;
671
+ const normalizedPath = currentPath === '/' ? '/' : currentPath.replace(/^\/+|\/+$/g, '');
672
+ const matches = !xRoute || window.ManifestRouting.matchesCondition(normalizedPath, xRoute);
673
+ if (!matches) continue;
674
+ }
675
+ await processor.processComponent(el);
676
+ }
677
+ }
678
+ }
679
+ }
680
+ });
681
+
682
+ // Ensure document.body exists before observing
683
+ if (document.body) {
684
+ observer.observe(document.body, { childList: true, subtree: true });
685
+ } else {
686
+ // Wait for body to be available
687
+ document.addEventListener('DOMContentLoaded', () => {
688
+ observer.observe(document.body, { childList: true, subtree: true });
689
+ });
690
+ }
691
+ }
692
+ };
693
+
694
+ // Main initialization for Manifest Components
695
+ function initializeComponents() {
696
+ if (window.ManifestComponentsRegistry) window.ManifestComponentsRegistry.initialize();
697
+ if (window.ManifestComponentsLoader) window.ManifestComponentsLoader.initialize();
698
+ if (window.ManifestComponentsProcessor) window.ManifestComponentsProcessor.initialize();
699
+ if (window.ManifestComponentsSwapping) window.ManifestComponentsSwapping.initialize();
700
+ if (window.ManifestComponentsMutation) window.ManifestComponentsMutation.initialize();
701
+ if (window.ManifestComponentsUtils) window.ManifestComponentsUtils.initialize?.();
702
+ window.__induxComponentsInitialized = true;
703
+ window.dispatchEvent(new CustomEvent('indux:components-ready'));
704
+ }
705
+
706
+ // Wait for data plugin to be ready before initializing components
707
+ // This ensures $x magic method is available when components are processed
708
+ function waitForDataThenInitialize() {
709
+ // Check if data plugin is already ready
710
+ if (window.__induxDataMagicRegistered) {
711
+ initializeComponents();
712
+ return;
713
+ }
714
+
715
+ // Wait for data-ready event
716
+ window.addEventListener('indux:data-ready', () => {
717
+ initializeComponents();
718
+ }, { once: true });
719
+
720
+ // Fallback: if data plugin doesn't fire event within reasonable time, initialize anyway
721
+ // This handles cases where data plugin isn't loaded or doesn't use magic methods
722
+ setTimeout(() => {
723
+ if (!window.__induxComponentsInitialized) {
724
+ initializeComponents();
725
+ }
726
+ }, 1000);
727
+ }
728
+
729
+ if (document.readyState === 'loading') {
730
+ document.addEventListener('DOMContentLoaded', waitForDataThenInitialize);
731
+ } else {
732
+ waitForDataThenInitialize();
733
+ }
734
+
735
+ window.ManifestComponents = {
736
+ initialize: initializeComponents
737
+ };