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.
- package/LICENSE +11 -0
- package/README.md +58 -0
- package/dist/manifest.accordion.css +81 -0
- package/dist/manifest.appwrite.auth.js +6247 -0
- package/dist/manifest.appwrite.data.js +1586 -0
- package/dist/manifest.appwrite.presence.js +1845 -0
- package/dist/manifest.avatar.css +113 -0
- package/dist/manifest.button.css +79 -0
- package/dist/manifest.checkbox.css +58 -0
- package/dist/manifest.code.css +453 -0
- package/dist/manifest.code.js +958 -0
- package/dist/manifest.code.min.css +1 -0
- package/dist/manifest.components.js +737 -0
- package/dist/manifest.css +3124 -0
- package/dist/manifest.data.js +11413 -0
- package/dist/manifest.dialog.css +130 -0
- package/dist/manifest.divider.css +77 -0
- package/dist/manifest.dropdown.css +278 -0
- package/dist/manifest.dropdowns.js +378 -0
- package/dist/manifest.form.css +169 -0
- package/dist/manifest.icons.js +161 -0
- package/dist/manifest.input.css +129 -0
- package/dist/manifest.js +302 -0
- package/dist/manifest.localization.js +571 -0
- package/dist/manifest.markdown.js +738 -0
- package/dist/manifest.min.css +1 -0
- package/dist/manifest.radio.css +38 -0
- package/dist/manifest.resize.css +233 -0
- package/dist/manifest.resize.js +442 -0
- package/dist/manifest.router.js +1207 -0
- package/dist/manifest.sidebar.css +102 -0
- package/dist/manifest.slides.css +80 -0
- package/dist/manifest.slides.js +173 -0
- package/dist/manifest.switch.css +44 -0
- package/dist/manifest.table.css +74 -0
- package/dist/manifest.tabs.js +273 -0
- package/dist/manifest.tailwind.js +578 -0
- package/dist/manifest.theme.css +119 -0
- package/dist/manifest.themes.js +109 -0
- package/dist/manifest.toast.css +92 -0
- package/dist/manifest.toasts.js +285 -0
- package/dist/manifest.tooltip.css +156 -0
- package/dist/manifest.tooltips.js +331 -0
- package/dist/manifest.typography.css +341 -0
- package/dist/manifest.utilities.css +399 -0
- package/dist/manifest.utilities.js +3197 -0
- 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
|
+
};
|