betal-fe 2.1.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 +351 -155
- 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
|
}
|
|
@@ -401,6 +479,9 @@ function objectsDiff(oldObj, newObj) {
|
|
|
401
479
|
updated,
|
|
402
480
|
};
|
|
403
481
|
}
|
|
482
|
+
function hasOwnProperty(obj, prop) {
|
|
483
|
+
return Object.prototype.hasOwnProperty.call(obj, prop);
|
|
484
|
+
}
|
|
404
485
|
|
|
405
486
|
function isNotEmptyString(str) {
|
|
406
487
|
return str !== ''
|
|
@@ -409,11 +490,11 @@ function isNotBlankOrEmptyString(str) {
|
|
|
409
490
|
return isNotEmptyString(str.trim())
|
|
410
491
|
}
|
|
411
492
|
|
|
412
|
-
function patchDOM(oldVdom, newVdom, parentEl) {
|
|
493
|
+
function patchDOM(oldVdom, newVdom, parentEl, hostComponent = null) {
|
|
413
494
|
if (!areNodesEqual(oldVdom, newVdom)) {
|
|
414
495
|
const index = findIndexInParent(parentEl, oldVdom.el);
|
|
415
496
|
destroyDOM(oldVdom);
|
|
416
|
-
mountDOM(newVdom, parentEl, index);
|
|
497
|
+
mountDOM(newVdom, parentEl, index, hostComponent);
|
|
417
498
|
return newVdom;
|
|
418
499
|
}
|
|
419
500
|
newVdom.el = oldVdom.el;
|
|
@@ -423,11 +504,15 @@ function patchDOM(oldVdom, newVdom, parentEl) {
|
|
|
423
504
|
return newVdom;
|
|
424
505
|
}
|
|
425
506
|
case DOM_TYPES.ELEMENT: {
|
|
426
|
-
patchElement(oldVdom, newVdom);
|
|
507
|
+
patchElement(oldVdom, newVdom, hostComponent);
|
|
508
|
+
break;
|
|
509
|
+
}
|
|
510
|
+
case DOM_TYPES.COMPONENT: {
|
|
511
|
+
patchComponent(oldVdom, newVdom);
|
|
427
512
|
break;
|
|
428
513
|
}
|
|
429
514
|
}
|
|
430
|
-
patchChildren(oldVdom, newVdom);
|
|
515
|
+
patchChildren(oldVdom, newVdom, hostComponent);
|
|
431
516
|
return newVdom;
|
|
432
517
|
}
|
|
433
518
|
function findIndexInParent(parentEl, el) {
|
|
@@ -444,7 +529,7 @@ function patchText(oldVdom, newVdom) {
|
|
|
444
529
|
newVdom.el.nodeValue = newText;
|
|
445
530
|
}
|
|
446
531
|
}
|
|
447
|
-
function patchElement(oldVdom, newVdom) {
|
|
532
|
+
function patchElement(oldVdom, newVdom, hostComponent) {
|
|
448
533
|
const el = oldVdom.el;
|
|
449
534
|
const {
|
|
450
535
|
class: oldClass,
|
|
@@ -462,7 +547,7 @@ function patchElement(oldVdom, newVdom) {
|
|
|
462
547
|
patchAttrs(el, oldAttrs, newAttrs);
|
|
463
548
|
patchClasses(el, oldClass, newClass);
|
|
464
549
|
patchStyles(el, oldStyle, newStyle);
|
|
465
|
-
newVdom.listeners = patchEvents(el, oldListeners, oldEvents, newEvents);
|
|
550
|
+
newVdom.listeners = patchEvents(el, oldListeners, oldEvents, newEvents, hostComponent);
|
|
466
551
|
}
|
|
467
552
|
function patchAttrs(el, oldAttrs, newAttrs) {
|
|
468
553
|
const { added, removed, updated } = objectsDiff(oldAttrs, newAttrs);
|
|
@@ -498,32 +583,29 @@ function patchStyles(el, oldStyle = {}, newStyle = {}) {
|
|
|
498
583
|
setStyle(el, style, newStyle[style]);
|
|
499
584
|
}
|
|
500
585
|
}
|
|
501
|
-
function patchEvents(el, oldListeners = {}, oldEvents = {}, newEvents = {}) {
|
|
586
|
+
function patchEvents(el, oldListeners = {}, oldEvents = {}, newEvents = {}, hostComponent) {
|
|
502
587
|
const { removed, added, updated } = objectsDiff(oldEvents, newEvents);
|
|
503
588
|
for (const eventName of removed.concat(updated)) {
|
|
504
589
|
el.removeEventListener(eventName, oldListeners[eventName]);
|
|
505
590
|
}
|
|
506
591
|
const addedListeners = {};
|
|
507
592
|
for (const eventName of added.concat(updated)) {
|
|
508
|
-
const listener = addEventListener(eventName, newEvents[eventName], el);
|
|
593
|
+
const listener = addEventListener(eventName, newEvents[eventName], el, hostComponent);
|
|
509
594
|
addedListeners[eventName] = listener;
|
|
510
595
|
}
|
|
511
596
|
return addedListeners;
|
|
512
597
|
}
|
|
513
|
-
function patchChildren(oldVdom, newVdom) {
|
|
598
|
+
function patchChildren(oldVdom, newVdom, hostComponent) {
|
|
514
599
|
const oldChildren = extractChildren(oldVdom);
|
|
515
600
|
const newChildren = extractChildren(newVdom);
|
|
516
601
|
const parentEl = oldVdom.el;
|
|
517
|
-
const diffSeq = arraysDiffSequence(
|
|
518
|
-
oldChildren,
|
|
519
|
-
newChildren,
|
|
520
|
-
areNodesEqual
|
|
521
|
-
);
|
|
602
|
+
const diffSeq = arraysDiffSequence(oldChildren, newChildren, areNodesEqual);
|
|
522
603
|
for (const operation of diffSeq) {
|
|
523
604
|
const { originalIndex, index, item } = operation;
|
|
605
|
+
const offset = hostComponent?.offset ?? 0;
|
|
524
606
|
switch (operation.op) {
|
|
525
607
|
case ARRAY_DIFF_OP.ADD: {
|
|
526
|
-
mountDOM(item, parentEl, index);
|
|
608
|
+
mountDOM(item, parentEl, index + offset, hostComponent);
|
|
527
609
|
break;
|
|
528
610
|
}
|
|
529
611
|
case ARRAY_DIFF_OP.REMOVE: {
|
|
@@ -534,53 +616,167 @@ function patchChildren(oldVdom, newVdom) {
|
|
|
534
616
|
const oldChild = oldChildren[originalIndex];
|
|
535
617
|
const newChild = newChildren[index];
|
|
536
618
|
const el = oldChild.el;
|
|
537
|
-
const elAtTargetIndex = parentEl.childNodes[index];
|
|
619
|
+
const elAtTargetIndex = parentEl.childNodes[index + offset];
|
|
538
620
|
parentEl.insertBefore(el, elAtTargetIndex);
|
|
539
|
-
patchDOM(oldChild, newChild, parentEl);
|
|
621
|
+
patchDOM(oldChild, newChild, parentEl, hostComponent);
|
|
540
622
|
break;
|
|
541
623
|
}
|
|
542
624
|
case ARRAY_DIFF_OP.NOOP: {
|
|
543
|
-
patchDOM(oldChildren[originalIndex], newChildren[index], parentEl);
|
|
625
|
+
patchDOM(oldChildren[originalIndex], newChildren[index], parentEl, hostComponent);
|
|
544
626
|
break;
|
|
545
627
|
}
|
|
546
628
|
}
|
|
547
629
|
}
|
|
548
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
|
+
}
|
|
549
638
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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
|
+
};
|
|
564
655
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
656
|
+
afterEveryCommand(handler) {
|
|
657
|
+
this.#afterHandlers.push(handler);
|
|
658
|
+
return () => {
|
|
659
|
+
const idx = this.#afterHandlers.indexOf(handler);
|
|
660
|
+
this.#afterHandlers.splice(idx, 1);
|
|
661
|
+
};
|
|
568
662
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
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");
|
|
573
699
|
}
|
|
574
|
-
|
|
575
|
-
vdom
|
|
576
|
-
|
|
577
|
-
|
|
700
|
+
this.#vdom = this.render();
|
|
701
|
+
mountDOM(this.#vdom, hostEl, index, this);
|
|
702
|
+
this.#wireEventHandlers();
|
|
703
|
+
this.#hostEl = hostEl;
|
|
704
|
+
this.#isMounted = true;
|
|
705
|
+
}
|
|
578
706
|
unmount() {
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
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`);
|
|
582
776
|
}
|
|
777
|
+
Component.prototype[methodName] = methods[methodName];
|
|
583
778
|
}
|
|
779
|
+
return Component;
|
|
584
780
|
}
|
|
585
781
|
|
|
586
|
-
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
|
}
|