@dinoreic/fez 0.1.0 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dinoreic/fez",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Runtime custom dom elements",
5
5
  "main": "dist/fez.js",
6
6
  "type": "module",
@@ -12,24 +12,29 @@
12
12
  "./rollup": {
13
13
  "import": "./src/rollup.js",
14
14
  "require": "./src/rollup.js"
15
- }
15
+ },
16
+ "./package.json": "./package.json"
17
+ },
18
+ "bin": {
19
+ "fez": "./bin/fez"
16
20
  },
17
21
  "files": [
22
+ "bin",
18
23
  "dist",
19
24
  "src",
20
25
  "README.md",
21
26
  "LICENSE"
22
27
  ],
23
28
  "scripts": {
24
- "pages": "ruby demo/helper.rb > ./index.html",
25
29
  "build": "bun build.js b",
26
30
  "b": "bun build.js b",
27
31
  "watch": "bun build.js w",
28
32
  "server": "bun run lib/server.js",
29
- "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\"'",
33
+ "dev": "bunx concurrently --kill-others \"bun run server\" \"find src demo lib | entr -c sh -c 'bun run index && bun run b'\"",
30
34
  "test": "bun test",
31
- "push": "bun run build && bun run test",
32
- "prepublishOnly": "bun run build && bun run 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"
33
38
  },
