@dinoreic/fez 0.2.0 → 0.3.0
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 +154 -157
- package/dist/fez.js +18 -18
- package/dist/fez.js.map +4 -4
- package/dist/log.js +5 -0
- package/dist/log.js.map +7 -0
- package/package.json +17 -13
- package/src/fez/compile.js +70 -47
- package/src/fez/connect.js +98 -63
- package/src/fez/defaults.js +64 -0
- package/src/fez/instance.js +131 -123
- package/src/fez/lib/template.js +4 -0
- package/src/fez/root.js +33 -134
- package/src/fez/utility.js +184 -0
- package/src/fez.js +5 -37
- package/src/log.js +154 -0
- package/src/rollup.js +73 -22
- package/dist/rollup.js +0 -3
- package/dist/rollup.js.map +0 -7
package/dist/log.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
(()=>{var u=o=>{let a=o.split(/(<\/?[^>]+>)/g).map(i=>i.trim()).filter(i=>i),n=0,t=[];for(let i=0;i<a.length;i++){let e=a[i],s=a[i+1],p=a[i+2];if(e.startsWith("<"))if(!e.startsWith("</")&&!e.endsWith("/>")&&s&&!s.startsWith("<")&&p&&p.startsWith("</")){let r=Math.max(0,n);t.push(" ".repeat(r)+e+s+p),i+=2}else if(e.startsWith("</")){n--;let r=Math.max(0,n);t.push(" ".repeat(r)+e)}else if(e.endsWith("/>")||e.includes(" />")){let r=Math.max(0,n);t.push(" ".repeat(r)+e)}else{let r=Math.max(0,n);t.push(" ".repeat(r)+e),n++}else if(e){let r=Math.max(0,n);t.push(" ".repeat(r)+e)}}return t.join(`
|
|
2
|
+
`)},c=(()=>{let o=[],a=[],n=0;return t=>{if(!document.body){window.requestAnimationFrame(()=>c(t));return}t instanceof Node&&(t=u(t.outerHTML));let i=typeof t;t===void 0&&(t="undefined"),t===null&&(t="null"),Array.isArray(t)?i="array":typeof t=="object"&&t!==null&&(i="object"),typeof t!="string"&&(t=JSON.stringify(t,(r,l)=>typeof l=="function"?String(l):l,2).replaceAll("<","<")),t=t.trim(),o.push(t+`
|
|
3
|
+
|
|
4
|
+
type: ${i}`),a.push(i);let e=document.getElementById("dump-dialog");e||(e=document.body.appendChild(document.createElement("div")),e.id="dump-dialog",e.style.cssText="position:fixed;top:30px;left:30px;right:50px;bottom:50px;background:#fff;border:1px solid#333;box-shadow:0 0 10px rgba(0,0,0,0.5);padding:20px;overflow:auto;z-index:9999;font:13px/1.4 monospace;white-space:pre");let s=parseInt(localStorage.getItem("_LOG_INDEX"));!isNaN(s)&&s>=0&&s<o.length?n=s:n=o.length-1;let p=()=>{let r=o.map((l,d)=>{let f="#f0f0f0";return d!==n&&(a[d]==="object"?f="#d6e3ef":a[d]==="array"&&(f="#d8d5ef")),`<button style="padding:4px 8px;margin:0;cursor:pointer;background:${d===n?"#333":f};color:${d===n?"#fff":"#000"}" data-index="${d}">${d+1}</button>`}).join("");e.innerHTML='<div style="display:flex;flex-direction:column;height:100%"><div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:10px"><div style="display:flex;flex-wrap:wrap;gap:4px;flex:1;margin-right:10px">'+r+'</div><button style="padding:4px 8px;cursor:pointer;flex-shrink:0">×</button></div><xmp style="flex:1;overflow:auto;margin:0;padding:0;color:#000;background:#fff;font-size:14px;line-height:22px">'+o[n]+"</xmp></div>",e.querySelector('button[style*="flex-shrink:0"]').onclick=()=>e.remove(),e.querySelectorAll("button[data-index]").forEach(l=>{l.onclick=()=>{n=parseInt(l.dataset.index),localStorage.setItem("_LOG_INDEX",n),p()}})};p()}})();typeof window<"u"&&(window.LOG=c,window.LOG_PP=u);var x=c;})();
|
|
5
|
+
//# sourceMappingURL=log.js.map
|
package/dist/log.js.map
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/log.js"],
|
|
4
|
+
"sourcesContent": ["// pretty print HTML\nconst LOG_PP = (html) => {\n const parts = html\n .split(/(<\\/?[^>]+>)/g)\n .map(p => p.trim())\n .filter(p => p);\n\n let indent = 0;\n const lines = [];\n\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i];\n const nextPart = parts[i + 1];\n const nextNextPart = parts[i + 2];\n\n // Check if it's a tag\n if (part.startsWith('<')) {\n // Check if this is an opening tag followed by text and then its closing tag\n if (!part.startsWith('</') && !part.endsWith('/>') && nextPart && !nextPart.startsWith('<') && nextNextPart && nextNextPart.startsWith('</')) {\n // Combine them on one line\n const actualIndent = Math.max(0, indent);\n lines.push(' '.repeat(actualIndent) + part + nextPart + nextNextPart);\n i += 2; // Skip the next two parts\n }\n // Closing tag\n else if (part.startsWith('</')) {\n indent--;\n const actualIndent = Math.max(0, indent);\n lines.push(' '.repeat(actualIndent) + part);\n }\n // Self-closing tag\n else if (part.endsWith('/>') || part.includes(' />')) {\n const actualIndent = Math.max(0, indent);\n lines.push(' '.repeat(actualIndent) + part);\n }\n // Opening tag\n else {\n const actualIndent = Math.max(0, indent);\n lines.push(' '.repeat(actualIndent) + part);\n indent++;\n }\n }\n // Text node\n else if (part) {\n const actualIndent = Math.max(0, indent);\n lines.push(' '.repeat(actualIndent) + part);\n }\n }\n\n return lines.join('\\n');\n}\n\nconst LOG = (() => {\n const logs = [];\n const logTypes = []; // Track the original type of each log\n let currentIndex = 0;\n\n return o => {\n if (!document.body) {\n window.requestAnimationFrame( () => LOG(o) )\n return\n }\n\n if (o instanceof Node) {\n o = LOG_PP(o.outerHTML)\n }\n\n // Store the original type\n let originalType = typeof o;\n\n if (o === undefined) { o = 'undefined' }\n if (o === null) { o = 'null' }\n\n if (Array.isArray(o)) {\n originalType = 'array';\n } else if (typeof o === 'object' && o !== null) {\n originalType = 'object';\n }\n\n if (typeof o != 'string') {\n o = JSON.stringify(o, (key, value) => {\n if (typeof value === 'function') {\n return String(value);\n }\n return value;\n }, 2).replaceAll('<', '<')\n }\n\n o = o.trim()\n\n logs.push(o + `\\n\\ntype: ${originalType}`);\n logTypes.push(originalType);\n\n let d = document.getElementById('dump-dialog');\n if (!d) {\n d = document.body.appendChild(document.createElement('div'));\n d.id = 'dump-dialog';\n d.style.cssText =\n 'position:fixed;top:30px;left:30px;right:50px;bottom:50px;' +\n 'background:#fff;border:1px solid#333;box-shadow:0 0 10px rgba(0,0,0,0.5);' +\n 'padding:20px;overflow:auto;z-index:9999;font:13px/1.4 monospace;white-space:pre';\n }\n\n // Check if we have a saved index and it's still valid\n const savedIndex = parseInt(localStorage.getItem('_LOG_INDEX'));\n if (!isNaN(savedIndex) && savedIndex >= 0 && savedIndex < logs.length) {\n currentIndex = savedIndex;\n } else {\n currentIndex = logs.length - 1;\n }\n\n const renderContent = () => {\n const buttons = logs.map((_, i) => {\n let bgColor = '#f0f0f0'; // default\n if (i !== currentIndex) {\n if (logTypes[i] === 'object') {\n bgColor = '#d6e3ef'; // super light blue\n } else if (logTypes[i] === 'array') {\n bgColor = '#d8d5ef'; // super light indigo\n }\n }\n return `<button style=\"padding:4px 8px;margin:0;cursor:pointer;background:${i === currentIndex ? '#333' : bgColor};color:${i === currentIndex ? '#fff' : '#000'}\" data-index=\"${i}\">${i + 1}</button>`\n }).join('');\n\n d.innerHTML =\n '<div style=\"display:flex;flex-direction:column;height:100%\">' +\n '<div style=\"display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:10px\">' +\n '<div style=\"display:flex;flex-wrap:wrap;gap:4px;flex:1;margin-right:10px\">' + buttons + '</div>' +\n '<button style=\"padding:4px 8px;cursor:pointer;flex-shrink:0\">×</button>' +\n '</div>' +\n '<xmp style=\"flex:1;overflow:auto;margin:0;padding:0;color:#000;background:#fff;font-size:14px;line-height:22px\">' + logs[currentIndex] + '</xmp>' +\n '</div>';\n\n d.querySelector('button[style*=\"flex-shrink:0\"]').onclick = () => d.remove();\n\n d.querySelectorAll('button[data-index]').forEach(btn => {\n btn.onclick = () => {\n currentIndex = parseInt(btn.dataset.index);\n localStorage.setItem('_LOG_INDEX', currentIndex);\n renderContent();\n };\n });\n };\n\n renderContent();\n };\n})();\n\nif (typeof window !== 'undefined') {\n window.LOG = LOG\n window.LOG_PP = LOG_PP\n}\n\nexport default LOG\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["LOG_PP", "html", "parts", "p", "indent", "lines", "part", "nextPart", "nextNextPart", "actualIndent", "LOG", "logs", "logTypes", "currentIndex", "o", "originalType", "key", "value", "d", "savedIndex", "renderContent", "buttons", "_", "i", "bgColor", "btn", "log_default"]
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dinoreic/fez",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Runtime custom dom elements",
|
|
5
5
|
"main": "dist/fez.js",
|
|
6
6
|
"type": "module",
|
|
@@ -13,10 +13,14 @@
|
|
|
13
13
|
"import": "./src/rollup.js",
|
|
14
14
|
"require": "./src/rollup.js"
|
|
15
15
|
},
|
|
16
|
+
"./log": {
|
|
17
|
+
"import": "./src/log.js",
|
|
18
|
+
"require": "./src/log.js"
|
|
19
|
+
},
|
|
16
20
|
"./package.json": "./package.json"
|
|
17
21
|
},
|
|
18
22
|
"bin": {
|
|
19
|
-
"fez": "
|
|
23
|
+
"fez": "bin/fez"
|
|
20
24
|
},
|
|
21
25
|
"files": [
|
|
22
26
|
"bin",
|
|
@@ -25,17 +29,6 @@
|
|
|
25
29
|
"README.md",
|
|
26
30
|
"LICENSE"
|
|
27
31
|
],
|
|
28
|
-
"scripts": {
|
|
29
|
-
"build": "bun build.js b",
|
|
30
|
-
"b": "bun build.js b",
|
|
31
|
-
"watch": "bun build.js w",
|
|
32
|
-
"server": "bun run lib/server.js",
|
|
33
|
-
"dev": "bunx concurrently --kill-others \"bun run server\" \"find src demo lib | entr -c sh -c 'bun run index && bun run b'\"",
|
|
34
|
-
"test": "bun test",
|
|
35
|
-
"prepublishOnly": "bun run build && bun run test",
|
|
36
|
-
"publish": "npm publish --access public",
|
|
37
|
-
"index": "ruby ./bin/fez-index 'demo/fez/*.fez' > demo/fez/index.json"
|
|
38
|
-
},
|
|
39
32
|
"keywords": [
|
|
40
33
|
"dom",
|
|
41
34
|
"elements",
|
|
@@ -62,5 +55,16 @@
|
|
|
62
55
|
"happy-dom": "^18.0.1",
|
|
63
56
|
"jsdom": "^26.1.0",
|
|
64
57
|
"mime": "^4.0.7"
|
|
58
|
+
},
|
|
59
|
+
"scripts": {
|
|
60
|
+
"build": "bun build.js b",
|
|
61
|
+
"b": "bun run build",
|
|
62
|
+
"watch": "bun build.js w",
|
|
63
|
+
"server": "bun run lib/server.js",
|
|
64
|
+
"dev": "bunx concurrently --kill-others \"bun run server\" \"find src demo lib | entr -cn sh -c 'bun run index && bun run b'\"",
|
|
65
|
+
"test": "bun test",
|
|
66
|
+
"prepublishOnly": "bun run build && bun run test",
|
|
67
|
+
"publish": "npm publish --access public",
|
|
68
|
+
"index": "ruby ./bin/fez-index 'demo/fez/*.fez' > demo/fez/index.json"
|
|
65
69
|
}
|
|
66
70
|
}
|
package/src/fez/compile.js
CHANGED
|
@@ -36,8 +36,7 @@ const compileToClass = (html) => {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
if (result.head) {
|
|
39
|
-
const container =
|
|
40
|
-
container.innerHTML = result.head
|
|
39
|
+
const container = Fez.domRoot(result.head)
|
|
41
40
|
|
|
42
41
|
// Process all children of the container
|
|
43
42
|
Array.from(container.children).forEach(node => {
|
|
@@ -89,66 +88,86 @@ const compileToClass = (html) => {
|
|
|
89
88
|
return klass
|
|
90
89
|
}
|
|
91
90
|
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
// Fez.compile(templateNode) # compile template node
|
|
97
|
-
// Fez.compile('ui-form', templateNode.innerHTML) # compile string
|
|
98
|
-
export default function (tagName, html) {
|
|
99
|
-
if (tagName instanceof Node) {
|
|
100
|
-
const node = tagName
|
|
91
|
+
// Handle single argument cases - compile all, compile node, or compile from URL
|
|
92
|
+
function compile_bulk(data) {
|
|
93
|
+
if (data instanceof Node) {
|
|
94
|
+
const node = data
|
|
101
95
|
node.remove()
|
|
102
96
|
|
|
103
97
|
const fezName = node.getAttribute('fez')
|
|
104
98
|
|
|
105
99
|
// Check if fezName contains dot or slash (indicates URL)
|
|
106
100
|
if (fezName && (fezName.includes('.') || fezName.includes('/'))) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
Fez.log(`Loading from ${url}`)
|
|
110
|
-
|
|
111
|
-
// Load HTML content via AJAX from URL
|
|
112
|
-
Fez.fetch(url)
|
|
113
|
-
.then(htmlContent => {
|
|
114
|
-
// Check if remote HTML has template/xmp tags with fez attribute
|
|
115
|
-
const parser = new DOMParser()
|
|
116
|
-
const doc = parser.parseFromString(htmlContent, 'text/html')
|
|
117
|
-
const fezElements = doc.querySelectorAll('template[fez], xmp[fez]')
|
|
118
|
-
|
|
119
|
-
if (fezElements.length > 0) {
|
|
120
|
-
// Compile each found fez element
|
|
121
|
-
fezElements.forEach(el => {
|
|
122
|
-
const name = el.getAttribute('fez')
|
|
123
|
-
if (name && !name.includes('-') && !name.includes('.') && !name.includes('/')) {
|
|
124
|
-
console.error(`Fez: Invalid custom element name "${name}". Custom element names must contain a dash (e.g., 'my-element', 'ui-button').`)
|
|
125
|
-
}
|
|
126
|
-
const content = el.innerHTML
|
|
127
|
-
Fez.compile(name, content)
|
|
128
|
-
})
|
|
129
|
-
} else {
|
|
130
|
-
// No fez elements found, use extracted name from URL
|
|
131
|
-
const name = url.split('/').pop().split('.')[0]
|
|
132
|
-
Fez.compile(name, htmlContent)
|
|
133
|
-
}
|
|
134
|
-
})
|
|
135
|
-
.catch(error => {
|
|
136
|
-
console.error(`FEZ template load error for "${fezName}": ${error.message}`)
|
|
137
|
-
})
|
|
101
|
+
compile_from_url(fezName)
|
|
138
102
|
return
|
|
139
103
|
} else {
|
|
140
104
|
// Validate fezName format for non-URL names
|
|
141
105
|
if (fezName && !fezName.includes('-')) {
|
|
142
106
|
console.error(`Fez: Invalid custom element name "${fezName}". Custom element names must contain a dash (e.g., 'my-element', 'ui-button').`)
|
|
143
107
|
}
|
|
144
|
-
|
|
145
|
-
|
|
108
|
+
// Compile the node directly
|
|
109
|
+
return compile(fezName, node.innerHTML)
|
|
146
110
|
}
|
|
147
111
|
}
|
|
148
|
-
else
|
|
149
|
-
|
|
112
|
+
else {
|
|
113
|
+
let root = data ? Fez.domRoot(data) : document.body
|
|
114
|
+
|
|
115
|
+
root.querySelectorAll('template[fez], xmp[fez]').forEach((n) => {
|
|
116
|
+
compile_bulk(n)
|
|
117
|
+
})
|
|
118
|
+
|
|
150
119
|
return
|
|
151
120
|
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function compile_from_url(url) {
|
|
124
|
+
Fez.log(`Loading from ${url}`)
|
|
125
|
+
|
|
126
|
+
// Load HTML content via AJAX from URL
|
|
127
|
+
Fez.fetch(url)
|
|
128
|
+
.then(htmlContent => {
|
|
129
|
+
// Check if remote HTML has template/xmp tags with fez attribute
|
|
130
|
+
const parser = new DOMParser()
|
|
131
|
+
const doc = parser.parseFromString(htmlContent, 'text/html')
|
|
132
|
+
const fezElements = doc.querySelectorAll('template[fez], xmp[fez]')
|
|
133
|
+
|
|
134
|
+
if (fezElements.length > 0) {
|
|
135
|
+
// Compile each found fez element
|
|
136
|
+
fezElements.forEach(el => {
|
|
137
|
+
const name = el.getAttribute('fez')
|
|
138
|
+
if (name && !name.includes('-') && !name.includes('.') && !name.includes('/')) {
|
|
139
|
+
console.error(`Fez: Invalid custom element name "${name}". Custom element names must contain a dash (e.g., 'my-element', 'ui-button').`)
|
|
140
|
+
}
|
|
141
|
+
const content = el.innerHTML
|
|
142
|
+
compile(name, content)
|
|
143
|
+
})
|
|
144
|
+
} else {
|
|
145
|
+
// No fez elements found, use extracted name from URL
|
|
146
|
+
const name = url.split('/').pop().split('.')[0]
|
|
147
|
+
compile(name, htmlContent)
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
.catch(error => {
|
|
151
|
+
console.error(`FEZ template load error for "${url}": ${error.message}`)
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// <template fez="ui-form">
|
|
156
|
+
// <script>
|
|
157
|
+
// ...
|
|
158
|
+
// Fez.compile() # compile all
|
|
159
|
+
// Fez.compile(templateNode) # compile template node or string with template or xmp tags
|
|
160
|
+
// Fez.compile('ui-form', templateNode.innerHTML) # compile string
|
|
161
|
+
function compile(tagName, html) {
|
|
162
|
+
// Handle single argument cases
|
|
163
|
+
if (arguments.length === 1) {
|
|
164
|
+
return compile_bulk(tagName)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// If html contains </xmp>, send to compile_bulk for processing
|
|
168
|
+
if (html && html.includes('</xmp>')) {
|
|
169
|
+
return compile_bulk(html)
|
|
170
|
+
}
|
|
152
171
|
|
|
153
172
|
// Validate element name if it's not a URL
|
|
154
173
|
if (tagName && !tagName.includes('-') && !tagName.includes('.') && !tagName.includes('/')) {
|
|
@@ -168,7 +187,8 @@ export default function (tagName, html) {
|
|
|
168
187
|
styleContainer.id = 'fez-hidden-styles'
|
|
169
188
|
document.head.appendChild(styleContainer)
|
|
170
189
|
}
|
|
171
|
-
|
|
190
|
+
const allTags = [...Object.keys(Fez.classes), tagName].sort().join(', ')
|
|
191
|
+
styleContainer.textContent = `${allTags} { display: none; }\n`
|
|
172
192
|
}
|
|
173
193
|
|
|
174
194
|
// we cant try/catch javascript modules (they use imports)
|
|
@@ -190,3 +210,6 @@ export default function (tagName, html) {
|
|
|
190
210
|
}
|
|
191
211
|
}
|
|
192
212
|
}
|
|
213
|
+
|
|
214
|
+
export { compile_from_url }
|
|
215
|
+
export default compile
|
package/src/fez/connect.js
CHANGED
|
@@ -1,94 +1,122 @@
|
|
|
1
|
-
// templating
|
|
2
1
|
import createTemplate from './lib/template.js'
|
|
3
2
|
import FezBase from './instance.js'
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Registers a new custom element with Fez framework
|
|
6
|
+
* @param {string} name - Custom element name (must contain a dash)
|
|
7
|
+
* @param {Class|Object} klass - Component class or configuration object
|
|
8
|
+
* @example
|
|
9
|
+
* Fez('my-component', class {
|
|
10
|
+
* HTML = '<div>Hello World</div>'
|
|
11
|
+
* CSS = '.my-component { color: blue; }'
|
|
12
|
+
* })
|
|
13
|
+
*/
|
|
14
|
+
export default function connect(name, klass) {
|
|
8
15
|
const Fez = globalThis.window?.Fez || globalThis.Fez;
|
|
9
16
|
// Validate custom element name format (must contain a dash)
|
|
10
17
|
if (!name.includes('-')) {
|
|
11
18
|
console.error(`Fez: Invalid custom element name "${name}". Custom element names must contain a dash (e.g., 'my-element', 'ui-button').`)
|
|
12
19
|
}
|
|
13
20
|
|
|
14
|
-
//
|
|
15
|
-
// Fez('ui-todo', class { ... # instead Fez('ui-todo', class extends FezBase {
|
|
21
|
+
// Transform simple class definitions into Fez components
|
|
16
22
|
if (!klass.fezHtmlRoot) {
|
|
17
23
|
const klassObj = new klass()
|
|
18
24
|
const newKlass = class extends FezBase {}
|
|
19
25
|
|
|
26
|
+
// Copy all properties and methods from the original class
|
|
20
27
|
const props = Object.getOwnPropertyNames(klassObj)
|
|
21
28
|
.concat(Object.getOwnPropertyNames(klass.prototype))
|
|
22
29
|
.filter(el => !['constructor', 'prototype'].includes(el))
|
|
23
30
|
|
|
24
31
|
props.forEach(prop => newKlass.prototype[prop] = klassObj[prop])
|
|
25
32
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (klassObj.
|
|
29
|
-
if (klassObj.
|
|
30
|
-
|
|
31
|
-
if (klassObj.NAME) { newKlass.nodeName = klassObj.NAME }
|
|
32
|
-
if (klassObj.FAST) {
|
|
33
|
-
newKlass.fastBind = klassObj.FAST
|
|
34
|
-
Fez.fastBindInfo.fast.push(typeof klassObj.FAST == 'function' ? `${name} (func)` : name)
|
|
35
|
-
} else {
|
|
36
|
-
Fez.fastBindInfo.slow.push(name)
|
|
33
|
+
// Map component configuration properties
|
|
34
|
+
if (klassObj.GLOBAL) { newKlass.fezGlobal = klassObj.GLOBAL } // Global instance reference
|
|
35
|
+
if (klassObj.CSS) { newKlass.css = klassObj.CSS } // Component styles
|
|
36
|
+
if (klassObj.HTML) {
|
|
37
|
+
newKlass.html = closeCustomTags(klassObj.HTML) // Component template
|
|
37
38
|
}
|
|
39
|
+
if (klassObj.NAME) { newKlass.nodeName = klassObj.NAME } // Custom DOM node name
|
|
38
40
|
|
|
41
|
+
// Auto-mount global components to body
|
|
39
42
|
if (klassObj.GLOBAL) {
|
|
40
|
-
const
|
|
43
|
+
const mountGlobalComponent = () => document.body.appendChild(document.createElement(name))
|
|
41
44
|
|
|
42
45
|
if (document.readyState === 'loading') {
|
|
43
|
-
document.addEventListener('DOMContentLoaded',
|
|
46
|
+
document.addEventListener('DOMContentLoaded', mountGlobalComponent);
|
|
44
47
|
} else {
|
|
45
|
-
|
|
48
|
+
mountGlobalComponent()
|
|
46
49
|
}
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
klass = newKlass
|
|
50
53
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
Fez.log(`${name} compiled`)
|
|
55
|
+
} else if (klass.html) {
|
|
56
|
+
// If klass already has html property, process it
|
|
57
|
+
klass.html = closeCustomTags(klass.html)
|
|
54
58
|
}
|
|
55
59
|
|
|
60
|
+
// Process component template
|
|
56
61
|
if (klass.html) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
// wrap slot to enable reactive re-renders. It will use existing .fez-slot if found
|
|
62
|
+
// Replace <slot /> with reactive slot containers
|
|
60
63
|
klass.html = klass.html.replace(/<slot\s*\/>|<slot\s*>\s*<\/slot>/g, () => {
|
|
61
|
-
const
|
|
62
|
-
return `<${
|
|
64
|
+
const slotTag = klass.SLOT || 'div'
|
|
65
|
+
return `<${slotTag} class="fez-slot" fez-keep="default-slot"></${slotTag}>`
|
|
63
66
|
})
|
|
64
67
|
|
|
68
|
+
// Compile template function
|
|
65
69
|
klass.fezHtmlFunc = createTemplate(klass.html)
|
|
66
70
|
}
|
|
67
71
|
|
|
68
|
-
//
|
|
72
|
+
// Register component styles globally (available to all components)
|
|
69
73
|
if (klass.css) {
|
|
70
74
|
klass.css = Fez.globalCss(klass.css, {name: name})
|
|
71
75
|
}
|
|
72
76
|
|
|
73
77
|
Fez.classes[name] = klass
|
|
74
78
|
|
|
79
|
+
connectCustomElement(name, klass)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Registers the custom element with the browser
|
|
84
|
+
* Sets up batched rendering for optimal performance
|
|
85
|
+
*/
|
|
86
|
+
function connectCustomElement(name, klass) {
|
|
87
|
+
const Fez = globalThis.window?.Fez || globalThis.Fez;
|
|
88
|
+
|
|
75
89
|
if (!customElements.get(name)) {
|
|
76
90
|
customElements.define(name, class extends HTMLElement {
|
|
77
91
|
connectedCallback() {
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
+
// Batch all renders using microtasks for consistent timing and DOM completeness
|
|
93
|
+
if (!Fez._pendingConnections) {
|
|
94
|
+
Fez._pendingConnections = []
|
|
95
|
+
Fez._batchScheduled = false
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
Fez._pendingConnections.push({ name, node: this })
|
|
99
|
+
|
|
100
|
+
if (!Fez._batchScheduled) {
|
|
101
|
+
Fez._batchScheduled = true
|
|
102
|
+
Promise.resolve().then(() => {
|
|
103
|
+
const connections = Fez._pendingConnections.slice()
|
|
104
|
+
// console.error(`Batch processing ${connections.length} components:`, connections.map(c => c.name))
|
|
105
|
+
Fez._pendingConnections = []
|
|
106
|
+
Fez._batchScheduled = false
|
|
107
|
+
|
|
108
|
+
// Sort by DOM order to ensure parent nodes are processed before children
|
|
109
|
+
connections.sort((a, b) => {
|
|
110
|
+
if (a.node.contains(b.node)) return -1
|
|
111
|
+
if (b.node.contains(a.node)) return 1
|
|
112
|
+
return 0
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
connections.forEach(({ name, node }) => {
|
|
116
|
+
if (node.isConnected && node.parentNode) {
|
|
117
|
+
connectNode(name, node)
|
|
118
|
+
}
|
|
119
|
+
})
|
|
92
120
|
})
|
|
93
121
|
}
|
|
94
122
|
}
|
|
@@ -96,8 +124,10 @@ export default function(name, klass) {
|
|
|
96
124
|
}
|
|
97
125
|
}
|
|
98
126
|
|
|
99
|
-
|
|
100
|
-
|
|
127
|
+
/**
|
|
128
|
+
* Converts self-closing custom tags to full open/close format
|
|
129
|
+
* Required for proper HTML parsing of custom elements
|
|
130
|
+
*/
|
|
101
131
|
function closeCustomTags(html) {
|
|
102
132
|
const selfClosingTags = new Set([
|
|
103
133
|
'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'source', 'track', 'wbr'
|
|
@@ -108,17 +138,11 @@ function closeCustomTags(html) {
|
|
|
108
138
|
})
|
|
109
139
|
}
|
|
110
140
|
|
|
111
|
-
function useFastRender(n, klass) {
|
|
112
|
-
const fezFast = n.getAttribute('fez-fast')
|
|
113
|
-
var isFast = typeof klass.fastBind === 'function' ? klass.fastBind(n) : klass.fastBind
|
|
114
|
-
|
|
115
|
-
if (fezFast == 'false') {
|
|
116
|
-
return false
|
|
117
|
-
} else {
|
|
118
|
-
return fezFast || isFast
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Initializes a Fez component instance from a DOM node
|
|
144
|
+
* Replaces the custom element with the component's rendered content
|
|
145
|
+
*/
|
|
122
146
|
function connectNode(name, node) {
|
|
123
147
|
const klass = Fez.classes[name]
|
|
124
148
|
const parentNode = node.parentNode
|
|
@@ -143,7 +167,7 @@ function connectNode(name, node) {
|
|
|
143
167
|
fez.props = klass.getProps(node, newNode)
|
|
144
168
|
fez.class = klass
|
|
145
169
|
|
|
146
|
-
//
|
|
170
|
+
// Move child nodes to preserve DOM event listeners
|
|
147
171
|
fez.slot(node, newNode)
|
|
148
172
|
|
|
149
173
|
newNode.fez = fez
|
|
@@ -160,9 +184,18 @@ function connectNode(name, node) {
|
|
|
160
184
|
newNode.setAttribute('id', fez.props.id)
|
|
161
185
|
}
|
|
162
186
|
|
|
163
|
-
|
|
164
|
-
|
|
187
|
+
// Component lifecycle initialization
|
|
188
|
+
fez.fezRegister()
|
|
189
|
+
|
|
190
|
+
// Call initialization method (init, created, or connect)
|
|
191
|
+
;(fez.init || fez.created || fez.connect).bind(fez)(fez.props)
|
|
192
|
+
|
|
193
|
+
// Initial render
|
|
165
194
|
fez.render()
|
|
195
|
+
fez.firstRender = true
|
|
196
|
+
|
|
197
|
+
// Trigger mount lifecycle hook
|
|
198
|
+
fez.onMount(fez.props)
|
|
166
199
|
|
|
167
200
|
if (fez.onSubmit) {
|
|
168
201
|
const form = fez.root.nodeName == 'FORM' ? fez.root : fez.find('form')
|
|
@@ -172,11 +205,11 @@ function connectNode(name, node) {
|
|
|
172
205
|
}
|
|
173
206
|
}
|
|
174
207
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
// if onPropsChange method defined, add observer and trigger call on all attributes once component is loaded
|
|
208
|
+
// Set up reactive attribute watching
|
|
178
209
|
if (fez.onPropsChange) {
|
|
179
210
|
observer.observe(newNode, {attributes:true})
|
|
211
|
+
|
|
212
|
+
// Trigger initial prop change callbacks
|
|
180
213
|
for (const [key, value] of Object.entries(fez.props)) {
|
|
181
214
|
fez.onPropsChange(key, value)
|
|
182
215
|
}
|
|
@@ -184,8 +217,10 @@ function connectNode(name, node) {
|
|
|
184
217
|
}
|
|
185
218
|
}
|
|
186
219
|
|
|
187
|
-
|
|
188
|
-
|
|
220
|
+
/**
|
|
221
|
+
* Global mutation observer for reactive attribute changes
|
|
222
|
+
* Watches for attribute changes and triggers component updates
|
|
223
|
+
*/
|
|
189
224
|
const observer = new MutationObserver((mutationsList, _) => {
|
|
190
225
|
for (const mutation of mutationsList) {
|
|
191
226
|
if (mutation.type === 'attributes') {
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Wrap defaults in a function to avoid immediate execution
|
|
2
|
+
const loadDefaults = () => {
|
|
3
|
+
// include fez component by name
|
|
4
|
+
//<fez-component name="some-node" :props="fez.props"></fez-component>
|
|
5
|
+
Fez('fez-component', class {
|
|
6
|
+
init(props) {
|
|
7
|
+
const tag = document.createElement(props.name)
|
|
8
|
+
tag.props = props.props || props['data-props'] || props
|
|
9
|
+
|
|
10
|
+
while (this.root.firstChild) {
|
|
11
|
+
this.root.parentNode.insertBefore(this.root.lastChild, tag.nextSibling);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
this.root.innerHTML = ''
|
|
15
|
+
this.root.appendChild(tag)
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
// include remote data from url
|
|
20
|
+
// <fez-include src="./demo/fez/ui-slider.html"></fez-include>
|
|
21
|
+
Fez('fez-include', class {
|
|
22
|
+
init(props) {
|
|
23
|
+
Fez.fetch(props.src, (data)=>{
|
|
24
|
+
const dom = Fez.domRoot(data)
|
|
25
|
+
Fez.head(dom) // include scripts and load fez components
|
|
26
|
+
this.root.innerHTML = dom.innerHTML
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
// include remote data from url
|
|
32
|
+
// <fez-inline :state="{count: 0}">
|
|
33
|
+
// <button onclick="fez.state.count += 1">+</button>
|
|
34
|
+
// {{ state.count }} * {{ state.count }} = {{ state.count * state.count }}
|
|
35
|
+
// </fez-inline>
|
|
36
|
+
Fez('fez-inline', class {
|
|
37
|
+
init(props) {
|
|
38
|
+
const html = this.root.innerHTML
|
|
39
|
+
|
|
40
|
+
if (this.root.innerHTML.includes('<')) {
|
|
41
|
+
const hash = Fez.fnv1(this.root.outerHTML)
|
|
42
|
+
const nodeName = `inline-${hash}`
|
|
43
|
+
Fez(nodeName, class {
|
|
44
|
+
HTML = html
|
|
45
|
+
init() {
|
|
46
|
+
Object.assign(this.state, props.state || {})
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const el = document.createElement(nodeName)
|
|
51
|
+
this.root.after(this.root.lastChild, el);
|
|
52
|
+
this.root.remove()
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Only load defaults if Fez is available
|
|
59
|
+
if (typeof Fez !== 'undefined' && Fez) {
|
|
60
|
+
loadDefaults()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Export for use in tests
|
|
64
|
+
export { loadDefaults }
|