@dinoreic/fez 0.1.1 → 0.2.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/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("<","&lt;")),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">&times;</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
@@ -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('<', '&lt;')\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\">&times;</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
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/rollup.js"],
4
- "sourcesContent": ["function fezPlugin() {\n return {\n name: 'fez-plugin',\n\n transform(code, filePath) {\n const baseName = filePath.split('/').pop().split('.');\n\n if (baseName[1] === 'fez') {\n code = code.replace(/`/g, '\\\\`').replace(/\\$/g, '\\\\$');\n const transformedCode = `Fez.compile('${baseName[0]}', \\`\\n${code}\\`)`;\n\n // if (baseName[0] === 'admin-menu') {\n // console.log('Transformed code:', baseName, transformedCode);\n // }\n\n return {\n code: transformedCode,\n map: null,\n };\n }\n },\n };\n}\n\n// Export for ES modules\nexport default fezPlugin;\n\n// Export for CommonJS\n// if (typeof module !== 'undefined') {\n// module.exports = fezPlugin;\n// }\n"],
5
- "mappings": "MAAA,SAASA,GAAY,CACnB,MAAO,CACL,KAAM,aAEN,UAAUC,EAAMC,EAAU,CACxB,IAAMC,EAAWD,EAAS,MAAM,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,EAEpD,GAAIC,EAAS,CAAC,IAAM,MAClB,OAAAF,EAAOA,EAAK,QAAQ,KAAM,KAAK,EAAE,QAAQ,MAAO,KAAK,EAO9C,CACL,KAPsB,gBAAgBE,EAAS,CAAC,CAAC;AAAA,EAAUF,CAAI,MAQ/D,IAAK,IACP,CAEJ,CACF,CACF,CAGA,IAAOG,EAAQJ",
4
+ "sourcesContent": ["function fezPlugin() {\n return {\n name: 'fez-plugin',\n\n transform(code, filePath) {\n const baseName = filePath.split('/').pop().split('.');\n\n if (baseName[1] === 'fez') {\n code = code.replace(/`/g, '\\\\`').replace(/\\$/g, '\\\\$');\n const transformedCode = `Fez.compile('${baseName[0]}', \\`\\n${code}\\`)`;\n\n // if (baseName[0] === 'admin-menu') {\n // console.log('Transformed code:', baseName, transformedCode);\n // }\n\n return {\n code: transformedCode,\n map: null,\n };\n }\n },\n };\n}\n\nexport default fezPlugin;\n"],
5
+ "mappings": "MAAA,SAASA,GAAY,CACnB,MAAO,CACL,KAAM,aAEN,UAAUC,EAAMC,EAAU,CACxB,IAAMC,EAAWD,EAAS,MAAM,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,EAEpD,GAAIC,EAAS,CAAC,IAAM,MAClB,OAAAF,EAAOA,EAAK,QAAQ,KAAM,KAAK,EAAE,QAAQ,MAAO,KAAK,EAO9C,CACL,KAPsB,gBAAgBE,EAAS,CAAC,CAAC;AAAA,EAAUF,CAAI,MAQ/D,IAAK,IACP,CAEJ,CACF,CACF,CAEA,IAAOG,EAAQJ",
6
6
  "names": ["fezPlugin", "code", "filePath", "baseName", "rollup_default"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dinoreic/fez",
3
- "version": "0.1.1",
3
+ "version": "0.2.2",
4
4
  "description": "Runtime custom dom elements",
5
5
  "main": "dist/fez.js",
6
6
  "type": "module",
@@ -13,24 +13,32 @@
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
  },
22
+ "bin": {
23
+ "fez": "./bin/fez"
24
+ },
18
25
  "files": [
26
+ "bin",
19
27
  "dist",
20
28
  "src",
21
29
  "README.md",
22
30
  "LICENSE"
23
31
  ],
24
32
  "scripts": {
25
- "pages": "ruby demo/helper.rb > ./index.html",
26
33
  "build": "bun build.js b",
27
- "b": "bun build.js b",
34
+ "b": "bun run build",
28
35
  "watch": "bun build.js w",
29
36
  "server": "bun run lib/server.js",
30
- "dev": "sh -c 'bun run server & SERVER_PID=$!; trap \"kill $SERVER_PID\" EXIT; find src demo lib| entr -c sh -c \"bun run pages && bun run b\"'",
37
+ "dev": "bunx concurrently --kill-others \"bun run server\" \"find src demo lib | entr -c sh -c 'bun run index && bun run b'\"",
31
38
  "test": "bun test",
32
39
  "prepublishOnly": "bun run build && bun run test",
33
- "publish": "bun prepublishOnly && npm publish --access public"
40
+ "publish": "npm publish --access public",
41
+ "index": "ruby ./bin/fez-index 'demo/fez/*.fez' > demo/fez/index.json"
34
42
  },
