@dinoreic/fez 0.4.1 → 0.5.2
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/README.md +707 -209
- package/bin/fez +16 -6
- package/bin/fez-compile +347 -0
- package/bin/fez-debug +25 -0
- package/bin/fez-index +16 -4
- package/bin/refactor +699 -0
- package/dist/fez.js +142 -33
- package/dist/fez.js.map +4 -4
- package/fez.d.ts +533 -0
- package/package.json +25 -15
- package/src/fez/compile.js +396 -164
- package/src/fez/connect.js +249 -146
- package/src/fez/defaults.js +272 -92
- package/src/fez/instance.js +673 -514
- package/src/fez/lib/await-helper.js +64 -0
- package/src/fez/lib/global-state.js +22 -4
- package/src/fez/lib/index.js +140 -0
- package/src/fez/lib/localstorage.js +44 -0
- package/src/fez/lib/n.js +38 -23
- package/src/fez/lib/pubsub.js +208 -0
- package/src/fez/lib/svelte-template-lib.js +339 -0
- package/src/fez/lib/svelte-template.js +472 -0
- package/src/fez/lib/template.js +114 -119
- package/src/fez/morph.js +384 -0
- package/src/fez/root.js +279 -209
- package/src/fez/utility.js +319 -149
- package/src/fez/utils/dump.js +114 -84
- package/src/fez/utils/highlight_all.js +1 -1
- package/src/fez.js +65 -43
- package/src/svelte-cde-adapter.coffee +10 -2
- package/src/fez/vendor/idiomorph.js +0 -860
package/src/fez/compile.js
CHANGED
|
@@ -1,212 +1,444 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
currentType = 'style';
|
|
16
|
-
} else if (line.endsWith('</script>') && currentType === 'script' && !result.script) {
|
|
17
|
-
result.script = currentBlock.join('\n');
|
|
18
|
-
currentBlock = [];
|
|
19
|
-
currentType = null;
|
|
20
|
-
} else if (line.endsWith('</style>') && currentType === 'style') {
|
|
21
|
-
result.style = currentBlock.join('\n');
|
|
22
|
-
currentBlock = [];
|
|
23
|
-
currentType = null;
|
|
24
|
-
} else if ((line.endsWith('</head>') || line.endsWith('</header>')) && currentType === 'head') {
|
|
25
|
-
result.head = currentBlock.join('\n');
|
|
26
|
-
currentBlock = [];
|
|
27
|
-
currentType = null;
|
|
28
|
-
} else if (currentType) {
|
|
29
|
-
// if (currentType == 'script' && line.startsWith('//')) {
|
|
30
|
-
// continue
|
|
31
|
-
// }
|
|
32
|
-
currentBlock.push(line);
|
|
33
|
-
} else {
|
|
34
|
-
result.html += line + '\n';
|
|
35
|
-
}
|
|
36
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Fez Component Compiler
|
|
3
|
+
*
|
|
4
|
+
* Compiles component definitions from various sources:
|
|
5
|
+
* - <template fez="name">...</template>
|
|
6
|
+
* - <xmp fez="name">...</xmp>
|
|
7
|
+
* - <script fez="name">...</script>
|
|
8
|
+
* - Remote URLs
|
|
9
|
+
*
|
|
10
|
+
* Flow:
|
|
11
|
+
* 1. Source (template/xmp/url) -> compile()
|
|
12
|
+
* 2. Extract parts (script/style/html/demo) -> compileToClass()
|
|
13
|
+
* 3. Generate class string -> Fez('name', class { ... })
|
|
14
|
+
*/
|
|
37
15
|
|
|
38
|
-
|
|
39
|
-
const container = Fez.domRoot(result.head)
|
|
40
|
-
|
|
41
|
-
// Process all children of the container
|
|
42
|
-
Array.from(container.children).forEach(node => {
|
|
43
|
-
if (node.tagName === 'SCRIPT') {
|
|
44
|
-
const script = document.createElement('script')
|
|
45
|
-
// Copy all attributes
|
|
46
|
-
Array.from(node.attributes).forEach(attr => {
|
|
47
|
-
script.setAttribute(attr.name, attr.value)
|
|
48
|
-
})
|
|
49
|
-
script.type ||= 'text/javascript'
|
|
50
|
-
|
|
51
|
-
if (node.src) {
|
|
52
|
-
// External script - will load automatically
|
|
53
|
-
document.head.appendChild(script)
|
|
54
|
-
} else if (script.type.includes('javascript') || script.type == 'module') {
|
|
55
|
-
// Inline script - set content and execute
|
|
56
|
-
script.textContent = node.textContent
|
|
57
|
-
document.head.appendChild(script)
|
|
58
|
-
}
|
|
59
|
-
} else {
|
|
60
|
-
// For other elements (link, meta, etc.), just append them
|
|
61
|
-
document.head.appendChild(node.cloneNode(true))
|
|
62
|
-
}
|
|
63
|
-
})
|
|
64
|
-
}
|
|
16
|
+
// Note: Uses Fez.index directly (set up in root.js)
|
|
65
17
|
|
|
66
|
-
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// HELPERS
|
|
20
|
+
// =============================================================================
|
|
67
21
|
|
|
68
|
-
|
|
69
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Remove common leading whitespace from all lines
|
|
24
|
+
*/
|
|
25
|
+
function dedent(text) {
|
|
26
|
+
const lines = text.split("\n");
|
|
27
|
+
// Find minimum indentation (ignoring empty lines)
|
|
28
|
+
const nonEmptyLines = lines.filter((l) => l.trim());
|
|
29
|
+
if (!nonEmptyLines.length) return text;
|
|
30
|
+
|
|
31
|
+
const minIndent = Math.min(
|
|
32
|
+
...nonEmptyLines.map((l) => l.match(/^(\s*)/)[1].length),
|
|
33
|
+
);
|
|
34
|
+
if (minIndent === 0) return text;
|
|
35
|
+
|
|
36
|
+
// Remove common indentation
|
|
37
|
+
return lines.map((l) => l.slice(minIndent)).join("\n");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// =============================================================================
|
|
41
|
+
// MAIN COMPILE FUNCTION
|
|
42
|
+
// =============================================================================
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Check if HTML has top-level <xmp fez> or <template fez> elements
|
|
46
|
+
* (not ones inside <demo> blocks)
|
|
47
|
+
*/
|
|
48
|
+
function hasTopLevelFezElements(html) {
|
|
49
|
+
if (!html) return false;
|
|
50
|
+
|
|
51
|
+
// Remove content inside <demo>...</demo> to avoid false positives
|
|
52
|
+
const withoutDemo = html.replace(/<demo>[\s\S]*?<\/demo>/gi, "");
|
|
53
|
+
|
|
54
|
+
// Check for <xmp fez= or <template fez= at top level
|
|
55
|
+
return /<(xmp|template)\s+fez\s*=/i.test(withoutDemo);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Compile a Fez component
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* Fez.compile() // Compile all templates in document
|
|
63
|
+
* Fez.compile(templateNode) // Compile a template node
|
|
64
|
+
* Fez.compile('ui-foo', htmlString) // Compile from string
|
|
65
|
+
*
|
|
66
|
+
* @param {string|Node} tagName - Component name or template node
|
|
67
|
+
* @param {string} [html] - Component HTML source
|
|
68
|
+
*/
|
|
69
|
+
export default function compile(tagName, html) {
|
|
70
|
+
// Single argument: compile node or all templates
|
|
71
|
+
if (arguments.length === 1) {
|
|
72
|
+
return compileBulk(tagName);
|
|
70
73
|
}
|
|
71
74
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
// Multiple xmp/template tags in html? Process them
|
|
76
|
+
// Check for top-level fez definitions (not ones inside <demo> blocks)
|
|
77
|
+
if (hasTopLevelFezElements(html)) {
|
|
78
|
+
// Extract top-level demo/info before processing inner components
|
|
79
|
+
if (tagName) {
|
|
80
|
+
const parts = compileToClass(html);
|
|
81
|
+
if (parts.info?.trim()) {
|
|
82
|
+
Fez.index.ensure(tagName).info = parts.info;
|
|
83
|
+
}
|
|
84
|
+
if (parts.demo?.trim()) {
|
|
85
|
+
Fez.index.ensure(tagName).demo = parts.demo;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return compileBulk(html);
|
|
76
89
|
}
|
|
77
90
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
91
|
+
// Validate component name
|
|
92
|
+
if (
|
|
93
|
+
tagName &&
|
|
94
|
+
!tagName.includes("-") &&
|
|
95
|
+
!tagName.includes(".") &&
|
|
96
|
+
!tagName.includes("/")
|
|
97
|
+
) {
|
|
98
|
+
console.error(
|
|
99
|
+
`Fez: Invalid name "${tagName}". Must contain a dash (e.g., 'my-element').`,
|
|
100
|
+
);
|
|
101
|
+
return;
|
|
83
102
|
}
|
|
84
103
|
|
|
85
|
-
|
|
104
|
+
// Store original source
|
|
105
|
+
Fez.index.ensure(tagName).source = html;
|
|
106
|
+
|
|
107
|
+
// Extract and compile
|
|
108
|
+
const classCode = generateClassCode(tagName, compileToClass(html));
|
|
109
|
+
|
|
110
|
+
// Hide custom element until compiled
|
|
111
|
+
hideCustomElement(tagName);
|
|
112
|
+
|
|
113
|
+
// Execute the class code
|
|
114
|
+
executeClassCode(tagName, classCode);
|
|
86
115
|
}
|
|
87
116
|
|
|
88
|
-
//
|
|
89
|
-
|
|
117
|
+
// =============================================================================
|
|
118
|
+
// COMPILE FROM VARIOUS SOURCES
|
|
119
|
+
// =============================================================================
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Compile from node or HTML string containing templates
|
|
123
|
+
*/
|
|
124
|
+
function compileBulk(data) {
|
|
125
|
+
// Single template node
|
|
90
126
|
if (data instanceof Node) {
|
|
91
|
-
const node = data
|
|
92
|
-
node.remove()
|
|
127
|
+
const node = data;
|
|
128
|
+
node.remove();
|
|
93
129
|
|
|
94
|
-
const fezName = node.getAttribute(
|
|
130
|
+
const fezName = node.getAttribute("fez");
|
|
95
131
|
|
|
96
|
-
//
|
|
97
|
-
if (fezName
|
|
98
|
-
|
|
99
|
-
return
|
|
100
|
-
} else {
|
|
101
|
-
// Validate fezName format for non-URL names
|
|
102
|
-
if (fezName && !fezName.includes('-')) {
|
|
103
|
-
console.error(`Fez: Invalid custom element name "${fezName}". Custom element names must contain a dash (e.g., 'my-element', 'ui-button').`)
|
|
104
|
-
}
|
|
105
|
-
compile(fezName, node.innerHTML)
|
|
106
|
-
return
|
|
132
|
+
// URL reference
|
|
133
|
+
if (fezName?.includes(".") || fezName?.includes("/")) {
|
|
134
|
+
return compileFromUrl(fezName);
|
|
107
135
|
}
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
110
|
-
let root = data ? Fez.domRoot(data) : document.body
|
|
111
136
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
137
|
+
// Validate name
|
|
138
|
+
if (fezName && !fezName.includes("-")) {
|
|
139
|
+
console.error(`Fez: Invalid name "${fezName}". Must contain a dash.`);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
115
142
|
|
|
116
|
-
return
|
|
143
|
+
return compile(fezName, node.innerHTML);
|
|
117
144
|
}
|
|
145
|
+
|
|
146
|
+
// HTML string or document
|
|
147
|
+
const root = data ? Fez.domRoot(data) : document.body;
|
|
148
|
+
root
|
|
149
|
+
.querySelectorAll("template[fez], xmp[fez]")
|
|
150
|
+
.forEach((n) => compileBulk(n));
|
|
118
151
|
}
|
|
119
152
|
|
|
120
|
-
|
|
121
|
-
|
|
153
|
+
/**
|
|
154
|
+
* Compile component(s) from remote URL
|
|
155
|
+
* Supports .fez files and .txt files (component lists)
|
|
156
|
+
*/
|
|
157
|
+
function compileFromUrl(url) {
|
|
158
|
+
Fez.consoleLog(`Loading from ${url}`);
|
|
159
|
+
|
|
160
|
+
// Handle .txt files as component lists
|
|
161
|
+
if (url.endsWith(".txt")) {
|
|
162
|
+
Fez.head({ fez: url });
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
122
165
|
|
|
123
|
-
// Load HTML content via AJAX from URL
|
|
124
166
|
Fez.fetch(url)
|
|
125
|
-
.then(
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
const doc = parser.parseFromString(htmlContent, 'text/html')
|
|
129
|
-
const fezElements = doc.querySelectorAll('template[fez], xmp[fez]')
|
|
167
|
+
.then((content) => {
|
|
168
|
+
const doc = new DOMParser().parseFromString(content, "text/html");
|
|
169
|
+
const fezElements = doc.querySelectorAll("template[fez], xmp[fez]");
|
|
130
170
|
|
|
131
171
|
if (fezElements.length > 0) {
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
172
|
+
// Extract top-level info/demo before the xmp elements (for multi-component files)
|
|
173
|
+
const fileName = url.split("/").pop().split(".")[0];
|
|
174
|
+
const parts = compileToClass(content);
|
|
175
|
+
if (parts.info?.trim()) {
|
|
176
|
+
Fez.index.ensure(fileName).info = parts.info;
|
|
177
|
+
}
|
|
178
|
+
if (parts.demo?.trim()) {
|
|
179
|
+
Fez.index.ensure(fileName).demo = parts.demo;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Multiple components in file
|
|
183
|
+
fezElements.forEach((el) => {
|
|
184
|
+
const name = el.getAttribute("fez");
|
|
185
|
+
if (
|
|
186
|
+
name &&
|
|
187
|
+
!name.includes("-") &&
|
|
188
|
+
!name.includes(".") &&
|
|
189
|
+
!name.includes("/")
|
|
190
|
+
) {
|
|
191
|
+
console.error(`Fez: Invalid name "${name}". Must contain a dash.`);
|
|
192
|
+
return;
|
|
137
193
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
})
|
|
194
|
+
compile(name, el.innerHTML);
|
|
195
|
+
});
|
|
141
196
|
} else {
|
|
142
|
-
//
|
|
143
|
-
const name = url.split(
|
|
144
|
-
compile(name,
|
|
197
|
+
// Single component, derive name from URL
|
|
198
|
+
const name = url.split("/").pop().split(".")[0];
|
|
199
|
+
compile(name, content);
|
|
145
200
|
}
|
|
146
201
|
})
|
|
147
|
-
.catch(error => {
|
|
148
|
-
|
|
149
|
-
})
|
|
202
|
+
.catch((error) => {
|
|
203
|
+
Fez.onError("compile", `Load error for "${url}": ${error.message}`);
|
|
204
|
+
});
|
|
150
205
|
}
|
|
151
206
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
207
|
+
export { compileFromUrl as compile_from_url };
|
|
208
|
+
|
|
209
|
+
// =============================================================================
|
|
210
|
+
// PARSE COMPONENT SOURCE
|
|
211
|
+
// =============================================================================
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Parse component HTML into { script, style, html, head, demo, info }
|
|
215
|
+
*/
|
|
216
|
+
function compileToClass(html) {
|
|
217
|
+
const result = {
|
|
218
|
+
script: "",
|
|
219
|
+
style: "",
|
|
220
|
+
html: "",
|
|
221
|
+
head: "",
|
|
222
|
+
demo: "",
|
|
223
|
+
info: "",
|
|
224
|
+
};
|
|
225
|
+
const lines = html.split("\n");
|
|
226
|
+
|
|
227
|
+
let block = [];
|
|
228
|
+
let type = "";
|
|
163
229
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
230
|
+
for (let line of lines) {
|
|
231
|
+
const trimmedLine = line.trim();
|
|
232
|
+
|
|
233
|
+
// Start blocks - demo/info can contain other tags, so skip nested detection
|
|
234
|
+
if (trimmedLine.startsWith("<demo") && !result.demo && !type) {
|
|
235
|
+
type = "demo";
|
|
236
|
+
} else if (trimmedLine.startsWith("<info") && !result.info && !type) {
|
|
237
|
+
type = "info";
|
|
238
|
+
} else if (
|
|
239
|
+
trimmedLine.startsWith("<script") &&
|
|
240
|
+
!result.script &&
|
|
241
|
+
type !== "head" &&
|
|
242
|
+
type !== "demo" &&
|
|
243
|
+
type !== "info"
|
|
244
|
+
) {
|
|
245
|
+
type = "script";
|
|
246
|
+
} else if (
|
|
247
|
+
trimmedLine.startsWith("<head") &&
|
|
248
|
+
!result.script &&
|
|
249
|
+
type !== "demo" &&
|
|
250
|
+
type !== "info"
|
|
251
|
+
) {
|
|
252
|
+
type = "head";
|
|
253
|
+
} else if (
|
|
254
|
+
trimmedLine.startsWith("<style") &&
|
|
255
|
+
type !== "demo" &&
|
|
256
|
+
type !== "info"
|
|
257
|
+
) {
|
|
258
|
+
type = "style";
|
|
259
|
+
|
|
260
|
+
// End blocks
|
|
261
|
+
} else if (trimmedLine.endsWith("</demo>") && type === "demo") {
|
|
262
|
+
result.demo = dedent(block.join("\n"));
|
|
263
|
+
block = [];
|
|
264
|
+
type = "";
|
|
265
|
+
} else if (trimmedLine.endsWith("</info>") && type === "info") {
|
|
266
|
+
result.info = dedent(block.join("\n"));
|
|
267
|
+
block = [];
|
|
268
|
+
type = "";
|
|
269
|
+
} else if (
|
|
270
|
+
trimmedLine.endsWith("</script>") &&
|
|
271
|
+
type === "script" &&
|
|
272
|
+
!result.script
|
|
273
|
+
) {
|
|
274
|
+
result.script = block.join("\n");
|
|
275
|
+
block = [];
|
|
276
|
+
type = "";
|
|
277
|
+
} else if (trimmedLine.endsWith("</style>") && type === "style") {
|
|
278
|
+
result.style = block.join("\n");
|
|
279
|
+
block = [];
|
|
280
|
+
type = "";
|
|
281
|
+
} else if (
|
|
282
|
+
(trimmedLine.endsWith("</head>") || trimmedLine.endsWith("</header>")) &&
|
|
283
|
+
type === "head"
|
|
284
|
+
) {
|
|
285
|
+
result.head = block.join("\n");
|
|
286
|
+
block = [];
|
|
287
|
+
type = "";
|
|
288
|
+
|
|
289
|
+
// Collect content - preserve original indentation for demo and info
|
|
290
|
+
} else if (type) {
|
|
291
|
+
block.push(type === "demo" || type === "info" ? line : trimmedLine);
|
|
292
|
+
} else {
|
|
293
|
+
result.html += trimmedLine + "\n";
|
|
294
|
+
}
|
|
167
295
|
}
|
|
168
296
|
|
|
169
|
-
//
|
|
170
|
-
if (
|
|
171
|
-
|
|
297
|
+
// Process head elements (scripts, links, etc.)
|
|
298
|
+
if (result.head) {
|
|
299
|
+
processHeadElements(result.head);
|
|
172
300
|
}
|
|
173
301
|
|
|
174
|
-
|
|
175
|
-
|
|
302
|
+
return result;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Process <head> elements from component
|
|
307
|
+
*/
|
|
308
|
+
function processHeadElements(headHtml) {
|
|
309
|
+
const container = Fez.domRoot(headHtml);
|
|
176
310
|
|
|
177
|
-
|
|
311
|
+
Array.from(container.children).forEach((node) => {
|
|
312
|
+
if (node.tagName === "SCRIPT") {
|
|
313
|
+
const script = document.createElement("script");
|
|
314
|
+
Array.from(node.attributes).forEach((attr) => {
|
|
315
|
+
script.setAttribute(attr.name, attr.value);
|
|
316
|
+
});
|
|
317
|
+
script.type ||= "text/javascript";
|
|
178
318
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
319
|
+
if (node.src) {
|
|
320
|
+
document.head.appendChild(script);
|
|
321
|
+
} else if (
|
|
322
|
+
script.type.includes("javascript") ||
|
|
323
|
+
script.type === "module"
|
|
324
|
+
) {
|
|
325
|
+
script.textContent = node.textContent;
|
|
326
|
+
document.head.appendChild(script);
|
|
327
|
+
}
|
|
328
|
+
} else {
|
|
329
|
+
document.head.appendChild(node.cloneNode(true));
|
|
186
330
|
}
|
|
187
|
-
|
|
188
|
-
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// =============================================================================
|
|
335
|
+
// GENERATE CLASS CODE
|
|
336
|
+
// =============================================================================
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Generate executable class code from parsed parts
|
|
340
|
+
*/
|
|
341
|
+
function generateClassCode(tagName, parts) {
|
|
342
|
+
let klass = parts.script;
|
|
343
|
+
|
|
344
|
+
// Wrap in class if needed
|
|
345
|
+
if (!/class\s+\{/.test(klass)) {
|
|
346
|
+
klass = `class {\n${klass}\n}`;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Add CSS
|
|
350
|
+
if (String(parts.style).includes(":")) {
|
|
351
|
+
let css = Fez.cssMixin(parts.style);
|
|
352
|
+
css =
|
|
353
|
+
css.includes(":fez") || /(?:^|\s)body\s*\{/.test(css)
|
|
354
|
+
? css
|
|
355
|
+
: `:fez {\n${css}\n}`;
|
|
356
|
+
klass = klass.replace(/\}\s*$/, `\n CSS = \`${css}\`\n}`);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Add HTML
|
|
360
|
+
if (/\w/.test(String(parts.html))) {
|
|
361
|
+
const html = parts.html.replaceAll("`", "`").replaceAll("$", "\\$");
|
|
362
|
+
klass = klass.replace(/\}\s*$/, `\n HTML = \`${html}\`\n}`);
|
|
189
363
|
}
|
|
190
364
|
|
|
191
|
-
//
|
|
192
|
-
if (
|
|
193
|
-
Fez.
|
|
365
|
+
// Store demo content in index
|
|
366
|
+
if (parts.demo?.trim()) {
|
|
367
|
+
Fez.index.ensure(tagName).demo = parts.demo;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Store info content in index
|
|
371
|
+
if (parts.info?.trim()) {
|
|
372
|
+
Fez.index.ensure(tagName).info = parts.info;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Wrap in Fez call
|
|
376
|
+
const [before, after] = klass.split(/class\s+\{/, 2);
|
|
377
|
+
return `${before};\n\nwindow.Fez('${tagName}', class {\n${after})`;
|
|
378
|
+
}
|
|
194
379
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
380
|
+
/**
|
|
381
|
+
* Execute generated class code
|
|
382
|
+
*/
|
|
383
|
+
function executeClassCode(tagName, code) {
|
|
384
|
+
// Module imports require script tag
|
|
385
|
+
if (code.includes("import ")) {
|
|
386
|
+
// Extract importmap and rewrite bare import specifiers to full URLs
|
|
387
|
+
const importmapRe =
|
|
388
|
+
/Fez\.head\(\s*\{\s*importmap\s*:\s*(\{[\s\S]*?\})\s*\}\s*\)\s*;?/g;
|
|
389
|
+
let match;
|
|
390
|
+
while ((match = importmapRe.exec(code)) !== null) {
|
|
391
|
+
try {
|
|
392
|
+
const imports = new Function(`return ${match[1]}`)();
|
|
393
|
+
// Sort by length descending so "three/addons/" matches before "three"
|
|
394
|
+
const sorted = Object.entries(imports).sort(
|
|
395
|
+
(a, b) => b[0].length - a[0].length,
|
|
396
|
+
);
|
|
397
|
+
for (const [specifier, url] of sorted) {
|
|
398
|
+
const escaped = specifier.replace(/[.*+?^${}()|[\]\\\/]/g, "\\$&");
|
|
399
|
+
code = code.replace(
|
|
400
|
+
new RegExp(`(from\\s+['"])${escaped}`, "g"),
|
|
401
|
+
`$1${url}`,
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
} catch (e) {
|
|
405
|
+
Fez.consoleError(`importmap parse error: ${e.message}`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
// Remove the Fez.head({importmap:...}) calls
|
|
409
|
+
code = code.replace(importmapRe, "");
|
|
410
|
+
|
|
411
|
+
Fez.head({ script: code });
|
|
412
|
+
|
|
413
|
+
// Check for compile errors after delay
|
|
414
|
+
setTimeout(() => {
|
|
415
|
+
if (!Fez.index[tagName]?.class) {
|
|
416
|
+
Fez.consoleError(`Template "${tagName}" possible compile error.`);
|
|
199
417
|
}
|
|
200
|
-
}, 2000)
|
|
418
|
+
}, 2000);
|
|
201
419
|
} else {
|
|
202
420
|
try {
|
|
203
|
-
new Function(
|
|
204
|
-
} catch(e) {
|
|
205
|
-
Fez.
|
|
206
|
-
console.log(
|
|
421
|
+
new Function(code)();
|
|
422
|
+
} catch (e) {
|
|
423
|
+
Fez.consoleError(`Template "${tagName}" compile error: ${e.message}`);
|
|
424
|
+
console.log(code);
|
|
207
425
|
}
|
|
208
426
|
}
|
|
209
427
|
}
|
|
210
428
|
|
|
211
|
-
|
|
212
|
-
|
|
429
|
+
/**
|
|
430
|
+
* Add CSS to hide custom element until compiled
|
|
431
|
+
*/
|
|
432
|
+
function hideCustomElement(tagName) {
|
|
433
|
+
if (!tagName) return;
|
|
434
|
+
|
|
435
|
+
let styleEl = document.getElementById("fez-hidden-styles");
|
|
436
|
+
if (!styleEl) {
|
|
437
|
+
styleEl = document.createElement("style");
|
|
438
|
+
styleEl.id = "fez-hidden-styles";
|
|
439
|
+
document.head.appendChild(styleEl);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const allTags = [...Fez.index.names(), tagName].sort().join(", ");
|
|
443
|
+
styleEl.textContent = `${allTags} { display: none; }\n`;
|
|
444
|
+
}
|