betal-fe 2.0.0 → 3.0.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/dist/betal-fe.js +363 -159
- package/package.json +6 -1
package/dist/betal-fe.js
CHANGED
|
@@ -1,20 +1,3 @@
|
|
|
1
|
-
function addEventListener(eventName, handler, el) {
|
|
2
|
-
el.addEventListener(eventName, handler);
|
|
3
|
-
return handler;
|
|
4
|
-
}
|
|
5
|
-
function addEventListeners(events = {}, el) {
|
|
6
|
-
const addedEventListeners = {};
|
|
7
|
-
Object.entries(events).forEach(([eventName, handler]) => {
|
|
8
|
-
addedEventListeners[eventName] = addEventListener(eventName, handler, el);
|
|
9
|
-
});
|
|
10
|
-
return addedEventListeners;
|
|
11
|
-
}
|
|
12
|
-
function removeEventListeners(listeners = {}, el) {
|
|
13
|
-
Object.entries(listeners).forEach(([eventName, handler]) => {
|
|
14
|
-
el.removeEventListener(eventName, handler);
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
|
|
18
1
|
const ARRAY_DIFF_OP = {
|
|
19
2
|
ADD: "add",
|
|
20
3
|
REMOVE: "remove",
|
|
@@ -157,10 +140,12 @@ const DOM_TYPES = {
|
|
|
157
140
|
TEXT: "text",
|
|
158
141
|
ELEMENT: "element",
|
|
159
142
|
FRAGMENT: "fragment",
|
|
143
|
+
COMPONENT: "component",
|
|
160
144
|
};
|
|
161
145
|
function h(tag, props = {}, children = []) {
|
|
146
|
+
const type = typeof tag === "string" ? DOM_TYPES.ELEMENT : DOM_TYPES.COMPONENT;
|
|
162
147
|
return {
|
|
163
|
-
type
|
|
148
|
+
type,
|
|
164
149
|
tag,
|
|
165
150
|
props,
|
|
166
151
|
children: mapTextNodes(withoutNulls(children)),
|
|
@@ -195,80 +180,6 @@ function extractChildren(vdom) {
|
|
|
195
180
|
return children;
|
|
196
181
|
}
|
|
197
182
|
|
|
198
|
-
function destroyDOM(vDom) {
|
|
199
|
-
const { type } = vDom;
|
|
200
|
-
switch (type) {
|
|
201
|
-
case DOM_TYPES.TEXT: {
|
|
202
|
-
removeTextNode(vDom);
|
|
203
|
-
break;
|
|
204
|
-
}
|
|
205
|
-
case DOM_TYPES.ELEMENT: {
|
|
206
|
-
removeElementNode(vDom);
|
|
207
|
-
break;
|
|
208
|
-
}
|
|
209
|
-
case DOM_TYPES.FRAGMENT: {
|
|
210
|
-
removeFragmentNodes(vDom);
|
|
211
|
-
break;
|
|
212
|
-
}
|
|
213
|
-
default: {
|
|
214
|
-
throw new Error(`Can't destroy DOM of type: ${type}`);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
delete vDom.el;
|
|
218
|
-
}
|
|
219
|
-
function removeTextNode(vDom) {
|
|
220
|
-
const { el } = vDom;
|
|
221
|
-
el.remove();
|
|
222
|
-
}
|
|
223
|
-
function removeElementNode(vdom) {
|
|
224
|
-
const { el, children, listeners } = vdom;
|
|
225
|
-
el.remove();
|
|
226
|
-
children.forEach(destroyDOM);
|
|
227
|
-
if (listeners) {
|
|
228
|
-
removeEventListeners(listeners, el);
|
|
229
|
-
delete vdom.listeners;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
function removeFragmentNodes(vDom) {
|
|
233
|
-
const { children } = vDom;
|
|
234
|
-
children.forEach(destroyDOM);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
class Dispatcher {
|
|
238
|
-
#subs = new Map();
|
|
239
|
-
#afterHandlers = [];
|
|
240
|
-
subscribe(commandName, handler) {
|
|
241
|
-
if (!this.#subs.has(commandName)) {
|
|
242
|
-
this.#subs.set(commandName, []);
|
|
243
|
-
}
|
|
244
|
-
const handlers = this.#subs.get(commandName);
|
|
245
|
-
if (handlers.includes(handler)) {
|
|
246
|
-
return () => {};
|
|
247
|
-
}
|
|
248
|
-
handlers.push(handler);
|
|
249
|
-
return () => {
|
|
250
|
-
const idx = handlers.indexOf(handler);
|
|
251
|
-
handlers.splice(idx, 1);
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
afterEveryCommand(handler) {
|
|
255
|
-
this.#afterHandlers.push(handler);
|
|
256
|
-
return () => {
|
|
257
|
-
const idx = this.#afterHandlers.indexOf(handler);
|
|
258
|
-
this.#afterHandlers.splice(idx, 1);
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
dispatch(commandName, payload) {
|
|
262
|
-
if (this.#subs.has(commandName)) {
|
|
263
|
-
this.#subs.get(commandName).forEach((handler) => handler(payload));
|
|
264
|
-
}
|
|
265
|
-
else {
|
|
266
|
-
console.warn(`No handlers for command: ${commandName}`);
|
|
267
|
-
}
|
|
268
|
-
this.#afterHandlers.forEach((handler) => handler());
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
183
|
function setAttributes(el, attrs) {
|
|
273
184
|
const { class: className, style, ...otherAttrs } = attrs;
|
|
274
185
|
if (className) {
|
|
@@ -311,22 +222,59 @@ function removeAttribute(el, name) {
|
|
|
311
222
|
el.removeAttribute(name);
|
|
312
223
|
}
|
|
313
224
|
|
|
314
|
-
function
|
|
225
|
+
function addEventListener(eventName, handler, el, hostComponent = null) {
|
|
226
|
+
function boundHandler() {
|
|
227
|
+
hostComponent
|
|
228
|
+
? handler.apply(hostComponent, arguments)
|
|
229
|
+
: handler(...arguments);
|
|
230
|
+
}
|
|
231
|
+
el.addEventListener(eventName, boundHandler);
|
|
232
|
+
return boundHandler;
|
|
233
|
+
}
|
|
234
|
+
function addEventListeners(events = {}, el, hostComponent = null) {
|
|
235
|
+
const addedEventListeners = {};
|
|
236
|
+
Object.entries(events).forEach(([eventName, handler]) => {
|
|
237
|
+
addedEventListeners[eventName] = addEventListener(
|
|
238
|
+
eventName,
|
|
239
|
+
handler,
|
|
240
|
+
el,
|
|
241
|
+
hostComponent
|
|
242
|
+
);
|
|
243
|
+
});
|
|
244
|
+
return addedEventListeners;
|
|
245
|
+
}
|
|
246
|
+
function removeEventListeners(listeners = {}, el) {
|
|
247
|
+
Object.entries(listeners).forEach(([eventName, handler]) => {
|
|
248
|
+
el.removeEventListener(eventName, handler);
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function extractPropsAndEvents(vdom) {
|
|
253
|
+
const { on: events = {}, ...props } = vdom.props;
|
|
254
|
+
delete props.key;
|
|
255
|
+
return { props, events };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function mountDOM(vDom, parentElement, index, hostComponent = null) {
|
|
315
259
|
switch (vDom.type) {
|
|
316
260
|
case DOM_TYPES.TEXT: {
|
|
317
261
|
createTextNode(vDom, parentElement, index);
|
|
318
262
|
break;
|
|
319
263
|
}
|
|
320
264
|
case DOM_TYPES.ELEMENT: {
|
|
321
|
-
createElementNode(vDom, parentElement, index);
|
|
265
|
+
createElementNode(vDom, parentElement, index, hostComponent);
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
case DOM_TYPES.COMPONENT: {
|
|
269
|
+
createComponentNode(vDom, parentElement, index, hostComponent);
|
|
322
270
|
break;
|
|
323
271
|
}
|
|
324
272
|
case DOM_TYPES.FRAGMENT: {
|
|
325
|
-
createFragmentNode(vDom, parentElement, index);
|
|
273
|
+
createFragmentNode(vDom, parentElement, index, hostComponent);
|
|
326
274
|
break;
|
|
327
275
|
}
|
|
328
276
|
default: {
|
|
329
|
-
throw new Error(`Can't mount DOM of type: ${vDom.type}`)
|
|
277
|
+
throw new Error(`Can't mount DOM of type: ${vDom.type}`);
|
|
330
278
|
}
|
|
331
279
|
}
|
|
332
280
|
}
|
|
@@ -336,22 +284,24 @@ function createTextNode(vDom, parentElement, index) {
|
|
|
336
284
|
vDom.el = textNode;
|
|
337
285
|
insert(textNode, parentElement, index);
|
|
338
286
|
}
|
|
339
|
-
function createFragmentNode(vDom, parentElement, index) {
|
|
287
|
+
function createFragmentNode(vDom, parentElement, index, hostComponent) {
|
|
340
288
|
const { children } = vDom;
|
|
341
289
|
vDom.el = parentElement;
|
|
342
|
-
children.forEach((child) =>
|
|
290
|
+
children.forEach((child) =>
|
|
291
|
+
mountDOM(child, parentElement, index ? index + 1 : null, hostComponent)
|
|
292
|
+
);
|
|
343
293
|
}
|
|
344
|
-
function createElementNode(vDom, parentElement, index) {
|
|
345
|
-
const { tag,
|
|
294
|
+
function createElementNode(vDom, parentElement, index, hostComponent) {
|
|
295
|
+
const { tag, children } = vDom;
|
|
346
296
|
const element = document.createElement(tag);
|
|
347
|
-
addProps(element,
|
|
297
|
+
addProps(element, vDom, hostComponent);
|
|
348
298
|
vDom.el = element;
|
|
349
|
-
children.forEach((child) => mountDOM(child, element));
|
|
299
|
+
children.forEach((child) => mountDOM(child, element, null, hostComponent));
|
|
350
300
|
insert(element, parentElement, index);
|
|
351
301
|
}
|
|
352
|
-
function addProps(el,
|
|
353
|
-
const {
|
|
354
|
-
vdom.listeners = addEventListeners(events, el);
|
|
302
|
+
function addProps(el,vdom, hostComponent) {
|
|
303
|
+
const { props: attrs, events } = extractPropsAndEvents(vdom);
|
|
304
|
+
vdom.listeners = addEventListeners(events, el, hostComponent);
|
|
355
305
|
setAttributes(el, attrs);
|
|
356
306
|
}
|
|
357
307
|
function insert(el, parentEl, index) {
|
|
@@ -369,15 +319,143 @@ function insert(el, parentEl, index) {
|
|
|
369
319
|
parentEl.insertBefore(el, children[index]);
|
|
370
320
|
}
|
|
371
321
|
}
|
|
322
|
+
function createComponentNode(vdom, parentEl, index, hostComponent) {
|
|
323
|
+
const Component = vdom.tag;
|
|
324
|
+
const { props, events } = extractPropsAndEvents(vdom);
|
|
325
|
+
const component = new Component(props, events, hostComponent);
|
|
326
|
+
component.mount(parentEl, index);
|
|
327
|
+
vdom.component = component;
|
|
328
|
+
vdom.el = component.firstElement;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function destroyDOM(vDom) {
|
|
332
|
+
const { type } = vDom;
|
|
333
|
+
switch (type) {
|
|
334
|
+
case DOM_TYPES.TEXT: {
|
|
335
|
+
removeTextNode(vDom);
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
case DOM_TYPES.ELEMENT: {
|
|
339
|
+
removeElementNode(vDom);
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
case DOM_TYPES.FRAGMENT: {
|
|
343
|
+
removeFragmentNodes(vDom);
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
case DOM_TYPES.COMPONENT: {
|
|
347
|
+
vDom.component.unmount();
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
default: {
|
|
351
|
+
throw new Error(`Can't destroy DOM of type: ${type}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
delete vDom.el;
|
|
355
|
+
}
|
|
356
|
+
function removeTextNode(vDom) {
|
|
357
|
+
const { el } = vDom;
|
|
358
|
+
el.remove();
|
|
359
|
+
}
|
|
360
|
+
function removeElementNode(vdom) {
|
|
361
|
+
const { el, children, listeners } = vdom;
|
|
362
|
+
el.remove();
|
|
363
|
+
children.forEach(destroyDOM);
|
|
364
|
+
if (listeners) {
|
|
365
|
+
removeEventListeners(listeners, el);
|
|
366
|
+
delete vdom.listeners;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
function removeFragmentNodes(vDom) {
|
|
370
|
+
const { children } = vDom;
|
|
371
|
+
children.forEach(destroyDOM);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function createApp(RootComponent, props = {}) {
|
|
375
|
+
let parentEl = null;
|
|
376
|
+
let isMounted = false;
|
|
377
|
+
let vdom = null;
|
|
378
|
+
function reset() {
|
|
379
|
+
parentEl = null;
|
|
380
|
+
isMounted = false;
|
|
381
|
+
vdom = null;
|
|
382
|
+
}
|
|
383
|
+
return {
|
|
384
|
+
mount(_parentEl) {
|
|
385
|
+
if (isMounted) {
|
|
386
|
+
throw new Error("The application is already mounted");
|
|
387
|
+
}
|
|
388
|
+
parentEl = _parentEl;
|
|
389
|
+
vdom = h(RootComponent, props);
|
|
390
|
+
mountDOM(vdom, parentEl);
|
|
391
|
+
isMounted = true;
|
|
392
|
+
},
|
|
393
|
+
unmount() {
|
|
394
|
+
if (!isMounted) {
|
|
395
|
+
throw new Error("The application is not mounted");
|
|
396
|
+
}
|
|
397
|
+
destroyDOM(vdom);
|
|
398
|
+
reset();
|
|
399
|
+
},
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function getDefaultExportFromCjs (x) {
|
|
404
|
+
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
var fastDeepEqual;
|
|
408
|
+
var hasRequiredFastDeepEqual;
|
|
409
|
+
function requireFastDeepEqual () {
|
|
410
|
+
if (hasRequiredFastDeepEqual) return fastDeepEqual;
|
|
411
|
+
hasRequiredFastDeepEqual = 1;
|
|
412
|
+
fastDeepEqual = function equal(a, b) {
|
|
413
|
+
if (a === b) return true;
|
|
414
|
+
if (a && b && typeof a == 'object' && typeof b == 'object') {
|
|
415
|
+
if (a.constructor !== b.constructor) return false;
|
|
416
|
+
var length, i, keys;
|
|
417
|
+
if (Array.isArray(a)) {
|
|
418
|
+
length = a.length;
|
|
419
|
+
if (length != b.length) return false;
|
|
420
|
+
for (i = length; i-- !== 0;)
|
|
421
|
+
if (!equal(a[i], b[i])) return false;
|
|
422
|
+
return true;
|
|
423
|
+
}
|
|
424
|
+
if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags;
|
|
425
|
+
if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf();
|
|
426
|
+
if (a.toString !== Object.prototype.toString) return a.toString() === b.toString();
|
|
427
|
+
keys = Object.keys(a);
|
|
428
|
+
length = keys.length;
|
|
429
|
+
if (length !== Object.keys(b).length) return false;
|
|
430
|
+
for (i = length; i-- !== 0;)
|
|
431
|
+
if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;
|
|
432
|
+
for (i = length; i-- !== 0;) {
|
|
433
|
+
var key = keys[i];
|
|
434
|
+
if (!equal(a[key], b[key])) return false;
|
|
435
|
+
}
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
438
|
+
return a!==a && b!==b;
|
|
439
|
+
};
|
|
440
|
+
return fastDeepEqual;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
var fastDeepEqualExports = requireFastDeepEqual();
|
|
444
|
+
var equal = /*@__PURE__*/getDefaultExportFromCjs(fastDeepEqualExports);
|
|
372
445
|
|
|
373
446
|
function areNodesEqual(nodeOne, nodeTwo) {
|
|
374
447
|
if (nodeOne.type !== nodeTwo.type) {
|
|
375
448
|
return false;
|
|
376
449
|
}
|
|
377
450
|
if (nodeOne.type === DOM_TYPES.ELEMENT) {
|
|
378
|
-
const { tag: tagOne } = nodeOne;
|
|
379
|
-
const { tag: tagTwo } = nodeTwo;
|
|
380
|
-
return tagOne === tagTwo;
|
|
451
|
+
const { tag: tagOne, props: { key: keyOne } } = nodeOne;
|
|
452
|
+
const { tag: tagTwo, props: { key: keyTwo } } = nodeTwo;
|
|
453
|
+
return tagOne === tagTwo && keyOne === keyTwo;
|
|
454
|
+
}
|
|
455
|
+
if (nodeOne.type === DOM_TYPES.COMPONENT) {
|
|
456
|
+
const { tag: componentOne, props: { key: keyOne } } = nodeOne;
|
|
457
|
+
const { tag: componentTwo, props: { key: keyTwo } } = nodeTwo;
|
|
458
|
+
return componentOne === componentTwo && keyOne === keyTwo;
|
|
381
459
|
}
|
|
382
460
|
return true;
|
|
383
461
|
}
|
|
@@ -385,14 +463,25 @@ function areNodesEqual(nodeOne, nodeTwo) {
|
|
|
385
463
|
function objectsDiff(oldObj, newObj) {
|
|
386
464
|
const oldKeys = Object.keys(oldObj);
|
|
387
465
|
const newKeys = Object.keys(newObj);
|
|
466
|
+
const added = [];
|
|
467
|
+
const updated = [];
|
|
468
|
+
newKeys.forEach(key => {
|
|
469
|
+
if (!(key in oldObj)) {
|
|
470
|
+
added.push(key);
|
|
471
|
+
}
|
|
472
|
+
if (key in oldObj && oldObj[key] !== newObj[key]) {
|
|
473
|
+
updated.push(key);
|
|
474
|
+
}
|
|
475
|
+
});
|
|
388
476
|
return {
|
|
389
|
-
added
|
|
477
|
+
added,
|
|
390
478
|
removed: oldKeys.filter((key) => !(key in newObj)),
|
|
391
|
-
updated
|
|
392
|
-
(key) => key in oldObj && oldObj[key] !== newObj[key]
|
|
393
|
-
),
|
|
479
|
+
updated,
|
|
394
480
|
};
|
|
395
481
|
}
|
|
482
|
+
function hasOwnProperty(obj, prop) {
|
|
483
|
+
return Object.prototype.hasOwnProperty.call(obj, prop);
|
|
484
|
+
}
|
|
396
485
|
|
|
397
486
|
function isNotEmptyString(str) {
|
|
398
487
|
return str !== ''
|
|
@@ -401,11 +490,11 @@ function isNotBlankOrEmptyString(str) {
|
|
|
401
490
|
return isNotEmptyString(str.trim())
|
|
402
491
|
}
|
|
403
492
|
|
|
404
|
-
function patchDOM(oldVdom, newVdom, parentEl) {
|
|
493
|
+
function patchDOM(oldVdom, newVdom, parentEl, hostComponent = null) {
|
|
405
494
|
if (!areNodesEqual(oldVdom, newVdom)) {
|
|
406
495
|
const index = findIndexInParent(parentEl, oldVdom.el);
|
|
407
496
|
destroyDOM(oldVdom);
|
|
408
|
-
mountDOM(newVdom, parentEl, index);
|
|
497
|
+
mountDOM(newVdom, parentEl, index, hostComponent);
|
|
409
498
|
return newVdom;
|
|
410
499
|
}
|
|
411
500
|
newVdom.el = oldVdom.el;
|
|
@@ -415,11 +504,15 @@ function patchDOM(oldVdom, newVdom, parentEl) {
|
|
|
415
504
|
return newVdom;
|
|
416
505
|
}
|
|
417
506
|
case DOM_TYPES.ELEMENT: {
|
|
418
|
-
patchElement(oldVdom, newVdom);
|
|
507
|
+
patchElement(oldVdom, newVdom, hostComponent);
|
|
508
|
+
break;
|
|
509
|
+
}
|
|
510
|
+
case DOM_TYPES.COMPONENT: {
|
|
511
|
+
patchComponent(oldVdom, newVdom);
|
|
419
512
|
break;
|
|
420
513
|
}
|
|
421
514
|
}
|
|
422
|
-
patchChildren(oldVdom, newVdom);
|
|
515
|
+
patchChildren(oldVdom, newVdom, hostComponent);
|
|
423
516
|
return newVdom;
|
|
424
517
|
}
|
|
425
518
|
function findIndexInParent(parentEl, el) {
|
|
@@ -436,7 +529,7 @@ function patchText(oldVdom, newVdom) {
|
|
|
436
529
|
newVdom.el.nodeValue = newText;
|
|
437
530
|
}
|
|
438
531
|
}
|
|
439
|
-
function patchElement(oldVdom, newVdom) {
|
|
532
|
+
function patchElement(oldVdom, newVdom, hostComponent) {
|
|
440
533
|
const el = oldVdom.el;
|
|
441
534
|
const {
|
|
442
535
|
class: oldClass,
|
|
@@ -454,7 +547,7 @@ function patchElement(oldVdom, newVdom) {
|
|
|
454
547
|
patchAttrs(el, oldAttrs, newAttrs);
|
|
455
548
|
patchClasses(el, oldClass, newClass);
|
|
456
549
|
patchStyles(el, oldStyle, newStyle);
|
|
457
|
-
newVdom.listeners = patchEvents(el, oldListeners, oldEvents, newEvents);
|
|
550
|
+
newVdom.listeners = patchEvents(el, oldListeners, oldEvents, newEvents, hostComponent);
|
|
458
551
|
}
|
|
459
552
|
function patchAttrs(el, oldAttrs, newAttrs) {
|
|
460
553
|
const { added, removed, updated } = objectsDiff(oldAttrs, newAttrs);
|
|
@@ -490,32 +583,29 @@ function patchStyles(el, oldStyle = {}, newStyle = {}) {
|
|
|
490
583
|
setStyle(el, style, newStyle[style]);
|
|
491
584
|
}
|
|
492
585
|
}
|
|
493
|
-
function patchEvents(el, oldListeners = {}, oldEvents = {}, newEvents = {}) {
|
|
586
|
+
function patchEvents(el, oldListeners = {}, oldEvents = {}, newEvents = {}, hostComponent) {
|
|
494
587
|
const { removed, added, updated } = objectsDiff(oldEvents, newEvents);
|
|
495
588
|
for (const eventName of removed.concat(updated)) {
|
|
496
589
|
el.removeEventListener(eventName, oldListeners[eventName]);
|
|
497
590
|
}
|
|
498
591
|
const addedListeners = {};
|
|
499
592
|
for (const eventName of added.concat(updated)) {
|
|
500
|
-
const listener = addEventListener(eventName, newEvents[eventName], el);
|
|
593
|
+
const listener = addEventListener(eventName, newEvents[eventName], el, hostComponent);
|
|
501
594
|
addedListeners[eventName] = listener;
|
|
502
595
|
}
|
|
503
596
|
return addedListeners;
|
|
504
597
|
}
|
|
505
|
-
function patchChildren(oldVdom, newVdom) {
|
|
598
|
+
function patchChildren(oldVdom, newVdom, hostComponent) {
|
|
506
599
|
const oldChildren = extractChildren(oldVdom);
|
|
507
600
|
const newChildren = extractChildren(newVdom);
|
|
508
601
|
const parentEl = oldVdom.el;
|
|
509
|
-
const diffSeq = arraysDiffSequence(
|
|
510
|
-
oldChildren,
|
|
511
|
-
newChildren,
|
|
512
|
-
areNodesEqual
|
|
513
|
-
);
|
|
602
|
+
const diffSeq = arraysDiffSequence(oldChildren, newChildren, areNodesEqual);
|
|
514
603
|
for (const operation of diffSeq) {
|
|
515
604
|
const { originalIndex, index, item } = operation;
|
|
605
|
+
const offset = hostComponent?.offset ?? 0;
|
|
516
606
|
switch (operation.op) {
|
|
517
607
|
case ARRAY_DIFF_OP.ADD: {
|
|
518
|
-
mountDOM(item, parentEl, index);
|
|
608
|
+
mountDOM(item, parentEl, index + offset, hostComponent);
|
|
519
609
|
break;
|
|
520
610
|
}
|
|
521
611
|
case ARRAY_DIFF_OP.REMOVE: {
|
|
@@ -526,53 +616,167 @@ function patchChildren(oldVdom, newVdom) {
|
|
|
526
616
|
const oldChild = oldChildren[originalIndex];
|
|
527
617
|
const newChild = newChildren[index];
|
|
528
618
|
const el = oldChild.el;
|
|
529
|
-
const elAtTargetIndex = parentEl.childNodes[index];
|
|
619
|
+
const elAtTargetIndex = parentEl.childNodes[index + offset];
|
|
530
620
|
parentEl.insertBefore(el, elAtTargetIndex);
|
|
531
|
-
patchDOM(oldChild, newChild, parentEl);
|
|
621
|
+
patchDOM(oldChild, newChild, parentEl, hostComponent);
|
|
532
622
|
break;
|
|
533
623
|
}
|
|
534
624
|
case ARRAY_DIFF_OP.NOOP: {
|
|
535
|
-
patchDOM(oldChildren[originalIndex], newChildren[index], parentEl);
|
|
625
|
+
patchDOM(oldChildren[originalIndex], newChildren[index], parentEl, hostComponent);
|
|
536
626
|
break;
|
|
537
627
|
}
|
|
538
628
|
}
|
|
539
629
|
}
|
|
540
630
|
}
|
|
631
|
+
function patchComponent(oldVdom, newVdom) {
|
|
632
|
+
const { component } = oldVdom;
|
|
633
|
+
const { props } = extractPropsAndEvents(newVdom);
|
|
634
|
+
component.updateProps(props);
|
|
635
|
+
newVdom.component = component;
|
|
636
|
+
newVdom.el = component.firstElement;
|
|
637
|
+
}
|
|
541
638
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
639
|
+
class Dispatcher {
|
|
640
|
+
#subs = new Map();
|
|
641
|
+
#afterHandlers = [];
|
|
642
|
+
subscribe(commandName, handler) {
|
|
643
|
+
if (!this.#subs.has(commandName)) {
|
|
644
|
+
this.#subs.set(commandName, []);
|
|
645
|
+
}
|
|
646
|
+
const handlers = this.#subs.get(commandName);
|
|
647
|
+
if (handlers.includes(handler)) {
|
|
648
|
+
return () => {};
|
|
649
|
+
}
|
|
650
|
+
handlers.push(handler);
|
|
651
|
+
return () => {
|
|
652
|
+
const idx = handlers.indexOf(handler);
|
|
653
|
+
handlers.splice(idx, 1);
|
|
654
|
+
};
|
|
556
655
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
656
|
+
afterEveryCommand(handler) {
|
|
657
|
+
this.#afterHandlers.push(handler);
|
|
658
|
+
return () => {
|
|
659
|
+
const idx = this.#afterHandlers.indexOf(handler);
|
|
660
|
+
this.#afterHandlers.splice(idx, 1);
|
|
661
|
+
};
|
|
560
662
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
663
|
+
dispatch(commandName, payload) {
|
|
664
|
+
if (this.#subs.has(commandName)) {
|
|
665
|
+
this.#subs.get(commandName).forEach((handler) => handler(payload));
|
|
666
|
+
}
|
|
667
|
+
else {
|
|
668
|
+
console.warn(`No handlers for command: ${commandName}`);
|
|
669
|
+
}
|
|
670
|
+
this.#afterHandlers.forEach((handler) => handler());
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function defineComponent({ render, state, ...methods }) {
|
|
675
|
+
class Component {
|
|
676
|
+
#vdom = null;
|
|
677
|
+
#isMounted = false;
|
|
678
|
+
#hostEl = null;
|
|
679
|
+
#eventHandlers = null;
|
|
680
|
+
#parentComponent = null;
|
|
681
|
+
#dispatcher = new Dispatcher();
|
|
682
|
+
#subscriptions = [];
|
|
683
|
+
constructor(props = {}, eventHandlers = {}, parentComponent = null) {
|
|
684
|
+
this.props = props;
|
|
685
|
+
this.state = state ? state(props) : {};
|
|
686
|
+
this.#eventHandlers = eventHandlers;
|
|
687
|
+
this.#parentComponent = parentComponent;
|
|
688
|
+
}
|
|
689
|
+
updateState(newState) {
|
|
690
|
+
this.state = { ...this.state, ...newState };
|
|
691
|
+
this.#patch();
|
|
692
|
+
}
|
|
693
|
+
render() {
|
|
694
|
+
return render.call(this);
|
|
695
|
+
}
|
|
696
|
+
mount(hostEl, index = null) {
|
|
697
|
+
if (this.#isMounted) {
|
|
698
|
+
throw new Error("Component is already mounted");
|
|
565
699
|
}
|
|
566
|
-
|
|
567
|
-
vdom
|
|
568
|
-
|
|
569
|
-
|
|
700
|
+
this.#vdom = this.render();
|
|
701
|
+
mountDOM(this.#vdom, hostEl, index, this);
|
|
702
|
+
this.#wireEventHandlers();
|
|
703
|
+
this.#hostEl = hostEl;
|
|
704
|
+
this.#isMounted = true;
|
|
705
|
+
}
|
|
570
706
|
unmount() {
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
707
|
+
if (!this.#isMounted) {
|
|
708
|
+
throw new Error("Component is not mounted");
|
|
709
|
+
}
|
|
710
|
+
destroyDOM(this.#vdom);
|
|
711
|
+
this.#subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
712
|
+
this.#subscriptions = [];
|
|
713
|
+
this.#vdom = null;
|
|
714
|
+
this.#hostEl = null;
|
|
715
|
+
this.#isMounted = false;
|
|
716
|
+
}
|
|
717
|
+
updateProps(props) {
|
|
718
|
+
const newProps = { ...this.props, ...props };
|
|
719
|
+
if (equal(this.props, newProps)) {
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
this.props = newProps;
|
|
723
|
+
this.#patch();
|
|
724
|
+
}
|
|
725
|
+
emit(eventName, payload) {
|
|
726
|
+
this.#dispatcher.dispatch(eventName, payload);
|
|
727
|
+
}
|
|
728
|
+
#patch() {
|
|
729
|
+
if (!this.#isMounted) {
|
|
730
|
+
throw new Error("Component is not mounted");
|
|
731
|
+
}
|
|
732
|
+
const vdom = this.render();
|
|
733
|
+
this.#vdom = patchDOM(this.#vdom, vdom, this.#hostEl, this);
|
|
734
|
+
}
|
|
735
|
+
#wireEventHandlers() {
|
|
736
|
+
this.#subscriptions = Object.entries(this.#eventHandlers).map(
|
|
737
|
+
([eventName, handler]) => this.#wireEventHandler(eventName, handler)
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
#wireEventHandler(eventName, handler) {
|
|
741
|
+
return this.#dispatcher.subscribe(eventName, (payload) => {
|
|
742
|
+
if (this.#parentComponent) {
|
|
743
|
+
handler.call(this.#parentComponent, payload);
|
|
744
|
+
} else {
|
|
745
|
+
handler(payload);
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
get elements() {
|
|
750
|
+
if (this.#vdom == null) {
|
|
751
|
+
return [];
|
|
752
|
+
}
|
|
753
|
+
if (this.#vdom.type === DOM_TYPES.FRAGMENT) {
|
|
754
|
+
return extractChildren(this.#vdom).flatMap((child) => {
|
|
755
|
+
if (child.type === DOM_TYPES.COMPONENT) {
|
|
756
|
+
return child.component.elements;
|
|
757
|
+
}
|
|
758
|
+
return [child.el];
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
return [this.#vdom.el];
|
|
762
|
+
}
|
|
763
|
+
get firstElement() {
|
|
764
|
+
return this.elements[0];
|
|
765
|
+
}
|
|
766
|
+
get offset() {
|
|
767
|
+
if (this.#vdom.type === DOM_TYPES.FRAGMENT) {
|
|
768
|
+
return Array.from(this.#hostEl.children).indexOf(this.firstElement);
|
|
769
|
+
}
|
|
770
|
+
return 0;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
for(const methodName in methods) {
|
|
774
|
+
if (hasOwnProperty(Component, methodName)) {
|
|
775
|
+
throw new Error(`Method "${methodName}()" already exists in the component`);
|
|
574
776
|
}
|
|
777
|
+
Component.prototype[methodName] = methods[methodName];
|
|
575
778
|
}
|
|
779
|
+
return Component;
|
|
576
780
|
}
|
|
577
781
|
|
|
578
|
-
export { createApp, h, hFragment, hString };
|
|
782
|
+
export { createApp, defineComponent, h, hFragment, hString };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "betal-fe",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/betal-fe.js",
|
|
6
6
|
"files": [
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
"description": "",
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@eslint/js": "^9.38.0",
|
|
23
|
+
"@rollup/plugin-commonjs": "^29.0.0",
|
|
24
|
+
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
23
25
|
"eslint": "^9.38.0",
|
|
24
26
|
"eslint-plugin-import": "^2.32.0",
|
|
25
27
|
"globals": "^16.4.0",
|
|
@@ -28,5 +30,8 @@
|
|
|
28
30
|
"rollup-plugin-cleanup": "^3.2.1",
|
|
29
31
|
"rollup-plugin-filesize": "^10.0.0",
|
|
30
32
|
"vitest": "^3.2.4"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"fast-deep-equal": "^3.1.3"
|
|
31
36
|
}
|
|
32
37
|
}
|