diff --git a/packages/block-editor/src/hooks/contrast-checker.js b/packages/block-editor/src/hooks/contrast-checker.js index 368e2e75264858..01ec394408fb98 100644 --- a/packages/block-editor/src/hooks/contrast-checker.js +++ b/packages/block-editor/src/hooks/contrast-checker.js @@ -13,40 +13,87 @@ function getComputedStyle( node ) { return node.ownerDocument.defaultView.getComputedStyle( node ); } +function getBlockColors( blockEl ) { + if ( ! blockEl ) { + return; + } + + const firstLinkElement = blockEl.querySelector( 'a' ); + const linkColor = + firstLinkElement && !! firstLinkElement.innerText + ? getComputedStyle( firstLinkElement ).color + : null; + + const textColor = getComputedStyle( blockEl ).color; + + let backgroundColorNode = blockEl; + let backgroundColor = + getComputedStyle( backgroundColorNode ).backgroundColor; + while ( + backgroundColor === 'rgba(0, 0, 0, 0)' && + backgroundColorNode.parentNode && + backgroundColorNode.parentNode.nodeType === + backgroundColorNode.parentNode.ELEMENT_NODE + ) { + backgroundColorNode = backgroundColorNode.parentNode; + backgroundColor = + getComputedStyle( backgroundColorNode ).backgroundColor; + } + + return { + textColor, + backgroundColor, + linkColor, + }; +} + +function createStyleObserver( node, callback ) { + // Watch for changes to style-related attributes. + const observer = new window.MutationObserver( ( mutations ) => { + const hasStyleChanges = mutations.some( + ( mutation ) => + mutation.attributeName === 'style' || + mutation.attributeName === 'class' + ); + + if ( hasStyleChanges ) { + callback(); + } + } ); + + observer.observe( node, { + attributeFilter: [ 'style', 'class' ], + } ); + + return observer; +} + export default function BlockColorContrastChecker( { clientId } ) { const [ detectedBackgroundColor, setDetectedBackgroundColor ] = useState(); const [ detectedColor, setDetectedColor ] = useState(); const [ detectedLinkColor, setDetectedLinkColor ] = useState(); const blockEl = useBlockElement( clientId ); - // There are so many things that can change the color of a block - // So we perform this check on every render. useEffect( () => { if ( ! blockEl ) { return; } - setDetectedColor( getComputedStyle( blockEl ).color ); - const firstLinkElement = blockEl.querySelector( 'a' ); - if ( firstLinkElement && !! firstLinkElement.innerText ) { - setDetectedLinkColor( getComputedStyle( firstLinkElement ).color ); + function updateContrastChecker() { + const colors = getBlockColors( blockEl ); + setDetectedColor( colors.textColor ); + setDetectedBackgroundColor( colors.backgroundColor ); + if ( colors.linkColor ) { + setDetectedLinkColor( colors.linkColor ); + } } - let backgroundColorNode = blockEl; - let backgroundColor = - getComputedStyle( backgroundColorNode ).backgroundColor; - while ( - backgroundColor === 'rgba(0, 0, 0, 0)' && - backgroundColorNode.parentNode && - backgroundColorNode.parentNode.nodeType === - backgroundColorNode.parentNode.ELEMENT_NODE - ) { - backgroundColorNode = backgroundColorNode.parentNode; - backgroundColor = - getComputedStyle( backgroundColorNode ).backgroundColor; - } + // Check colors on mount. + updateContrastChecker(); + + const observer = createStyleObserver( blockEl, updateContrastChecker ); - setDetectedBackgroundColor( backgroundColor ); + return () => observer.disconnect(); }, [ blockEl ] ); return (