diff --git a/packages/component/src/providers/CustomElements/customElements/CodeBlock.ts b/packages/component/src/providers/CustomElements/customElements/CodeBlock.ts index 6352482279..caf6df3bf7 100644 --- a/packages/component/src/providers/CustomElements/customElements/CodeBlock.ts +++ b/packages/component/src/providers/CustomElements/customElements/CodeBlock.ts @@ -82,6 +82,10 @@ class CodeBlock extends HTMLElement { highlightedCodeFragment.insertBefore(this.copyButtonElement, highlightedCodeFragment.firstChild); this.replaceChildren(highlightedCodeFragment); + + if (this.copyButtonElement) { + this.copyButtonElement.dataset.value = code; + } } highlightCode(...args: Parameters) { diff --git a/packages/component/src/providers/CustomElements/customElements/CodeBlockCopyButton.tsx b/packages/component/src/providers/CustomElements/customElements/CodeBlockCopyButton.tsx index 09e037f035..ebf67a89c9 100644 --- a/packages/component/src/providers/CustomElements/customElements/CodeBlockCopyButton.tsx +++ b/packages/component/src/providers/CustomElements/customElements/CodeBlockCopyButton.tsx @@ -1,8 +1,7 @@ import classNames from 'classnames'; -import React, { memo, useCallback, useState, type MouseEventHandler } from 'react'; +import React, { memo, useCallback, useState } from 'react'; import { useRefFrom } from 'use-ref-from'; import { useStateWithRef } from 'use-state-with-ref'; - import testIds from '../../../testIds'; import wrapAsCustomElement from './wrapAsCustomElement'; @@ -24,45 +23,37 @@ const CodeBlockCopyButton = memo( const [pressed, setPressed] = useState(false); const valueRef = useRefFrom(value); - const handleClick = useCallback>( - event => { - if (disabledRef.current) { - return; - } - - let obsoleted = false; + const handleClick = useCallback(() => { + if (disabledRef.current) { + return; + } - (async () => { - try { - const parentElement = (event.currentTarget as HTMLButtonElement)?.parentElement?.parentElement; + let obsoleted = false; - const { state } = await navigator.permissions.query({ name: 'clipboard-write' as any }); + (async () => { + try { + const { state } = await navigator.permissions.query({ name: 'clipboard-write' as any }); - const value = - (valueRef.current ?? (parentElement && 'code' in parentElement)) ? parentElement['code'] : undefined; + if (!obsoleted) { + if (state === 'granted') { + await navigator.clipboard?.write([ + new ClipboardItem({ 'text/plain': new Blob([valueRef.current], { type: 'text/plain' }) }) + ]); - if (!obsoleted) { - if (state === 'granted') { - await navigator.clipboard?.write([ - new ClipboardItem({ 'text/plain': new Blob([value], { type: 'text/plain' }) }) - ]); - - obsoleted || setPressed(true); - } else if (state === 'denied') { - setDisabled(true); - } + obsoleted || setPressed(true); + } else if (state === 'denied') { + setDisabled(true); } - } catch (error) { - console.warn('botframework-webchat: Failed to copy code block to clipboard.', error); } - })(); + } catch (error) { + console.warn('botframework-webchat: Failed to copy code block to clipboard.', error); + } + })(); - return () => { - obsoleted = true; - }; - }, - [disabledRef, setDisabled, setPressed, valueRef] - ); + return () => { + obsoleted = true; + }; + }, [disabledRef, setDisabled, setPressed, valueRef]); const handleAnimationEnd = useCallback(() => setPressed(false), [setPressed]); diff --git a/packages/component/src/providers/CustomElements/customElements/wrapAsCustomElement.ts b/packages/component/src/providers/CustomElements/customElements/wrapAsCustomElement.ts index 935aae0e5f..145f9293e9 100644 --- a/packages/component/src/providers/CustomElements/customElements/wrapAsCustomElement.ts +++ b/packages/component/src/providers/CustomElements/customElements/wrapAsCustomElement.ts @@ -55,9 +55,11 @@ export default function wrapAsCustomElement - Object.is(prevProps.get(key), this.#propMap.get(key)) - ); + const areEqual = + prevProps.size === this.#propMap.size && + Array.from(new Set(prevProps.keys()).union(new Set(this.#propMap))).every((key: string) => + Object.is(prevProps.get(key), this.#propMap.get(key)) + ); // For every attribute change, browser will call this function again. It is not batched. !areEqual &&