@@ -631,51 +631,40 @@ const Tooltip = ({
631
631
] )
632
632
633
633
useEffect ( ( ) => {
634
+ /**
635
+ * TODO(V6): break down observer callback for clarity
636
+ * - `handleAddedAnchors()`
637
+ * - `handleRemovedAnchors()`
638
+ */
634
639
let selector = imperativeOptions ?. anchorSelect ?? anchorSelect ?? ''
635
640
if ( ! selector && id ) {
636
641
selector = `[data-tooltip-id='${ id } ']`
637
642
}
638
643
const documentObserverCallback : MutationCallback = ( mutationList ) => {
639
- const newAnchors : HTMLElement [ ] = [ ]
640
- const removedAnchors : HTMLElement [ ] = [ ]
644
+ const addedAnchors = new Set < HTMLElement > ( )
645
+ const removedAnchors = new Set < HTMLElement > ( )
641
646
mutationList . forEach ( ( mutation ) => {
642
647
if ( mutation . type === 'attributes' && mutation . attributeName === 'data-tooltip-id' ) {
643
- const newId = ( mutation . target as HTMLElement ) . getAttribute ( 'data-tooltip-id' )
648
+ const target = mutation . target as HTMLElement
649
+ const newId = target . getAttribute ( 'data-tooltip-id' )
644
650
if ( newId === id ) {
645
- newAnchors . push ( mutation . target as HTMLElement )
651
+ addedAnchors . add ( target )
646
652
} else if ( mutation . oldValue === id ) {
647
653
// data-tooltip-id has now been changed, so we need to remove this anchor
648
- removedAnchors . push ( mutation . target as HTMLElement )
654
+ removedAnchors . add ( target )
649
655
}
650
656
}
651
657
if ( mutation . type !== 'childList' ) {
652
658
return
653
659
}
660
+ const removedNodes = [ ...mutation . removedNodes ] . filter ( ( node ) => node . nodeType === 1 )
654
661
if ( activeAnchor ) {
655
- const elements = [ ...mutation . removedNodes ] . filter ( ( node ) => node . nodeType === 1 )
656
- if ( selector ) {
657
- try {
658
- removedAnchors . push (
659
- // the element itself is an anchor
660
- ...( elements . filter ( ( element ) =>
661
- ( element as HTMLElement ) . matches ( selector ) ,
662
- ) as HTMLElement [ ] ) ,
663
- )
664
- removedAnchors . push (
665
- // the element has children which are anchors
666
- ...elements . flatMap (
667
- ( element ) =>
668
- [ ...( element as HTMLElement ) . querySelectorAll ( selector ) ] as HTMLElement [ ] ,
669
- ) ,
670
- )
671
- } catch {
672
- /**
673
- * invalid CSS selector.
674
- * already warned on tooltip controller
675
- */
676
- }
677
- }
678
- elements . some ( ( node ) => {
662
+ removedNodes . some ( ( node ) => {
663
+ /**
664
+ * TODO(V6)
665
+ * - isn't `!activeAnchor.isConnected` better?
666
+ * - maybe move to `handleDisconnectedAnchor()`
667
+ */
679
668
if ( node ?. contains ?.( activeAnchor ) ) {
680
669
setRendered ( false )
681
670
handleShow ( false )
@@ -695,31 +684,63 @@ const Tooltip = ({
695
684
return
696
685
}
697
686
try {
698
- const elements = [ ...mutation . addedNodes ] . filter ( ( node ) => node . nodeType === 1 )
699
- newAnchors . push (
700
- // the element itself is an anchor
701
- ...( elements . filter ( ( element ) =>
702
- ( element as HTMLElement ) . matches ( selector ) ,
703
- ) as HTMLElement [ ] ) ,
704
- )
705
- newAnchors . push (
706
- // the element has children which are anchors
707
- ...elements . flatMap (
708
- ( element ) =>
709
- [ ...( element as HTMLElement ) . querySelectorAll ( selector ) ] as HTMLElement [ ] ,
710
- ) ,
711
- )
687
+ removedNodes . forEach ( ( node ) => {
688
+ const element = node as HTMLElement
689
+ if ( element . matches ( selector ) ) {
690
+ // the element itself is an anchor
691
+ removedAnchors . add ( element )
692
+ } else {
693
+ /**
694
+ * TODO(V6): do we care if an element which is an anchor,
695
+ * has children which are also anchors?
696
+ * (i.e. should we remove `else` and always do this)
697
+ */
698
+ // the element has children which are anchors
699
+ element
700
+ . querySelectorAll ( selector )
701
+ . forEach ( ( innerNode ) => removedAnchors . add ( innerNode as HTMLElement ) )
702
+ }
703
+ } )
712
704
} catch {
713
- /**
714
- * invalid CSS selector.
715
- * already warned on tooltip controller
716
- */
705
+ /* c8 ignore start */
706
+ if ( ! process . env . NODE_ENV || process . env . NODE_ENV !== 'production' ) {
707
+ // eslint-disable-next-line no-console
708
+ console . warn ( `[react-tooltip] "${ selector } " is not a valid CSS selector` )
709
+ }
710
+ /* c8 ignore end */
711
+ }
712
+ try {
713
+ const addedNodes = [ ...mutation . addedNodes ] . filter ( ( node ) => node . nodeType === 1 )
714
+ addedNodes . forEach ( ( node ) => {
715
+ const element = node as HTMLElement
716
+ if ( element . matches ( selector ) ) {
717
+ // the element itself is an anchor
718
+ addedAnchors . add ( element )
719
+ } else {
720
+ /**
721
+ * TODO(V6): do we care if an element which is an anchor,
722
+ * has children which are also anchors?
723
+ * (i.e. should we remove `else` and always do this)
724
+ */
725
+ // the element has children which are anchors
726
+ element
727
+ . querySelectorAll ( selector )
728
+ . forEach ( ( innerNode ) => addedAnchors . add ( innerNode as HTMLElement ) )
729
+ }
730
+ } )
731
+ } catch {
732
+ /* c8 ignore start */
733
+ if ( ! process . env . NODE_ENV || process . env . NODE_ENV !== 'production' ) {
734
+ // eslint-disable-next-line no-console
735
+ console . warn ( `[react-tooltip] "${ selector } " is not a valid CSS selector` )
736
+ }
737
+ /* c8 ignore end */
717
738
}
718
739
} )
719
- if ( newAnchors . length || removedAnchors . length ) {
740
+ if ( addedAnchors . size || removedAnchors . size ) {
720
741
setAnchorElements ( ( anchors ) => [
721
- ...anchors . filter ( ( anchor ) => ! removedAnchors . includes ( anchor ) ) ,
722
- ...newAnchors ,
742
+ ...anchors . filter ( ( anchor ) => ! removedAnchors . has ( anchor ) ) ,
743
+ ...addedAnchors ,
723
744
] )
724
745
}
725
746
}
0 commit comments