@wordpress/compose 7.41.0 → 7.41.1-next.v.202603161435.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -220,9 +220,9 @@ Copies the text to the clipboard when the element is clicked.
220
220
 
221
221
  _Parameters_
222
222
 
223
- - _ref_ `React.RefObject<string | Element | NodeListOf<Element>>`: Reference with the element.
224
- - _text_ `string|Function`: The text to copy.
225
- - _timeout_ `[number]`: Optional timeout to reset the returned state. 4 seconds by default.
223
+ - _ref_ `RefObject< string | Element | NodeListOf< Element > >`: Reference with the element.
224
+ - _text_ `string | ( () => string )`: The text to copy.
225
+ - _timeout_ `number`: Optional timeout to reset the returned state. 4 seconds by default.
226
226
 
227
227
  _Returns_
228
228
 
@@ -234,12 +234,12 @@ Copies the given text to the clipboard when the element is clicked.
234
234
 
235
235
  _Parameters_
236
236
 
237
- - _text_ `string | (() => string)`: The text to copy. Use a function if not already available and expensive to compute.
238
- - _onSuccess_ `Function`: Called when to text is copied.
237
+ - _text_ `string | ( () => string )`: The text to copy. Use a function if not already available and expensive to compute.
238
+ - _onSuccess_ `() => void`: Called when to text is copied.
239
239
 
240
240
  _Returns_
241
241
 
242
- - `React.Ref<TElementType>`: A ref to assign to the target element.
242
+ - `RefCallback< T >`: A ref to assign to the target element.
243
243
 
244
244
  ### useDebounce
245
245
 
@@ -27,48 +27,73 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
27
  ));
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
 
30
- // packages/compose/src/hooks/use-copy-on-click/index.js
30
+ // packages/compose/src/hooks/use-copy-on-click/index.ts
31
31
  var use_copy_on_click_exports = {};
32
32
  __export(use_copy_on_click_exports, {
33
33
  default: () => useCopyOnClick
34
34
  });
35
35
  module.exports = __toCommonJS(use_copy_on_click_exports);
36
- var import_clipboard = __toESM(require("clipboard"));
37
36
  var import_element = require("@wordpress/element");
38
37
  var import_deprecated = __toESM(require("@wordpress/deprecated"));