34
39
  "keywords": [
35
40
  "dom",
@@ -50,6 +55,7 @@
50
55
  "homepage": "https://github.com/dux/fez#readme",
51
56
  "devDependencies": {
52
57
  "coffeescript": "^2.7.0",
58
+ "concurrently": "^9.1.2",
53
59
  "esbuild": "0.23.0",
54
60
  "esbuild-coffeescript": "^2.2.0",
55
61
  "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';
@@ -106,13 +109,7 @@ export default function (tagName, html) {
106
109
  Fez.log(`Loading from ${url}`)
107
110
 
108
111
  // 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
- })
112
+ Fez.fetch(url)
116
113
  .then(htmlContent => {
117
114
  // Check if remote HTML has template/xmp tags with fez attribute
118
115
  const parser = new DOMParser()
@@ -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,8 @@ 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()
180
166
 
181
167
  if (fez.onSubmit) {
182
168
  const form = fez.root.nodeName == 'FORM' ? fez.root : fez.find('form')
@@ -19,6 +19,7 @@ export default class FezBase {
19
19
 
20
20
  for (const [key, val] of Object.entries(attrs)) {
21
21
  if ([':'].includes(key[0])) {
22
+ // LOG([key, val])
22
23
  delete attrs[key]
23
24
  try {
24
25
  const newVal = new Function(`return (${val})`).bind(newNode)()
@@ -110,14 +111,29 @@ export default class FezBase {
110
111
  }
111
112
  }
112
113
 
114
+ // clear all node references
113
115
  fezRemoveSelf() {
114
116
  this._setIntervalCache ||= {}
115
117
  Object.keys(this._setIntervalCache).forEach((key)=> {
116
118
  clearInterval(this._setIntervalCache[key])
117
119
  })
118
120
 
121
+ if (this._eventHandlers) {
122
+ Object.entries(this._eventHandlers).forEach(([eventName, handler]) => {
123
+ window.removeEventListener(eventName, handler);
124
+ });
125
+ this._eventHandlers = {};
126
+ }
127
+
128
+ if (this._timeouts) {
129
+ Object.values(this._timeouts).forEach(timeoutId => {
130
+ clearTimeout(timeoutId);
131
+ });
132
+ this._timeouts = {};
133
+ }
134
+
119
135
  this.onDestroy()
120
- this.onDestroy = ()=> {}
136
+ this.onDestroy = () => {}
121
137
 
122
138
  if (this.root) {
123
139
  this.root.fez = undefined
@@ -162,43 +178,78 @@ export default class FezBase {
162
178
  }
163
179
  }
164
180
 
165
- // helper function to execute stuff on window resize, and clean after node is not connected any more
166
- // if delay given, throttle, if not debounce
167
- onResize(func, delay) {
168
- let timeoutId;
169
- let lastRun = 0;
181
+ // Generic function to handle window events with automatic cleanup
182
+ // eventName: 'resize', 'scroll', etc.
183
+ // func: callback function to execute
184
+ // delay: throttle delay in ms (default: 100ms)
185
+ on(eventName, func, delay = 200) {
186
+ this._eventHandlers = this._eventHandlers || {};
187
+ this._timeouts = this._timeouts || {};
188
+
189
+ if (this._eventHandlers[eventName]) {
190
+ window.removeEventListener(eventName, this._eventHandlers[eventName]);
191
+ }
192
+
193
+ if (this._timeouts[eventName]) {
194
+ clearTimeout(this._timeouts[eventName]);
195
+ }
170
196
 
171
- func()
197
+ let lastRun = 0;
172
198
 
173
- const checkAndExecute = () => {
199
+ const doExecute = () => {
174
200
  if (!this.isConnected) {
175
- window.removeEventListener('resize', handleResize);
176
- return;
201
+ if (this._eventHandlers[eventName]) {
202
+ window.removeEventListener(eventName, this._eventHandlers[eventName]);
203
+ delete this._eventHandlers[eventName];
204
+ }
205
+ if (this._timeouts[eventName]) {
206
+ clearTimeout(this._timeouts[eventName]);
207
+ delete this._timeouts[eventName];
208
+ }
209
+ return false;
177
210
  }
178
211
  func.call(this);
212
+ return true;
179
213
  };
180
214
 
181
- const handleResize = () => {
182
- if (!this.isConnected) {
183
- window.removeEventListener('resize', handleResize);
184
- return;
185
- }
215
+ const handleEvent = () => {
216
+ const now = Date.now();
186
217
 
187
- if (delay) {
188
- // Throttle
189
- const now = Date.now();
190
- if (now - lastRun >= delay) {
191
- checkAndExecute();
218
+ if (now - lastRun >= delay) {
219
+ if (doExecute()) {
192
220
  lastRun = now;
221
+ } else {
222
+ return;
193
223
  }
194
- } else {
195
- // Debounce
196
- clearTimeout(timeoutId);
197
- timeoutId = setTimeout(checkAndExecute, 200);
198
224
  }
225
+
226
+ // Clear previous timeout and set new one to ensure final event
227
+ if (this._timeouts[eventName]) {
228
+ clearTimeout(this._timeouts[eventName]);
229
+ }
230
+
231
+ this._timeouts[eventName] = setTimeout(() => {
232
+ if (now > lastRun && doExecute()) {
233
+ lastRun = Date.now();
234
+ }
235
+ delete this._timeouts[eventName];
236
+ }, delay);
199
237
  };
200
238
 
201
- window.addEventListener('resize', handleResize);
239
+ this._eventHandlers[eventName] = handleEvent;
240
+ window.addEventListener(eventName, handleEvent);
241
+ }
242
+
243
+ // Helper function for resize events
244
+ onResize(func, delay) {
245
+ this.on('resize', func, delay);
246
+ func();
247
+ }
248
+
249
+ // Helper function for scroll events
250
+ onScroll(func, delay) {
251
+ this.on('scroll', func, delay);
252
+ func();
202
253
  }
203
254
 
204
255
  // copy child nodes, natively to preserve bound events
@@ -242,7 +293,7 @@ export default class FezBase {
242
293
  const base = this.fezHtmlRoot.replaceAll('"', '"')
243
294
 
244
295
  text = text
245
- .replace(/([^\w\.])fez\./g, `$1${base}`)
296
+ .replace(/(['"\s;])fez\./g, `$1${base}`)
246
297
  .replace(/>\s+</g, '><')
247
298
 
248
299
  return text.trim()
@@ -304,12 +355,13 @@ export default class FezBase {
304
355
  slot.parentNode.removeChild(slot)
305
356
  }
306
357
 
307
- //let currentSlot = this.root.querySelector(':not(span.fez):not(div.fez) > .fez-slot, .fez-slot:not(span.fez *):not(div.fez *)');
308
- let currentSlot = this.find('.fez-slot')
309
- if (currentSlot) {
310
- const newSLot = newNode.querySelector('.fez-slot')
311
- if (newSLot) {
312
- newSLot.parentNode.replaceChild(currentSlot, newSLot)
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)
313
365
  }
314
366
  }
315
367
 
@@ -40,6 +40,11 @@ function parseBlock(data, ifStack) {
40
40
  else {
41
41
  const prefix = '@html '
42
42
 
43
+ if (data.startsWith('json ')) {
44
+ data = data.replace('json ', "@html '<pre class=json>'+JSON.stringify(")
45
+ data += ", null, 2) + '</pre>'"
46
+ }
47
+
43
48
  if (data.startsWith(prefix)) {
44
49
  data = data.replace(prefix, '')
45
50
  } else {
package/src/fez/root.js CHANGED
@@ -141,6 +141,7 @@ Fez.htmlEscape = (text) => {
141
141
  text = text
142
142
  // .replaceAll('&', "&amp;")
143
143
  .replace(/font-family\s*:\s*(?:&[^;]+;|[^;])*?;/gi, '')
144
+ .replaceAll("&", '&amp;')
144
145
  .replaceAll("'", '&apos;')
145
146
  .replaceAll('"', '&quot;')
146
147
  .replaceAll('<', '&lt;')
@@ -191,6 +192,7 @@ Fez.error = (text, show) => {
191
192
  }
192
193
  Fez.log = (text) => {
193
194
  if (Fez.LOG === true) {
195
+ text = String(text).substring(0, 180)
194
196
  console.log(`Fez: ${text}`)
195
197
  }
196
198
  }
@@ -210,20 +212,34 @@ Fez.untilTrue = (func, pingRate) => {
210
212
  }
211
213
 
212
214
  // Script from URL
213
- // head({ js: 'https://example.com/script.js' });
215
+ // Fez.head({ js: 'https://example.com/script.js' });
214
216
  // Script with attributes
215
- // head({ js: 'https://example.com/script.js', type: 'module', async: true });
217
+ // Fez.head({ js: 'https://example.com/script.js', type: 'module', async: true });
216
218
  // Script with callback
217
- // head({ js: 'https://example.com/script.js' }, () => { console.log('loaded') });
219
+ // Fez.head({ js: 'https://example.com/script.js' }, () => { console.log('loaded') });
218
220
  // Module loading with auto-import to window
219
- // head({ js: 'https://example.com/module.js', module: 'MyModule' }); // imports and sets window.MyModule
221
+ // Fez.head({ js: 'https://example.com/module.js', module: 'MyModule' }); // imports and sets window.MyModule
220
222
  // CSS inclusion
221
- // head({ css: 'https://example.com/styles.css' });
223
+ // Fez.head({ css: 'https://example.com/styles.css' });
222
224
  // CSS with additional attributes and callback
223
- // head({ css: 'https://example.com/styles.css', media: 'print' }, () => { console.log('CSS loaded') })
225
+ // Fez.head({ css: 'https://example.com/styles.css', media: 'print' }, () => { console.log('CSS loaded') })
224
226
  // Inline script evaluation
225
- // head({ script: 'console.log("Hello world")' })
227
+ // Fez.head({ script: 'console.log("Hello world")' })
228
+ // Extract from nodes
229
+ // Fez.head(domNode)
226
230
  Fez.head = (config, callback) => {
231
+ if (config.nodeName) {
232
+ if (config.nodeName == 'SCRIPT') {
233
+ Fez.head({script: config.innerText})
234
+ config.remove()
235
+ } else {
236
+ config.querySelectorAll('script').forEach((n) => Fez.head(n) )
237
+ config.querySelectorAll('template[fez], xmp[fez], script[fez]').forEach((n) => Fez.compile(n) )
238
+ }
239
+
240
+ return
241
+ }
242
+
227
243
  if (typeof config !== 'object' || config === null) {
228
244
  throw new Error('head requires an object parameter');
229
245
  }
package/src/fez.js CHANGED
@@ -22,18 +22,23 @@ const observer = new MutationObserver((mutations) => {
22
22
  for (const { addedNodes, removedNodes } of mutations) {
23
23
  addedNodes.forEach((node) => {
24
24
  if (node.nodeType !== 1) return; // only elements
25
- // check the node itself
25
+
26
26
  if (node.matches('template[fez], xmp[fez], script[fez]')) {
27
- window.requestAnimationFrame(()=>{
28
- Fez.compile(node);
29
- node.remove();
30
- })
27
+ Fez.compile(node);
28
+ node.remove();
29
+ }
30
+
31
+ if (node.querySelectorAll) {
32
+ const nestedTemplates = node.querySelectorAll('template[fez], xmp[fez], script[fez]');
33
+ nestedTemplates.forEach(template => {
34
+ Fez.compile(template);
35
+ template.remove();
36
+ });
31
37
  }
32
38
  });
33
39
 
34
40
  removedNodes.forEach((node) => {
35
41
  if (node.nodeType === 1 && node.querySelectorAll) {
36
- // check both the node itself and its descendants
37
42
  const fezElements = node.querySelectorAll('.fez, :scope.fez');
38
43
  fezElements
39
44
  .forEach(el => {
@@ -55,8 +60,11 @@ observer.observe(document.documentElement, {
55
60
 
56
61
  // fez custom tags
57
62
 
63
+ // include fez component by name
58
64
  //<fez-component name="some-node" :props="fez.props"></fez-node>
59
65
  Fez('fez-component', class {
66
+ FAST = true
67
+
60
68
  init(props) {
61
69
  const tag = document.createElement(props.name)
62
70
  tag.props = props.props || props['data-props'] || props
@@ -70,4 +78,20 @@ Fez('fez-component', class {
70
78
  }
71
79
  })
72
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
+
73
96
  export default Fez
97
+ export { Fez }