This is an automated email from the ASF dual-hosted git repository.

rusackas pushed a commit to branch chore/consolidate-copy-to-clipboard
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 806dbb4aa5d2390edb09cc2c0a410ed0d0b16abd
Author: Evan Rusackas <[email protected]>
AuthorDate: Fri Feb 6 14:26:35 2026 -0800

    chore: consolidate CopyToClipboard and clipboard utilities to 
@superset-ui/core
    
    This change:
    - Creates a generic CopyToClipboard component in @superset-ui/core
      - Uses onSuccess/onError callbacks (not tied to any toast system)
      - Includes tooltip, text display, and async getText support
    - Creates clipboard utilities in @superset-ui/core
      - copyTextToClipboard with Safari/Chrome compatibility
      - isSafari browser detection utility
    - Creates ErrorBoundary in @superset-ui/core (removes react-error-boundary 
dep)
    - Updates src/components/CopyToClipboard to be a thin wrapper that:
      - Integrates with Superset's toast notification system
      - Delegates to the core component for actual functionality
    - Updates src/utils/copy.ts to re-export from core for backward 
compatibility
    
    Developers can now:
    - Use the generic CopyToClipboard from @superset-ui/core/components directly
    - Use the toast-integrated version from src/components for 
Superset-specific use
    
    Co-Authored-By: Claude Opus 4.5 <[email protected]>
---
 superset-frontend/package-lock.json                |  10 --
 .../packages/superset-ui-core/package.json         |   1 -
 .../src/chart/components/ErrorBoundary.tsx         |  83 +++++++++++
 .../src/chart/components/SuperChart.tsx            |   6 +-
 .../packages/superset-ui-core/src/chart/index.ts   |   5 +
 .../src/components/CopyToClipboard/index.tsx       |  55 +++++---
 .../superset-ui-core/src/components/index.ts       |   1 +
 .../superset-ui-core/src/utils/clipboard.ts}       |  39 ++++--
 .../packages/superset-ui-core/src/utils/index.ts   |   1 +
 .../test/chart/components/SuperChart.test.tsx      |   2 +-
 .../src/components/CopyToClipboard/index.tsx       | 156 +++++++--------------
 .../src/components/CopyToClipboard/types.ts        |  32 -----
 superset-frontend/src/utils/copy.ts                |  84 ++---------
 13 files changed, 219 insertions(+), 256 deletions(-)