38
+ var import_use_copy_to_clipboard = require("../use-copy-to-clipboard/index.cjs");
39
39
  function useCopyOnClick(ref, text, timeout = 4e3) {
40
40
  (0, import_deprecated.default)("wp.compose.useCopyOnClick", {
41
41
  since: "5.8",
42
42
  alternative: "wp.compose.useCopyToClipboard"
43
43
  });
44
- const clipboardRef = (0, import_element.useRef)(void 0);
45
44
  const [hasCopied, setHasCopied] = (0, import_element.useState)(false);
46
45
  (0, import_element.useEffect)(() => {
46
+ let isActive = true;
47
47
  let timeoutId;
48
48
  if (!ref.current) {
49
49
  return;
50
50
  }
51
- clipboardRef.current = new import_clipboard.default(ref.current, {
52
- text: () => typeof text === "function" ? text() : text
53
- });
54
- clipboardRef.current.on("success", ({ clearSelection, trigger }) => {
55
- clearSelection();
56
- if (trigger) {
57
- trigger.focus();
51
+ let targets;
52
+ if (typeof ref.current === "string") {
53
+ targets = typeof document !== "undefined" ? Array.from(document.querySelectorAll(ref.current)) : [];
54
+ } else if ("length" in ref.current && typeof ref.current.length === "number") {
55
+ targets = Array.from(ref.current);
56
+ } else {
57
+ targets = [ref.current];
58
+ }
59
+ if (targets.length === 0) {
60
+ return;
61
+ }
62
+ const handleClick = async (event) => {
63
+ const trigger = event.currentTarget;
64
+ if (!trigger) {
65
+ return;
58
66
  }
59
- if (timeout) {
60
- setHasCopied(true);
61
- clearTimeout(timeoutId);
62
- timeoutId = setTimeout(() => setHasCopied(false), timeout);
67
+ const success = await (0, import_use_copy_to_clipboard.copyToClipboard)(
68
+ typeof text === "function" ? text() : text || "",
69
+ trigger
70
+ );
71
+ if (!isActive) {
72
+ return;
63
73
  }
64
- });
74
+ if (success) {
75
+ (0, import_use_copy_to_clipboard.clearSelection)(trigger);
76
+ if (timeout) {
77
+ setHasCopied(true);
78
+ clearTimeout(timeoutId);
79
+ timeoutId = setTimeout(
80
+ () => setHasCopied(false),
81
+ timeout
82
+ );
83
+ }
84
+ }
85
+ };
86
+ for (const target of targets) {
87
+ target.addEventListener("click", handleClick);
88
+ }
65
89
  return () => {
66
- if (clipboardRef.current) {
67
- clipboardRef.current.destroy();
90
+ isActive = false;
91
+ for (const target of targets) {
92
+ target.removeEventListener("click", handleClick);
68
93
  }
69
94
  clearTimeout(timeoutId);
70
95
  };
71
- }, [text, timeout, setHasCopied]);
96
+ }, [ref, text, timeout]);
72
97
  return hasCopied;
73
98
  }
74
99
  //# sourceMappingURL=index.cjs.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../../src/hooks/use-copy-on-click/index.js"],
4
- "sourcesContent": ["/**\n * External dependencies\n */\nimport Clipboard from 'clipboard';\n\n/**\n * WordPress dependencies\n */\nimport { useRef, useEffect, useState } from '@wordpress/element';\nimport deprecated from '@wordpress/deprecated';\n\n/**\n * Copies the text to the clipboard when the element is clicked.\n *\n * @deprecated\n *\n * @param {React.RefObject<string | Element | NodeListOf<Element>>} ref Reference with the element.\n * @param {string|Function} text The text to copy.\n * @param {number} [timeout] Optional timeout to reset the returned\n * state. 4 seconds by default.\n *\n * @return {boolean} Whether or not the text has been copied. Resets after the\n * timeout.\n */\nexport default function useCopyOnClick( ref, text, timeout = 4000 ) {\n\tdeprecated( 'wp.compose.useCopyOnClick', {\n\t\tsince: '5.8',\n\t\talternative: 'wp.compose.useCopyToClipboard',\n\t} );\n\n\t/** @type {React.MutableRefObject<Clipboard | undefined>} */\n\tconst clipboardRef = useRef( undefined );\n\tconst [ hasCopied, setHasCopied ] = useState( false );\n\n\tuseEffect( () => {\n\t\t/** @type {number | undefined} */\n\t\tlet timeoutId;\n\n\t\tif ( ! ref.current ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Clipboard listens to click events.\n\t\tclipboardRef.current = new Clipboard( ref.current, {\n\t\t\ttext: () => ( typeof text === 'function' ? text() : text ),\n\t\t} );\n\n\t\tclipboardRef.current.on( 'success', ( { clearSelection, trigger } ) => {\n\t\t\t// Clearing selection will move focus back to the triggering button,\n\t\t\t// ensuring that it is not reset to the body, and further that it is\n\t\t\t// kept within the rendered node.\n\t\t\tclearSelection();\n\n\t\t\t// Handle ClipboardJS focus bug, see https://github.com/zenorocha/clipboard.js/issues/680\n\t\t\tif ( trigger ) {\n\t\t\t\t/** @type {HTMLElement} */ ( trigger ).focus();\n\t\t\t}\n\n\t\t\tif ( timeout ) {\n\t\t\t\tsetHasCopied( true );\n\t\t\t\tclearTimeout( timeoutId );\n\t\t\t\ttimeoutId = setTimeout( () => setHasCopied( false ), timeout );\n\t\t\t}\n\t\t} );\n\n\t\treturn () => {\n\t\t\tif ( clipboardRef.current ) {\n\t\t\t\tclipboardRef.current.destroy();\n\t\t\t}\n\t\t\tclearTimeout( timeoutId );\n\t\t};\n\t}, [ text, timeout, setHasCopied ] );\n\n\treturn hasCopied;\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,uBAAsB;AAKtB,qBAA4C;AAC5C,wBAAuB;AAeR,SAAR,eAAiC,KAAK,MAAM,UAAU,KAAO;AACnE,wBAAAA,SAAY,6BAA6B;AAAA,IACxC,OAAO;AAAA,IACP,aAAa;AAAA,EACd,CAAE;AAGF,QAAM,mBAAe,uBAAQ,MAAU;AACvC,QAAM,CAAE,WAAW,YAAa,QAAI,yBAAU,KAAM;AAEpD,gCAAW,MAAM;AAEhB,QAAI;AAEJ,QAAK,CAAE,IAAI,SAAU;AACpB;AAAA,IACD;AAGA,iBAAa,UAAU,IAAI,iBAAAC,QAAW,IAAI,SAAS;AAAA,MAClD,MAAM,MAAQ,OAAO,SAAS,aAAa,KAAK,IAAI;AAAA,IACrD,CAAE;AAEF,iBAAa,QAAQ,GAAI,WAAW,CAAE,EAAE,gBAAgB,QAAQ,MAAO;AAItE,qBAAe;AAGf,UAAK,SAAU;AACa,QAAE,QAAU,MAAM;AAAA,MAC9C;AAEA,UAAK,SAAU;AACd,qBAAc,IAAK;AACnB,qBAAc,SAAU;AACxB,oBAAY,WAAY,MAAM,aAAc,KAAM,GAAG,OAAQ;AAAA,MAC9D;AAAA,IACD,CAAE;AAEF,WAAO,MAAM;AACZ,UAAK,aAAa,SAAU;AAC3B,qBAAa,QAAQ,QAAQ;AAAA,MAC9B;AACA,mBAAc,SAAU;AAAA,IACzB;AAAA,EACD,GAAG,CAAE,MAAM,SAAS,YAAa,CAAE;AAEnC,SAAO;AACR;",
6
- "names": ["deprecated", "Clipboard"]
3
+ "sources": ["../../../src/hooks/use-copy-on-click/index.ts"],
4
+ "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { useEffect, useState } from '@wordpress/element';\nimport deprecated from '@wordpress/deprecated';\nimport type { RefObject } from 'react';\n\n/**\n * Internal dependencies\n */\nimport { clearSelection, copyToClipboard } from '../use-copy-to-clipboard';\n\n/**\n * Copies the text to the clipboard when the element is clicked.\n *\n * @deprecated\n * @param ref Reference with the element.\n * @param text The text to copy.\n * @param timeout Optional timeout to reset the returned\n * state. 4 seconds by default.\n * @return Whether or not the text has been copied. Resets after the\n * timeout.\n */\nexport default function useCopyOnClick(\n\tref: RefObject< string | Element | NodeListOf< Element > >,\n\ttext: string | ( () => string ),\n\ttimeout: number = 4000\n): boolean {\n\tdeprecated( 'wp.compose.useCopyOnClick', {\n\t\tsince: '5.8',\n\t\talternative: 'wp.compose.useCopyToClipboard',\n\t} );\n\n\tconst [ hasCopied, setHasCopied ] = useState( false );\n\n\tuseEffect( () => {\n\t\t// Flag to prevent state updates after unmount when the Promise resolves.\n\t\tlet isActive = true;\n\t\tlet timeoutId: ReturnType< typeof setTimeout > | undefined;\n\t\tif ( ! ref.current ) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet targets: Element[];\n\t\tif ( typeof ref.current === 'string' ) {\n\t\t\ttargets =\n\t\t\t\ttypeof document !== 'undefined'\n\t\t\t\t\t? Array.from( document.querySelectorAll( ref.current ) )\n\t\t\t\t\t: [];\n\t\t} else if (\n\t\t\t'length' in ref.current &&\n\t\t\ttypeof ref.current.length === 'number'\n\t\t) {\n\t\t\ttargets = Array.from( ref.current );\n\t\t} else {\n\t\t\ttargets = [ ref.current as Element ];\n\t\t}\n\n\t\tif ( targets.length === 0 ) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst handleClick = async ( event: Event ) => {\n\t\t\tconst trigger = event.currentTarget as Element;\n\t\t\tif ( ! trigger ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst success = await copyToClipboard(\n\t\t\t\ttypeof text === 'function' ? text() : text || '',\n\t\t\t\ttrigger\n\t\t\t);\n\t\t\tif ( ! isActive ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif ( success ) {\n\t\t\t\tclearSelection( trigger );\n\t\t\t\tif ( timeout ) {\n\t\t\t\t\tsetHasCopied( true );\n\t\t\t\t\tclearTimeout( timeoutId );\n\t\t\t\t\ttimeoutId = setTimeout(\n\t\t\t\t\t\t() => setHasCopied( false ),\n\t\t\t\t\t\ttimeout\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tfor ( const target of targets ) {\n\t\t\ttarget.addEventListener( 'click', handleClick );\n\t\t}\n\t\treturn () => {\n\t\t\tisActive = false;\n\t\t\tfor ( const target of targets ) {\n\t\t\t\ttarget.removeEventListener( 'click', handleClick );\n\t\t\t}\n\t\t\tclearTimeout( timeoutId );\n\t\t};\n\t}, [ ref, text, timeout ] );\n\n\treturn hasCopied;\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,qBAAoC;AACpC,wBAAuB;AAMvB,mCAAgD;AAajC,SAAR,eACN,KACA,MACA,UAAkB,KACR;AACV,wBAAAA,SAAY,6BAA6B;AAAA,IACxC,OAAO;AAAA,IACP,aAAa;AAAA,EACd,CAAE;AAEF,QAAM,CAAE,WAAW,YAAa,QAAI,yBAAU,KAAM;AAEpD,gCAAW,MAAM;AAEhB,QAAI,WAAW;AACf,QAAI;AACJ,QAAK,CAAE,IAAI,SAAU;AACpB;AAAA,IACD;AAEA,QAAI;AACJ,QAAK,OAAO,IAAI,YAAY,UAAW;AACtC,gBACC,OAAO,aAAa,cACjB,MAAM,KAAM,SAAS,iBAAkB,IAAI,OAAQ,CAAE,IACrD,CAAC;AAAA,IACN,WACC,YAAY,IAAI,WAChB,OAAO,IAAI,QAAQ,WAAW,UAC7B;AACD,gBAAU,MAAM,KAAM,IAAI,OAAQ;AAAA,IACnC,OAAO;AACN,gBAAU,CAAE,IAAI,OAAmB;AAAA,IACpC;AAEA,QAAK,QAAQ,WAAW,GAAI;AAC3B;AAAA,IACD;AAEA,UAAM,cAAc,OAAQ,UAAkB;AAC7C,YAAM,UAAU,MAAM;AACtB,UAAK,CAAE,SAAU;AAChB;AAAA,MACD;AACA,YAAM,UAAU,UAAM;AAAA,QACrB,OAAO,SAAS,aAAa,KAAK,IAAI,QAAQ;AAAA,QAC9C;AAAA,MACD;AACA,UAAK,CAAE,UAAW;AACjB;AAAA,MACD;AACA,UAAK,SAAU;AACd,yDAAgB,OAAQ;AACxB,YAAK,SAAU;AACd,uBAAc,IAAK;AACnB,uBAAc,SAAU;AACxB,sBAAY;AAAA,YACX,MAAM,aAAc,KAAM;AAAA,YAC1B;AAAA,UACD;AAAA,QACD;AAAA,MACD;AAAA,IACD;AAEA,eAAY,UAAU,SAAU;AAC/B,aAAO,iBAAkB,SAAS,WAAY;AAAA,IAC/C;AACA,WAAO,MAAM;AACZ,iBAAW;AACX,iBAAY,UAAU,SAAU;AAC/B,eAAO,oBAAqB,SAAS,WAAY;AAAA,MAClD;AACA,mBAAc,SAAU;AAAA,IACzB;AAAA,EACD,GAAG,CAAE,KAAK,MAAM,OAAQ,CAAE;AAE1B,SAAO;AACR;",
6
+ "names": ["deprecated"]
7
7
  }
@@ -27,15 +27,51 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
27
  ));
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
 