35
43
  "keywords": [
36
44
  "dom",
@@ -51,6 +59,7 @@
51
59
  "homepage": "https://github.com/dux/fez#readme",
52
60
  "devDependencies": {
53
61
  "coffeescript": "^2.7.0",
62
+ "concurrently": "^9.1.2",
54
63
  "esbuild": "0.23.0",
55
64
  "esbuild-coffeescript": "^2.2.0",
56
65
  "glob-cli": "^1.0.0",
@@ -26,6 +26,9 @@ const compileToClass = (html) => {
26
26
  currentBlock = [];
27
27
  currentType = null;
28
28
  } else if (currentType) {
29
+ // if (currentType == 'script' && line.startsWith('//')) {
30
+ // continue
31
+ // }
29
32
  currentBlock.push(line);
30
33
  } else {
31
34
  result.html += line + '\n';
@@ -33,8 +36,7 @@ const compileToClass = (html) => {
33
36
  }
34
37
 
35
38
  if (result.head) {
36
- const container = document.createElement('div')
37
- container.innerHTML = result.head
39
+ const container = Fez.domRoot(result.head)
38
40
 
39
41
  // Process all children of the container
40
42
  Array.from(container.children).forEach(node => {
@@ -86,72 +88,86 @@ const compileToClass = (html) => {
86
88
  return klass
87
89
  }
88
90
 
89
- // <template fez="ui-form">
90
- // <script>
91
- // ...
92
- // Fez.compile() # compile all
93
- // Fez.compile(templateNode) # compile template node
94
- // Fez.compile('ui-form', templateNode.innerHTML) # compile string
95
- export default function (tagName, html) {
96
- if (tagName instanceof Node) {
97
- 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
98
95
  node.remove()
99
96
 
100
97
  const fezName = node.getAttribute('fez')
101
98
 
102
99
  // Check if fezName contains dot or slash (indicates URL)
103
100
  if (fezName && (fezName.includes('.') || fezName.includes('/'))) {
104
- const url = fezName
105
-
106
- Fez.log(`Loading from ${url}`)
107
-
108
- // Load HTML content via AJAX from URL
109
- fetch(url)
110
- .then(response => {
111
- if (!response.ok) {
112
- throw new Error(`Failed to load ${url}: ${response.status}`)
113
- }
114
- return response.text()
115
- })
116
- .then(htmlContent => {
117
- // Check if remote HTML has template/xmp tags with fez attribute
118
- const parser = new DOMParser()
119
- const doc = parser.parseFromString(htmlContent, 'text/html')
120
- const fezElements = doc.querySelectorAll('template[fez], xmp[fez]')
121
-
122
- if (fezElements.length > 0) {
123
- // Compile each found fez element
124
- fezElements.forEach(el => {
125
- const name = el.getAttribute('fez')
126
- if (name && !name.includes('-') && !name.includes('.') && !name.includes('/')) {
127
- console.error(`Fez: Invalid custom element name "${name}". Custom element names must contain a dash (e.g., 'my-element', 'ui-button').`)
128
- }
129
- const content = el.innerHTML
130
- Fez.compile(name, content)
131
- })
132
- } else {
133
- // No fez elements found, use extracted name from URL
134
- const name = url.split('/').pop().split('.')[0]
135
- Fez.compile(name, htmlContent)
136
- }
137
- })
138
- .catch(error => {
139
- console.error(`FEZ template load error for "${fezName}": ${error.message}`)
140
- })
101
+ compile_from_url(fezName)
141
102
  return
142
103
  } else {
143
104
  // Validate fezName format for non-URL names
144
105
  if (fezName && !fezName.includes('-')) {
145
106
  console.error(`Fez: Invalid custom element name "${fezName}". Custom element names must contain a dash (e.g., 'my-element', 'ui-button').`)
146
107
  }
147
- html = node.innerHTML
148
- tagName = fezName
108
+ // Compile the node directly
109
+ return compile(fezName, node.innerHTML)
149
110
  }
150
111
  }
151
- else if (typeof html != 'string') {
152
- document.body.querySelectorAll('template[fez], xmp[fez]').forEach((n) => Fez.compile(n))
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
+
153
119
  return
154
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
+ }
155
171
 
156
172
  // Validate element name if it's not a URL
157
173
  if (tagName && !tagName.includes('-') && !tagName.includes('.') && !tagName.includes('/')) {
@@ -171,7 +187,8 @@ export default function (tagName, html) {
171
187
  styleContainer.id = 'fez-hidden-styles'
172
188
  document.head.appendChild(styleContainer)
173
189
  }
174
- styleContainer.textContent += `${tagName} { display: none; }\n`
190
+ const allTags = [...Object.keys(Fez.classes), tagName].sort().join(', ')
191
+ styleContainer.textContent = `${allTags} { display: none; }\n`
175
192
  }
176
193
 
177
194
  // we cant try/catch javascript modules (they use imports)
@@ -193,3 +210,6 @@ export default function (tagName, html) {
193
210
  }
194
211
  }
195
212
  }
213
+
214
+ export { compile_from_url }
215
+ export default compile
@@ -58,8 +58,8 @@ export default function(name, klass) {
58
58
 
59
59
  // wrap slot to enable reactive re-renders. It will use existing .fez-slot if found
60
60
  klass.html = klass.html.replace(/<slot\s*\/>|<slot\s*>\s*<\/slot>/g, () => {
61
- const name = klass.slotNodeName || 'div'
62
- return `<${name} class="fez-slot"></${name}>`
61
+ const name = klass.SLOT || 'div'
62
+ return `<${name} class="fez-slot" fez-keep="default-slot"></${name}>`
63
63
  })
64
64
 
65
65
  klass.fezHtmlFunc = createTemplate(klass.html)
@@ -143,7 +143,7 @@ function connectNode(name, node) {
143
143
  fez.props = klass.getProps(node, newNode)
144
144
  fez.class = klass
145
145
 
146
- // copy child nodes, natively to preserve bound events
146
+ // move child nodes, natively to preserve bound events
147
147
  fez.slot(node, newNode)
148
148
 
149
149
  newNode.fez = fez
@@ -161,22 +161,10 @@ function connectNode(name, node) {
161
161
  }
162
162
 
163
163
  fez.fezRegister();
164
- (fez.init || fez.created || fez.connect).bind(fez)(fez.props);
165
-
166
- const oldRoot = fez.root.cloneNode(true)
167
-
168
- if (fez.class.fezHtmlFunc) {
169
- fez.render()
170
- }
171
-
172
- const slot = fez.root.querySelector('.fez-slot')
173
- if (slot) {
174
- if (fez.props.html) {
175
- slot.innerHTML = fez.props.html
176
- } else {
177
- fez.slot(oldRoot, slot)
178
- }
179
- }
164
+ ;(fez.init || fez.created || fez.connect).bind(fez)(fez.props);
165
+ fez.render()
166
+ fez.firstRender = true
167
+ fez.onMount(fez.props)
180
168
 
181
169
  if (fez.onSubmit) {
182
170
  const form = fez.root.nodeName == 'FORM' ? fez.root : fez.find('form')
@@ -186,8 +174,6 @@ function connectNode(name, node) {
186
174
  }
187
175
  }
188
176
 
189
- fez.onMount(fez.props)
190
-
191
177
  // if onPropsChange method defined, add observer and trigger call on all attributes once component is loaded
192
178
  if (fez.onPropsChange) {
193
179
  observer.observe(newNode, {attributes:true})
@@ -0,0 +1,69 @@
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
+ FAST = true
7
+
8
+ init(props) {
9
+ const tag = document.createElement(props.name)
10
+ tag.props = props.props || props['data-props'] || props
11
+
12
+ while (this.root.firstChild) {
13
+ this.root.parentNode.insertBefore(this.root.lastChild, tag.nextSibling);
14
+ }
15
+
16
+ this.root.innerHTML = ''
17
+ this.root.appendChild(tag)
18
+ }
19
+ })
20
+
21
+ // include remote data from url
22
+ // <fez-include src="./demo/fez/ui-slider.html"></fez-include>
23
+ Fez('fez-include', class {
24
+ FAST = true
25
+
26
+ init(props) {
27
+ Fez.fetch(props.src, (data)=>{
28
+ const dom = Fez.domRoot(data)
29
+ Fez.head(dom) // include scripts and load fez components
30
+ this.root.innerHTML = dom.innerHTML
31
+ })
32
+ }
33
+ })
34
+
35
+ // include remote data from url
36
+ // <fez-inline :state="{count: 0}">
37
+ // <button onclick="fez.state.count += 1">&plus;</button>
38
+ // {{ state.count }} * {{ state.count }} = {{ state.count * state.count }}
39
+ // </fez-inline>
40
+ Fez('fez-inline', class {
41
+ init(props) {
42
+ const html = this.root.innerHTML
43
+
44
+ if (this.root.innerHTML.includes('<')) {
45
+ const hash = Fez.fnv1(this.root.outerHTML)
46
+ const nodeName = `inline-${hash}`
47
+ Fez(nodeName, class {
48
+ FAST = true
49
+ HTML = html
50
+ init() {
51
+ Object.assign(this.state, props.state || {})
52
+ }
53
+ })
54
+
55
+ const el = document.createElement(nodeName)
56
+ this.root.after(this.root.lastChild, el);
57
+ this.root.remove()
58
+ }
59
+ }
60
+ })
61
+ }
62
+
63
+ // Only load defaults if Fez is available
64
+ if (typeof Fez !== 'undefined' && Fez) {
65
+ loadDefaults()
66
+ }
67
+
68
+ // Export for use in tests
69
+ export { loadDefaults }