From 3584758f92c902613b65db7a8de57a0154a82819 Mon Sep 17 00:00:00 2001 From: Michail Yasonik Date: Fri, 29 Jan 2021 20:20:03 -0500 Subject: [PATCH 1/3] Make tabbable codeblocks if content overflows Co-authored-by: Chandler Prall --- src/components/code/_code_block.tsx | 82 ++++++++++++++++------------- 1 file changed, 45 insertions(+), 37 deletions(-) diff --git a/src/components/code/_code_block.tsx b/src/components/code/_code_block.tsx index dd9d1a9ddb4..c6c38940f22 100644 --- a/src/components/code/_code_block.tsx +++ b/src/components/code/_code_block.tsx @@ -17,30 +17,27 @@ * under the License. */ +import classNames from 'classnames'; +import hljs from 'highlight.js'; import React, { + CSSProperties, FunctionComponent, KeyboardEvent, - CSSProperties, useEffect, useRef, useState, } from 'react'; import { createPortal } from 'react-dom'; -import classNames from 'classnames'; -import hljs from 'highlight.js'; - -import { EuiCopy } from '../copy'; - +import { keys, useCombinedRefs } from '../../services'; import { EuiButtonIcon } from '../button'; - -import { EuiOverlayMask } from '../overlay_mask'; - +import { keysOf } from '../common'; +import { EuiCopy } from '../copy'; import { EuiFocusTrap } from '../focus_trap'; - -import { keys } from '../../services'; import { EuiI18n } from '../i18n'; -import { EuiInnerText } from '../inner_text'; -import { keysOf } from '../common'; +import { useInnerText } from '../inner_text'; +import { useMutationObserver } from '../observer/mutation_observer'; +import { useResizeObserver } from '../observer/resize_observer'; +import { EuiOverlayMask } from '../overlay_mask'; import { FontSize, PaddingSize } from './code_block'; const fontSizeToClassNameMap = { @@ -110,10 +107,31 @@ export const EuiCodeBlockImpl: FunctionComponent = ({ const [isPortalTargetReady, setIsPortalTargetReady] = useState(false); const codeTarget = useRef(null); const code = useRef(null); + const [wrapperRef, setWrapperRef] = useState(null); + const [innerTextRef, innerText] = useInnerText(''); + const combinedRef = useCombinedRefs([innerTextRef, setWrapperRef]); + const { width, height } = useResizeObserver(wrapperRef); const [codeFullScreen, setCodeFullScreen] = useState( null ); + const doesOverflow = () => { + if (!wrapperRef) return; + + const { clientWidth, clientHeight, scrollWidth, scrollHeight } = wrapperRef; + const doesOverflow = + scrollHeight > clientHeight || scrollWidth > clientWidth; + + wrapperRef.setAttribute('tabindex', doesOverflow ? '0' : '-1'); + }; + + useMutationObserver(wrapperRef, doesOverflow, { + subtree: true, + childList: true, + }); + + useEffect(doesOverflow, [width, height, wrapperRef]); + useEffect(() => { codeTarget.current = document.createElement('div'); setIsPortalTargetReady(true); @@ -292,11 +310,10 @@ export const EuiCodeBlockImpl: FunctionComponent = ({
-
+              
                 
               
@@ -311,31 +328,22 @@ export const EuiCodeBlockImpl: FunctionComponent = ({ return fullScreenDisplay; }; + const codeBlockControls = getCodeBlockControls(innerText); return isPortalTargetReady ? ( <> {createPortal(children, codeTarget.current!)} - - {(innerTextRef, innerText) => { - const codeBlockControls = getCodeBlockControls(innerText); - return ( -
-
-                {codeSnippet}
-              
- - {/* - If the below fullScreen code renders, it actually attaches to the body because of - EuiOverlayMask's React portal usage. - */} - {codeBlockControls} - {getFullScreenDisplay(codeBlockControls)} -
- ); - }} -
+
+
+          {codeSnippet}
+        
+ + {/* + If the below fullScreen code renders, it actually attaches to the body because of + EuiOverlayMask's React portal usage. + */} + {codeBlockControls} + {getFullScreenDisplay(codeBlockControls)} +
) : null; }; From f8ce770aee5f8cdbc6347ea5339a4263836e2b1e Mon Sep 17 00:00:00 2001 From: Michail Yasonik Date: Mon, 1 Feb 2021 15:16:45 -0500 Subject: [PATCH 2/3] fixing TS error and adding changelog --- CHANGELOG.md | 1 + .../code/__snapshots__/_code_block.test.tsx.snap | 4 ++++ .../code/__snapshots__/code_block.test.tsx.snap | 16 ++++++++++++++-- src/components/code/_code_block.tsx | 5 ++++- 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef409deed98..9e420608b54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Fixed `id` usage throughout `EuiTreeView` to respect custom ids and stop conflicts in generated ids ([#4435](https://github.com/elastic/eui/pull/4435)) - Fixed `EuiTabs` `role` if no tabs are passed in ([#4435](https://github.com/elastic/eui/pull/4435)) +- Fixed `EuiCodeBlock` focus-state if content overflows ([#4463]https://github.com/elastic/eui/pull/4463) ## [`31.4.0`](https://github.com/elastic/eui/tree/v31.4.0) diff --git a/src/components/code/__snapshots__/_code_block.test.tsx.snap b/src/components/code/__snapshots__/_code_block.test.tsx.snap index e639fafbf7f..d2369076574 100644 --- a/src/components/code/__snapshots__/_code_block.test.tsx.snap +++ b/src/components/code/__snapshots__/_code_block.test.tsx.snap @@ -6,6 +6,7 @@ exports[`EuiCodeBlockImpl block highlights javascript code, adding "js" class 1` >
     
   
     
   
     
   
     
   
-
+    
       
         constvalue =
         'State 1'
@@ -16,7 +16,7 @@ exports[`EuiCodeBlock dynamic content updates DOM when input changes 1`] = `
 exports[`EuiCodeBlock dynamic content updates DOM when input changes 2`] = `
 "
-
+    
       
         constvalue =
         'State 2'
@@ -32,6 +32,7 @@ exports[`EuiCodeBlock props fontSize l is rendered 1`] = `
 >
   
     
   
     
   
     
   
     
   
     
     
   
     
   
     
   
     
   
     
   
     
   
      = ({
   const code = useRef(null);
   const [wrapperRef, setWrapperRef] = useState(null);
   const [innerTextRef, innerText] = useInnerText('');
-  const combinedRef = useCombinedRefs([innerTextRef, setWrapperRef]);
+  const combinedRef = useCombinedRefs([
+    innerTextRef,
+    setWrapperRef,
+  ]);
   const { width, height } = useResizeObserver(wrapperRef);
   const [codeFullScreen, setCodeFullScreen] = useState(
     null

From d37576be354720056d048b953931e8b7db7e9e63 Mon Sep 17 00:00:00 2001
From: Michail Yasonik 
Date: Mon, 8 Feb 2021 17:47:14 -0500
Subject: [PATCH 3/3] use state instead of DOM APIs

---
 src/components/code/_code_block.tsx | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/src/components/code/_code_block.tsx b/src/components/code/_code_block.tsx
index 65ca911388a..606911bf7c5 100644
--- a/src/components/code/_code_block.tsx
+++ b/src/components/code/_code_block.tsx
@@ -109,6 +109,7 @@ export const EuiCodeBlockImpl: FunctionComponent = ({
   const code = useRef(null);
   const [wrapperRef, setWrapperRef] = useState(null);
   const [innerTextRef, innerText] = useInnerText('');
+  const [tabIndex, setTabIndex] = useState<-1 | 0>(-1);
   const combinedRef = useCombinedRefs([
     innerTextRef,
     setWrapperRef,
@@ -125,7 +126,7 @@ export const EuiCodeBlockImpl: FunctionComponent = ({
     const doesOverflow =
       scrollHeight > clientHeight || scrollWidth > clientWidth;
 
-    wrapperRef.setAttribute('tabindex', doesOverflow ? '0' : '-1');
+    setTabIndex(doesOverflow ? 0 : -1);
   };
 
   useMutationObserver(wrapperRef, doesOverflow, {
@@ -336,7 +337,11 @@ export const EuiCodeBlockImpl: FunctionComponent = ({
     <>
       {createPortal(children, codeTarget.current!)}
       
-
+        
           {codeSnippet}