diff --git a/superset-frontend/package-lock.json 
b/superset-frontend/package-lock.json
index 1a97f933ab2..d8d810d8c9b 100644
--- a/superset-frontend/package-lock.json
+++ b/superset-frontend/package-lock.json
@@ -49999,15 +49999,6 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/react-error-boundary": {
-      "version": "6.1.0",
-      "resolved": 
"https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-6.1.0.tgz";,
-      "integrity": 
"sha512-02k9WQ/mUhdbXir0tC1NiMesGzRPaCsJEWU/4bcFrbY1YMZOtHShtZP6zw0SJrBWA/31H0KT9/FgdL8+sPKgHA==",
-      "license": "MIT",
-      "peerDependencies": {
-        "react": "^18.0.0 || ^19.0.0"
-      }
-    },
     "node_modules/react-google-recaptcha": {
       "version": "3.1.0",
       "resolved": 
"https://registry.npmjs.org/react-google-recaptcha/-/react-google-recaptcha-3.1.0.tgz";,
@@ -63712,7 +63703,6 @@
         "re-resizable": "^6.11.2",
         "react-ace": "^14.0.1",
         "react-draggable": "^4.5.0",
-        "react-error-boundary": "^6.1.0",
         "react-js-cron": "^5.2.0",
         "react-markdown": "^8.0.7",
         "react-resize-detector": "^7.1.2",
diff --git a/superset-frontend/packages/superset-ui-core/package.json 
b/superset-frontend/packages/superset-ui-core/package.json
index c8bc4627b1d..f02d4cbc2e9 100644
--- a/superset-frontend/packages/superset-ui-core/package.json
+++ b/superset-frontend/packages/superset-ui-core/package.json
@@ -55,7 +55,6 @@
     "react-resize-detector": "^7.1.2",
     "react-syntax-highlighter": "^16.1.0",
     "react-ultimate-pagination": "^1.3.2",
-    "react-error-boundary": "^6.1.0",
     "react-markdown": "^8.0.7",
     "regenerator-runtime": "^0.14.1",
     "rehype-raw": "^7.0.0",
diff --git 
a/superset-frontend/packages/superset-ui-core/src/chart/components/ErrorBoundary.tsx
 
b/superset-frontend/packages/superset-ui-core/src/chart/components/ErrorBoundary.tsx
new file mode 100644
index 00000000000..39c6031a76d
--- /dev/null
+++ 
b/superset-frontend/packages/superset-ui-core/src/chart/components/ErrorBoundary.tsx
@@ -0,0 +1,83 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { Component, ComponentType, ErrorInfo, ReactNode } from 'react';
+
+export interface FallbackProps {
+  error: Error;
+  resetErrorBoundary: () => void;
+}
+
+export interface ErrorBoundaryProps {
+  children: ReactNode;
+  FallbackComponent?: ComponentType<FallbackProps>;
+  fallbackRender?: (props: FallbackProps) => ReactNode;
+  onError?: (error: Error, info: ErrorInfo) => void;
+}
+
+interface ErrorBoundaryState {
+  error: Error | null;
+}
+
+export class ErrorBoundary extends Component<
+  ErrorBoundaryProps,
+  ErrorBoundaryState
+> {
+  constructor(props: ErrorBoundaryProps) {
+    super(props);
+    this.state = { error: null };
+  }
+
+  static getDerivedStateFromError(error: Error): ErrorBoundaryState {
+    return { error };
+  }
+
+  componentDidCatch(error: Error, info: ErrorInfo): void {
+    this.props.onError?.(error, info);
+  }
+
+  resetErrorBoundary = (): void => {
+    this.setState({ error: null });
+  };
+
+  render() {
+    const { error } = this.state;
+    const { children, FallbackComponent, fallbackRender } = this.props;
+
+    if (error) {
+      const fallbackProps: FallbackProps = {
+        error,
+        resetErrorBoundary: this.resetErrorBoundary,
+      };
+
+      if (fallbackRender) {
+        return fallbackRender(fallbackProps);
+      }
+
+      if (FallbackComponent) {
+        return <FallbackComponent {...fallbackProps} />;
+      }
+
+      return null;
+    }
+
+    return children;
+  }
+}
+
+export default ErrorBoundary;
diff --git 
a/superset-frontend/packages/superset-ui-core/src/chart/components/SuperChart.tsx
 
b/superset-frontend/packages/superset-ui-core/src/chart/components/SuperChart.tsx
index 553a98f568a..f4b40649c66 100644
--- 
a/superset-frontend/packages/superset-ui-core/src/chart/components/SuperChart.tsx
+++ 
b/superset-frontend/packages/superset-ui-core/src/chart/components/SuperChart.tsx
@@ -27,9 +27,9 @@ import {
 
 import {
   ErrorBoundary,
-  ErrorBoundaryProps,
-  FallbackProps,
-} from 'react-error-boundary';
+  type ErrorBoundaryProps,
+  type FallbackProps,
+} from './ErrorBoundary';
 import { ParentSize } from '@visx/responsive';
 import { createSelector } from 'reselect';
 import { withTheme } from '@emotion/react';
diff --git a/superset-frontend/packages/superset-ui-core/src/chart/index.ts 
b/superset-frontend/packages/superset-ui-core/src/chart/index.ts
index 69081f9740d..edfd1e57f39 100644
--- a/superset-frontend/packages/superset-ui-core/src/chart/index.ts
+++ b/superset-frontend/packages/superset-ui-core/src/chart/index.ts
@@ -26,6 +26,11 @@ export { ChartProps };
 export type { ChartPropsConfig };
 
 export { default as createLoadableRenderer } from 
'./components/createLoadableRenderer';
+export {
+  ErrorBoundary,
+  type ErrorBoundaryProps,
+  type FallbackProps,
+} from './components/ErrorBoundary';
 export { default as reactify } from './components/reactify';
 export { default as SuperChart } from './components/SuperChart';
 
diff --git a/superset-frontend/src/components/CopyToClipboard/index.tsx 
b/superset-frontend/packages/superset-ui-core/src/components/CopyToClipboard/index.tsx
similarity index 69%
copy from superset-frontend/src/components/CopyToClipboard/index.tsx
copy to 
superset-frontend/packages/superset-ui-core/src/components/CopyToClipboard/index.tsx
index 976f0059c30..490bc747193 100644
--- a/superset-frontend/src/components/CopyToClipboard/index.tsx
+++ 
b/superset-frontend/packages/superset-ui-core/src/components/CopyToClipboard/index.tsx
@@ -16,13 +16,39 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { Component, cloneElement, ReactElement } from 'react';
+import {
+  Component,
+  cloneElement,
+  type ReactElement,
+  type ReactNode,
+} from 'react';
+import { css, type SupersetTheme } from '@apache-superset/core/ui';
 import { t } from '@apache-superset/core';
-import { css, SupersetTheme } from '@apache-superset/core/ui';
-import copyTextToClipboard from 'src/utils/copy';
-import { Tooltip } from '@superset-ui/core/components';
-import withToasts from '../MessageToasts/withToasts';
-import type { CopyToClipboardProps } from './types';
+import { copyTextToClipboard } from '../../utils/clipboard';
+import { Tooltip } from '../Tooltip';
+
+export interface CopyToClipboardProps {
+  /** The clickable element that triggers the copy action */
+  copyNode?: ReactNode;
+  /** Async function to get text to copy (alternative to `text` prop) */
+  getText?: (callback: (data: string) => void) => void;
+  /** Called after copy attempt completes (success or failure) */
+  onCopyEnd?: () => void;
+  /** Called when copy succeeds */
+  onSuccess?: () => void;
+  /** Called when copy fails */
+  onError?: (error: Error) => void;
+  /** Whether to show the text alongside the copy button */
+  shouldShowText?: boolean;
+  /** Static text to copy (alternative to `getText` prop) */
+  text?: string;
+  /** Whether to wrap in a span with text display */
+  wrapped?: boolean;
+  /** Tooltip text shown on hover */
+  tooltipText?: string;
+  /** Hide the tooltip entirely */
+  hideTooltip?: boolean;
+}
 
 const defaultProps: Partial<CopyToClipboardProps> = {
   copyNode: <span>{t('Copy')}</span>,
@@ -33,7 +59,7 @@ const defaultProps: Partial<CopyToClipboardProps> = {
   hideTooltip: false,
 };
 
-class CopyToClip extends Component<CopyToClipboardProps> {
+export class CopyToClipboard extends Component<CopyToClipboardProps> {
   static defaultProps = defaultProps;
 
   constructor(props: CopyToClipboardProps) {
@@ -62,17 +88,13 @@ class CopyToClip extends Component<CopyToClipboardProps> {
   copyToClipboard(textToCopy: Promise<string>) {
     copyTextToClipboard(() => textToCopy)
       .then(() => {
-        this.props.addSuccessToast(t('Copied to clipboard!'));
+        this.props.onSuccess?.();
       })
-      .catch(() => {
-        this.props.addDangerToast(
-          t(
-            'Sorry, your browser does not support copying. Use Ctrl / Cmd + 
C!',
-          ),
-        );
+      .catch((error: Error) => {
+        this.props.onError?.(error);
       })
       .finally(() => {
-        if (this.props.onCopyEnd) this.props.onCopyEnd();
+        this.props.onCopyEnd?.();
       });
   }
 
@@ -128,5 +150,4 @@ class CopyToClip extends Component<CopyToClipboardProps> {
   }
 }
 
-export const CopyToClipboard = withToasts(CopyToClip);
-export type { CopyToClipboardProps };
+export default CopyToClipboard;
diff --git 
a/superset-frontend/packages/superset-ui-core/src/components/index.ts 
b/superset-frontend/packages/superset-ui-core/src/components/index.ts
index 30bad84f4d8..d01ec8e4478 100644
--- a/superset-frontend/packages/superset-ui-core/src/components/index.ts
+++ b/superset-frontend/packages/superset-ui-core/src/components/index.ts
@@ -210,3 +210,4 @@ export {
   type CodeEditorTheme,
 } from './CodeEditor';
 export { ActionButton, type ActionProps } from './ActionButton';
+export { CopyToClipboard, type CopyToClipboardProps } from './CopyToClipboard';
diff --git a/superset-frontend/src/utils/copy.ts 
b/superset-frontend/packages/superset-ui-core/src/utils/clipboard.ts
similarity index 73%
copy from superset-frontend/src/utils/copy.ts
copy to superset-frontend/packages/superset-ui-core/src/utils/clipboard.ts
index 0980f2ab170..6de402aeb1d 100644
--- a/superset-frontend/src/utils/copy.ts
+++ b/superset-frontend/packages/superset-ui-core/src/utils/clipboard.ts
@@ -17,10 +17,24 @@
  * under the License.
  */
 
-import { isSafari } from './common';
+/**
+ * Detects if the browser is Safari (WebKit without Chrome).
+ */
+export const isSafari = (): boolean => {
+  const { userAgent } = navigator;
+  return Boolean(userAgent && 
/^((?!chrome|android).)*safari/i.test(userAgent));
+};
 
-// Use the new Clipboard API if the browser supports it
-const copyTextWithClipboardApi = async (getText: () => Promise<string>) => {
+/**
+ * Copy text to clipboard using the modern Clipboard API with fallback
+ * for older browsers.
+ *
+ * @param getText - Function that returns a Promise resolving to the text to 
copy
+ * @returns Promise that resolves when copy succeeds, rejects on failure
+ */
+const copyTextWithClipboardApi = async (
+  getText: () => Promise<string>,
+): Promise<void> => {
   // Safari (WebKit) does not support delayed generation of clipboard.
   // This means that writing to the clipboard, from the moment the user
   // interacts with the app, must be instantaneous.
@@ -49,7 +63,18 @@ const copyTextWithClipboardApi = async (getText: () => 
Promise<string>) => {
   }
 };
 
-const copyTextToClipboard = (getText: () => Promise<string>) =>
+/**
+ * Copy text to clipboard with automatic fallback for older browsers.
+ *
+ * Uses the modern Clipboard API when available, falling back to
+ * document.execCommand('copy') for legacy browser support.
+ *
+ * @param getText - Function that returns a Promise resolving to the text to 
copy
+ * @returns Promise that resolves when copy succeeds, rejects on failure
+ */
+export const copyTextToClipboard = (
+  getText: () => Promise<string>,
+): Promise<void> =>
   copyTextWithClipboardApi(getText)
     // If the Clipboard API is not supported, fallback to the older method.
     .catch(() =>
@@ -73,10 +98,10 @@ const copyTextToClipboard = (getText: () => 
Promise<string>) =>
 
               try {
                 if (!document.execCommand('copy')) {
-                  reject();
+                  reject(new Error('execCommand copy failed'));
                 }
               } catch (err) {
-                reject();
+                reject(err);
               }
 
               document.body.removeChild(span);
@@ -91,5 +116,3 @@ const copyTextToClipboard = (getText: () => Promise<string>) 
=>
           }),
       ),
     );
-
-export default copyTextToClipboard;
diff --git a/superset-frontend/packages/superset-ui-core/src/utils/index.ts 
b/superset-frontend/packages/superset-ui-core/src/utils/index.ts
index 4d6e869cd0c..5f717de0c32 100644
--- a/superset-frontend/packages/superset-ui-core/src/utils/index.ts
+++ b/superset-frontend/packages/superset-ui-core/src/utils/index.ts
@@ -34,3 +34,4 @@ export * from './typedMemo';
 export * from './html';
 export * from './tooltip';
 export * from './merge';
+export * from './clipboard';
diff --git 
a/superset-frontend/packages/superset-ui-core/test/chart/components/SuperChart.test.tsx
 
b/superset-frontend/packages/superset-ui-core/test/chart/components/SuperChart.test.tsx
index a739748d2ee..9f647320588 100644
--- 
a/superset-frontend/packages/superset-ui-core/test/chart/components/SuperChart.test.tsx
+++ 
b/superset-frontend/packages/superset-ui-core/test/chart/components/SuperChart.test.tsx
@@ -21,7 +21,7 @@ import '@testing-library/jest-dom';
 import { render, screen } from '@superset-ui/core/spec';
 import mockConsole, { RestoreConsole } from 'jest-mock-console';
 import { triggerResizeObserver } from 'resize-observer-polyfill';
-import { ErrorBoundary } from 'react-error-boundary';
+import { ErrorBoundary } from '../../../src/chart/components/ErrorBoundary';
 
 import { promiseTimeout, SuperChart } from '@superset-ui/core';
 import { WrapperProps } from '../../../src/chart/components/SuperChart';
diff --git a/superset-frontend/src/components/CopyToClipboard/index.tsx 
b/superset-frontend/src/components/CopyToClipboard/index.tsx
index 976f0059c30..58f61bd3738 100644
--- a/superset-frontend/src/components/CopyToClipboard/index.tsx
+++ b/superset-frontend/src/components/CopyToClipboard/index.tsx
@@ -16,117 +16,57 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { Component, cloneElement, ReactElement } from 'react';
-import { t } from '@apache-superset/core';
-import { css, SupersetTheme } from '@apache-superset/core/ui';
-import copyTextToClipboard from 'src/utils/copy';
-import { Tooltip } from '@superset-ui/core/components';
-import withToasts from '../MessageToasts/withToasts';
-import type { CopyToClipboardProps } from './types';
-
-const defaultProps: Partial<CopyToClipboardProps> = {
-  copyNode: <span>{t('Copy')}</span>,
-  onCopyEnd: () => {},
-  shouldShowText: true,
-  wrapped: true,
-  tooltipText: t('Copy to clipboard'),
-  hideTooltip: false,
-};
-
-class CopyToClip extends Component<CopyToClipboardProps> {
-  static defaultProps = defaultProps;
-
-  constructor(props: CopyToClipboardProps) {
-    super(props);
-    this.copyToClipboard = this.copyToClipboard.bind(this);
-    this.onClick = this.onClick.bind(this);
-  }
-
-  onClick() {
-    if (this.props.getText) {
-      this.props.getText((d: string) => {
-        this.copyToClipboard(Promise.resolve(d));
-      });
-    } else {
-      this.copyToClipboard(Promise.resolve(this.props.text || ''));
-    }
-  }
-
-  getDecoratedCopyNode() {
-    return cloneElement(this.props.copyNode as ReactElement, {
-      style: { cursor: 'pointer' },
-      onClick: this.onClick,
-    });
-  }
 
-  copyToClipboard(textToCopy: Promise<string>) {
-    copyTextToClipboard(() => textToCopy)
-      .then(() => {
-        this.props.addSuccessToast(t('Copied to clipboard!'));
-      })
-      .catch(() => {
-        this.props.addDangerToast(
-          t(
-            'Sorry, your browser does not support copying. Use Ctrl / Cmd + 
C!',
-          ),
-        );
-      })
-      .finally(() => {
-        if (this.props.onCopyEnd) this.props.onCopyEnd();
-      });
-  }
-
-  renderTooltip(cursor: string) {
-    return (
-      <>
-        {!this.props.hideTooltip ? (
-          <Tooltip
-            id="copy-to-clipboard-tooltip"
-            placement="topRight"
-            style={{ cursor }}
-            title={this.props.tooltipText || ''}
-            trigger={['hover']}
-            arrow={{ pointAtCenter: true }}
-          >
-            {this.getDecoratedCopyNode()}
-          </Tooltip>
-        ) : (
-          this.getDecoratedCopyNode()
-        )}
-      </>
-    );
-  }
+/**
+ * This is a Superset-specific wrapper around the generic CopyToClipboard
+ * component from @superset-ui/core. It integrates with Superset's toast
+ * notification system to show success/error messages.
+ *
+ * For the generic component without toast integration, import directly from:
+ * import { CopyToClipboard } from '@superset-ui/core/components';
+ */
+import { t } from '@apache-superset/core';
+import {
+  CopyToClipboard as BaseCopyToClipboard,
+  type CopyToClipboardProps as BaseCopyToClipboardProps,
+} from '@superset-ui/core/components';
+import withToasts, { type ToastProps } from '../MessageToasts/withToasts';
 
-  renderNotWrapped() {
-    return this.renderTooltip('pointer');
-  }
+export interface CopyToClipboardProps extends Omit<
+  BaseCopyToClipboardProps,
+  'onSuccess' | 'onError'
+> {
+  /** Custom success message (defaults to "Copied to clipboard!") */
+  successMessage?: string;
+  /** Custom error message (defaults to browser not supporting copying 
message) */
+  errorMessage?: string;
+}
 
-  renderLink() {
-    return (
-      <span css={{ display: 'inline-flex', alignItems: 'center' }}>
-        {this.props.shouldShowText && this.props.text && (
-          <span
-            data-test="short-url"
-            css={(theme: SupersetTheme) => css`
-              margin-right: ${theme.sizeUnit}px;
-            `}
-          >
-            {this.props.text}
-          </span>
-        )}
-        {this.renderTooltip('pointer')}
-      </span>
-    );
-  }
+type CopyToClipboardWithToastsProps = CopyToClipboardProps & ToastProps;
 
-  render() {
-    const { wrapped } = this.props;
-    if (!wrapped) {
-      return this.renderNotWrapped();
-    }
-    return this.renderLink();
-  }
+function CopyToClipboardWithToasts({
+  addSuccessToast,
+  addDangerToast,
+  successMessage,
+  errorMessage,
+  ...props
+}: CopyToClipboardWithToastsProps) {
+  return (
+    <BaseCopyToClipboard
+      {...props}
+      onSuccess={() =>
+        addSuccessToast(successMessage || t('Copied to clipboard!'))
+      }
+      onError={() =>
+        addDangerToast(
+          errorMessage ||
+            t(
+              'Sorry, your browser does not support copying. Use Ctrl / Cmd + 
C!',
+            ),
+        )
+      }
+    />
+  );
 }
 
-export const CopyToClipboard = withToasts(CopyToClip);
-export type { CopyToClipboardProps };
+export const CopyToClipboard = withToasts(CopyToClipboardWithToasts);
diff --git a/superset-frontend/src/components/CopyToClipboard/types.ts 
b/superset-frontend/src/components/CopyToClipboard/types.ts
deleted file mode 100644
index 10d87e8decf..00000000000
--- a/superset-frontend/src/components/CopyToClipboard/types.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import type { ReactNode } from 'react';
-
-export interface CopyToClipboardProps {
-  copyNode?: ReactNode;
-  getText?: (callback: (data: string) => void) => void;
-  onCopyEnd?: () => void;
-  shouldShowText?: boolean;
-  text?: string;
-  wrapped?: boolean;
-  tooltipText?: string;
-  addDangerToast: (msg: string) => void;
-  addSuccessToast: (msg: string) => void;
-  hideTooltip?: boolean;
-}
diff --git a/superset-frontend/src/utils/copy.ts 
b/superset-frontend/src/utils/copy.ts
index 0980f2ab170..041e1a93898 100644
--- a/superset-frontend/src/utils/copy.ts
+++ b/superset-frontend/src/utils/copy.ts
@@ -17,79 +17,11 @@
  * under the License.
  */
 
-import { isSafari } from './common';
-
-// Use the new Clipboard API if the browser supports it
-const copyTextWithClipboardApi = async (getText: () => Promise<string>) => {
-  // Safari (WebKit) does not support delayed generation of clipboard.
-  // This means that writing to the clipboard, from the moment the user
-  // interacts with the app, must be instantaneous.
-  // However, neither writeText nor write accepts a Promise, so
-  // we need to create a ClipboardItem that accepts said Promise to
-  // delay the text generation, as needed.
-  // Source: https://bugs.webkit.org/show_bug.cgi?id=222262P
-  if (isSafari()) {
-    try {
-      const clipboardItem = new ClipboardItem({
-        'text/plain': getText(),
-      });
-      await navigator.clipboard.write([clipboardItem]);
-    } catch {
-      // Fallback to default clipboard API implementation
-      const text = await getText();
-      await navigator.clipboard.writeText(text);
-    }
-  } else {
-    // For Blink, the above method won't work, but we can use the
-    // default (intended) API, since the delayed generation of the
-    // clipboard is now supported.
-    // Source: https://bugs.chromium.org/p/chromium/issues/detail?id=1014310
-    const text = await getText();
-    await navigator.clipboard.writeText(text);
-  }
-};
-
-const copyTextToClipboard = (getText: () => Promise<string>) =>
-  copyTextWithClipboardApi(getText)
-    // If the Clipboard API is not supported, fallback to the older method.
-    .catch(() =>
-      getText().then(
-        text =>
-          new Promise<void>((resolve, reject) => {
-            const selection: Selection | null = document.getSelection();
-            if (selection) {
-              selection.removeAllRanges();
-              const range = document.createRange();
-              const span = document.createElement('span');
-              span.textContent = text;
-              span.style.position = 'fixed';
-              span.style.top = '0';
-              span.style.clip = 'rect(0, 0, 0, 0)';
-              span.style.whiteSpace = 'pre';
-
-              document.body.appendChild(span);
-              range.selectNode(span);
-              selection.addRange(range);
-
-              try {
-                if (!document.execCommand('copy')) {
-                  reject();
-                }
-              } catch (err) {
-                reject();
-              }
-
-              document.body.removeChild(span);
-              if (selection.removeRange) {
-                selection.removeRange(range);
-              } else {
-                selection.removeAllRanges();
-              }
-            }
-
-            resolve();
-          }),
-      ),
-    );
-
-export default copyTextToClipboard;
+/**
+ * Re-export clipboard utilities from @superset-ui/core for backward 
compatibility.
+ *
+ * For new code, prefer importing directly from:
+ * import { copyTextToClipboard, isSafari } from '@superset-ui/core';
+ */
+export { copyTextToClipboard, isSafari } from '@superset-ui/core';
+export { copyTextToClipboard as default } from '@superset-ui/core';

Reply via email to