/
githubmirror
/
material-ui
Обзор
Документация
Войти
/
githubmirror
/
material-ui
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
packages-internal/core-docs/src/CodeCopy/CodeCopy.tsx
185 строк
6 KB
code-infra-renovate[bot]
Bump prettier to v3.9.5 (#48808)
15 июл 2026, 13:03
Не верифицирован
15 июл 2026, 13:03
85eaf79
Код
Авторство
О чём код?
import * as React from 'react'; import clipboardCopy from 'clipboard-copy'; const CodeBlockContext = React.createContext<React.MutableRefObject<HTMLDivElement | null>>({ current: null, }); /** * How to use: spread the handlers to the .MuiCode-root * * The html structure should be: * <div className="MuiCode-root"> * <pre>...</pre> * <button className="MuiCode-copy">...</button> * </div> */ export function useCodeCopy(): React.HTMLAttributes<HTMLDivElement> { const rootNode = React.useContext(CodeBlockContext); return { onMouseEnter: (event) => { rootNode.current = event.currentTarget; }, onMouseLeave: (event) => { if (rootNode.current === event.currentTarget) { (rootNode.current.querySelector('.MuiCode-copy') as null | HTMLButtonElement)?.blur(); rootNode.current = null; } }, onFocus: (event) => { rootNode.current = event.currentTarget; }, onBlur: (event) => { if (rootNode.current === event.currentTarget) { rootNode.current = null; } }, }; } /** * Returns the copy button generated by the markdown renderer for a given event target, * or `null` for anything else. */ function getMarkdownCopyButton(target: EventTarget | null): HTMLButtonElement | null { if (!(target instanceof Element)) { return null; } const button = target.closest('.MuiCode-copy'); if (!button || !button.parentElement?.classList.contains('MuiCode-root')) { return null; } return button as HTMLButtonElement; } function InitCodeCopy() { const rootNode = React.useContext(CodeBlockContext); React.useEffect(() => { // Flag the OS once so the copy-shortcut hint can be resolved purely in CSS. if (window.navigator.platform.toUpperCase().includes('MAC')) { document.documentElement.dataset.mac = ''; } let copiedTimeout: ReturnType<typeof setTimeout> | undefined; // Delegate from `document` so copy survives React recreating the markdown nodes (#48629). async function handleClick(event: MouseEvent) { const trigger = getMarkdownCopyButton(event.target); if (!trigger) { return; } const pre = trigger.previousElementSibling; try { if (pre?.textContent) { await clipboardCopy(pre.textContent); trigger.dataset.copied = 'true'; clearTimeout(copiedTimeout); copiedTimeout = setTimeout(() => { delete trigger.dataset.copied; }, 2000); } // eslint-disable-next-line no-empty } catch (error) {} } // Track the hovered/focused code block so `CodeCopyProvider` can copy it on Ctrl/⌘+C. const trackActiveRoot = (event: Event) => { const root = event.target instanceof Element ? (event.target.closest('.MuiCode-root') as HTMLDivElement | null) : null; if (root) { rootNode.current = root; } }; const clearActiveRoot = (event: Event) => { const related = (event as MouseEvent | FocusEvent).relatedTarget; // Only clear when the pointer/focus actually left the active block. if (rootNode.current && (!(related instanceof Node) || !rootNode.current.contains(related))) { // Drop focus so the `:focus` copy-shortcut hint doesn't linger after the pointer leaves. (rootNode.current.querySelector('.MuiCode-copy') as HTMLButtonElement | null)?.blur(); rootNode.current = null; } }; document.addEventListener('click', handleClick); document.addEventListener('pointerover', trackActiveRoot); document.addEventListener('focusin', trackActiveRoot); document.addEventListener('pointerout', clearActiveRoot); document.addEventListener('focusout', clearActiveRoot); return () => { clearTimeout(copiedTimeout); document.removeEventListener('click', handleClick); document.removeEventListener('pointerover', trackActiveRoot); document.removeEventListener('focusin', trackActiveRoot); document.removeEventListener('pointerout', clearActiveRoot); document.removeEventListener('focusout', clearActiveRoot); }; }, [rootNode]); return null; } function hasNativeSelection(element: HTMLTextAreaElement) { if (window.getSelection()?.toString()) { return true; } // window.getSelection() returns an empty string in Firefox for selections inside a form element. // See: https://bugzilla.mozilla.org/show_bug.cgi?id=85686. // Instead, we can use element.selectionStart that is only defined on form elements. if (element && (element.selectionEnd || 0) - (element.selectionStart || 0) > 0) { return true; } return false; } interface CodeCopyProviderProps { children: React.ReactNode; } /** * Place <CodeCopyProvider> at the page level. It will check the keydown event and try to initiate copy click if rootNode exist. * Any code block inside the tree can set the rootNode when mouse enter to leverage keyboard copy. */ export function CodeCopyProvider({ children }: CodeCopyProviderProps) { const rootNode = React.useRef<HTMLDivElement>(null); React.useEffect(() => { document.addEventListener('keydown', (event) => { if (!rootNode.current) { return; } // Skip if user is highlighting a text. if (hasNativeSelection(event.target as HTMLTextAreaElement)) { return; } // Skip if it's not a copy keyboard event if (!( (event.ctrlKey || event.metaKey) && String.fromCharCode(event.keyCode) === 'C' && !event.shiftKey && !event.altKey )) { return; } const copyBtn = rootNode.current.querySelector('.MuiCode-copy') as HTMLButtonElement; const initialEventAction = copyBtn.getAttribute('data-ga-event-action'); // update the 'data-ga-event-action' on the button to track keyboard interaction copyBtn.dataset.gaEventAction = initialEventAction?.replace('click', 'keyboard') || 'copy-keyboard'; copyBtn.click(); // let the GA setup in GoogleAnalytics.js do the job copyBtn.dataset.gaEventAction = initialEventAction!; // reset the 'data-ga-event-action' back to initial }); }, []); return ( <CodeBlockContext.Provider value={rootNode}> <InitCodeCopy /> {children} </CodeBlockContext.Provider> ); }