11import type { ReactiveController , ReactiveControllerHost } from 'lit' ;
2-
32import { isElement } from '../util.js' ;
43
54/** @ignore */
6- export interface MutationControllerConfig < T > {
5+ export interface MutationControllerConfig < T extends Node = Node > {
76 /** The callback function to run when a mutation occurs. */
87 callback : MutationControllerCallback < T > ;
98 /** The underlying mutation observer configuration parameters. */
@@ -20,27 +19,42 @@ export interface MutationControllerConfig<T> {
2019 filter ?: MutationControllerFilter < T > ;
2120}
2221
23- type MutationControllerCallback < T > = (
22+ type MutationControllerCallback < T extends Node = Node > = (
2423 params : MutationControllerParams < T >
2524) => unknown ;
2625
2726/**
2827 * Filter configuration to return elements that either match
2928 * an array of selector strings or a predicate function.
3029 */
31- type MutationControllerFilter < T > = string [ ] | ( ( node : T ) => boolean ) ;
32- type MutationDOMChange < T > = { target : Element ; node : T } ;
30+ type MutationControllerFilter < T extends Node = Node > =
31+ | string [ ]
32+ | ( ( node : T ) => boolean ) ;
33+
34+ type MutationDOMChange < T extends Node = Node > = {
35+ /** The parent of the added/removed element. */
36+ target : Element ;
37+ /** The added/removed element. */
38+ node : T ;
39+ } ;
40+
41+ type MutationAttributeChange < T extends Node = Node > = {
42+ /** The host element of the changed attribute. */
43+ node : T ;
44+ /** The changed attribute name. */
45+ attributeName : string | null ;
46+ } ;
3347
34- type MutationChange < T > = {
48+ type MutationChange < T extends Node = Node > = {
3549 /** Elements that have attribute(s) changes. */
36- attributes : T [ ] ;
50+ attributes : MutationAttributeChange < T > [ ] ;
3751 /** Elements that have been added. */
3852 added : MutationDOMChange < T > [ ] ;
3953 /** Elements that have been removed. */
4054 removed : MutationDOMChange < T > [ ] ;
4155} ;
4256
43- export type MutationControllerParams < T > = {
57+ export type MutationControllerParams < T extends Node = Node > = {
4458 /** The original mutation records from the underlying observer. */
4559 records : MutationRecord [ ] ;
4660 /** The aggregated changes. */
@@ -49,25 +63,30 @@ export type MutationControllerParams<T> = {
4963 observer : MutationController < T > ;
5064} ;
5165
52- function mutationFilter < T > ( nodes : T [ ] , filter ?: MutationControllerFilter < T > ) {
53- if ( ! filter ) {
66+ function applyNodeFilter < T extends Node = Node > (
67+ nodes : T [ ] ,
68+ predicate ?: MutationControllerFilter < T >
69+ ) : T [ ] {
70+ if ( ! predicate ) {
5471 return nodes ;
5572 }
5673
57- return Array . isArray ( filter )
58- ? nodes . filter ( ( node ) =>
59- filter . some ( ( selector ) => isElement ( node ) && node . matches ( selector ) )
74+ return Array . isArray ( predicate )
75+ ? nodes . filter (
76+ ( node ) =>
77+ isElement ( node ) &&
78+ predicate . some ( ( selector ) => node . matches ( selector ) )
6079 )
61- : nodes . filter ( ( node ) => filter ( node ) ) ;
80+ : nodes . filter ( predicate ) ;
6281}
6382
64- class MutationController < T > implements ReactiveController {
65- private _host : ReactiveControllerHost & Element ;
66- private _observer : MutationObserver ;
67- private _target : Element ;
68- private _config : MutationObserverInit ;
69- private _callback : MutationControllerCallback < T > ;
70- private _filter ?: MutationControllerFilter < T > ;
83+ class MutationController < T extends Node = Node > implements ReactiveController {
84+ private readonly _host : ReactiveControllerHost & Element ;
85+ private readonly _observer : MutationObserver ;
86+ private readonly _target : Element ;
87+ private readonly _config : MutationObserverInit ;
88+ private readonly _callback : MutationControllerCallback < T > ;
89+ private readonly _filter ?: MutationControllerFilter < T > ;
7190
7291 constructor (
7392 host : ReactiveControllerHost & Element ,
@@ -77,7 +96,7 @@ class MutationController<T> implements ReactiveController {
7796 this . _callback = options . callback ;
7897 this . _config = options . config ;
7998 this . _target = options . target ?? this . _host ;
80- this . _filter = options . filter ?? [ ] ;
99+ this . _filter = options . filter ;
81100
82101 this . _observer = new MutationObserver ( ( records ) => {
83102 this . disconnect ( ) ;
@@ -88,36 +107,47 @@ class MutationController<T> implements ReactiveController {
88107 host . addController ( this ) ;
89108 }
90109
91- public hostConnected ( ) {
110+ /** @internal */
111+ public hostConnected ( ) : void {
92112 this . observe ( ) ;
93113 }
94114
95- public hostDisconnected ( ) {
115+ /** @internal */
116+ public hostDisconnected ( ) : void {
96117 this . disconnect ( ) ;
97118 }
98119
99120 private _process ( records : MutationRecord [ ] ) : MutationControllerParams < T > {
121+ const predicate = this . _filter ;
100122 const changes : MutationChange < T > = {
101123 attributes : [ ] ,
102124 added : [ ] ,
103125 removed : [ ] ,
104126 } ;
105- const filter = this . _filter ;
106127
107128 for ( const record of records ) {
108- if ( record . type === 'attributes' ) {
129+ const { type, target, attributeName, addedNodes, removedNodes } = record ;
130+
131+ if ( type === 'attributes' ) {
109132 changes . attributes . push (
110- ...mutationFilter ( [ record . target as T ] , filter )
133+ ...applyNodeFilter ( [ target as T ] , predicate ) . map ( ( node ) => ( {
134+ node,
135+ attributeName,
136+ } ) )
111137 ) ;
112- } else if ( record . type === 'childList' ) {
138+ } else if ( type === 'childList' ) {
113139 changes . added . push (
114- ...mutationFilter ( Array . from ( record . addedNodes ) as T [ ] , filter ) . map (
115- ( node ) => ( { target : record . target as Element , node } )
116- )
140+ ...applyNodeFilter ( [ ...addedNodes ] as T [ ] , predicate ) . map ( ( node ) => ( {
141+ target : target as Element ,
142+ node,
143+ } ) )
117144 ) ;
118145 changes . removed . push (
119- ...mutationFilter ( Array . from ( record . removedNodes ) as T [ ] , filter ) . map (
120- ( node ) => ( { target : record . target as Element , node } )
146+ ...applyNodeFilter ( [ ...removedNodes ] as T [ ] , predicate ) . map (
147+ ( node ) => ( {
148+ target : target as Element ,
149+ node,
150+ } )
121151 )
122152 ) ;
123153 }
@@ -130,12 +160,12 @@ class MutationController<T> implements ReactiveController {
130160 * Begin receiving notifications of changes to the DOM based
131161 * on the configured {@link MutationControllerConfig.target|target} and observer {@link MutationControllerConfig.config|options}.
132162 */
133- public observe ( ) {
163+ public observe ( ) : void {
134164 this . _observer . observe ( this . _target , this . _config ) ;
135165 }
136166
137167 /** Stop watching for mutations. */
138- public disconnect ( ) {
168+ public disconnect ( ) : void {
139169 this . _observer . disconnect ( ) ;
140170 }
141171}
@@ -149,9 +179,9 @@ class MutationController<T> implements ReactiveController {
149179 * The mutation observer is disconnected before invoking the passed in callback and re-attached
150180 * after that in order to not loop itself in endless stream of changes.
151181 */
152- export function createMutationController < T > (
182+ export function createMutationController < T extends Node = Node > (
153183 host : ReactiveControllerHost & Element ,
154184 config : MutationControllerConfig < T >
155- ) {
185+ ) : MutationController < T > {
156186 return new MutationController ( host , config ) ;
157187}
0 commit comments