30
- // packages/compose/src/hooks/use-copy-to-clipboard/index.js
30
+ // packages/compose/src/hooks/use-copy-to-clipboard/index.ts
31
31
  var use_copy_to_clipboard_exports = {};
32
32
  __export(use_copy_to_clipboard_exports, {
33
+ clearSelection: () => clearSelection,
34
+ copyToClipboard: () => copyToClipboard,
33
35
  default: () => useCopyToClipboard
34
36
  });
35
37
  module.exports = __toCommonJS(use_copy_to_clipboard_exports);
36
- var import_clipboard = __toESM(require("clipboard"));
37
38
  var import_element = require("@wordpress/element");
38
39
  var import_use_ref_effect = __toESM(require("../use-ref-effect/index.cjs"));
40
+ async function copyToClipboard(text, trigger) {
41
+ if (!trigger) {
42
+ return false;
43
+ }
44
+ const { ownerDocument } = trigger;
45
+ if (!ownerDocument) {
46
+ return false;
47
+ }
48
+ const { defaultView } = ownerDocument;
49
+ try {
50
+ if (defaultView?.navigator?.clipboard?.writeText) {
51
+ await defaultView.navigator.clipboard.writeText(text);
52
+ return true;
53
+ }
54
+ const textarea = ownerDocument.createElement("textarea");
55
+ textarea.value = text;
56
+ textarea.setAttribute("readonly", "");
57
+ textarea.style.position = "fixed";
58
+ textarea.style.left = "-9999px";
59
+ textarea.style.top = "-9999px";
60
+ ownerDocument.body.appendChild(textarea);
61
+ textarea.select();
62
+ const success = ownerDocument.execCommand("copy");
63
+ textarea.remove();
64
+ return success;
65
+ } catch {
66
+ return false;
67
+ }
68
+ }
69
+ function clearSelection(trigger) {
70
+ if ("focus" in trigger && typeof trigger.focus === "function") {
71
+ trigger.focus();
72
+ }
73
+ trigger.ownerDocument?.defaultView?.getSelection()?.removeAllRanges();
74
+ }
39
75
  function useUpdatedRef(value) {
40
76
  const ref = (0, import_element.useRef)(value);
41
77
  (0, import_element.useLayoutEffect)(() => {
@@ -47,20 +83,30 @@ function useCopyToClipboard(text, onSuccess) {
47
83
  const textRef = useUpdatedRef(text);
48
84
  const onSuccessRef = useUpdatedRef(onSuccess);
49
85
  return (0, import_use_ref_effect.default)((node) => {
50
- const clipboard = new import_clipboard.default(node, {
51
- text() {
52
- return typeof textRef.current === "function" ? textRef.current() : textRef.current || "";
86
+ let isActive = true;
87
+ const handleClick = async () => {
88
+ const textToCopy = typeof textRef.current === "function" ? textRef.current() : textRef.current || "";
89
+ const success = await copyToClipboard(textToCopy, node);
90
+ if (!isActive) {
91
+ return;
53
92
  }
54
- });
55
- clipboard.on("success", ({ clearSelection }) => {
56
- clearSelection();
57
- if (onSuccessRef.current) {
58
- onSuccessRef.current();
93
+ if (success) {
94
+ clearSelection(node);
95
+ if (onSuccessRef.current) {
96
+ onSuccessRef.current();
97
+ }
59
98
  }
60
- });
99
+ };
100
+ node.addEventListener("click", handleClick);
61
101
  return () => {
62
- clipboard.destroy();
102
+ isActive = false;
103
+ node.removeEventListener("click", handleClick);
63
104
  };
64
105
  }, []);
65
106
  }
107
+ // Annotate the CommonJS export names for ESM import in node:
108
+ 0 && (module.exports = {
109
+ clearSelection,
110
+ copyToClipboard
111
+ });
66
112
  //# sourceMappingURL=index.cjs.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../../src/hooks/use-copy-to-clipboard/index.js"],
4
- "sourcesContent": ["/**\n * External dependencies\n */\nimport Clipboard from 'clipboard';\n\n/**\n * WordPress dependencies\n */\nimport { useRef, useLayoutEffect } from '@wordpress/element';\n\n/**\n * Internal dependencies\n */\nimport useRefEffect from '../use-ref-effect';\n\n/**\n * @template T\n * @param {T} value\n * @return {React.RefObject<T>} The updated ref\n */\nfunction useUpdatedRef( value ) {\n\tconst ref = useRef( value );\n\tuseLayoutEffect( () => {\n\t\tref.current = value;\n\t}, [ value ] );\n\treturn ref;\n}\n\n/**\n * Copies the given text to the clipboard when the element is clicked.\n *\n * @template {HTMLElement} TElementType\n * @param {string | (() => string)} text The text to copy. Use a function if not\n * already available and expensive to compute.\n * @param {Function} onSuccess Called when to text is copied.\n *\n * @return {React.Ref<TElementType>} A ref to assign to the target element.\n */\nexport default function useCopyToClipboard( text, onSuccess ) {\n\t// Store the dependencies as refs and continuously update them so they're\n\t// fresh when the callback is called.\n\tconst textRef = useUpdatedRef( text );\n\tconst onSuccessRef = useUpdatedRef( onSuccess );\n\treturn useRefEffect( ( node ) => {\n\t\t// Clipboard listens to click events.\n\t\tconst clipboard = new Clipboard( node, {\n\t\t\ttext() {\n\t\t\t\treturn typeof textRef.current === 'function'\n\t\t\t\t\t? textRef.current()\n\t\t\t\t\t: textRef.current || '';\n\t\t\t},\n\t\t} );\n\n\t\tclipboard.on( 'success', ( { clearSelection } ) => {\n\t\t\t// Clearing selection will move focus back to the triggering\n\t\t\t// button, ensuring that it is not reset to the body, and\n\t\t\t// further that it is kept within the rendered node.\n\t\t\tclearSelection();\n\n\t\t\tif ( onSuccessRef.current ) {\n\t\t\t\tonSuccessRef.current();\n\t\t\t}\n\t\t} );\n\n\t\treturn () => {\n\t\t\tclipboard.destroy();\n\t\t};\n\t}, [] );\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,uBAAsB;AAKtB,qBAAwC;AAKxC,4BAAyB;AAOzB,SAAS,cAAe,OAAQ;AAC/B,QAAM,UAAM,uBAAQ,KAAM;AAC1B,sCAAiB,MAAM;AACtB,QAAI,UAAU;AAAA,EACf,GAAG,CAAE,KAAM,CAAE;AACb,SAAO;AACR;AAYe,SAAR,mBAAqC,MAAM,WAAY;AAG7D,QAAM,UAAU,cAAe,IAAK;AACpC,QAAM,eAAe,cAAe,SAAU;AAC9C,aAAO,sBAAAA,SAAc,CAAE,SAAU;AAEhC,UAAM,YAAY,IAAI,iBAAAC,QAAW,MAAM;AAAA,MACtC,OAAO;AACN,eAAO,OAAO,QAAQ,YAAY,aAC/B,QAAQ,QAAQ,IAChB,QAAQ,WAAW;AAAA,MACvB;AAAA,IACD,CAAE;AAEF,cAAU,GAAI,WAAW,CAAE,EAAE,eAAe,MAAO;AAIlD,qBAAe;AAEf,UAAK,aAAa,SAAU;AAC3B,qBAAa,QAAQ;AAAA,MACtB;AAAA,IACD,CAAE;AAEF,WAAO,MAAM;AACZ,gBAAU,QAAQ;AAAA,IACnB;AAAA,EACD,GAAG,CAAC,CAAE;AACP;",
6
- "names": ["useRefEffect", "Clipboard"]
3
+ "sources": ["../../../src/hooks/use-copy-to-clipboard/index.ts"],
4
+ "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { useRef, useLayoutEffect } from '@wordpress/element';\nimport type { MutableRefObject, RefCallback } from 'react';\n\n/**\n * Internal dependencies\n */\nimport useRefEffect from '../use-ref-effect';\n\n/**\n * Copies text to the clipboard using the Clipboard API when available,\n * with a fallback for non-secure contexts (e.g. HTTP) and older browsers.\n *\n * @param text The text to copy.\n * @param trigger The element that triggered the copy.\n * @return Resolves to true if successful, false otherwise.\n */\nexport async function copyToClipboard(\n\ttext: string,\n\ttrigger: Element | null\n): Promise< boolean > {\n\tif ( ! trigger ) {\n\t\treturn false;\n\t}\n\tconst { ownerDocument } = trigger;\n\tif ( ! ownerDocument ) {\n\t\treturn false;\n\t}\n\tconst { defaultView } = ownerDocument;\n\ttry {\n\t\tif ( defaultView?.navigator?.clipboard?.writeText ) {\n\t\t\tawait defaultView.navigator.clipboard.writeText( text );\n\t\t\treturn true;\n\t\t}\n\t\t// Fallback for non-secure contexts (HTTP) and older browsers.\n\t\tconst textarea = ownerDocument.createElement( 'textarea' );\n\t\ttextarea.value = text;\n\t\ttextarea.setAttribute( 'readonly', '' );\n\t\ttextarea.style.position = 'fixed';\n\t\ttextarea.style.left = '-9999px';\n\t\ttextarea.style.top = '-9999px';\n\t\townerDocument.body.appendChild( textarea );\n\t\ttextarea.select();\n\t\tconst success = ownerDocument.execCommand( 'copy' );\n\t\ttextarea.remove();\n\t\treturn success;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Clears the current selection and restores focus to the trigger element.\n *\n * @param trigger The element that triggered the copy.\n */\nexport function clearSelection( trigger: Element ): void {\n\tif ( 'focus' in trigger && typeof trigger.focus === 'function' ) {\n\t\ttrigger.focus();\n\t}\n\ttrigger.ownerDocument?.defaultView?.getSelection()?.removeAllRanges();\n}\n\n/**\n * @template T\n * @param value\n * @return A ref to assign to the target element.\n */\nfunction useUpdatedRef< T >( value: T ): MutableRefObject< T > {\n\tconst ref = useRef< T >( value );\n\tuseLayoutEffect( () => {\n\t\tref.current = value;\n\t}, [ value ] );\n\treturn ref;\n}\n\n/**\n * Copies the given text to the clipboard when the element is clicked.\n *\n * @template T\n * @param text The text to copy. Use a function if not\n * already available and expensive to compute.\n * @param onSuccess Called when to text is copied.\n *\n * @return A ref to assign to the target element.\n */\nexport default function useCopyToClipboard< T extends HTMLElement >(\n\ttext: string | ( () => string ),\n\tonSuccess?: () => void\n): RefCallback< T > {\n\tconst textRef = useUpdatedRef( text );\n\tconst onSuccessRef = useUpdatedRef( onSuccess );\n\treturn useRefEffect( ( node ) => {\n\t\t// Flag to prevent callbacks after unmount when the Promise resolves.\n\t\tlet isActive = true;\n\t\tconst handleClick = async () => {\n\t\t\tconst textToCopy =\n\t\t\t\ttypeof textRef.current === 'function'\n\t\t\t\t\t? textRef.current()\n\t\t\t\t\t: textRef.current || '';\n\t\t\tconst success = await copyToClipboard( textToCopy, node );\n\t\t\tif ( ! isActive ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif ( success ) {\n\t\t\t\tclearSelection( node );\n\t\t\t\tif ( onSuccessRef.current ) {\n\t\t\t\t\tonSuccessRef.current();\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tnode.addEventListener( 'click', handleClick );\n\t\treturn () => {\n\t\t\tisActive = false;\n\t\t\tnode.removeEventListener( 'click', handleClick );\n\t\t};\n\t}, [] );\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,qBAAwC;AAMxC,4BAAyB;AAUzB,eAAsB,gBACrB,MACA,SACqB;AACrB,MAAK,CAAE,SAAU;AAChB,WAAO;AAAA,EACR;AACA,QAAM,EAAE,cAAc,IAAI;AAC1B,MAAK,CAAE,eAAgB;AACtB,WAAO;AAAA,EACR;AACA,QAAM,EAAE,YAAY,IAAI;AACxB,MAAI;AACH,QAAK,aAAa,WAAW,WAAW,WAAY;AACnD,YAAM,YAAY,UAAU,UAAU,UAAW,IAAK;AACtD,aAAO;AAAA,IACR;AAEA,UAAM,WAAW,cAAc,cAAe,UAAW;AACzD,aAAS,QAAQ;AACjB,aAAS,aAAc,YAAY,EAAG;AACtC,aAAS,MAAM,WAAW;AAC1B,aAAS,MAAM,OAAO;AACtB,aAAS,MAAM,MAAM;AACrB,kBAAc,KAAK,YAAa,QAAS;AACzC,aAAS,OAAO;AAChB,UAAM,UAAU,cAAc,YAAa,MAAO;AAClD,aAAS,OAAO;AAChB,WAAO;AAAA,EACR,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAOO,SAAS,eAAgB,SAAyB;AACxD,MAAK,WAAW,WAAW,OAAO,QAAQ,UAAU,YAAa;AAChE,YAAQ,MAAM;AAAA,EACf;AACA,UAAQ,eAAe,aAAa,aAAa,GAAG,gBAAgB;AACrE;AAOA,SAAS,cAAoB,OAAkC;AAC9D,QAAM,UAAM,uBAAa,KAAM;AAC/B,sCAAiB,MAAM;AACtB,QAAI,UAAU;AAAA,EACf,GAAG,CAAE,KAAM,CAAE;AACb,SAAO;AACR;AAYe,SAAR,mBACN,MACA,WACmB;AACnB,QAAM,UAAU,cAAe,IAAK;AACpC,QAAM,eAAe,cAAe,SAAU;AAC9C,aAAO,sBAAAA,SAAc,CAAE,SAAU;AAEhC,QAAI,WAAW;AACf,UAAM,cAAc,YAAY;AAC/B,YAAM,aACL,OAAO,QAAQ,YAAY,aACxB,QAAQ,QAAQ,IAChB,QAAQ,WAAW;AACvB,YAAM,UAAU,MAAM,gBAAiB,YAAY,IAAK;AACxD,UAAK,CAAE,UAAW;AACjB;AAAA,MACD;AACA,UAAK,SAAU;AACd,uBAAgB,IAAK;AACrB,YAAK,aAAa,SAAU;AAC3B,uBAAa,QAAQ;AAAA,QACtB;AAAA,MACD;AAAA,IACD;AACA,SAAK,iBAAkB,SAAS,WAAY;AAC5C,WAAO,MAAM;AACZ,iBAAW;AACX,WAAK,oBAAqB,SAAS,WAAY;AAAA,IAChD;AAAA,EACD,GAAG,CAAC,CAAE;AACP;",
6
+ "names": ["useRefEffect"]
7
7
  }
@@ -1,40 +1,65 @@
1
- // packages/compose/src/hooks/use-copy-on-click/index.js
2
- import Clipboard from "clipboard";
3
- import { useRef, useEffect, useState } from "@wordpress/element";
1
+ // packages/compose/src/hooks/use-copy-on-click/index.ts
2
+ import { useEffect, useState } from "@wordpress/element";
4
3
  import deprecated from "@wordpress/deprecated";
4
+ import { clearSelection, copyToClipboard } from "../use-copy-to-clipboard/index.mjs";
5
5
  function useCopyOnClick(ref, text, timeout = 4e3) {
6
6
  deprecated("wp.compose.useCopyOnClick", {
7
7
  since: "5.8",
8
8
  alternative: "wp.compose.useCopyToClipboard"
9
9
  });
10
- const clipboardRef = useRef(void 0);
11
10
  const [hasCopied, setHasCopied] = useState(false);
12
11
  useEffect(() => {
12
+ let isActive = true;
13
13
  let timeoutId;
14
14
  if (!ref.current) {
15
15
  return;
16
16
  }
17
- clipboardRef.current = new Clipboard(ref.current, {
18
- text: () => typeof text === "function" ? text() : text
19
- });
20
- clipboardRef.current.on("success", ({ clearSelection, trigger }) => {
21
- clearSelection();
22
- if (trigger) {
23
- trigger.focus();
17
+ let targets;
18
+ if (typeof ref.current === "string") {
19
+ targets = typeof document !== "undefined" ? Array.from(document.querySelectorAll(ref.current)) : [];
20
+ } else if ("length" in ref.current && typeof ref.current.length === "number") {
21
+ targets = Array.from(ref.current);
22
+ } else {
23
+ targets = [ref.current];
24
+ }
25
+ if (targets.length === 0) {
26
+ return;
27
+ }
28
+ const handleClick = async (event) => {
29
+ const trigger = event.currentTarget;
30
+ if (!trigger) {
31
+ return;
24
32
  }
25
- if (timeout) {
26
- setHasCopied(true);
27
- clearTimeout(timeoutId);
28
- timeoutId = setTimeout(() => setHasCopied(false), timeout);
33
+ const success = await copyToClipboard(
34
+ typeof text === "function" ? text() : text || "",
35
+ trigger
36
+ );
37
+ if (!isActive) {
38
+ return;
29
39
  }
30
- });
40
+ if (success) {
41
+ clearSelection(trigger);
42
+ if (timeout) {
43
+ setHasCopied(true);
44
+ clearTimeout(timeoutId);
45
+ timeoutId = setTimeout(
46
+ () => setHasCopied(false),
47
+ timeout
48
+ );
49
+ }
50
+ }
51
+ };
52
+ for (const target of targets) {
53
+ target.addEventListener("click", handleClick);
54
+ }
31
55
  return () => {
32
- if (clipboardRef.current) {
33
- clipboardRef.current.destroy();
56
+ isActive = false;
57
+ for (const target of targets) {
58
+ target.removeEventListener("click", handleClick);
34
59
  }
35
60
  clearTimeout(timeoutId);
36
61
  };
37
- }, [text, timeout, setHasCopied]);
62
+ }, [ref, text, timeout]);
38
63
  return hasCopied;
39
64
  }
40
65
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../../src/hooks/use-copy-on-click/index.js"],
4
- "sourcesContent": ["/**\n * External dependencies\n */\nimport Clipboard from 'clipboard';\n\n/**\n * WordPress dependencies\n */\nimport { useRef, useEffect, useState } from '@wordpress/element';\nimport deprecated from '@wordpress/deprecated';\n\n/**\n * Copies the text to the clipboard when the element is clicked.\n *\n * @deprecated\n *\n * @param {React.RefObject<string | Element | NodeListOf<Element>>} ref Reference with the element.\n * @param {string|Function} text The text to copy.\n * @param {number} [timeout] Optional timeout to reset the returned\n * state. 4 seconds by default.\n *\n * @return {boolean} Whether or not the text has been copied. Resets after the\n * timeout.\n */\nexport default function useCopyOnClick( ref, text, timeout = 4000 ) {\n\tdeprecated( 'wp.compose.useCopyOnClick', {\n\t\tsince: '5.8',\n\t\talternative: 'wp.compose.useCopyToClipboard',\n\t} );\n\n\t/** @type {React.MutableRefObject<Clipboard | undefined>} */\n\tconst clipboardRef = useRef( undefined );\n\tconst [ hasCopied, setHasCopied ] = useState( false );\n\n\tuseEffect( () => {\n\t\t/** @type {number | undefined} */\n\t\tlet timeoutId;\n\n\t\tif ( ! ref.current ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Clipboard listens to click events.\n\t\tclipboardRef.current = new Clipboard( ref.current, {\n\t\t\ttext: () => ( typeof text === 'function' ? text() : text ),\n\t\t} );\n\n\t\tclipboardRef.current.on( 'success', ( { clearSelection, trigger } ) => {\n\t\t\t// Clearing selection will move focus back to the triggering button,\n\t\t\t// ensuring that it is not reset to the body, and further that it is\n\t\t\t// kept within the rendered node.\n\t\t\tclearSelection();\n\n\t\t\t// Handle ClipboardJS focus bug, see https://github.com/zenorocha/clipboard.js/issues/680\n\t\t\tif ( trigger ) {\n\t\t\t\t/** @type {HTMLElement} */ ( trigger ).focus();\n\t\t\t}\n\n\t\t\tif ( timeout ) {\n\t\t\t\tsetHasCopied( true );\n\t\t\t\tclearTimeout( timeoutId );\n\t\t\t\ttimeoutId = setTimeout( () => setHasCopied( false ), timeout );\n\t\t\t}\n\t\t} );\n\n\t\treturn () => {\n\t\t\tif ( clipboardRef.current ) {\n\t\t\t\tclipboardRef.current.destroy();\n\t\t\t}\n\t\t\tclearTimeout( timeoutId );\n\t\t};\n\t}, [ text, timeout, setHasCopied ] );\n\n\treturn hasCopied;\n}\n"],
5
- "mappings": ";AAGA,OAAO,eAAe;AAKtB,SAAS,QAAQ,WAAW,gBAAgB;AAC5C,OAAO,gBAAgB;AAeR,SAAR,eAAiC,KAAK,MAAM,UAAU,KAAO;AACnE,aAAY,6BAA6B;AAAA,IACxC,OAAO;AAAA,IACP,aAAa;AAAA,EACd,CAAE;AAGF,QAAM,eAAe,OAAQ,MAAU;AACvC,QAAM,CAAE,WAAW,YAAa,IAAI,SAAU,KAAM;AAEpD,YAAW,MAAM;AAEhB,QAAI;AAEJ,QAAK,CAAE,IAAI,SAAU;AACpB;AAAA,IACD;AAGA,iBAAa,UAAU,IAAI,UAAW,IAAI,SAAS;AAAA,MAClD,MAAM,MAAQ,OAAO,SAAS,aAAa,KAAK,IAAI;AAAA,IACrD,CAAE;AAEF,iBAAa,QAAQ,GAAI,WAAW,CAAE,EAAE,gBAAgB,QAAQ,MAAO;AAItE,qBAAe;AAGf,UAAK,SAAU;AACa,QAAE,QAAU,MAAM;AAAA,MAC9C;AAEA,UAAK,SAAU;AACd,qBAAc,IAAK;AACnB,qBAAc,SAAU;AACxB,oBAAY,WAAY,MAAM,aAAc,KAAM,GAAG,OAAQ;AAAA,MAC9D;AAAA,IACD,CAAE;AAEF,WAAO,MAAM;AACZ,UAAK,aAAa,SAAU;AAC3B,qBAAa,QAAQ,QAAQ;AAAA,MAC9B;AACA,mBAAc,SAAU;AAAA,IACzB;AAAA,EACD,GAAG,CAAE,MAAM,SAAS,YAAa,CAAE;AAEnC,SAAO;AACR;",
3
+ "sources": ["../../../src/hooks/use-copy-on-click/index.ts"],
4
+ "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { useEffect, useState } from '@wordpress/element';\nimport deprecated from '@wordpress/deprecated';\nimport type { RefObject } from 'react';\n\n/**\n * Internal dependencies\n */\nimport { clearSelection, copyToClipboard } from '../use-copy-to-clipboard';\n\n/**\n * Copies the text to the clipboard when the element is clicked.\n *\n * @deprecated\n * @param ref Reference with the element.\n * @param text The text to copy.\n * @param timeout Optional timeout to reset the returned\n * state. 4 seconds by default.\n * @return Whether or not the text has been copied. Resets after the\n * timeout.\n */\nexport default function useCopyOnClick(\n\tref: RefObject< string | Element | NodeListOf< Element > >,\n\ttext: string | ( () => string ),\n\ttimeout: number = 4000\n): boolean {\n\tdeprecated( 'wp.compose.useCopyOnClick', {\n\t\tsince: '5.8',\n\t\talternative: 'wp.compose.useCopyToClipboard',\n\t} );\n\n\tconst [ hasCopied, setHasCopied ] = useState( false );\n\n\tuseEffect( () => {\n\t\t// Flag to prevent state updates after unmount when the Promise resolves.\n\t\tlet isActive = true;\n\t\tlet timeoutId: ReturnType< typeof setTimeout > | undefined;\n\t\tif ( ! ref.current ) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet targets: Element[];\n\t\tif ( typeof ref.current === 'string' ) {\n\t\t\ttargets =\n\t\t\t\ttypeof document !== 'undefined'\n\t\t\t\t\t? Array.from( document.querySelectorAll( ref.current ) )\n\t\t\t\t\t: [];\n\t\t} else if (\n\t\t\t'length' in ref.current &&\n\t\t\ttypeof ref.current.length === 'number'\n\t\t) {\n\t\t\ttargets = Array.from( ref.current );\n\t\t} else {\n\t\t\ttargets = [ ref.current as Element ];\n\t\t}\n\n\t\tif ( targets.length === 0 ) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst handleClick = async ( event: Event ) => {\n\t\t\tconst trigger = event.currentTarget as Element;\n\t\t\tif ( ! trigger ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst success = await copyToClipboard(\n\t\t\t\ttypeof text === 'function' ? text() : text || '',\n\t\t\t\ttrigger\n\t\t\t);\n\t\t\tif ( ! isActive ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif ( success ) {\n\t\t\t\tclearSelection( trigger );\n\t\t\t\tif ( timeout ) {\n\t\t\t\t\tsetHasCopied( true );\n\t\t\t\t\tclearTimeout( timeoutId );\n\t\t\t\t\ttimeoutId = setTimeout(\n\t\t\t\t\t\t() => setHasCopied( false ),\n\t\t\t\t\t\ttimeout\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tfor ( const target of targets ) {\n\t\t\ttarget.addEventListener( 'click', handleClick );\n\t\t}\n\t\treturn () => {\n\t\t\tisActive = false;\n\t\t\tfor ( const target of targets ) {\n\t\t\t\ttarget.removeEventListener( 'click', handleClick );\n\t\t\t}\n\t\t\tclearTimeout( timeoutId );\n\t\t};\n\t}, [ ref, text, timeout ] );\n\n\treturn hasCopied;\n}\n"],
5
+ "mappings": ";AAGA,SAAS,WAAW,gBAAgB;AACpC,OAAO,gBAAgB;AAMvB,SAAS,gBAAgB,uBAAuB;AAajC,SAAR,eACN,KACA,MACA,UAAkB,KACR;AACV,aAAY,6BAA6B;AAAA,IACxC,OAAO;AAAA,IACP,aAAa;AAAA,EACd,CAAE;AAEF,QAAM,CAAE,WAAW,YAAa,IAAI,SAAU,KAAM;AAEpD,YAAW,MAAM;AAEhB,QAAI,WAAW;AACf,QAAI;AACJ,QAAK,CAAE,IAAI,SAAU;AACpB;AAAA,IACD;AAEA,QAAI;AACJ,QAAK,OAAO,IAAI,YAAY,UAAW;AACtC,gBACC,OAAO,aAAa,cACjB,MAAM,KAAM,SAAS,iBAAkB,IAAI,OAAQ,CAAE,IACrD,CAAC;AAAA,IACN,WACC,YAAY,IAAI,WAChB,OAAO,IAAI,QAAQ,WAAW,UAC7B;AACD,gBAAU,MAAM,KAAM,IAAI,OAAQ;AAAA,IACnC,OAAO;AACN,gBAAU,CAAE,IAAI,OAAmB;AAAA,IACpC;AAEA,QAAK,QAAQ,WAAW,GAAI;AAC3B;AAAA,IACD;AAEA,UAAM,cAAc,OAAQ,UAAkB;AAC7C,YAAM,UAAU,MAAM;AACtB,UAAK,CAAE,SAAU;AAChB;AAAA,MACD;AACA,YAAM,UAAU,MAAM;AAAA,QACrB,OAAO,SAAS,aAAa,KAAK,IAAI,QAAQ;AAAA,QAC9C;AAAA,MACD;AACA,UAAK,CAAE,UAAW;AACjB;AAAA,MACD;AACA,UAAK,SAAU;AACd,uBAAgB,OAAQ;AACxB,YAAK,SAAU;AACd,uBAAc,IAAK;AACnB,uBAAc,SAAU;AACxB,sBAAY;AAAA,YACX,MAAM,aAAc,KAAM;AAAA,YAC1B;AAAA,UACD;AAAA,QACD;AAAA,MACD;AAAA,IACD;AAEA,eAAY,UAAU,SAAU;AAC/B,aAAO,iBAAkB,SAAS,WAAY;AAAA,IAC/C;AACA,WAAO,MAAM;AACZ,iBAAW;AACX,iBAAY,UAAU,SAAU;AAC/B,eAAO,oBAAqB,SAAS,WAAY;AAAA,MAClD;AACA,mBAAc,SAAU;AAAA,IACzB;AAAA,EACD,GAAG,CAAE,KAAK,MAAM,OAAQ,CAAE;AAE1B,SAAO;AACR;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,41 @@
1
- // packages/compose/src/hooks/use-copy-to-clipboard/index.js
2
- import Clipboard from "clipboard";
1
+ // packages/compose/src/hooks/use-copy-to-clipboard/index.ts
3
2
  import { useRef, useLayoutEffect } from "@wordpress/element";
4
3
  import useRefEffect from "../use-ref-effect/index.mjs";
4
+ async function copyToClipboard(text, trigger) {
5
+ if (!trigger) {
6
+ return false;
7
+ }
8
+ const { ownerDocument } = trigger;
9
+ if (!ownerDocument) {
10
+ return false;
11
+ }
12
+ const { defaultView } = ownerDocument;
13
+ try {
14
+ if (defaultView?.navigator?.clipboard?.writeText) {
15
+ await defaultView.navigator.clipboard.writeText(text);
16
+ return true;
17
+ }
18
+ const textarea = ownerDocument.createElement("textarea");
19
+ textarea.value = text;
20
+ textarea.setAttribute("readonly", "");
21
+ textarea.style.position = "fixed";
22
+ textarea.style.left = "-9999px";
23
+ textarea.style.top = "-9999px";
24
+ ownerDocument.body.appendChild(textarea);
25
+ textarea.select();
26
+ const success = ownerDocument.execCommand("copy");
27
+ textarea.remove();
28
+ return success;
29
+ } catch {
30
+ return false;
31
+ }
32
+ }
33
+ function clearSelection(trigger) {
34
+ if ("focus" in trigger && typeof trigger.focus === "function") {
35
+ trigger.focus();
36
+ }
37
+ trigger.ownerDocument?.defaultView?.getSelection()?.removeAllRanges();
38
+ }
5
39
  function useUpdatedRef(value) {
6
40
  const ref = useRef(value);
7
41
  useLayoutEffect(() => {
@@ -13,23 +47,30 @@ function useCopyToClipboard(text, onSuccess) {
13
47
  const textRef = useUpdatedRef(text);
14
48
  const onSuccessRef = useUpdatedRef(onSuccess);
15
49
  return useRefEffect((node) => {
16
- const clipboard = new Clipboard(node, {
17
- text() {
18
- return typeof textRef.current === "function" ? textRef.current() : textRef.current || "";
50
+ let isActive = true;
51
+ const handleClick = async () => {
52
+ const textToCopy = typeof textRef.current === "function" ? textRef.current() : textRef.current || "";
53
+ const success = await copyToClipboard(textToCopy, node);
54
+ if (!isActive) {
55
+ return;
19
56
  }
20
- });
21
- clipboard.on("success", ({ clearSelection }) => {
22
- clearSelection();
23
- if (onSuccessRef.current) {
24
- onSuccessRef.current();
57
+ if (success) {
58
+ clearSelection(node);
59
+ if (onSuccessRef.current) {
60
+ onSuccessRef.current();
61
+ }
25
62
  }
26
- });
63
+ };
64
+ node.addEventListener("click", handleClick);
27
65
  return () => {
28
- clipboard.destroy();
66
+ isActive = false;
67
+ node.removeEventListener("click", handleClick);
29
68
  };
30
69
  }, []);
31
70
  }
32
71
  export {
72
+ clearSelection,
73
+ copyToClipboard,
33
74
  useCopyToClipboard as default
34
75
  };
35
76
  //# sourceMappingURL=index.mjs.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../../src/hooks/use-copy-to-clipboard/index.js"],
4
- "sourcesContent": ["/**\n * External dependencies\n */\nimport Clipboard from 'clipboard';\n\n/**\n * WordPress dependencies\n */\nimport { useRef, useLayoutEffect } from '@wordpress/element';\n\n/**\n * Internal dependencies\n */\nimport useRefEffect from '../use-ref-effect';\n\n/**\n * @template T\n * @param {T} value\n * @return {React.RefObject<T>} The updated ref\n */\nfunction useUpdatedRef( value ) {\n\tconst ref = useRef( value );\n\tuseLayoutEffect( () => {\n\t\tref.current = value;\n\t}, [ value ] );\n\treturn ref;\n}\n\n/**\n * Copies the given text to the clipboard when the element is clicked.\n *\n * @template {HTMLElement} TElementType\n * @param {string | (() => string)} text The text to copy. Use a function if not\n * already available and expensive to compute.\n * @param {Function} onSuccess Called when to text is copied.\n *\n * @return {React.Ref<TElementType>} A ref to assign to the target element.\n */\nexport default function useCopyToClipboard( text, onSuccess ) {\n\t// Store the dependencies as refs and continuously update them so they're\n\t// fresh when the callback is called.\n\tconst textRef = useUpdatedRef( text );\n\tconst onSuccessRef = useUpdatedRef( onSuccess );\n\treturn useRefEffect( ( node ) => {\n\t\t// Clipboard listens to click events.\n\t\tconst clipboard = new Clipboard( node, {\n\t\t\ttext() {\n\t\t\t\treturn typeof textRef.current === 'function'\n\t\t\t\t\t? textRef.current()\n\t\t\t\t\t: textRef.current || '';\n\t\t\t},\n\t\t} );\n\n\t\tclipboard.on( 'success', ( { clearSelection } ) => {\n\t\t\t// Clearing selection will move focus back to the triggering\n\t\t\t// button, ensuring that it is not reset to the body, and\n\t\t\t// further that it is kept within the rendered node.\n\t\t\tclearSelection();\n\n\t\t\tif ( onSuccessRef.current ) {\n\t\t\t\tonSuccessRef.current();\n\t\t\t}\n\t\t} );\n\n\t\treturn () => {\n\t\t\tclipboard.destroy();\n\t\t};\n\t}, [] );\n}\n"],
5
- "mappings": ";AAGA,OAAO,eAAe;AAKtB,SAAS,QAAQ,uBAAuB;AAKxC,OAAO,kBAAkB;AAOzB,SAAS,cAAe,OAAQ;AAC/B,QAAM,MAAM,OAAQ,KAAM;AAC1B,kBAAiB,MAAM;AACtB,QAAI,UAAU;AAAA,EACf,GAAG,CAAE,KAAM,CAAE;AACb,SAAO;AACR;AAYe,SAAR,mBAAqC,MAAM,WAAY;AAG7D,QAAM,UAAU,cAAe,IAAK;AACpC,QAAM,eAAe,cAAe,SAAU;AAC9C,SAAO,aAAc,CAAE,SAAU;AAEhC,UAAM,YAAY,IAAI,UAAW,MAAM;AAAA,MACtC,OAAO;AACN,eAAO,OAAO,QAAQ,YAAY,aAC/B,QAAQ,QAAQ,IAChB,QAAQ,WAAW;AAAA,MACvB;AAAA,IACD,CAAE;AAEF,cAAU,GAAI,WAAW,CAAE,EAAE,eAAe,MAAO;AAIlD,qBAAe;AAEf,UAAK,aAAa,SAAU;AAC3B,qBAAa,QAAQ;AAAA,MACtB;AAAA,IACD,CAAE;AAEF,WAAO,MAAM;AACZ,gBAAU,QAAQ;AAAA,IACnB;AAAA,EACD,GAAG,CAAC,CAAE;AACP;",
3
+ "sources": ["../../../src/hooks/use-copy-to-clipboard/index.ts"],
4
+ "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { useRef, useLayoutEffect } from '@wordpress/element';\nimport type { MutableRefObject, RefCallback } from 'react';\n\n/**\n * Internal dependencies\n */\nimport useRefEffect from '../use-ref-effect';\n\n/**\n * Copies text to the clipboard using the Clipboard API when available,\n * with a fallback for non-secure contexts (e.g. HTTP) and older browsers.\n *\n * @param text The text to copy.\n * @param trigger The element that triggered the copy.\n * @return Resolves to true if successful, false otherwise.\n */\nexport async function copyToClipboard(\n\ttext: string,\n\ttrigger: Element | null\n): Promise< boolean > {\n\tif ( ! trigger ) {\n\t\treturn false;\n\t}\n\tconst { ownerDocument } = trigger;\n\tif ( ! ownerDocument ) {\n\t\treturn false;\n\t}\n\tconst { defaultView } = ownerDocument;\n\ttry {\n\t\tif ( defaultView?.navigator?.clipboard?.writeText ) {\n\t\t\tawait defaultView.navigator.clipboard.writeText( text );\n\t\t\treturn true;\n\t\t}\n\t\t// Fallback for non-secure contexts (HTTP) and older browsers.\n\t\tconst textarea = ownerDocument.createElement( 'textarea' );\n\t\ttextarea.value = text;\n\t\ttextarea.setAttribute( 'readonly', '' );\n\t\ttextarea.style.position = 'fixed';\n\t\ttextarea.style.left = '-9999px';\n\t\ttextarea.style.top = '-9999px';\n\t\townerDocument.body.appendChild( textarea );\n\t\ttextarea.select();\n\t\tconst success = ownerDocument.execCommand( 'copy' );\n\t\ttextarea.remove();\n\t\treturn success;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Clears the current selection and restores focus to the trigger element.\n *\n * @param trigger The element that triggered the copy.\n */\nexport function clearSelection( trigger: Element ): void {\n\tif ( 'focus' in trigger && typeof trigger.focus === 'function' ) {\n\t\ttrigger.focus();\n\t}\n\ttrigger.ownerDocument?.defaultView?.getSelection()?.removeAllRanges();\n}\n\n/**\n * @template T\n * @param value\n * @return A ref to assign to the target element.\n */\nfunction useUpdatedRef< T >( value: T ): MutableRefObject< T > {\n\tconst ref = useRef< T >( value );\n\tuseLayoutEffect( () => {\n\t\tref.current = value;\n\t}, [ value ] );\n\treturn ref;\n}\n\n/**\n * Copies the given text to the clipboard when the element is clicked.\n *\n * @template T\n * @param text The text to copy. Use a function if not\n * already available and expensive to compute.\n * @param onSuccess Called when to text is copied.\n *\n * @return A ref to assign to the target element.\n */\nexport default function useCopyToClipboard< T extends HTMLElement >(\n\ttext: string | ( () => string ),\n\tonSuccess?: () => void\n): RefCallback< T > {\n\tconst textRef = useUpdatedRef( text );\n\tconst onSuccessRef = useUpdatedRef( onSuccess );\n\treturn useRefEffect( ( node ) => {\n\t\t// Flag to prevent callbacks after unmount when the Promise resolves.\n\t\tlet isActive = true;\n\t\tconst handleClick = async () => {\n\t\t\tconst textToCopy =\n\t\t\t\ttypeof textRef.current === 'function'\n\t\t\t\t\t? textRef.current()\n\t\t\t\t\t: textRef.current || '';\n\t\t\tconst success = await copyToClipboard( textToCopy, node );\n\t\t\tif ( ! isActive ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif ( success ) {\n\t\t\t\tclearSelection( node );\n\t\t\t\tif ( onSuccessRef.current ) {\n\t\t\t\t\tonSuccessRef.current();\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tnode.addEventListener( 'click', handleClick );\n\t\treturn () => {\n\t\t\tisActive = false;\n\t\t\tnode.removeEventListener( 'click', handleClick );\n\t\t};\n\t}, [] );\n}\n"],
5
+ "mappings": ";AAGA,SAAS,QAAQ,uBAAuB;AAMxC,OAAO,kBAAkB;AAUzB,eAAsB,gBACrB,MACA,SACqB;AACrB,MAAK,CAAE,SAAU;AAChB,WAAO;AAAA,EACR;AACA,QAAM,EAAE,cAAc,IAAI;AAC1B,MAAK,CAAE,eAAgB;AACtB,WAAO;AAAA,EACR;AACA,QAAM,EAAE,YAAY,IAAI;AACxB,MAAI;AACH,QAAK,aAAa,WAAW,WAAW,WAAY;AACnD,YAAM,YAAY,UAAU,UAAU,UAAW,IAAK;AACtD,aAAO;AAAA,IACR;AAEA,UAAM,WAAW,cAAc,cAAe,UAAW;AACzD,aAAS,QAAQ;AACjB,aAAS,aAAc,YAAY,EAAG;AACtC,aAAS,MAAM,WAAW;AAC1B,aAAS,MAAM,OAAO;AACtB,aAAS,MAAM,MAAM;AACrB,kBAAc,KAAK,YAAa,QAAS;AACzC,aAAS,OAAO;AAChB,UAAM,UAAU,cAAc,YAAa,MAAO;AAClD,aAAS,OAAO;AAChB,WAAO;AAAA,EACR,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAOO,SAAS,eAAgB,SAAyB;AACxD,MAAK,WAAW,WAAW,OAAO,QAAQ,UAAU,YAAa;AAChE,YAAQ,MAAM;AAAA,EACf;AACA,UAAQ,eAAe,aAAa,aAAa,GAAG,gBAAgB;AACrE;AAOA,SAAS,cAAoB,OAAkC;AAC9D,QAAM,MAAM,OAAa,KAAM;AAC/B,kBAAiB,MAAM;AACtB,QAAI,UAAU;AAAA,EACf,GAAG,CAAE,KAAM,CAAE;AACb,SAAO;AACR;AAYe,SAAR,mBACN,MACA,WACmB;AACnB,QAAM,UAAU,cAAe,IAAK;AACpC,QAAM,eAAe,cAAe,SAAU;AAC9C,SAAO,aAAc,CAAE,SAAU;AAEhC,QAAI,WAAW;AACf,UAAM,cAAc,YAAY;AAC/B,YAAM,aACL,OAAO,QAAQ,YAAY,aACxB,QAAQ,QAAQ,IAChB,QAAQ,WAAW;AACvB,YAAM,UAAU,MAAM,gBAAiB,YAAY,IAAK;AACxD,UAAK,CAAE,UAAW;AACjB;AAAA,MACD;AACA,UAAK,SAAU;AACd,uBAAgB,IAAK;AACrB,YAAK,aAAa,SAAU;AAC3B,uBAAa,QAAQ;AAAA,QACtB;AAAA,MACD;AAAA,IACD;AACA,SAAK,iBAAkB,SAAS,WAAY;AAC5C,WAAO,MAAM;AACZ,iBAAW;AACX,WAAK,oBAAqB,SAAS,WAAY;AAAA,IAChD;AAAA,EACD,GAAG,CAAC,CAAE;AACP;",
6
6
  "names": []
7
7
  }
@@ -1,15 +1,14 @@
1
+ import type { RefObject } from 'react';
1
2
  /**
2
3
  * Copies the text to the clipboard when the element is clicked.
3
4
  *
4
5
  * @deprecated
5
- *
6
- * @param {React.RefObject<string | Element | NodeListOf<Element>>} ref Reference with the element.
7
- * @param {string|Function} text The text to copy.
8
- * @param {number} [timeout] Optional timeout to reset the returned
9
- * state. 4 seconds by default.
10
- *
11
- * @return {boolean} Whether or not the text has been copied. Resets after the
12
- * timeout.
6
+ * @param ref Reference with the element.
7
+ * @param text The text to copy.
8
+ * @param timeout Optional timeout to reset the returned
9
+ * state. 4 seconds by default.
10
+ * @return Whether or not the text has been copied. Resets after the
11
+ * timeout.
13
12
  */
14
- export default function useCopyOnClick(ref: React.RefObject<string | Element | NodeListOf<Element>>, text: string | Function, timeout?: number): boolean;
13
+ export default function useCopyOnClick(ref: RefObject<string | Element | NodeListOf<Element>>, text: string | (() => string), timeout?: number): boolean;
15
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-copy-on-click/index.js"],"names":[],"mappings":"AAWA;;;;;;;;;;;;GAYG;AACH,4CARW,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,QACvD,MAAM,WAAS,YACf,MAAM,GAGL,OAAO,CAqDlB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-copy-on-click/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAOvC;;;;;;;;;;GAUG;AACH,MAAM,CAAC,OAAO,UAAU,cAAc,CACrC,GAAG,EAAE,SAAS,CAAE,MAAM,GAAG,OAAO,GAAG,UAAU,CAAE,OAAO,CAAE,CAAE,EAC1D,IAAI,EAAE,MAAM,GAAG,CAAE,MAAM,MAAM,CAAE,EAC/B,OAAO,GAAE,MAAa,GACpB,OAAO,CAyET"}
@@ -1,12 +1,28 @@
1
+ import type { RefCallback } from 'react';
2
+ /**
3
+ * Copies text to the clipboard using the Clipboard API when available,
4
+ * with a fallback for non-secure contexts (e.g. HTTP) and older browsers.
5
+ *
6
+ * @param text The text to copy.
7
+ * @param trigger The element that triggered the copy.
8
+ * @return Resolves to true if successful, false otherwise.
9
+ */
10
+ export declare function copyToClipboard(text: string, trigger: Element | null): Promise<boolean>;
11
+ /**
12
+ * Clears the current selection and restores focus to the trigger element.
13
+ *
14
+ * @param trigger The element that triggered the copy.
15
+ */
16
+ export declare function clearSelection(trigger: Element): void;
1
17
  /**
2
18
  * Copies the given text to the clipboard when the element is clicked.
3
19
  *
4
- * @template {HTMLElement} TElementType
5
- * @param {string | (() => string)} text The text to copy. Use a function if not
6
- * already available and expensive to compute.
7
- * @param {Function} onSuccess Called when to text is copied.
20
+ * @template T
21
+ * @param text The text to copy. Use a function if not
22
+ * already available and expensive to compute.
23
+ * @param onSuccess Called when to text is copied.
8
24
  *
9
- * @return {React.Ref<TElementType>} A ref to assign to the target element.
25
+ * @return A ref to assign to the target element.
10
26
  */
11
- export default function useCopyToClipboard<TElementType extends HTMLElement>(text: string | (() => string), onSuccess: Function): React.Ref<TElementType>;
27
+ export default function useCopyToClipboard<T extends HTMLElement>(text: string | (() => string), onSuccess?: () => void): RefCallback<T>;
12
28
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-copy-to-clipboard/index.js"],"names":[],"mappings":"AA4BA;;;;;;;;;GASG;AACH,2CAP2B,YAAY,SAAzB,WAAY,QACf,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,wBAItB,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAgClC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-copy-to-clipboard/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAoB,WAAW,EAAE,MAAM,OAAO,CAAC;AAO3D;;;;;;;GAOG;AACH,wBAAsB,eAAe,CACpC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,GAAG,IAAI,GACrB,OAAO,CAAE,OAAO,CAAE,CA6BpB;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAE,OAAO,EAAE,OAAO,GAAI,IAAI,CAKvD;AAeD;;;;;;;;;GASG;AACH,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAE,CAAC,SAAS,WAAW,EAChE,IAAI,EAAE,MAAM,GAAG,CAAE,MAAM,MAAM,CAAE,EAC/B,SAAS,CAAC,EAAE,MAAM,IAAI,GACpB,WAAW,CAAE,CAAC,CAAE,CA4BlB"}
@@ -4,6 +4,6 @@ export default useIsomorphicLayoutEffect;
4
4
  * server rendered components (SSR) because currently React
5
5
  * throws a warning when using useLayoutEffect in that environment.
6
6
  */
7
- declare const useIsomorphicLayoutEffect: typeof useEffect;
8
- import { useEffect } from '@wordpress/element';
7
+ declare const useIsomorphicLayoutEffect: typeof useLayoutEffect;
8
+ import { useLayoutEffect } from '@wordpress/element';
9
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-isomorphic-layout-effect/index.js"],"names":[],"mappings":";AAKA;;;;GAIG;AACH,0DAC6D;0BARlB,oBAAoB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-isomorphic-layout-effect/index.js"],"names":[],"mappings":";AAKA;;;;GAIG;AACH,gEAC6D;gCARlB,oBAAoB"}