@@ -633,18 +633,36 @@ function render(vnode, parentDomNode, options = {}) {
633
633
}
634
634
635
635
function toCamelCase ( name ) {
636
- if ( ! name . includes ( "-" ) ) return name
636
+ if ( ! name . includes ( '-' ) ) return name
637
637
return name . replace ( / - ( [ a - z ] ) / g, ( match , letter ) => letter . toUpperCase ( ) )
638
638
}
639
+
640
+ function sanitizeAttribute ( attrType , attrValue ) {
641
+ if ( attrType === 'object' ) return sanitizeJsonAttribute ( attrValue )
642
+ if ( attrType === 'boolean' ) return attrValue === "" || ! ! attrValue
643
+ if ( attrType === 'number' ) return Number ( attrValue )
644
+ return attrValue
645
+ }
646
+
647
+ function sanitizeJsonAttribute ( attrValue ) {
648
+ try {
649
+ return JSON . parse ( attrValue )
650
+ } catch ( _ ) {
651
+ return attrValue
652
+ }
653
+ }
654
+
639
655
class Component extends HTMLElement {
640
656
state = { }
641
657
useShadowDOM = true
658
+ #watchProps = [ ]
642
659
#isConnected = false
643
660
#isInitialized = false
661
+ #customEvents = [ ]
644
662
645
663
#ready( ) {
646
664
this . init ?. ( ) ;
647
- this . watchProps = Object . keys ( this . state ) ;
665
+ this . # watchProps = Object . keys ( this . state ) ;
648
666
this . #syncAttributesToState( ) ;
649
667
this . document = this . useShadowDOM
650
668
? this . attachShadow ( { mode : "open" } )
@@ -654,10 +672,14 @@ class Component extends HTMLElement {
654
672
655
673
#syncAttributesToState( ) {
656
674
this . state = Array . from ( this . attributes ) . reduce (
657
- ( state , attr ) => ( {
658
- ...state ,
659
- [ toCamelCase ( attr . name ) ] : attr . value ,
660
- } ) ,
675
+ ( state , attr ) => {
676
+ const camelCaseName = toCamelCase ( attr . name ) ;
677
+ const attrType = typeof this . state [ camelCaseName ] ;
678
+ return {
679
+ ...state ,
680
+ [ camelCaseName ] : sanitizeAttribute ( attrType , attr . value ) ,
681
+ }
682
+ } ,
661
683
this . state
662
684
) ;
663
685
}
@@ -671,17 +693,19 @@ class Component extends HTMLElement {
671
693
}
672
694
673
695
setAttribute ( name , value ) {
674
- super . setAttribute ( name , value ) ;
696
+ if ( name . match ( / @ [ a - z ] + (?: - [ a - z ] + ) * / ) ) return this . #customEvents. push ( [ name . slice ( 1 ) , value ] )
697
+ super . setAttribute ( name , typeof value === 'object' ? JSON . stringify ( value ) : value ) ;
675
698
const prop = toCamelCase ( name ) ;
676
- if ( this . watchProps . includes ( prop ) ) this . render ( { [ prop ] : value } ) ;
699
+ const attrType = typeof this . state [ prop ] ;
700
+ if ( this . #watchProps. includes ( prop ) ) this . render ( { [ prop ] : sanitizeAttribute ( attrType , value ) } ) ;
677
701
}
678
702
679
703
removeAttribute ( name ) {
680
704
super . removeAttribute ( name ) ;
681
705
const prop = toCamelCase ( name ) ;
682
- if ( this . watchProps . includes ( prop ) && prop in this . state ) {
683
- this . render ( { [ prop ] : null } ) ;
684
- delete this . state [ prop ] ;
706
+ const attrType = typeof this . state [ prop ] ;
707
+ if ( this . #watchProps . includes ( prop ) && prop in this . state ) {
708
+ this . render ( { [ prop ] : sanitizeAttribute ( attrType , null ) } ) ;
685
709
}
686
710
}
687
711
@@ -690,11 +714,13 @@ class Component extends HTMLElement {
690
714
this . #isConnected = true ;
691
715
// Load the DOM
692
716
this . render ( ) ;
717
+ this . #customEvents. forEach ( ( [ customEvent , listener ] ) => this . addEventListener ( customEvent , listener ) ) ;
693
718
this . connected ?. ( ) ;
694
719
}
695
720
696
721
disconnectedCallback ( ) {
697
722
this . #isConnected = false ;
723
+ this . #customEvents. forEach ( ( [ customEvent , listener ] ) => this . removeEventListener ( customEvent , listener ) ) ;
698
724
this . disconnected ?. ( ) ;
699
725
}
700
726
@@ -712,13 +738,15 @@ class Component extends HTMLElement {
712
738
}
713
739
714
740
render ( state ) {
715
- this . setState ( state ) ;
741
+ if ( state ) this . setState ( state ) ;
716
742
if ( ! this . #isConnected) return
717
743
718
- return render (
744
+ render (
719
745
[ this . vdom ( { state : this . state } ) , this . vstyle ( { state : this . state } ) ] ,
720
746
this . document
721
- )
747
+ ) ;
748
+
749
+ this . rendered ?. ( state ) ;
722
750
}
723
751
}
724
752
0 commit comments