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 +15 -0
- package/dist/index.cjs +81 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.mjs +81 -30
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
10659
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
19138
|
+
const flash = () => {
|
|
19111
19139
|
setCopied(true);
|
|
19112
19140
|
clearTimeout(timerRef.current);
|
|
19113
19141
|
timerRef.current = setTimeout(() => setCopied(false), 2e3);
|
|
19114
|
-
}
|
|
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,
|