twico-ui 1.37.0 → 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,21 @@
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
+
13
+ ## [1.37.1](https://github.com/EncryptionL/twico-ui/compare/v1.37.0...v1.37.1) (2026-09-14)
14
+
15
+
16
+ ### Bug Fixes
17
+
18
+ * **image:** let explicit width/height win over fit's container fill ([#378](https://github.com/EncryptionL/twico-ui/issues/378)) ([770823d](https://github.com/EncryptionL/twico-ui/commit/770823ddced42288e623bb00e8f92886bd01d373))
19
+
5
20
  # [1.37.0](https://github.com/EncryptionL/twico-ui/compare/v1.36.1...v1.37.0) (2026-09-11)
6
21
 
7
22
 
package/dist/index.cjs CHANGED
@@ -325,6 +325,8 @@ var Image = React51__namespace.default.forwardRef(function Image2({
325
325
  className = "",
326
326
  style,
327
327
  onError,
328
+ width,
329
+ height,
328
330
  ...rest
329
331
  }, ref) {
330
332
  const __twcStyles = useScopedStyles("twc-image-styles", IMAGE_CSS);
@@ -334,6 +336,14 @@ var Image = React51__namespace.default.forwardRef(function Image2({
334
336
  objectFit: fit,
335
337
  borderRadius: radius ? `var(--radius-${radius})` : void 0,
336
338
  aspectRatio,
339
+ // #378: only when `fit` is set, also emit explicit width/height as inline style — that's the sole case
340
+ // where the `[data-fit] { width/height: 100% }` rule overrides the width/height ATTRIBUTES (a
341
+ // presentational attr loses to a real CSS rule), silently sizing `<Image width height fit>` to the
342
+ // container/intrinsic size. Inline style beats that rule so the props mean what they say (object-fit
343
+ // still crops within the box), placed before `...style` so a caller's own `style` still wins. Gated on
344
+ // `fit` so the no-fit path stays attribute-only — a consumer's own CSS/className can still override it.
345
+ ...fit && width != null ? { width } : null,
346
+ ...fit && height != null ? { height } : null,
337
347
  ...style
338
348
  };
339
349
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -347,6 +357,8 @@ var Image = React51__namespace.default.forwardRef(function Image2({
347
357
  loading,
348
358
  className: `twc-image ${className}`.trim(),
349
359
  "data-fit": fit || void 0,
360
+ width,
361
+ height,
350
362
  style: s,
351
363
  onError: (e) => {
352
364
  onError?.(e);
@@ -10655,8 +10667,10 @@ function Datatable({
10655
10667
  return;
10656
10668
  }
10657
10669
  if (k === "v") {
10658
- e.preventDefault();
10659
- pasteSelection();
10670
+ if (typeof navigator !== "undefined" && navigator.clipboard && navigator.clipboard.readText) {
10671
+ e.preventDefault();
10672
+ pasteSelection();
10673
+ }
10660
10674
  return;
10661
10675
  }
10662
10676
  }
@@ -10876,19 +10890,19 @@ function Datatable({
10876
10890
  const copyTypeOf = (col) => col && col.copyType != null ? col.copyType : col && col.type === "number" ? "number" : "text";
10877
10891
  const writeClipboard = (text) => {
10878
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();
10879
10901
  try {
10880
- if (typeof document !== "undefined") {
10881
- const ta = document.createElement("textarea");
10882
- ta.value = text;
10883
- ta.style.position = "fixed";
10884
- ta.style.opacity = "0";
10885
- document.body.appendChild(ta);
10886
- ta.focus();
10887
- ta.select();
10888
- document.execCommand("copy");
10889
- document.body.removeChild(ta);
10890
- }
10902
+ document.execCommand("copy");
10891
10903
  } catch {
10904
+ } finally {
10905
+ if (ta.parentNode) ta.parentNode.removeChild(ta);
10892
10906
  }
10893
10907
  };
10894
10908
  try {
@@ -10959,19 +10973,8 @@ function Datatable({
10959
10973
  announceClip(`Copied ${n} cell${n === 1 ? "" : "s"}`, cellRect);
10960
10974
  }
10961
10975
  };
10962
- const pasteSelection = async () => {
10963
- if (!cellRect) return;
10964
- let text = null;
10965
- try {
10966
- if (typeof navigator !== "undefined" && navigator.clipboard && navigator.clipboard.readText) text = await navigator.clipboard.readText();
10967
- } catch {
10968
- setClipboardMsg("Paste unavailable \u2014 clipboard permission denied");
10969
- return;
10970
- }
10971
- if (text == null) {
10972
- setClipboardMsg("Nothing to paste");
10973
- return;
10974
- }
10976
+ const applyPastedText = (text) => {
10977
+ if (text == null || !cellRect) return;
10975
10978
  const inApp = clipboardRef.current && clipboardRef.current.tsv === text ? clipboardRef.current : null;
10976
10979
  let matrix;
10977
10980
  if (inApp) matrix = inApp.matrix;
@@ -11016,6 +11019,31 @@ function Datatable({
11016
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;
11017
11020
  announceClip(`Pasted ${written} cell${written === 1 ? "" : "s"}${skipped ? `, ${skipped} skipped (incompatible column)` : ""}`, pasteRect);
11018
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
+ };
11019
11047
  React51__namespace.default.useEffect(() => {
11020
11048
  if (!editing) return;
11021
11049
  const onDown = (e) => {
@@ -12031,6 +12059,7 @@ function Datatable({
12031
12059
  "aria-activedescendant": activeCellId,
12032
12060
  "aria-busy": loading || void 0,
12033
12061
  onKeyDown: onGridKeyDown,
12062
+ onPaste: onGridPaste,
12034
12063
  children: [
12035
12064
  /* @__PURE__ */ jsxRuntime.jsx("thead", { ref: theadRef, children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { role: "row", "aria-rowindex": 1, children: [
12036
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,
@@ -19105,14 +19134,36 @@ function Code({
19105
19134
  const timerRef = React51__namespace.default.useRef(null);
19106
19135
  React51__namespace.default.useEffect(() => () => clearTimeout(timerRef.current), []);
19107
19136
  const doCopy = () => {
19108
- if (typeof navigator === "undefined" || !navigator.clipboard) return;
19109
19137
  const text = typeof children === "string" ? children : rootRef.current?.textContent ?? "";
19110
- navigator.clipboard.writeText(text).then(() => {
19138
+ const flash = () => {
19111
19139
  setCopied(true);
19112
19140
  clearTimeout(timerRef.current);
19113
19141
  timerRef.current = setTimeout(() => setCopied(false), 2e3);
19114
- }).catch(() => {
19115
- });
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();
19116
19167
  };
19117
19168
  return /* @__PURE__ */ jsxRuntime.jsxs(Tag2, { ref: rootRef, className: `twc-code ${className}`.trim(), "data-block": block || void 0, ...rest, children: [
19118
19169
  __twcStyles,