@dinoreic/fez 0.2.0 → 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.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Runtime custom dom elements",
5
5
  "main": "dist/fez.js",
6
6
  "type": "module",
@@ -13,6 +13,10 @@
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": {
@@ -27,7 +31,7 @@
27
31
  ],
28
32
  "scripts": {
29
33
  "build": "bun build.js b",
30
- "b": "bun build.js b",
34
+ "b": "bun run build",
31
35
  "watch": "bun build.js w",
32
36
  "server": "bun run lib/server.js",
33
37
  "dev": "bunx concurrently --kill-others \"bun run server\" \"find src demo lib | entr -c sh -c 'bun run index && bun run b'\"",
@@ -36,8 +36,7 @@ const compileToClass = (html) => {
36
36
  }
37
37
 
38
38
  if (result.head) {
39
- const container = document.createElement('div')
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
- // <template fez="ui-form">
93
- // <script>
94
- // ...
95
- // Fez.compile() # compile all
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
- const url = fezName
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
- html = node.innerHTML
145
- tagName = fezName
108
+ // Compile the node directly
109
+ return compile(fezName, node.innerHTML)
146
110
  }
147
111
  }
148
- else if (typeof html != 'string') {
149
- 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
+
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
- styleContainer.textContent += `${tagName} { display: none; }\n`
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
@@ -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)
@@ -163,6 +163,8 @@ function connectNode(name, node) {
163
163
  fez.fezRegister();
164
164
  ;(fez.init || fez.created || fez.connect).bind(fez)(fez.props);
165
165
  fez.render()
166
+ fez.firstRender = true
167
+ fez.onMount(fez.props)
166
168
 
167
169
  if (fez.onSubmit) {
168
170
  const form = fez.root.nodeName == 'FORM' ? fez.root : fez.find('form')
@@ -172,8 +174,6 @@ function connectNode(name, node) {
172
174
  }
173
175
  }
174
176
 
175
- fez.onMount(fez.props)
176
-
177
177
  // if onPropsChange method defined, add observer and trigger call on all attributes once component is loaded
178
178
  if (fez.onPropsChange) {
179
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 }
@@ -286,14 +286,48 @@ export default class FezBase {
286
286
  onDestroy() {}
287
287
  onStateChange() {}
288
288
  onGlobalStateChange() {}
289
- publish = Fez.publish
289
+
290
+ // component publish will search for parent component that subscribes by name
291
+ publish(channel, ...args) {
292
+ const handle_publish = (component) => {
293
+ if (Fez._subs && Fez._subs[channel]) {
294
+ const sub = Fez._subs[channel].find(([comp]) => comp === component)
295
+ if (sub) {
296
+ sub[1].bind(component)(...args)
297
+ return true
298
+ }
299
+ }
300
+ return false
301
+ }
302
+
303
+ // Check if current component has subscription
304
+ if (handle_publish(this)) {
305
+ return true
306
+ }
307
+
308
+ // Bubble up to parent components
309
+ let parent = this.root.parentElement
310
+ while (parent) {
311
+ if (parent.fez) {
312
+ if (handle_publish(parent.fez)) {
313
+ return true
314
+ }
315
+ }
316
+ parent = parent.parentElement
317
+ }
318
+
319
+ // If no parent handled it, fall back to global publish
320
+ // Fez.publish(channel, ...args)
321
+ return false
322
+ }
323
+
290
324
  fezBlocks = {}
291
325
 
292
326
  parseHtml(text) {
293
327
  const base = this.fezHtmlRoot.replaceAll('"', '&quot;')
294
328
 
295
329
  text = text
296
- .replace(/(['"\s;])fez\./g, `$1${base}`)
330
+ .replace(/([!'"\s;])fez\.(\w)/g, `$1${base}$2`)
297
331
  .replace(/>\s+</g, '><')
298
332
 
299
333
  return text.trim()
@@ -348,22 +382,18 @@ export default class FezBase {
348
382
  newNode.innerHTML = this.parseHtml(renderedTpl)
349
383
  }
350
384
 
351
- // this comes only from array nodes this.n(...)
352
- const slot = newNode.querySelector('slot')
353
- if (slot) {
354
- this.slot(this.root, slot.parentNode)
355
- slot.parentNode.removeChild(slot)
356
- }
385
+ newNode.querySelectorAll('[fez-keep]').forEach(newEl => {
386
+ const key = newEl.getAttribute('fez-keep')
387
+ const oldEl = this.root.querySelector(`[fez-keep="${key}"]`)
357
388
 
358
- let newSlot = newNode.querySelector('.fez-slot')
359
- if(newSlot) {
360
- let currentSlot = this.find('.fez-slot')
361
- if (currentSlot) {
362
- newSlot.parentNode.replaceChild(currentSlot, newSlot)
363
- } else {
364
- this.slot(this.root, newSlot)
389
+ if (oldEl) {
390
+ // Keep the old element in place of the new one
391
+ newEl.parentNode.replaceChild(oldEl, newEl)
392
+ } else if (key === 'default-slot') {
393
+ // First render - populate the slot with current root children
394
+ Array.from(this.root.childNodes).forEach(child => newEl.appendChild(child))
365
395
  }
366
- }
396
+ })
367
397
 
368
398
  Fez.morphdom(this.root, newNode)
369
399
 
@@ -437,8 +467,7 @@ export default class FezBase {
437
467
  refresh(selector) {
438
468
  alert('NEEDS FIX and remove htmlTemplate')
439
469
  if (selector) {
440
- const n = document.createElement('div')
441
- n.innerHTML = this.class.htmlTemplate
470
+ const n = Fez.domRoot(this.class.htmlTemplate)
442
471
  const tpl = n.querySelector(selector).innerHTML
443
472
  this.render(selector, tpl)
444
473
  } else {
@@ -511,7 +540,7 @@ export default class FezBase {
511
540
 
512
541
  // get root node child nodes as array
513
542
  childNodes(func) {
514
- const children = Array.from(this.root.children)
543
+ let children = Array.from(this.root.children)
515
544
 
516
545
  if (func) {
517
546
  // Create temporary container to avoid ancestor-parent errors
@@ -520,12 +549,11 @@ export default class FezBase {
520
549
  document.body.appendChild(tmpContainer)
521
550
  children.forEach(child => tmpContainer.appendChild(child))
522
551
 
523
- let list = Array.from(tmpContainer.children).map(func)
552
+ children = Array.from(tmpContainer.children).map(func)
524
553
  document.body.removeChild(tmpContainer)
525
- return list
526
- } else {
527
- return children
528
554
  }
555
+
556
+ return children
529
557
  }
530
558
 
531
559
  subscribe(channel, func) {
@@ -565,19 +593,14 @@ export default class FezBase {
565
593
 
566
594
  fezHide() {
567
595
  const node = this.root
596
+ const nodes = this.childNodes()
568
597
  const parent = this.root.parentNode
569
- const fragment = document.createDocumentFragment();
570
598
 
571
- while (node.firstChild) {
572
- fragment.appendChild(node.firstChild)
573
- }
599
+ nodes.reverse().forEach(el => parent.insertBefore(el, node.nextSibling))
574
600
 
575
- // Replace the target element with the fragment (which contains the child elements)
576
- node.parentNode.replaceChild(fragment, node);
577
- // parent.classList.add('fez')
578
- // parent.classList.add(`fez-${this.fezName}`)
601
+ this.root.remove()
579
602
  this.root = parent
580
- return Array.from(this.root.children)
603
+ return nodes
581
604
  }
582
605
 
583
606
  reactiveStore(obj, handler) {
@@ -98,6 +98,10 @@ export default function createTemplate(text, opts = {}) {
98
98
  return parsedData
99
99
  });
100
100
 
101
+ result = result
102
+ .replace(/<!\-\-.*?\-\->/g, '')
103
+ .replace(/>\s+</g, '><')
104
+
101
105
  result = '`' + result.trim() + '`'
102
106
 
103
107
  try {
package/src/fez/root.js CHANGED
@@ -475,6 +475,25 @@ Fez.store = {
475
475
  }
476
476
  };
477
477
 
478
+ // create dom root and return it
479
+ Fez.domRoot = (data, name = 'div') => {
480
+ if (data instanceof Node) {
481
+ return data
482
+ } else {
483
+ const root = document.createElement(name)
484
+ root.innerHTML = data
485
+ return root
486
+ }
487
+ }
488
+
489
+ // add class by name to node and remove it from siblings
490
+ Fez.activateNode = (node, klass = 'active') => {
491
+ Array.from(node.parentElement.children).forEach(child => {
492
+ child.classList.remove(klass)
493
+ })
494
+ node.classList.add(klass)
495
+ }
496
+
478
497
  Fez.compile = compile
479
498
  Fez.state = state
480
499
 
package/src/fez.js CHANGED
@@ -6,6 +6,9 @@ if (typeof window !== 'undefined') window.FezBase = FezBase
6
6
  import Fez from './fez/root.js'
7
7
  if (typeof window !== 'undefined') window.Fez = Fez
8
8
 
9
+ // Load defaults after Fez is properly initialized
10
+ import('./fez/defaults.js')
11
+
9
12
  // clear all unattached nodes
10
13
  setInterval(() => {
11
14
  for (const [key, el] of Fez.instances) {
@@ -58,40 +61,5 @@ observer.observe(document.documentElement, {
58
61
  subtree: true
59
62
  });
60
63
 
61
- // fez custom tags
62
-
63
- // include fez component by name
64
- //<fez-component name="some-node" :props="fez.props"></fez-node>
65
- Fez('fez-component', class {
66
- FAST = true
67
-
68
- init(props) {
69
- const tag = document.createElement(props.name)
70
- tag.props = props.props || props['data-props'] || props
71
-
72
- while (this.root.firstChild) {
73
- this.root.parentNode.insertBefore(this.root.lastChild, tag.nextSibling);
74
- }
75
-
76
- this.root.innerHTML = ''
77
- this.root.appendChild(tag)
78
- }
79
- })
80
-
81
- // include remote data from url
82
- // <fez-include src="./demo/fez/ui-slider.html"></fez-include>
83
- Fez('fez-include', class {
84
- FAST = true
85
-
86
- init(props) {
87
- Fez.fetch(props.src, (data)=>{
88
- const dom = document.createElement('div')
89
- dom.innerHTML = data
90
- Fez.head(dom) // include scripts and load fez components
91
- this.root.innerHTML = dom.innerHTML
92
- })
93
- }
94
- })
95
-
96
64
  export default Fez
97
65
  export { Fez }