twico-ui 1.37.1 → 1.37.2

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/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  All notable changes to Twico UI are documented here (newest first). This file is generated automatically on each release by [semantic-release](https://semantic-release.gitbook.io/) from [Conventional Commits](https://www.conventionalcommits.org/) — do not edit it by hand. The same notes appear on each [GitHub Release](https://github.com/EncryptionL/twico-ui/releases).
4
4
 
5
+ ## [1.37.2](https://github.com/EncryptionL/twico-ui/compare/v1.37.1...v1.37.2) (2026-09-18)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * **datatable,code:** clipboard paste/copy work outside a secure context ([#380](https://github.com/EncryptionL/twico-ui/issues/380)) ([29aa913](https://github.com/EncryptionL/twico-ui/commit/29aa91382df6df40333ec49132b8883ec6f56b9f)), closes [#343](https://github.com/EncryptionL/twico-ui/issues/343)
11
+ * **datatable,code:** harden [#380](https://github.com/EncryptionL/twico-ui/issues/380) clipboard fix (review follow-ups) ([849c9f8](https://github.com/EncryptionL/twico-ui/commit/849c9f88d331e38d5fd1faf41401b8424e904541))
12
+
5
13
  ## [1.37.1](https://github.com/EncryptionL/twico-ui/compare/v1.37.0...v1.37.1) (2026-09-14)
6
14
 
7
15
 
package/dist/index.cjs CHANGED
@@ -10667,8 +10667,10 @@ function Datatable({
10667
10667
  return;
10668
10668
  }
10669
10669
  if (k === "v") {
10670
- e.preventDefault();
10671
- pasteSelection();
10670
+ if (typeof navigator !== "undefined" && navigator.clipboard && navigator.clipboard.readText) {
10671
+ e.preventDefault();
10672
+ pasteSelection();
10673
+ }
10672
10674
  return;
10673
10675
  }
10674
10676
  }
@@ -10888,19 +10890,19 @@ function Datatable({
10888
10890
  const copyTypeOf = (col) => col && col.copyType != null ? col.copyType : col && col.type === "number" ? "number" : "text";
10889
10891
  const writeClipboard = (text) => {
10890
10892
  const execFallback = () => {
10893
+ if (typeof document === "undefined") return;
10894
+ const ta = document.createElement("textarea");
10895
+ ta.value = text;
10896
+ ta.style.position = "fixed";
10897
+ ta.style.opacity = "0";
10898
+ document.body.appendChild(ta);
10899
+ ta.focus();
10900
+ ta.select();
10891
10901
  try {
10892
- if (typeof document !== "undefined") {
10893
- const ta = document.createElement("textarea");
10894
- ta.value = text;
10895
- ta.style.position = "fixed";
10896
- ta.style.opacity = "0";
10897
- document.body.appendChild(ta);
10898
- ta.focus();
10899
- ta.select();
10900
- document.execCommand("copy");
10901
- document.body.removeChild(ta);
10902
- }
10902
+ document.execCommand("copy");
10903
10903
  } catch {
10904
+ } finally {
10905
+ if (ta.parentNode) ta.parentNode.removeChild(ta);
10904
10906
  }
10905
10907
  };
10906
10908
  try {
@@ -10971,19 +10973,8 @@ function Datatable({
10971
10973
  announceClip(`Copied ${n} cell${n === 1 ? "" : "s"}`, cellRect);
10972
10974
  }
10973
10975
  };
10974
- const pasteSelection = async () => {
10975
- if (!cellRect) return;
10976
- let text = null;
10977
- try {
10978
- if (typeof navigator !== "undefined" && navigator.clipboard && navigator.clipboard.readText) text = await navigator.clipboard.readText();
10979
- } catch {
10980
- setClipboardMsg("Paste unavailable \u2014 clipboard permission denied");
10981
- return;
10982
- }
10983
- if (text == null) {
10984
- setClipboardMsg("Nothing to paste");
10985
- return;
10986
- }
10976
+ const applyPastedText = (text) => {
10977
+ if (text == null || !cellRect) return;
10987
10978
  const inApp = clipboardRef.current && clipboardRef.current.tsv === text ? clipboardRef.current : null;
10988
10979
  let matrix;
10989
10980
  if (inApp) matrix = inApp.matrix;
@@ -11028,6 +11019,31 @@ function Datatable({
11028
11019
  const pasteRect = written ? { r0: cellRect.r0, c0: cellRect.c0, r1: Math.min(cellRect.r0 + matrix.length - 1, leafRows.length - 1), c1: Math.min(cellRect.c0 + maxCols - 1, ordered.length - 1) } : null;
11029
11020
  announceClip(`Pasted ${written} cell${written === 1 ? "" : "s"}${skipped ? `, ${skipped} skipped (incompatible column)` : ""}`, pasteRect);
11030
11021
  };
11022
+ const pasteSelection = async () => {
11023
+ if (!cellRect) return;
11024
+ let text = null;
11025
+ try {
11026
+ if (typeof navigator !== "undefined" && navigator.clipboard && navigator.clipboard.readText) text = await navigator.clipboard.readText();
11027
+ } catch {
11028
+ setClipboardMsg("Paste unavailable \u2014 clipboard permission denied");
11029
+ return;
11030
+ }
11031
+ if (text == null) {
11032
+ setClipboardMsg("Nothing to paste");
11033
+ return;
11034
+ }
11035
+ applyPastedText(text);
11036
+ };
11037
+ const onGridPaste = (e) => {
11038
+ if (e.target.closest("input, textarea, select, [contenteditable='true']") || editing) return;
11039
+ if (!e.target.closest(".twc-dt__td[data-r]")) return;
11040
+ if (!(enableClipboard && selectionMode === "cell") || !cellRect) return;
11041
+ if (typeof navigator !== "undefined" && navigator.clipboard && navigator.clipboard.readText) return;
11042
+ const text = e.clipboardData ? e.clipboardData.getData("text/plain") : null;
11043
+ if (text == null || text === "") return;
11044
+ e.preventDefault();
11045
+ applyPastedText(text);
11046
+ };
11031
11047
  React51__namespace.default.useEffect(() => {
11032
11048
  if (!editing) return;
11033
11049
  const onDown = (e) => {
@@ -12043,6 +12059,7 @@ function Datatable({
12043
12059
  "aria-activedescendant": activeCellId,
12044
12060
  "aria-busy": loading || void 0,
12045
12061
  onKeyDown: onGridKeyDown,
12062
+ onPaste: onGridPaste,
12046
12063
  children: [
12047
12064
  /* @__PURE__ */ jsxRuntime.jsx("thead", { ref: theadRef, children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { role: "row", "aria-rowindex": 1, children: [
12048
12065
  hasExpandCol ? /* @__PURE__ */ jsxRuntime.jsx("th", { className: "twc-dt__th twc-dt__expand-cell", role: "columnheader", "aria-label": "Expand", "data-pin": "left", "data-pin-edge": checkboxSelection || showRowNum || visLeft.length ? void 0 : "left", style: { insetInlineStart: 0, width: EXP_W, minWidth: EXP_W } }) : null,
@@ -19117,14 +19134,36 @@ function Code({
19117
19134
  const timerRef = React51__namespace.default.useRef(null);
19118
19135
  React51__namespace.default.useEffect(() => () => clearTimeout(timerRef.current), []);
19119
19136
  const doCopy = () => {
19120
- if (typeof navigator === "undefined" || !navigator.clipboard) return;
19121
19137
  const text = typeof children === "string" ? children : rootRef.current?.textContent ?? "";
19122
- navigator.clipboard.writeText(text).then(() => {
19138
+ const flash = () => {
19123
19139
  setCopied(true);
19124
19140
  clearTimeout(timerRef.current);
19125
19141
  timerRef.current = setTimeout(() => setCopied(false), 2e3);
19126
- }).catch(() => {
19127
- });
19142
+ };
19143
+ const execFallback = () => {
19144
+ if (typeof document === "undefined") return;
19145
+ const ta = document.createElement("textarea");
19146
+ ta.value = text;
19147
+ ta.style.position = "fixed";
19148
+ ta.style.opacity = "0";
19149
+ document.body.appendChild(ta);
19150
+ ta.focus();
19151
+ ta.select();
19152
+ try {
19153
+ if (document.execCommand("copy")) flash();
19154
+ } catch {
19155
+ } finally {
19156
+ if (ta.parentNode) ta.parentNode.removeChild(ta);
19157
+ }
19158
+ };
19159
+ try {
19160
+ if (typeof navigator !== "undefined" && navigator.clipboard && navigator.clipboard.writeText) {
19161
+ navigator.clipboard.writeText(text).then(flash).catch(execFallback);
19162
+ return;
19163
+ }
19164
+ } catch {
19165
+ }
19166
+ execFallback();
19128
19167
  };
19129
19168
  return /* @__PURE__ */ jsxRuntime.jsxs(Tag2, { ref: rootRef, className: `twc-code ${className}`.trim(), "data-block": block || void 0, ...rest, children: [
19130
19169
  __twcStyles,