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.
Files changed (2) hide show
  1. package/dist/betal-fe.js +363 -159
  2. 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: DOM_TYPES.ELEMENT,
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 mountDOM(vDom, parentElement, index) {
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) => mountDOM(child, parentElement, index ? index + 1 : null));
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, props, children } = vDom;
294
+ function createElementNode(vDom, parentElement, index, hostComponent) {
295
+ const { tag, children } = vDom;
346
296
  const element = document.createElement(tag);
347
- addProps(element, props, vDom);
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, props, vdom) {
353
- const { on: events, ...attrs } = props;
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: newKeys.filter((key) => !(key in oldObj)),
477
+ added,
390
478
  removed: oldKeys.filter((key) => !(key in newObj)),
391
- updated: newKeys.filter(
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
- function createApp({ state, view, reducers = {} }) {
543
- let parentEl = null;
544
- let vdom = null;
545
- const dispatcher = new Dispatcher();
546
- const subscriptions = [dispatcher.afterEveryCommand(renderApp)];
547
- function emit(eventName, payload) {
548
- dispatcher.dispatch(eventName, payload);
549
- }
550
- for (const actionName in reducers) {
551
- const reducer = reducers[actionName];
552
- const subs = dispatcher.subscribe(actionName, (payload) => {
553
- state = reducer(state, payload);
554
- });
555
- subscriptions.push(subs);
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
- function renderApp() {
558
- const newVdom = view(state, emit);
559
- vdom = patchDOM(vdom, newVdom, parentEl);
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
- return {
562
- mount(_parentEl) {
563
- if (vdom) {
564
- throw new Error('App is already mounted');
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
- parentEl = _parentEl;
567
- vdom = view(state, emit);
568
- mountDOM(vdom, parentEl);
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
- destroyDOM(vdom);
572
- vdom = null;
573
- subscriptions.forEach((unsub) => unsub());
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": "2.0.0",
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
  }