@@ -28,6 +28,13 @@ import {
2828
2929import { logError } from "./utils" ;
3030
31+ type FormInputLike = HTMLElement & {
32+ readonly form ?: HTMLFormElement | null ;
33+ readonly type ?: string ;
34+ readonly validity ?: ValidityState ;
35+ readonly name ?: string ;
36+ } ;
37+
3138const DOM = {
3239 byId ( id ) {
3340 return document . getElementById ( id ) || logError ( `no id found for ${ id } ` ) ;
@@ -40,7 +47,11 @@ const DOM = {
4047 }
4148 } ,
4249
43- all ( node , query , callback ) {
50+ all (
51+ node : Element | Document | DocumentFragment ,
52+ query : string ,
53+ callback ?: ( el : Element ) => void ,
54+ ) : Element [ ] {
4455 if ( ! node ) {
4556 return [ ] ;
4657 }
@@ -57,7 +68,7 @@ const DOM = {
5768 return template . content . childElementCount ;
5869 } ,
5970
60- isUploadInput ( el ) {
71+ isUploadInput ( el ) : el is HTMLInputElement {
6172 return el . type === "file" && el . getAttribute ( PHX_UPLOAD_REF ) !== null ;
6273 } ,
6374
@@ -208,7 +219,7 @@ const DOM = {
208219 ) . forEach ( ( parent ) => {
209220 parentCids . add ( cid ) ;
210221 this . all ( parent , `[${ PHX_VIEW_REF } ="${ viewId } "][${ PHX_COMPONENT } ]` )
211- . map ( ( el ) => parseInt ( el . getAttribute ( PHX_COMPONENT ) ) )
222+ . map ( ( el ) => parseInt ( el . getAttribute ( PHX_COMPONENT ) ! ) )
212223 . forEach ( ( childCID ) => childrenCids . add ( childCID ) ) ;
213224 } ) ;
214225 } ) ;
@@ -376,7 +387,7 @@ const DOM = {
376387 }
377388 } ,
378389
379- triggerCycle ( el , key , currentCycle ) {
390+ triggerCycle ( el , key , currentCycle ? ) {
380391 const [ cycle , trigger ] = this . private ( el , key ) ;
381392 if ( ! currentCycle ) {
382393 currentCycle = cycle ;
@@ -491,7 +502,7 @@ const DOM = {
491502 return null ;
492503 } ,
493504
494- dispatchEvent ( target , name , opts = { } ) {
505+ dispatchEvent ( target , name , opts : { bubbles ?: boolean ; detail ?: any } = { } ) {
495506 let defaultBubble = true ;
496507 const isUploadTarget =
497508 target . nodeName === "INPUT" && target . type === "file" ;
@@ -524,7 +535,11 @@ const DOM = {
524535 // merge attributes from source to target
525536 // if an element is ignored, we only merge data attributes
526537 // including removing data attributes that are no longer in the source
527- mergeAttrs ( target , source , opts = { } ) {
538+ mergeAttrs (
539+ target ,
540+ source ,
541+ opts : { exclude ?: string [ ] ; isIgnored ?: boolean } = { } ,
542+ ) {
528543 const exclude = new Set ( opts . exclude || [ ] ) ;
529544 const isIgnored = opts . isIgnored ;
530545 const sourceAttrs = source . attributes ;
@@ -611,21 +626,27 @@ const DOM = {
611626 }
612627 } ,
613628
614- isFormInput ( el ) {
615- if ( el . localName && customElements . get ( el . localName ) ) {
616- // Custom Elements may be form associated. This allows them
617- // to participate within a form's lifecycle, including form
618- // validity and form submissions.
619- // The spec for Form Associated custom elements requires the
620- // custom element's class to contain a static boolean value of `formAssociated`
621- // which identifies this class as allowed to associate to a form.
622- // See https://html.spec.whatwg.org/dev/custom-elements.html#custom-elements-face-example
623- // for details.
624- return customElements . get ( el . localName ) [ `formAssociated` ] ;
629+ isFormInput ( el : Element | EventTarget | null ) : el is FormInputLike {
630+ if ( ! ( el instanceof HTMLElement ) ) return false ;
631+ if ( el . localName ) {
632+ const customEl = customElements . get ( el . localName ) ;
633+ if ( customEl ) {
634+ // Custom Elements may be form associated. This allows them
635+ // to participate within a form's lifecycle, including form
636+ // validity and form submissions.
637+ // The spec for Form Associated custom elements requires the
638+ // custom element's class to contain a static boolean value of `formAssociated`
639+ // which identifies this class as allowed to associate to a form.
640+ // See https://html.spec.whatwg.org/dev/custom-elements.html#custom-elements-face-example
641+ // for details.
642+ return (
643+ ( customEl as { formAssociated ?: boolean } ) . formAssociated === true
644+ ) ;
645+ }
625646 }
626-
627647 return (
628- / ^ (?: i n p u t | s e l e c t | t e x t a r e a ) $ / i. test ( el . tagName ) && el . type !== "button"
648+ / ^ (?: i n p u t | s e l e c t | t e x t a r e a ) $ / i. test ( el . tagName ) &&
649+ ( el as HTMLInputElement ) . type !== "button"
629650 ) ;
630651 } ,
631652
@@ -650,21 +671,22 @@ const DOM = {
650671 ) ;
651672 } ,
652673
653- cleanChildNodes ( container , phxUpdate ) {
674+ cleanChildNodes ( container : Element , phxUpdate : string ) {
654675 if (
655676 DOM . isPhxUpdate ( container , phxUpdate , [ "append" , "prepend" , PHX_STREAM ] )
656677 ) {
657- const toRemove = [ ] ;
678+ const toRemove : Array < ChildNode > = [ ] ;
658679 container . childNodes . forEach ( ( childNode ) => {
659- if ( ! childNode . id ) {
680+ if ( ! ( "id" in childNode ) || ! childNode . id ) {
660681 // Skip warning if it's an empty text node (e.g. a new-line)
661682 const isEmptyTextNode =
662683 childNode . nodeType === Node . TEXT_NODE &&
684+ childNode . nodeValue &&
663685 childNode . nodeValue . trim ( ) === "" ;
664686 if ( ! isEmptyTextNode && childNode . nodeType !== Node . COMMENT_NODE ) {
665687 logError (
666688 "only HTML element tags with an id are allowed inside containers with phx-update.\n\n" +
667- `removing illegal node: "${ ( childNode . outerHTML || childNode . nodeValue ) . trim ( ) } "\n\n` ,
689+ `removing illegal node: "${ ( ( "outerHTML" in childNode && ( childNode . outerHTML as string ) ) || childNode . nodeValue || "" ) . trim ( ) } "\n\n` ,
668690 ) ;
669691 }
670692 toRemove . push ( childNode ) ;
@@ -674,7 +696,11 @@ const DOM = {
674696 }
675697 } ,
676698
677- replaceRootContainer ( container , tagName , attrs ) {
699+ replaceRootContainer (
700+ container : HTMLElement ,
701+ tagName : string ,
702+ attrs : Record < string , string > ,
703+ ) {
678704 const retainedAttrs = new Set ( [
679705 "id" ,
680706 PHX_SESSION ,
@@ -697,9 +723,12 @@ const DOM = {
697723 Object . keys ( attrs ) . forEach ( ( attr ) =>
698724 newContainer . setAttribute ( attr , attrs [ attr ] ) ,
699725 ) ;
700- retainedAttrs . forEach ( ( attr ) =>
701- newContainer . setAttribute ( attr , container . getAttribute ( attr ) ) ,
702- ) ;
726+ retainedAttrs . forEach ( ( attr ) => {
727+ const value = container . getAttribute ( attr ) ;
728+ if ( value !== null ) {
729+ newContainer . setAttribute ( attr , value ) ;
730+ }
731+ } ) ;
703732 newContainer . innerHTML = container . innerHTML ;
704733 container . replaceWith ( newContainer ) ;
705734 return newContainer ;
0 commit comments