@dinoreic/fez 0.3.0 → 0.4.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 +49 -5
- package/dist/fez.js +43 -17
- package/dist/fez.js.map +4 -4
- package/package.json +2 -2
- package/src/fez/compile.js +4 -7
- package/src/fez/connect.js +50 -77
- package/src/fez/defaults.js +36 -1
- package/src/fez/instance.js +100 -21
- package/src/fez/root.js +13 -187
- package/src/fez/utility.js +210 -1
- package/src/fez/utils/css_mixin.js +25 -0
- package/src/fez/utils/dump.js +240 -0
- package/src/fez/utils/highlight_all.js +98 -0
- package/src/svelte-cde-adapter.coffee +122 -0
- package/dist/log.js +0 -5
- package/dist/log.js.map +0 -7
- package/src/log.js +0 -154
package/package.json
CHANGED
package/src/fez/compile.js
CHANGED
|
@@ -70,10 +70,7 @@ const compileToClass = (html) => {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
if (String(result.style).includes(':')) {
|
|
73
|
-
|
|
74
|
-
result.style = result.style.replaceAll(`:${key} `, `${val} `)
|
|
75
|
-
})
|
|
76
|
-
|
|
73
|
+
result.style = Fez.cssMixin(result.style)
|
|
77
74
|
result.style = result.style.includes(':fez') || /(?:^|\s)body\s*\{/.test(result.style) ? result.style : `:fez {\n${result.style}\n}`
|
|
78
75
|
klass = klass.replace(/\}\s*$/, `\n CSS = \`${result.style}\`\n}`)
|
|
79
76
|
}
|
|
@@ -105,8 +102,8 @@ function compile_bulk(data) {
|
|
|
105
102
|
if (fezName && !fezName.includes('-')) {
|
|
106
103
|
console.error(`Fez: Invalid custom element name "${fezName}". Custom element names must contain a dash (e.g., 'my-element', 'ui-button').`)
|
|
107
104
|
}
|
|
108
|
-
|
|
109
|
-
return
|
|
105
|
+
compile(fezName, node.innerHTML)
|
|
106
|
+
return
|
|
110
107
|
}
|
|
111
108
|
}
|
|
112
109
|
else {
|
|
@@ -195,7 +192,7 @@ function compile(tagName, html) {
|
|
|
195
192
|
if (klass.includes('import ')) {
|
|
196
193
|
Fez.head({script: klass})
|
|
197
194
|
|
|
198
|
-
// best we can do it inform that node did not compile, so we assume there is
|
|
195
|
+
// best we can do it inform that node did not compile, so we assume there is an error
|
|
199
196
|
setTimeout(()=>{
|
|
200
197
|
if (!Fez.classes[tagName]) {
|
|
201
198
|
Fez.error(`Template "${tagName}" possible compile error. (can be a false positive, it imports are not loaded)`)
|
package/src/fez/connect.js
CHANGED
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
import createTemplate from './lib/template.js'
|
|
2
2
|
import FezBase from './instance.js'
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Global mutation observer for reactive attribute changes
|
|
6
|
+
* Watches for attribute changes and triggers component updates
|
|
7
|
+
*/
|
|
8
|
+
const observer = new MutationObserver((mutationsList, _) => {
|
|
9
|
+
for (const mutation of mutationsList) {
|
|
10
|
+
if (mutation.type === 'attributes') {
|
|
11
|
+
const fez = mutation.target.fez
|
|
12
|
+
const name = mutation.attributeName
|
|
13
|
+
const value = mutation.target.getAttribute(name)
|
|
14
|
+
|
|
15
|
+
if (fez) {
|
|
16
|
+
fez.props[name] = value
|
|
17
|
+
fez.onPropsChange(name, value)
|
|
18
|
+
// console.log(`The [${name}] attribute was modified to [${value}].`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
4
24
|
/**
|
|
5
25
|
* Registers a new custom element with Fez framework
|
|
6
26
|
* @param {string} name - Custom element name (must contain a dash)
|
|
@@ -31,22 +51,17 @@ export default function connect(name, klass) {
|
|
|
31
51
|
props.forEach(prop => newKlass.prototype[prop] = klassObj[prop])
|
|
32
52
|
|
|
33
53
|
// Map component configuration properties
|
|
34
|
-
if (klassObj.
|
|
35
|
-
if (klassObj.
|
|
54
|
+
if (klassObj.FAST) { newKlass.FAST = klassObj.FAST } // Global instance reference
|
|
55
|
+
if (klassObj.GLOBAL) { newKlass.GLOBAL = klassObj.GLOBAL } // Global instance reference
|
|
56
|
+
if (klassObj.CSS) { newKlass.css = klassObj.CSS } // Component styles
|
|
36
57
|
if (klassObj.HTML) {
|
|
37
|
-
newKlass.html = closeCustomTags(klassObj.HTML)
|
|
58
|
+
newKlass.html = closeCustomTags(klassObj.HTML) // Component template
|
|
38
59
|
}
|
|
39
|
-
if (klassObj.NAME) { newKlass.nodeName = klassObj.NAME }
|
|
60
|
+
if (klassObj.NAME) { newKlass.nodeName = klassObj.NAME } // Custom DOM node name
|
|
40
61
|
|
|
41
62
|
// Auto-mount global components to body
|
|
42
63
|
if (klassObj.GLOBAL) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (document.readyState === 'loading') {
|
|
46
|
-
document.addEventListener('DOMContentLoaded', mountGlobalComponent);
|
|
47
|
-
} else {
|
|
48
|
-
mountGlobalComponent()
|
|
49
|
-
}
|
|
64
|
+
document.body.appendChild(document.createElement(name))
|
|
50
65
|
}
|
|
51
66
|
|
|
52
67
|
klass = newKlass
|
|
@@ -59,11 +74,11 @@ export default function connect(name, klass) {
|
|
|
59
74
|
|
|
60
75
|
// Process component template
|
|
61
76
|
if (klass.html) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
77
|
+
let slotTag = klass.SLOT || 'div'
|
|
78
|
+
|
|
79
|
+
klass.html = klass.html
|
|
80
|
+
.replace('<slot', `<${slotTag} class="fez-slot" fez-keep="default-slot"`)
|
|
81
|
+
.replace('</slot>', `</${slotTag}>`)
|
|
67
82
|
|
|
68
83
|
// Compile template function
|
|
69
84
|
klass.fezHtmlFunc = createTemplate(klass.html)
|
|
@@ -76,47 +91,16 @@ export default function connect(name, klass) {
|
|
|
76
91
|
|
|
77
92
|
Fez.classes[name] = klass
|
|
78
93
|
|
|
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
|
-
|
|
89
94
|
if (!customElements.get(name)) {
|
|
90
95
|
customElements.define(name, class extends HTMLElement {
|
|
91
96
|
connectedCallback() {
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
})
|
|
97
|
+
// Fez.onReady(()=>{connectNode(name, this)})
|
|
98
|
+
// connectNode(name, this)
|
|
99
|
+
if (useFastRender(this, klass)) {
|
|
100
|
+
connectNode(name, this)
|
|
101
|
+
} else {
|
|
102
|
+
requestAnimationFrame(()=>{
|
|
103
|
+
connectNode(name, this)
|
|
120
104
|
})
|
|
121
105
|
}
|
|
122
106
|
}
|
|
@@ -124,6 +108,16 @@ function connectCustomElement(name, klass) {
|
|
|
124
108
|
}
|
|
125
109
|
}
|
|
126
110
|
|
|
111
|
+
function useFastRender(node, klass) {
|
|
112
|
+
const fezFast = node.getAttribute('fez-fast')
|
|
113
|
+
var isFast = typeof klass.FAST === 'function' ? klass.FAST(node) : klass.FAST
|
|
114
|
+
if (fezFast == 'false') {
|
|
115
|
+
return false
|
|
116
|
+
} else {
|
|
117
|
+
return fezFast || isFast
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
127
121
|
/**
|
|
128
122
|
* Converts self-closing custom tags to full open/close format
|
|
129
123
|
* Required for proper HTML parsing of custom elements
|
|
@@ -138,7 +132,6 @@ function closeCustomTags(html) {
|
|
|
138
132
|
})
|
|
139
133
|
}
|
|
140
134
|
|
|
141
|
-
|
|
142
135
|
/**
|
|
143
136
|
* Initializes a Fez component instance from a DOM node
|
|
144
137
|
* Replaces the custom element with the component's rendered content
|
|
@@ -172,8 +165,8 @@ function connectNode(name, node) {
|
|
|
172
165
|
|
|
173
166
|
newNode.fez = fez
|
|
174
167
|
|
|
175
|
-
if (klass.
|
|
176
|
-
window[klass.
|
|
168
|
+
if (klass.GLOBAL && klass.GLOBAL != true) {
|
|
169
|
+
window[klass.GLOBAL] = fez
|
|
177
170
|
}
|
|
178
171
|
|
|
179
172
|
if (window.$) {
|
|
@@ -216,23 +209,3 @@ function connectNode(name, node) {
|
|
|
216
209
|
}
|
|
217
210
|
}
|
|
218
211
|
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Global mutation observer for reactive attribute changes
|
|
222
|
-
* Watches for attribute changes and triggers component updates
|
|
223
|
-
*/
|
|
224
|
-
const observer = new MutationObserver((mutationsList, _) => {
|
|
225
|
-
for (const mutation of mutationsList) {
|
|
226
|
-
if (mutation.type === 'attributes') {
|
|
227
|
-
const fez = mutation.target.fez
|
|
228
|
-
const name = mutation.attributeName
|
|
229
|
-
const value = mutation.target.getAttribute(name)
|
|
230
|
-
|
|
231
|
-
if (fez) {
|
|
232
|
-
fez.props[name] = value
|
|
233
|
-
fez.onPropsChange(name, value)
|
|
234
|
-
// console.log(`The [${name}] attribute was modified to [${value}].`);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
});
|
package/src/fez/defaults.js
CHANGED
|
@@ -53,6 +53,41 @@ const loadDefaults = () => {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
})
|
|
56
|
+
|
|
57
|
+
// Memory store for memoization
|
|
58
|
+
const memoStore = new Map()
|
|
59
|
+
|
|
60
|
+
// memoize component content by key
|
|
61
|
+
// <fez-memoize key="unique-key">content to memoize</fez-memoize>
|
|
62
|
+
Fez('fez-memoize', class {
|
|
63
|
+
init(props) {
|
|
64
|
+
if (!props.key) {
|
|
65
|
+
Fez.error('fez-memoize: key prop is required')
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (memoStore.has(props.key)) {
|
|
70
|
+
// Restore from memory in init
|
|
71
|
+
const storedNode = memoStore.get(props.key)
|
|
72
|
+
Fez.log(`Memoize - key: "${props.key}" - restore`)
|
|
73
|
+
this.root.innerHTML = ''
|
|
74
|
+
this.root.appendChild(storedNode.cloneNode(true))
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
onMount(props) {
|
|
79
|
+
// Only store if not already in memory
|
|
80
|
+
if (!memoStore.has(props.key)) {
|
|
81
|
+
requestAnimationFrame(() => {
|
|
82
|
+
// Store current DOM content
|
|
83
|
+
const contentNode = document.createElement('div')
|
|
84
|
+
contentNode.innerHTML = this.root.innerHTML
|
|
85
|
+
Fez.log(`Memoize - key: "${props.key}" - set`)
|
|
86
|
+
memoStore.set(props.key, contentNode)
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
})
|
|
56
91
|
}
|
|
57
92
|
|
|
58
93
|
// Only load defaults if Fez is available
|
|
@@ -61,4 +96,4 @@ if (typeof Fez !== 'undefined' && Fez) {
|
|
|
61
96
|
}
|
|
62
97
|
|
|
63
98
|
// Export for use in tests
|
|
64
|
-
export { loadDefaults }
|
|
99
|
+
export { loadDefaults }
|
package/src/fez/instance.js
CHANGED
|
@@ -359,27 +359,20 @@ export default class FezBase {
|
|
|
359
359
|
newNode.innerHTML = this.parseHtml(renderedTpl)
|
|
360
360
|
}
|
|
361
361
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
const oldEl = this.root.querySelector(`[fez-keep="${key}"]`)
|
|
362
|
+
// Handle fez-keep attributes
|
|
363
|
+
this.fezKeepNode(newNode)
|
|
365
364
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
newEl.parentNode.replaceChild(oldEl, newEl)
|
|
369
|
-
} else if (key === 'default-slot') {
|
|
370
|
-
// First render - populate the slot with current root children
|
|
371
|
-
Array.from(this.root.childNodes).forEach(child => newEl.appendChild(child))
|
|
372
|
-
}
|
|
373
|
-
})
|
|
365
|
+
// Handle fez-memoize attributes
|
|
366
|
+
this.fezMemoization(newNode)
|
|
374
367
|
|
|
375
368
|
Fez.morphdom(this.root, newNode)
|
|
376
369
|
|
|
377
|
-
this.
|
|
370
|
+
this.fezRenderPostProcess()
|
|
378
371
|
|
|
379
372
|
this.afterRender()
|
|
380
373
|
}
|
|
381
374
|
|
|
382
|
-
|
|
375
|
+
fezRenderPostProcess() {
|
|
383
376
|
const fetchAttr = (name, func) => {
|
|
384
377
|
this.root.querySelectorAll(`*[${name}]`).forEach((n)=>{
|
|
385
378
|
let value = n.getAttribute(name)
|
|
@@ -397,11 +390,24 @@ export default class FezBase {
|
|
|
397
390
|
|
|
398
391
|
// <button fez-use="animate" -> this.animate(node]
|
|
399
392
|
fetchAttr('fez-use', (value, n) => {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
}
|
|
404
|
-
|
|
393
|
+
if (value.includes('=>')) {
|
|
394
|
+
// fez-use="el => el.focus()"
|
|
395
|
+
Fez.getFunction(value)(n)
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
if (value.includes('.')) {
|
|
399
|
+
// fez-use="this.focus()"
|
|
400
|
+
Fez.getFunction(value).bind(n)()
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
// fez-use="animate"
|
|
404
|
+
const target = this[value]
|
|
405
|
+
if (typeof target == 'function') {
|
|
406
|
+
target(n)
|
|
407
|
+
} else {
|
|
408
|
+
console.error(`Fez error: "${value}" is not a function in ${this.fezName}`)
|
|
409
|
+
}
|
|
410
|
+
}
|
|
405
411
|
}
|
|
406
412
|
})
|
|
407
413
|
|
|
@@ -413,7 +419,7 @@ export default class FezBase {
|
|
|
413
419
|
if (lastClass) {
|
|
414
420
|
setTimeout(()=>{
|
|
415
421
|
n.classList.add(lastClass)
|
|
416
|
-
},
|
|
422
|
+
}, 1)
|
|
417
423
|
}
|
|
418
424
|
})
|
|
419
425
|
|
|
@@ -440,6 +446,63 @@ export default class FezBase {
|
|
|
440
446
|
})
|
|
441
447
|
}
|
|
442
448
|
|
|
449
|
+
fezKeepNode(newNode) {
|
|
450
|
+
newNode.querySelectorAll('[fez-keep]').forEach(newEl => {
|
|
451
|
+
const key = newEl.getAttribute('fez-keep')
|
|
452
|
+
const oldEl = this.root.querySelector(`[fez-keep="${key}"]`)
|
|
453
|
+
|
|
454
|
+
if (oldEl) {
|
|
455
|
+
// Keep the old element in place of the new one
|
|
456
|
+
newEl.parentNode.replaceChild(oldEl, newEl)
|
|
457
|
+
} else if (key === 'default-slot') {
|
|
458
|
+
if (newEl.getAttribute('hide')) {
|
|
459
|
+
// You cant use state any more
|
|
460
|
+
this.state = null
|
|
461
|
+
|
|
462
|
+
const parent = newEl.parentNode
|
|
463
|
+
|
|
464
|
+
// Insert all root children before the slot's next sibling
|
|
465
|
+
Array.from(this.root.childNodes).forEach(child => {
|
|
466
|
+
parent.insertBefore(child, newEl)
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
// Remove the slot element
|
|
470
|
+
newEl.remove()
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
// First render - populate the slot with current root children
|
|
474
|
+
Array.from(this.root.childNodes).forEach(
|
|
475
|
+
child => {
|
|
476
|
+
newEl.appendChild(child)
|
|
477
|
+
}
|
|
478
|
+
)
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
})
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
fezMemoization(newNode) {
|
|
485
|
+
// Find the single memoize element in new DOM (excluding fez components)
|
|
486
|
+
const newMemoEl = newNode.querySelector('[fez-memoize]:not(.fez)')
|
|
487
|
+
if (!newMemoEl) return
|
|
488
|
+
|
|
489
|
+
this.fezMemoStore ||= new Map()
|
|
490
|
+
|
|
491
|
+
const newMemoElKey = newMemoEl.getAttribute('fez-memoize')
|
|
492
|
+
const storedNode = this.fezMemoStore.get(newMemoElKey)
|
|
493
|
+
|
|
494
|
+
if (storedNode) {
|
|
495
|
+
Fez.log(`Memoize restore ${this.fezName}: ${newMemoElKey}`)
|
|
496
|
+
newMemoEl.parentNode.replaceChild(storedNode.cloneNode(true), newMemoEl)
|
|
497
|
+
} else {
|
|
498
|
+
const oldMemoEl = this.root.querySelector('[fez-memoize]:not(.fez)')
|
|
499
|
+
if (oldMemoEl) {
|
|
500
|
+
const oldMemoElKey = oldMemoEl.getAttribute('fez-memoize')
|
|
501
|
+
this.fezMemoStore.set(oldMemoElKey, oldMemoEl.cloneNode(true))
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
443
506
|
// refresh single node only
|
|
444
507
|
refresh(selector) {
|
|
445
508
|
alert('NEEDS FIX and remove htmlTemplate')
|
|
@@ -576,7 +639,18 @@ export default class FezBase {
|
|
|
576
639
|
methods.forEach(method => this[method] = this[method].bind(this))
|
|
577
640
|
}
|
|
578
641
|
|
|
579
|
-
|
|
642
|
+
// dissolve into parent, if you want to promote first child or given node with this.root
|
|
643
|
+
dissolve(inNode) {
|
|
644
|
+
if (inNode) {
|
|
645
|
+
inNode.classList.add('fez')
|
|
646
|
+
inNode.classList.add(`fez-${this.fezName}`)
|
|
647
|
+
inNode.fez = this
|
|
648
|
+
if (this.attr('id')) inNode.setAttribute('id', this.attr('id'))
|
|
649
|
+
|
|
650
|
+
this.root.innerHTML = ''
|
|
651
|
+
this.root.appendChild(inNode)
|
|
652
|
+
}
|
|
653
|
+
|
|
580
654
|
const node = this.root
|
|
581
655
|
const nodes = this.childNodes()
|
|
582
656
|
const parent = this.root.parentNode
|
|
@@ -584,7 +658,12 @@ export default class FezBase {
|
|
|
584
658
|
nodes.reverse().forEach(el => parent.insertBefore(el, node.nextSibling))
|
|
585
659
|
|
|
586
660
|
this.root.remove()
|
|
587
|
-
this.root =
|
|
661
|
+
this.root = undefined
|
|
662
|
+
|
|
663
|
+
if (inNode) {
|
|
664
|
+
this.root = inNode
|
|
665
|
+
}
|
|
666
|
+
|
|
588
667
|
return nodes
|
|
589
668
|
}
|
|
590
669
|
|
package/src/fez/root.js
CHANGED
|
@@ -4,6 +4,8 @@ import Gobber from './vendor/gobber.js'
|
|
|
4
4
|
// morph dom from one state to another
|
|
5
5
|
import { Idiomorph } from './vendor/idiomorph.js'
|
|
6
6
|
|
|
7
|
+
import objectDump from './utils/dump.js'
|
|
8
|
+
import highlightAll from './utils/highlight_all.js'
|
|
7
9
|
import connect from './connect.js'
|
|
8
10
|
import compile from './compile.js'
|
|
9
11
|
import state from './lib/global-state.js'
|
|
@@ -105,13 +107,9 @@ Fez.globalCss = (cssClass, opts = {}) => {
|
|
|
105
107
|
cssClass = Fez.cssClass(text)
|
|
106
108
|
}
|
|
107
109
|
|
|
108
|
-
|
|
110
|
+
Fez.onReady(() => {
|
|
109
111
|
document.body.parentElement.classList.add(cssClass)
|
|
110
|
-
}
|
|
111
|
-
document.addEventListener("DOMContentLoaded", () => {
|
|
112
|
-
document.body.parentElement.classList.add(cssClass)
|
|
113
|
-
})
|
|
114
|
-
}
|
|
112
|
+
})
|
|
115
113
|
|
|
116
114
|
return cssClass
|
|
117
115
|
}
|
|
@@ -144,40 +142,6 @@ Fez.publish = (channel, ...args) => {
|
|
|
144
142
|
})
|
|
145
143
|
}
|
|
146
144
|
|
|
147
|
-
// get unique id from string
|
|
148
|
-
Fez.fnv1 = (str) => {
|
|
149
|
-
var FNV_OFFSET_BASIS, FNV_PRIME, hash, i, j, ref;
|
|
150
|
-
FNV_OFFSET_BASIS = 2166136261;
|
|
151
|
-
FNV_PRIME = 16777619;
|
|
152
|
-
hash = FNV_OFFSET_BASIS;
|
|
153
|
-
for (i = j = 0, ref = str.length - 1; (0 <= ref ? j <= ref : j >= ref); i = 0 <= ref ? ++j : --j) {
|
|
154
|
-
hash ^= str.charCodeAt(i);
|
|
155
|
-
hash *= FNV_PRIME;
|
|
156
|
-
}
|
|
157
|
-
return hash.toString(36).replaceAll('-', '');
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
Fez.tag = (tag, opts = {}, html = '') => {
|
|
161
|
-
const json = encodeURIComponent(JSON.stringify(opts))
|
|
162
|
-
return `<${tag} data-props="${json}">${html}</${tag}>`
|
|
163
|
-
// const json = JSON.stringify(opts, null, 2)
|
|
164
|
-
// const data = `<script type="text/template">${json}</script><${tag} data-json-template="true">${html}</${tag}>`
|
|
165
|
-
// return data
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
// Resolve a function from a string or function reference
|
|
169
|
-
Fez.getFunction = (pointer) => {
|
|
170
|
-
if (!pointer) {
|
|
171
|
-
return ()=>{}
|
|
172
|
-
}
|
|
173
|
-
else if (typeof pointer === 'function') {
|
|
174
|
-
return pointer;
|
|
175
|
-
}
|
|
176
|
-
else if (typeof pointer === 'string') {
|
|
177
|
-
return new Function(pointer);
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
|
|
181
145
|
Fez.error = (text, show) => {
|
|
182
146
|
text = `Fez: ${text}`
|
|
183
147
|
console.error(text)
|
|
@@ -185,151 +149,13 @@ Fez.error = (text, show) => {
|
|
|
185
149
|
return `<span style="border: 1px solid red; font-size: 14px; padding: 3px 7px; background: #fee; border-radius: 4px;">${text}</span>`
|
|
186
150
|
}
|
|
187
151
|
}
|
|
152
|
+
|
|
188
153
|
Fez.log = (text) => {
|
|
189
154
|
if (Fez.LOG === true) {
|
|
190
155
|
text = String(text).substring(0, 180)
|
|
191
156
|
console.log(`Fez: ${text}`)
|
|
192
157
|
}
|
|
193
158
|
}
|
|
194
|
-
document.addEventListener('DOMContentLoaded', () => {
|
|
195
|
-
Fez.log('Fez.LOG === true, logging enabled.')
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
// execute function until it returns true
|
|
199
|
-
Fez.untilTrue = (func, pingRate) => {
|
|
200
|
-
pingRate ||= 200
|
|
201
|
-
|
|
202
|
-
if (!func()) {
|
|
203
|
-
setTimeout(()=>{
|
|
204
|
-
Fez.untilTrue(func, pingRate)
|
|
205
|
-
} ,pingRate)
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// throttle function calls
|
|
210
|
-
Fez.throttle = (func, delay = 200) => {
|
|
211
|
-
let lastRun = 0;
|
|
212
|
-
let timeout;
|
|
213
|
-
|
|
214
|
-
return function(...args) {
|
|
215
|
-
const now = Date.now();
|
|
216
|
-
|
|
217
|
-
if (now - lastRun >= delay) {
|
|
218
|
-
func.apply(this, args);
|
|
219
|
-
lastRun = now;
|
|
220
|
-
} else {
|
|
221
|
-
clearTimeout(timeout);
|
|
222
|
-
timeout = setTimeout(() => {
|
|
223
|
-
func.apply(this, args);
|
|
224
|
-
lastRun = Date.now();
|
|
225
|
-
}, delay - (now - lastRun));
|
|
226
|
-
}
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Fetch wrapper with automatic caching and data handling
|
|
231
|
-
// Usage:
|
|
232
|
-
// Fez.fetch(url) - GET request (default)
|
|
233
|
-
// Fez.fetch(url, callback) - GET with callback
|
|
234
|
-
// Fez.fetch(url, data) - GET with query params (?foo=bar&baz=qux)
|
|
235
|
-
// Fez.fetch(url, data, callback) - GET with query params and callback
|
|
236
|
-
// Fez.fetch('POST', url, data) - POST with FormData body (multipart/form-data)
|
|
237
|
-
// Fez.fetch('POST', url, data, callback) - POST with FormData and callback
|
|
238
|
-
// Data object is automatically converted:
|
|
239
|
-
// - GET: appended as URL query parameters
|
|
240
|
-
// - POST: sent as FormData (multipart/form-data) without custom headers
|
|
241
|
-
Fez.fetch = function(...args) {
|
|
242
|
-
// Initialize cache if not exists
|
|
243
|
-
Fez._fetchCache ||= {};
|
|
244
|
-
|
|
245
|
-
let method = 'GET';
|
|
246
|
-
let url;
|
|
247
|
-
let callback;
|
|
248
|
-
|
|
249
|
-
// Check if first arg is HTTP method (uppercase letters)
|
|
250
|
-
if (typeof args[0] === 'string' && /^[A-Z]+$/.test(args[0])) {
|
|
251
|
-
method = args.shift();
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// URL is required
|
|
255
|
-
url = args.shift();
|
|
256
|
-
|
|
257
|
-
// Check for data/options object
|
|
258
|
-
let opts = {};
|
|
259
|
-
let data = null;
|
|
260
|
-
if (typeof args[0] === 'object') {
|
|
261
|
-
data = args.shift();
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Check for callback function
|
|
265
|
-
if (typeof args[0] === 'function') {
|
|
266
|
-
callback = args.shift();
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Handle data based on method
|
|
270
|
-
if (data) {
|
|
271
|
-
if (method === 'GET') {
|
|
272
|
-
// For GET, append data as query parameters
|
|
273
|
-
const params = new URLSearchParams(data);
|
|
274
|
-
url += (url.includes('?') ? '&' : '?') + params.toString();
|
|
275
|
-
} else if (method === 'POST') {
|
|
276
|
-
// For POST, convert to FormData
|
|
277
|
-
const formData = new FormData();
|
|
278
|
-
for (const [key, value] of Object.entries(data)) {
|
|
279
|
-
formData.append(key, value);
|
|
280
|
-
}
|
|
281
|
-
opts.body = formData;
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Set method
|
|
286
|
-
opts.method = method;
|
|
287
|
-
|
|
288
|
-
// Create cache key from method, url, and stringified opts
|
|
289
|
-
const cacheKey = `${method}:${url}:${JSON.stringify(opts)}`;
|
|
290
|
-
|
|
291
|
-
// Check cache first
|
|
292
|
-
if (Fez._fetchCache[cacheKey]) {
|
|
293
|
-
const cachedData = Fez._fetchCache[cacheKey];
|
|
294
|
-
Fez.log(`fetch cache hit: ${method} ${url}`);
|
|
295
|
-
if (callback) {
|
|
296
|
-
callback(cachedData);
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
return Promise.resolve(cachedData);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Log live fetch
|
|
303
|
-
Fez.log(`fetch live: ${method} ${url}`);
|
|
304
|
-
|
|
305
|
-
// Helper to process and cache response
|
|
306
|
-
const processResponse = (response) => {
|
|
307
|
-
if (response.headers.get('content-type')?.includes('application/json')) {
|
|
308
|
-
return response.json();
|
|
309
|
-
}
|
|
310
|
-
return response.text();
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
// If callback provided, execute and handle
|
|
314
|
-
if (callback) {
|
|
315
|
-
fetch(url, opts)
|
|
316
|
-
.then(processResponse)
|
|
317
|
-
.then(data => {
|
|
318
|
-
Fez._fetchCache[cacheKey] = data;
|
|
319
|
-
callback(data);
|
|
320
|
-
})
|
|
321
|
-
.catch(error => Fez.onError('fetch', error));
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// Return promise with automatic JSON parsing
|
|
326
|
-
return fetch(url, opts)
|
|
327
|
-
.then(processResponse)
|
|
328
|
-
.then(data => {
|
|
329
|
-
Fez._fetchCache[cacheKey] = data;
|
|
330
|
-
return data;
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
159
|
|
|
334
160
|
Fez.onError = (kind, message) => {
|
|
335
161
|
// Ensure kind is always a string
|
|
@@ -340,14 +166,6 @@ Fez.onError = (kind, message) => {
|
|
|
340
166
|
console.error(`${kind}: ${message.toString()}`);
|
|
341
167
|
}
|
|
342
168
|
|
|
343
|
-
// define custom style macro
|
|
344
|
-
// Fez.styleMacro('mobile', '@media (max-width: 768px)')
|
|
345
|
-
// :mobile { ... } -> @media (max-width: 768px) { ... }
|
|
346
|
-
Fez._styleMacros = {}
|
|
347
|
-
Fez.styleMacro = (name, content) => {
|
|
348
|
-
Fez._styleMacros[name] = content
|
|
349
|
-
}
|
|
350
|
-
|
|
351
169
|
// work with tmp store
|
|
352
170
|
Fez.store = {
|
|
353
171
|
store: new Map(),
|
|
@@ -372,9 +190,17 @@ Fez.store = {
|
|
|
372
190
|
|
|
373
191
|
// Load utility functions
|
|
374
192
|
import addUtilities from './utility.js'
|
|
193
|
+
import cssMixin from './utils/css_mixin.js'
|
|
375
194
|
addUtilities(Fez)
|
|
195
|
+
cssMixin(Fez)
|
|
376
196
|
|
|
377
197
|
Fez.compile = compile
|
|
378
198
|
Fez.state = state
|
|
199
|
+
Fez.dump = objectDump
|
|
200
|
+
Fez.dump = highlightAll
|
|
201
|
+
|
|
202
|
+
Fez.onReady(() => {
|
|
203
|
+
Fez.log('Fez.LOG === true, logging enabled.')
|
|
204
|
+
})
|
|
379
205
|
|
|
380
206
|
export default Fez
|