@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/README.md +15 -3
- package/bin/fez +32 -0
- package/bin/fez-index +44 -0
- package/dist/fez.js +15 -15
- package/dist/fez.js.map +3 -3
- package/package.json +12 -6
- package/src/fez/compile.js +4 -7
- package/src/fez/connect.js +3 -17
- package/src/fez/instance.js +84 -32
- package/src/fez/lib/template.js +5 -0
- package/src/fez/root.js +23 -7
- package/src/fez.js +30 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dinoreic/fez",
|
|
3
|
-
"version": "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": "
|
|
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
|
-
"
|
|
32
|
-
"
|
|
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",
|
package/src/fez/compile.js
CHANGED
|
@@ -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()
|
package/src/fez/connect.js
CHANGED
|
@@ -143,7 +143,7 @@ function connectNode(name, node) {
|
|
|
143
143
|
fez.props = klass.getProps(node, newNode)
|
|
144
144
|
fez.class = klass
|
|
145
145
|
|
|
146
|
-
//
|
|
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')
|
package/src/fez/instance.js
CHANGED
|
@@ -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
|
-
//
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
197
|
+
let lastRun = 0;
|
|
172
198
|
|
|
173
|
-
const
|
|
199
|
+
const doExecute = () => {
|
|
174
200
|
if (!this.isConnected) {
|
|
175
|
-
|
|
176
|
-
|
|
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
|
|
182
|
-
|
|
183
|
-
window.removeEventListener('resize', handleResize);
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
215
|
+
const handleEvent = () => {
|
|
216
|
+
const now = Date.now();
|
|
186
217
|
|
|
187
|
-
if (delay) {
|
|
188
|
-
|
|
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
|
-
|
|
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(/([
|
|
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
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
|
package/src/fez/lib/template.js
CHANGED
|
@@ -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('&', "&")
|
|
143
143
|
.replace(/font-family\s*:\s*(?:&[^;]+;|[^;])*?;/gi, '')
|
|
144
|
+
.replaceAll("&", '&')
|
|
144
145
|
.replaceAll("'", ''')
|
|
145
146
|
.replaceAll('"', '"')
|
|
146
147
|
.replaceAll('<', '<')
|
|
@@ -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
|
-
|
|
25
|
+
|
|
26
26
|
if (node.matches('template[fez], xmp[fez], script[fez]')) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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 }
|