@dinoreic/fez 0.1.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/LICENSE +30 -0
- package/README.md +638 -0
- package/dist/fez.js +28 -0
- package/dist/fez.js.map +7 -0
- package/dist/rollup.js +3 -0
- package/dist/rollup.js.map +7 -0
- package/package.json +60 -0
- package/src/fez/compile.js +195 -0
- package/src/fez/connect.js +217 -0
- package/src/fez/instance.js +582 -0
- package/src/fez/lib/global-state.js +157 -0
- package/src/fez/lib/n.js +65 -0
- package/src/fez/lib/template.js +119 -0
- package/src/fez/root.js +465 -0
- package/src/fez/vendor/gobber.js +8 -0
- package/src/fez/vendor/idiomorph.js +860 -0
- package/src/fez.js +73 -0
- package/src/rollup.js +31 -0
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
// HTML node builder
|
|
2
|
+
import parseNode from './lib/n.js'
|
|
3
|
+
import createTemplate from './lib/template.js'
|
|
4
|
+
|
|
5
|
+
export default class FezBase {
|
|
6
|
+
// get node attributes as object
|
|
7
|
+
static getProps(node, newNode) {
|
|
8
|
+
let attrs = {}
|
|
9
|
+
|
|
10
|
+
// we can directly attach props to DOM node instance
|
|
11
|
+
if (node.props) {
|
|
12
|
+
return node.props
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// LOG(node.nodeName, node.attributes)
|
|
16
|
+
for (const attr of node.attributes) {
|
|
17
|
+
attrs[attr.name] = attr.value
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
for (const [key, val] of Object.entries(attrs)) {
|
|
21
|
+
if ([':'].includes(key[0])) {
|
|
22
|
+
delete attrs[key]
|
|
23
|
+
try {
|
|
24
|
+
const newVal = new Function(`return (${val})`).bind(newNode)()
|
|
25
|
+
attrs[key.replace(/[\:_]/, '')] = newVal
|
|
26
|
+
|
|
27
|
+
} catch (e) {
|
|
28
|
+
console.error(`Fez: Error evaluating attribute ${key}="${val}" for ${node.tagName}: ${e.message}`)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (attrs['data-props']) {
|
|
34
|
+
let data = attrs['data-props']
|
|
35
|
+
|
|
36
|
+
if (typeof data == 'object') {
|
|
37
|
+
return data
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
if (data[0] != '{') {
|
|
41
|
+
data = decodeURIComponent(data)
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
attrs = JSON.parse(data)
|
|
45
|
+
} catch (e) {
|
|
46
|
+
console.error(`Fez: Invalid JSON in data-props for ${node.tagName}: ${e.message}`)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// pass props as json template
|
|
52
|
+
// <script type="text/template">{...}</script>
|
|
53
|
+
// <foo-bar data-json-template="true"></foo-bar>
|
|
54
|
+
else if (attrs['data-json-template']) {
|
|
55
|
+
const data = newNode.previousSibling?.textContent
|
|
56
|
+
if (data) {
|
|
57
|
+
try {
|
|
58
|
+
attrs = JSON.parse(data)
|
|
59
|
+
newNode.previousSibling.remove()
|
|
60
|
+
} catch (e) {
|
|
61
|
+
console.error(`Fez: Invalid JSON in template for ${node.tagName}: ${e.message}`)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return attrs
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
static formData(node) {
|
|
70
|
+
const formNode = node.closest('form') || node.querySelector('form')
|
|
71
|
+
if (!formNode) {
|
|
72
|
+
Fez.log('No form found for formData()')
|
|
73
|
+
return {}
|
|
74
|
+
}
|
|
75
|
+
const formData = new FormData(formNode)
|
|
76
|
+
const formObject = {}
|
|
77
|
+
formData.forEach((value, key) => {
|
|
78
|
+
formObject[key] = value
|
|
79
|
+
});
|
|
80
|
+
return formObject
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
static fastBind() {
|
|
84
|
+
// return true to bind without requestAnimationFrame
|
|
85
|
+
// you can do this if you are sure you are not expecting innerHTML data
|
|
86
|
+
return false
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
static nodeName = 'div'
|
|
90
|
+
|
|
91
|
+
// instance methods
|
|
92
|
+
|
|
93
|
+
constructor() {}
|
|
94
|
+
|
|
95
|
+
n = parseNode
|
|
96
|
+
|
|
97
|
+
// string selector for use in HTML nodes
|
|
98
|
+
get fezHtmlRoot() {
|
|
99
|
+
return `Fez(${this.UID}).`
|
|
100
|
+
// return this.props.id ? `Fez.find("#${this.props.id}").` : `Fez.find(this, "${this.fezName}").`
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// checks if node is attached and clears all if not
|
|
104
|
+
get isConnected() {
|
|
105
|
+
if (this.root?.isConnected) {
|
|
106
|
+
return true
|
|
107
|
+
} else {
|
|
108
|
+
this.fezRemoveSelf()
|
|
109
|
+
return false
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
fezRemoveSelf() {
|
|
114
|
+
this._setIntervalCache ||= {}
|
|
115
|
+
Object.keys(this._setIntervalCache).forEach((key)=> {
|
|
116
|
+
clearInterval(this._setIntervalCache[key])
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
this.onDestroy()
|
|
120
|
+
this.onDestroy = ()=> {}
|
|
121
|
+
|
|
122
|
+
if (this.root) {
|
|
123
|
+
this.root.fez = undefined
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
this.root = undefined
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// get single node property
|
|
130
|
+
prop(name) {
|
|
131
|
+
let v = this.oldRoot[name] || this.props[name]
|
|
132
|
+
if (typeof v == 'function') {
|
|
133
|
+
// if this.prop('onclick'), we want "this" to point to this.root (dom node)
|
|
134
|
+
v = v.bind(this.root)
|
|
135
|
+
}
|
|
136
|
+
return v
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// copy attributes to root node
|
|
140
|
+
copy() {
|
|
141
|
+
for (const name of Array.from(arguments)) {
|
|
142
|
+
let value = this.props[name]
|
|
143
|
+
|
|
144
|
+
if (value !== undefined) {
|
|
145
|
+
if (name == 'class') {
|
|
146
|
+
const klass = this.root.getAttribute(name, value)
|
|
147
|
+
|
|
148
|
+
if (klass) {
|
|
149
|
+
value = [klass, value].join(' ')
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (name == 'style' || !this.root[name]) {
|
|
154
|
+
if (typeof value == 'string') {
|
|
155
|
+
this.root.setAttribute(name, value)
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
this.root[name] = value
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
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;
|
|
170
|
+
|
|
171
|
+
func()
|
|
172
|
+
|
|
173
|
+
const checkAndExecute = () => {
|
|
174
|
+
if (!this.isConnected) {
|
|
175
|
+
window.removeEventListener('resize', handleResize);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
func.call(this);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const handleResize = () => {
|
|
182
|
+
if (!this.isConnected) {
|
|
183
|
+
window.removeEventListener('resize', handleResize);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (delay) {
|
|
188
|
+
// Throttle
|
|
189
|
+
const now = Date.now();
|
|
190
|
+
if (now - lastRun >= delay) {
|
|
191
|
+
checkAndExecute();
|
|
192
|
+
lastRun = now;
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
// Debounce
|
|
196
|
+
clearTimeout(timeoutId);
|
|
197
|
+
timeoutId = setTimeout(checkAndExecute, 200);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
window.addEventListener('resize', handleResize);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// copy child nodes, natively to preserve bound events
|
|
205
|
+
// if node name is SLOT insert adjacent and remove SLOT, else as a child nodes
|
|
206
|
+
slot(source, target) {
|
|
207
|
+
target ||= document.createElement('template')
|
|
208
|
+
const isSlot = target.nodeName == 'SLOT'
|
|
209
|
+
|
|
210
|
+
while (source.firstChild) {
|
|
211
|
+
if (isSlot) {
|
|
212
|
+
target.parentNode.insertBefore(source.lastChild, target.nextSibling);
|
|
213
|
+
} else {
|
|
214
|
+
target.appendChild(source.firstChild)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (isSlot) {
|
|
219
|
+
target.parentNode.removeChild(target)
|
|
220
|
+
} else {
|
|
221
|
+
source.innerHTML = ''
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return target
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
setStyle(key, value) {
|
|
228
|
+
this.root.style.setProperty(key, value);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
connect() {}
|
|
232
|
+
onMount() {}
|
|
233
|
+
beforeRender() {}
|
|
234
|
+
afterRender() {}
|
|
235
|
+
onDestroy() {}
|
|
236
|
+
onStateChange() {}
|
|
237
|
+
onGlobalStateChange() {}
|
|
238
|
+
publish = Fez.publish
|
|
239
|
+
fezBlocks = {}
|
|
240
|
+
|
|
241
|
+
parseHtml(text) {
|
|
242
|
+
const base = this.fezHtmlRoot.replaceAll('"', '"')
|
|
243
|
+
|
|
244
|
+
text = text
|
|
245
|
+
.replace(/([^\w\.])fez\./g, `$1${base}`)
|
|
246
|
+
.replace(/>\s+</g, '><')
|
|
247
|
+
|
|
248
|
+
return text.trim()
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
// pass name to have only one tick of a kind
|
|
253
|
+
nextTick(func, name) {
|
|
254
|
+
if (name) {
|
|
255
|
+
this._nextTicks ||= {}
|
|
256
|
+
this._nextTicks[name] ||= window.requestAnimationFrame(() => {
|
|
257
|
+
func.bind(this)()
|
|
258
|
+
this._nextTicks[name] = null
|
|
259
|
+
}, name)
|
|
260
|
+
} else {
|
|
261
|
+
window.requestAnimationFrame(func.bind(this))
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// inject htmlString as innerHTML and replace $$. with local pointer
|
|
266
|
+
// $$. will point to current fez instance
|
|
267
|
+
// <slot></slot> will be replaced with current root
|
|
268
|
+
// this.render('...loading')
|
|
269
|
+
// this.render('.images', '...loading')
|
|
270
|
+
render(template) {
|
|
271
|
+
template ||= this?.class?.fezHtmlFunc
|
|
272
|
+
|
|
273
|
+
if (!template || !this.root) return
|
|
274
|
+
|
|
275
|
+
this.beforeRender()
|
|
276
|
+
|
|
277
|
+
const newNode = document.createElement(this.class.nodeName || 'div')
|
|
278
|
+
|
|
279
|
+
let renderedTpl
|
|
280
|
+
if (Array.isArray(template)) {
|
|
281
|
+
// array nodes this.n(...), look tabs example
|
|
282
|
+
if (template[0] instanceof Node) {
|
|
283
|
+
template.forEach( n => newNode.appendChild(n) )
|
|
284
|
+
} else{
|
|
285
|
+
renderedTpl = template.join('')
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
else if (typeof template == 'string') {
|
|
289
|
+
renderedTpl = createTemplate(template)(this)
|
|
290
|
+
}
|
|
291
|
+
else if (typeof template == 'function') {
|
|
292
|
+
renderedTpl = template(this)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (renderedTpl) {
|
|
296
|
+
renderedTpl = renderedTpl.replace(/\s\w+="undefined"/g, '')
|
|
297
|
+
newNode.innerHTML = this.parseHtml(renderedTpl)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// this comes only from array nodes this.n(...)
|
|
301
|
+
const slot = newNode.querySelector('slot')
|
|
302
|
+
if (slot) {
|
|
303
|
+
this.slot(this.root, slot.parentNode)
|
|
304
|
+
slot.parentNode.removeChild(slot)
|
|
305
|
+
}
|
|
306
|
+
|
|
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)
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
Fez.morphdom(this.root, newNode)
|
|
317
|
+
|
|
318
|
+
this.renderFezPostProcess()
|
|
319
|
+
|
|
320
|
+
this.afterRender()
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
renderFezPostProcess() {
|
|
324
|
+
const fetchAttr = (name, func) => {
|
|
325
|
+
this.root.querySelectorAll(`*[${name}]`).forEach((n)=>{
|
|
326
|
+
let value = n.getAttribute(name)
|
|
327
|
+
n.removeAttribute(name)
|
|
328
|
+
if (value) {
|
|
329
|
+
func.bind(this)(value, n)
|
|
330
|
+
}
|
|
331
|
+
})
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// <button fez-this="button" -> this.button = node
|
|
335
|
+
fetchAttr('fez-this', (value, n) => {
|
|
336
|
+
(new Function('n', `this.${value} = n`)).bind(this)(n)
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
// <button fez-use="animate" -> this.animate(node]
|
|
340
|
+
fetchAttr('fez-use', (value, n) => {
|
|
341
|
+
const target = this[value]
|
|
342
|
+
if (typeof target == 'function') {
|
|
343
|
+
target(n)
|
|
344
|
+
} else {
|
|
345
|
+
console.error(`Fez error: "${value}" is not a function in ${this.fezName}`)
|
|
346
|
+
}
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
// <button fez-class="dialog animate" -> add class "animate" after node init to trigger animation
|
|
350
|
+
fetchAttr('fez-class', (value, n) => {
|
|
351
|
+
let classes = value.split(/\s+/)
|
|
352
|
+
let lastClass = classes.pop()
|
|
353
|
+
classes.forEach((c)=> n.classList.add(c) )
|
|
354
|
+
if (lastClass) {
|
|
355
|
+
setTimeout(()=>{
|
|
356
|
+
n.classList.add(lastClass)
|
|
357
|
+
}, 300)
|
|
358
|
+
}
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
// <input fez-bind="state.inputNode" -> this.state.inputNode will be the value of input
|
|
362
|
+
fetchAttr('fez-bind', (text, n) => {
|
|
363
|
+
if (['INPUT', 'SELECT', 'TEXTAREA'].includes(n.nodeName)) {
|
|
364
|
+
const value = (new Function(`return this.${text}`)).bind(this)()
|
|
365
|
+
const isCb = n.type.toLowerCase() == 'checkbox'
|
|
366
|
+
const eventName = ['SELECT'].includes(n.nodeName) || isCb ? 'onchange' : 'onkeyup'
|
|
367
|
+
n.setAttribute(eventName, `${this.fezHtmlRoot}${text} = this.${isCb ? 'checked' : 'value'}`)
|
|
368
|
+
this.val(n, value)
|
|
369
|
+
} else {
|
|
370
|
+
console.error(`Cant fez-bind="${text}" to ${n.nodeName} (needs INPUT, SELECT or TEXTAREA. Want to use fez-this?).`)
|
|
371
|
+
}
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
this.root.querySelectorAll(`*[disabled]`).forEach((n)=>{
|
|
375
|
+
let value = n.getAttribute('disabled')
|
|
376
|
+
if (['false'].includes(value)) {
|
|
377
|
+
n.removeAttribute('disabled')
|
|
378
|
+
} else {
|
|
379
|
+
n.setAttribute('disabled', 'true')
|
|
380
|
+
}
|
|
381
|
+
})
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// refresh single node only
|
|
385
|
+
refresh(selector) {
|
|
386
|
+
alert('NEEDS FIX and remove htmlTemplate')
|
|
387
|
+
if (selector) {
|
|
388
|
+
const n = document.createElement('div')
|
|
389
|
+
n.innerHTML = this.class.htmlTemplate
|
|
390
|
+
const tpl = n.querySelector(selector).innerHTML
|
|
391
|
+
this.render(selector, tpl)
|
|
392
|
+
} else {
|
|
393
|
+
this.render()
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// run only if node is attached, clear otherwise
|
|
398
|
+
setInterval(func, tick, name) {
|
|
399
|
+
if (typeof func == 'number') {
|
|
400
|
+
[tick, func] = [func, tick]
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
name ||= Fez.fnv1(String(func))
|
|
404
|
+
|
|
405
|
+
this._setIntervalCache ||= {}
|
|
406
|
+
clearInterval(this._setIntervalCache[name])
|
|
407
|
+
|
|
408
|
+
this._setIntervalCache[name] = setInterval(() => {
|
|
409
|
+
if (this.isConnected) {
|
|
410
|
+
func()
|
|
411
|
+
}
|
|
412
|
+
}, tick)
|
|
413
|
+
|
|
414
|
+
return this._setIntervalCache[name]
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
find(selector) {
|
|
418
|
+
return typeof selector == 'string' ? this.root.querySelector(selector) : selector
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// get or set node value
|
|
422
|
+
val(selector, data) {
|
|
423
|
+
const node = this.find(selector)
|
|
424
|
+
|
|
425
|
+
if (node) {
|
|
426
|
+
if (['INPUT', 'TEXTAREA', 'SELECT'].includes(node.nodeName)) {
|
|
427
|
+
if (typeof data != 'undefined') {
|
|
428
|
+
if (node.type == 'checkbox') {
|
|
429
|
+
node.checked = !!data
|
|
430
|
+
} else {
|
|
431
|
+
node.value = data
|
|
432
|
+
}
|
|
433
|
+
} else {
|
|
434
|
+
return node.value
|
|
435
|
+
}
|
|
436
|
+
} else {
|
|
437
|
+
if (typeof data != 'undefined') {
|
|
438
|
+
node.innerHTML = data
|
|
439
|
+
} else {
|
|
440
|
+
return node.innerHTML
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
formData(node) {
|
|
447
|
+
return this.class.formData(node || this.root)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// get or set attribute
|
|
451
|
+
attr(name, value) {
|
|
452
|
+
if (typeof value === 'undefined') {
|
|
453
|
+
return this.root.getAttribute(name)
|
|
454
|
+
} else {
|
|
455
|
+
this.root.setAttribute(name, value)
|
|
456
|
+
return value
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// get root node child nodes as array
|
|
461
|
+
childNodes(func) {
|
|
462
|
+
const children = Array.from(this.root.children)
|
|
463
|
+
|
|
464
|
+
if (func) {
|
|
465
|
+
// Create temporary container to avoid ancestor-parent errors
|
|
466
|
+
const tmpContainer = document.createElement('div')
|
|
467
|
+
tmpContainer.style.display = 'none'
|
|
468
|
+
document.body.appendChild(tmpContainer)
|
|
469
|
+
children.forEach(child => tmpContainer.appendChild(child))
|
|
470
|
+
|
|
471
|
+
let list = Array.from(tmpContainer.children).map(func)
|
|
472
|
+
document.body.removeChild(tmpContainer)
|
|
473
|
+
return list
|
|
474
|
+
} else {
|
|
475
|
+
return children
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
subscribe(channel, func) {
|
|
480
|
+
Fez._subs ||= {}
|
|
481
|
+
Fez._subs[channel] ||= []
|
|
482
|
+
Fez._subs[channel] = Fez._subs[channel].filter((el) => el[0].isConnected)
|
|
483
|
+
Fez._subs[channel].push([this, func])
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// get and set root node ID
|
|
487
|
+
rootId() {
|
|
488
|
+
this.root.id ||= `fez_${this.UID}`
|
|
489
|
+
return this.root.id
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
fezRegister() {
|
|
493
|
+
if (this.css) {
|
|
494
|
+
this.css = Fez.globalCss(this.css, {name: this.fezName, wrap: true})
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (this.class.css) {
|
|
498
|
+
this.class.css = Fez.globalCss(this.class.css, {name: this.fezName})
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
this.state ||= this.reactiveStore()
|
|
502
|
+
this.globalState = Fez.state.createProxy(this)
|
|
503
|
+
this.fezRegisterBindMethods()
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// bind all instance method to this, to avoid calling with .bind(this)
|
|
507
|
+
fezRegisterBindMethods() {
|
|
508
|
+
const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(this))
|
|
509
|
+
.filter(method => method !== 'constructor' && typeof this[method] === 'function')
|
|
510
|
+
|
|
511
|
+
methods.forEach(method => this[method] = this[method].bind(this))
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
fezHide() {
|
|
515
|
+
const node = this.root
|
|
516
|
+
const parent = this.root.parentNode
|
|
517
|
+
const fragment = document.createDocumentFragment();
|
|
518
|
+
|
|
519
|
+
while (node.firstChild) {
|
|
520
|
+
fragment.appendChild(node.firstChild)
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Replace the target element with the fragment (which contains the child elements)
|
|
524
|
+
node.parentNode.replaceChild(fragment, node);
|
|
525
|
+
// parent.classList.add('fez')
|
|
526
|
+
// parent.classList.add(`fez-${this.fezName}`)
|
|
527
|
+
this.root = parent
|
|
528
|
+
return Array.from(this.root.children)
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
reactiveStore(obj, handler) {
|
|
532
|
+
obj ||= {}
|
|
533
|
+
|
|
534
|
+
handler ||= (o, k, v, oldValue) => {
|
|
535
|
+
this.onStateChange(k, v, oldValue)
|
|
536
|
+
this.nextTick(this.render, 'render')
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
handler.bind(this)
|
|
540
|
+
|
|
541
|
+
// licence ? -> generated by ChatGPT 2024
|
|
542
|
+
function createReactive(obj, handler) {
|
|
543
|
+
if (typeof obj !== 'object' || obj === null) {
|
|
544
|
+
return obj;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return new Proxy(obj, {
|
|
548
|
+
set(target, property, value, receiver) {
|
|
549
|
+
// Get the current value of the property
|
|
550
|
+
const currentValue = Reflect.get(target, property, receiver);
|
|
551
|
+
|
|
552
|
+
// Only proceed if the new value is different from the current value
|
|
553
|
+
if (currentValue !== value) {
|
|
554
|
+
if (typeof value === 'object' && value !== null) {
|
|
555
|
+
value = createReactive(value, handler); // Recursively make nested objects reactive
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Set the new value
|
|
559
|
+
const result = Reflect.set(target, property, value, receiver);
|
|
560
|
+
|
|
561
|
+
// Call the handler only if the value has changed
|
|
562
|
+
handler(target, property, value, currentValue);
|
|
563
|
+
|
|
564
|
+
return result;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// If the value hasn't changed, return true (indicating success) without calling the handler
|
|
568
|
+
return true;
|
|
569
|
+
},
|
|
570
|
+
get(target, property, receiver) {
|
|
571
|
+
const value = Reflect.get(target, property, receiver);
|
|
572
|
+
if (typeof value === 'object' && value !== null) {
|
|
573
|
+
return createReactive(value, handler); // Recursively make nested objects reactive
|
|
574
|
+
}
|
|
575
|
+
return value;
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
return createReactive(obj, handler);
|
|
581
|
+
}
|
|
582
|
+
}
|