@aiquants/spreadsheet 0.15.0 → 0.16.1
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 +105 -0
- package/README.md +209 -23
- package/dist/index.cjs +8 -8
- package/dist/index.d.cts +123 -5
- package/dist/index.d.ts +123 -5
- package/dist/index.js +4315 -4166
- package/dist/styles/spreadsheet.css +1 -1
- package/dist/styles/spreadsheet.standalone.css +1 -1
- package/dist/tsv.cjs +8 -1
- package/dist/tsv.d.cts +79 -4
- package/dist/tsv.d.ts +79 -4
- package/dist/tsv.js +85 -3
- package/dist/view-BkwCUzTl.cjs +1 -0
- package/dist/view-DycJZi_t.js +41 -0
- package/dist/view.cjs +1 -1
- package/dist/view.d.cts +31 -7
- package/dist/view.d.ts +31 -7
- package/dist/view.js +12 -12
- package/package.json +3 -3
- package/src/Spreadsheet.tsx +110 -14
- package/src/clipboard/tsv.ts +236 -89
- package/src/clipboard/useClipboard.ts +3 -2
- package/src/commands/useCommands.ts +43 -1
- package/src/grid/GridBody.tsx +51 -6
- package/src/header/resizeGates.ts +34 -0
- package/src/header/useHeaderResize.ts +83 -16
- package/src/index.ts +1 -1
- package/src/keyboard/shortcuts.ts +67 -0
- package/src/model/dataAdapter.ts +74 -0
- package/src/overlay/SelectionOverlay.tsx +22 -2
- package/src/styles/spreadsheet.css +193 -123
- package/src/tsv.ts +17 -10
- package/src/view.ts +67 -14
- package/dist/tsv-B6TDExlk.js +0 -91
- package/dist/tsv-BHXbWK5W.cjs +0 -8
package/src/tsv.ts
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview The TSV codec the grid copies and pastes with (
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* @fileoverview The TSV codec the grid copies and pastes with (one trailing line terminator dropped, empty text read as
|
|
3
|
+
* one empty cell, ragged rows padded with "", a field quoted when it holds a tab or a line break or starts with `"`, so
|
|
4
|
+
* `parseTsv(serializeTsv(m))` equals `m` for every rectangular matrix of strings of at least 1 × 1) and the dimension scan `measureTsv`
|
|
5
|
+
* (the rows × columns `parseTsv` would build, without building them), React-free, so a host reads, writes or sizes a
|
|
6
|
+
* grid's text before the grid itself is loaded — a server-rendered placeholder table, a lazily imported grid. Published
|
|
7
|
+
* as `@aiquants/spreadsheet/tsv`, which loads none of the grid; the barrel exports the same six functions (`iterateTsvRows` yields the rows lazily, unpadded, and
|
|
8
|
+
* `iterateTsvPrefixRows` the rows of a text that may be only a TSV's beginning, those the rest cannot change). The clipboard
|
|
9
|
+
* writer `serializeClipboardTsv` (Excel's form) is for text/plain on the clipboard; files and stored bodies use the strict
|
|
10
|
+
* `serializeTsv`.
|
|
7
11
|
*
|
|
8
|
-
* グリッドがコピーと貼り付けに使う TSV の符号化 (
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
+
* グリッドがコピーと貼り付けに使う TSV の符号化 (末尾の改行 1 つを除く・空の文字は空のセル 1 つ・不揃いの行は "" で埋める・
|
|
13
|
+
* タブか改行を含むか `"` で始まる値を引用符で囲むので、文字列の少なくとも 1 × 1 のどの矩形行列 `m` でも `parseTsv(serializeTsv(m))` が `m` に
|
|
14
|
+
* 等しい) と寸法の走査 `measureTsv` (`parseTsv` が組む行 × 列を、組まずに返す) を React を読まずに渡すモジュール。ホストが
|
|
15
|
+
* グリッドを読み込む前に (サーバーで描く代わりの表、遅延で読み込むグリッド) グリッドの文字を読み書きし寸法を測れる。グリッドを
|
|
16
|
+
* 読み込まない `@aiquants/spreadsheet/tsv` として公開し、barrel も同じ 6 つの関数を公開する (`iterateTsvRows` は行を遅延で、埋めずに出し、
|
|
17
|
+
* `iterateTsvPrefixRows` は TSV の先頭だけかもしれない文字の行のうち、続きが変えられない行を出す)。クリップボードの text/plain は
|
|
18
|
+
* Excel の形の `serializeClipboardTsv`、ファイルと保存する本文は厳密な `serializeTsv` を使う。
|
|
12
19
|
*/
|
|
13
|
-
export { parseTsv, serializeTsv } from "./clipboard/tsv.ts"
|
|
20
|
+
export { iterateTsvPrefixRows, iterateTsvRows, measureTsv, parseTsv, serializeClipboardTsv, serializeTsv } from "./clipboard/tsv.ts"
|
package/src/view.ts
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview The grid's fixed view geometry and its zoom
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* @fileoverview The grid's fixed view geometry, its defaults and its zoom ladder, React-free, so a host can size a box
|
|
3
|
+
* for the grid, paint a static stand-in of its first screen (column letters through `columnIndexToName`) and drive the
|
|
4
|
+
* zoom from its own controls with exactly the numbers the grid itself uses (each value is defined once here and the
|
|
5
|
+
* grid reads it from here). Published as `@aiquants/spreadsheet/view`, which loads none of the grid: its only module
|
|
6
|
+
* dependency is the column-name codec, which imports nothing.
|
|
5
7
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
8
|
+
* グリッドの固定の寸法・既定値・拡大率の段 (React を読まない)。ホストがグリッドの箱の大きさを決め、最初の画面の静的な代わり
|
|
9
|
+
* (列名は `columnIndexToName`) を描き、自前の操作部品から拡大率を動かすときに、グリッド自身と同じ数を使えるようにする
|
|
10
|
+
* モジュール (値はどれもここで 1 度だけ定義し、グリッドもここから読む)。グリッドを読み込まない `@aiquants/spreadsheet/view` と
|
|
11
|
+
* して公開する。読むモジュールは何も import しない列名の符号化だけ。
|
|
9
12
|
*/
|
|
10
13
|
|
|
14
|
+
export { columnIndexToName } from "./header/columnName.ts"
|
|
15
|
+
|
|
11
16
|
/** The height of the column-header band in CSS px (`--aqsp-col-header-height` in the stylesheet). 列見出しの帯の高さ (CSS px。スタイルシートの `--aqsp-col-header-height`)。 */
|
|
12
17
|
export const SPREADSHEET_COLUMN_HEADER_HEIGHT_PX = 24
|
|
13
18
|
|
|
@@ -21,6 +26,16 @@ export const SPREADSHEET_ROW_HEADER_WIDTH_PX = 64
|
|
|
21
26
|
*/
|
|
22
27
|
export const SPREADSHEET_SCROLLBAR_SIZE_PX = 12
|
|
23
28
|
|
|
29
|
+
/** The default column width in CSS px, the width of every column the host does not size. 既定の列幅 (CSS px。ホストが寸法を決めない列の幅)。 */
|
|
30
|
+
export const SPREADSHEET_DEFAULT_COLUMN_WIDTH_PX = 100
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The default of `maxOperationCells` (ADR-12): the largest number of cells one operation (paste, fill, clear, format)
|
|
34
|
+
* may touch before the grid refuses it.
|
|
35
|
+
* `maxOperationCells` の既定値 (ADR-12)。1 つの操作 (貼り付け・フィル・消去・書式) が触れてよいセル数の上限で、超えるとグリッドが拒む。
|
|
36
|
+
*/
|
|
37
|
+
export const SPREADSHEET_MAX_OPERATION_CELLS = 100_000
|
|
38
|
+
|
|
24
39
|
/** The smallest package zoom. パッケージの拡大率の下限。 */
|
|
25
40
|
export const SPREADSHEET_ZOOM_MIN = 0.25
|
|
26
41
|
|
|
@@ -48,15 +63,53 @@ export const clampSpreadsheetZoom = (value: number): number => {
|
|
|
48
63
|
}
|
|
49
64
|
|
|
50
65
|
/**
|
|
51
|
-
* The
|
|
52
|
-
*
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
66
|
+
* The ratio between neighbouring steps of the zoom ladder: four steps double the zoom.
|
|
67
|
+
* 拡大率の段の隣どうしの比。4 段で拡大率が 2 倍になる。
|
|
68
|
+
*/
|
|
69
|
+
const ZOOM_LADDER_RATIO = 2 ** (1 / 4)
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The grid every ladder step is rounded to: hundredths, so each step reads as a whole percentage.
|
|
73
|
+
* 段の値を丸める格子。100 分の 1 で、どの段も整数の百分率として読める。
|
|
74
|
+
*/
|
|
75
|
+
const ZOOM_LADDER_GRID = 100
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The zoom ladder of the gestures (Alt+Wheel and Ctrl+Alt+= / -): the geometric series `2^(k/4)` for every integer `k`
|
|
79
|
+
* from −8 to 8, rounded to hundredths, so it runs from SPREADSHEET_ZOOM_MIN (0.25) through 1 to SPREADSHEET_ZOOM_MAX (4)
|
|
80
|
+
* in steps of about 19 % and every power of two is a step.
|
|
81
|
+
* 拡大縮小の操作 (Alt+ホイールと Ctrl+Alt+= / -) の段。−8 から 8 までの整数 `k` の等比数列 `2^(k/4)` を 100 分の 1 に丸めた
|
|
82
|
+
* もので、SPREADSHEET_ZOOM_MIN (0.25) から 1 を通って SPREADSHEET_ZOOM_MAX (4) まで約 19 % ずつ進み、2 の累乗はどれも段に乗る。
|
|
83
|
+
*/
|
|
84
|
+
export const SPREADSHEET_ZOOM_STEPS: readonly number[] = Object.freeze(Array.from({ length: 17 }, (_, index) => Math.round(ZOOM_LADDER_RATIO ** (index - 8) * ZOOM_LADDER_GRID) / ZOOM_LADDER_GRID))
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The tolerance under which a zoom counts as standing on a ladder step, so a value that carries float drift (0.59 + 1e-12)
|
|
88
|
+
* moves to the next step instead of the one it already shows.
|
|
89
|
+
* 拡大率が段の上にあるとみなす許容差。浮動小数のずれを持つ値 (0.59 + 1e-12) が、すでに表している段でなく次の段へ動くための幅。
|
|
90
|
+
*/
|
|
91
|
+
const ZOOM_STEP_TOLERANCE = 1e-9
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The next step of SPREADSHEET_ZOOM_STEPS in the travel direction, from any finite value, including a value off the
|
|
95
|
+
* ladder (a host's 1.25 goes to 1.41 up and 1.19 down). The input is clamped first, as clampSpreadsheetZoom does, and
|
|
96
|
+
* the step is taken from the clamped value: 9 zooms out to 3.36 and 0.01 zooms in to 0.3, and at a bound the result stays
|
|
97
|
+
* at that bound (in from SPREADSHEET_ZOOM_MAX, out from SPREADSHEET_ZOOM_MIN). A non-finite value fails fast.
|
|
98
|
+
* 進む向きの SPREADSHEET_ZOOM_STEPS の次の段を返す処理。段の外の値 (ホストの 1.25 は上で 1.41、下で 1.19) を含む、どの有限の
|
|
99
|
+
* 値からでも求める。入力を先に clampSpreadsheetZoom と同じく範囲へ切り、切った値から一歩進む (9 からの縮小は 3.36、0.01 からの
|
|
100
|
+
* 拡大は 0.3)。端では端に留まる (SPREADSHEET_ZOOM_MAX からの拡大、SPREADSHEET_ZOOM_MIN からの縮小)。有限でない値はすぐに投げる。
|
|
57
101
|
*
|
|
58
102
|
* @param value - The current zoom. 今の拡大率。
|
|
59
103
|
* @param direction - 1 to zoom in, -1 to zoom out. 拡大は 1、縮小は -1。
|
|
60
|
-
* @returns The next
|
|
104
|
+
* @returns The next ladder step in that direction from the clamped input. 範囲へ切った入力からのその向きの次の段。
|
|
105
|
+
* @throws {RangeError} When the value is not finite. 有限でない場合。
|
|
61
106
|
*/
|
|
62
|
-
export const stepSpreadsheetZoom = (value: number, direction: 1 | -1): number =>
|
|
107
|
+
export const stepSpreadsheetZoom = (value: number, direction: 1 | -1): number => {
|
|
108
|
+
const current = clampSpreadsheetZoom(value)
|
|
109
|
+
if (direction > 0) {
|
|
110
|
+
const next = SPREADSHEET_ZOOM_STEPS.find((step) => step > current + ZOOM_STEP_TOLERANCE)
|
|
111
|
+
return next === undefined ? SPREADSHEET_ZOOM_MAX : next
|
|
112
|
+
}
|
|
113
|
+
const previous = SPREADSHEET_ZOOM_STEPS.findLast((step) => step < current - ZOOM_STEP_TOLERANCE)
|
|
114
|
+
return previous === undefined ? SPREADSHEET_ZOOM_MIN : previous
|
|
115
|
+
}
|
package/dist/tsv-B6TDExlk.js
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
const o = (e) => (
|
|
2
|
-
// 終端 CRLF は必須 (Excel 実挙動)。parseTsv の「終端の改行 1 つを剥がす」規約と対 —
|
|
3
|
-
// 終端なしだと [["a"],[""]] が [["a"]] に潰れ (cut-move で clear されるべき先に旧値が残る)、
|
|
4
|
-
// [[""]] は "" になって §6.5.4-5 の空ガードに飲まれ「空セル貼り付けで clear」が不発になる
|
|
5
|
-
`${e.map((l) => l.map(h).join(" ")).join(`\r
|
|
6
|
-
`)}\r
|
|
7
|
-
`
|
|
8
|
-
), h = (e) => /[\t\r\n]/.test(e) ? `"${e.replaceAll('"', '""')}"` : e, u = (e) => e.endsWith(`\r
|
|
9
|
-
`) ? e.slice(0, -2) : e.endsWith(`
|
|
10
|
-
`) || e.endsWith("\r") ? e.slice(0, -1) : e, a = (e, l, t) => {
|
|
11
|
-
let n = 0;
|
|
12
|
-
for (; ; ) {
|
|
13
|
-
let s;
|
|
14
|
-
if (e[n] === '"') {
|
|
15
|
-
const r = f(e, n);
|
|
16
|
-
if (r !== null)
|
|
17
|
-
s = r.value, n = r.nextIndex;
|
|
18
|
-
else {
|
|
19
|
-
const i = c(e, n);
|
|
20
|
-
s = e.slice(n, i), n = i;
|
|
21
|
-
}
|
|
22
|
-
} else {
|
|
23
|
-
const r = c(e, n);
|
|
24
|
-
s = e.slice(n, r), n = r;
|
|
25
|
-
}
|
|
26
|
-
if (l(s), n >= e.length) {
|
|
27
|
-
t();
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
e[n] === " " ? n += 1 : (n += e[n] === "\r" && e[n + 1] === `
|
|
31
|
-
` ? 2 : 1, t());
|
|
32
|
-
}
|
|
33
|
-
}, p = (e) => {
|
|
34
|
-
const l = u(e);
|
|
35
|
-
if (l === "")
|
|
36
|
-
return [[""]];
|
|
37
|
-
const t = [];
|
|
38
|
-
let n = [];
|
|
39
|
-
a(
|
|
40
|
-
l,
|
|
41
|
-
(r) => {
|
|
42
|
-
n.push(r);
|
|
43
|
-
},
|
|
44
|
-
() => {
|
|
45
|
-
t.push(n), n = [];
|
|
46
|
-
}
|
|
47
|
-
);
|
|
48
|
-
let s = 0;
|
|
49
|
-
for (const r of t)
|
|
50
|
-
r.length > s && (s = r.length);
|
|
51
|
-
return t.map((r) => r.length === s ? r : [...r, ...Array(s - r.length).fill("")]);
|
|
52
|
-
}, g = (e) => {
|
|
53
|
-
const l = u(e);
|
|
54
|
-
if (l === "")
|
|
55
|
-
return { rows: 1, cols: 1 };
|
|
56
|
-
let t = 0, n = 0, s = 0;
|
|
57
|
-
return a(
|
|
58
|
-
l,
|
|
59
|
-
() => {
|
|
60
|
-
s += 1;
|
|
61
|
-
},
|
|
62
|
-
() => {
|
|
63
|
-
t += 1, s > n && (n = s), s = 0;
|
|
64
|
-
}
|
|
65
|
-
), { rows: t, cols: n };
|
|
66
|
-
}, f = (e, l) => {
|
|
67
|
-
let t = "", n = l + 1;
|
|
68
|
-
for (; n < e.length; )
|
|
69
|
-
if (e[n] === '"')
|
|
70
|
-
if (e[n + 1] === '"')
|
|
71
|
-
t += '"', n += 2;
|
|
72
|
-
else {
|
|
73
|
-
const s = n + 1;
|
|
74
|
-
return (s >= e.length || e[s] === " " || e[s] === `
|
|
75
|
-
` || e[s] === "\r") && /[\t\r\n]/.test(t) ? { value: t, nextIndex: s } : null;
|
|
76
|
-
}
|
|
77
|
-
else
|
|
78
|
-
t += e[n], n += 1;
|
|
79
|
-
return null;
|
|
80
|
-
}, c = (e, l) => {
|
|
81
|
-
for (let t = l; t < e.length; t += 1)
|
|
82
|
-
if (e[t] === " " || e[t] === `
|
|
83
|
-
` || e[t] === "\r")
|
|
84
|
-
return t;
|
|
85
|
-
return e.length;
|
|
86
|
-
};
|
|
87
|
-
export {
|
|
88
|
-
g as m,
|
|
89
|
-
p,
|
|
90
|
-
o as s
|
|
91
|
-
};
|
package/dist/tsv-BHXbWK5W.cjs
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
"use strict";const h=e=>`${e.map(l=>l.map(f).join(" ")).join(`\r
|
|
2
|
-
`)}\r
|
|
3
|
-
`,f=e=>/[\t\r\n]/.test(e)?`"${e.replaceAll('"','""')}"`:e,u=e=>e.endsWith(`\r
|
|
4
|
-
`)?e.slice(0,-2):e.endsWith(`
|
|
5
|
-
`)||e.endsWith("\r")?e.slice(0,-1):e,a=(e,l,t)=>{let n=0;for(;;){let s;if(e[n]==='"'){const r=p(e,n);if(r!==null)s=r.value,n=r.nextIndex;else{const i=c(e,n);s=e.slice(n,i),n=i}}else{const r=c(e,n);s=e.slice(n,r),n=r}if(l(s),n>=e.length){t();return}e[n]===" "?n+=1:(n+=e[n]==="\r"&&e[n+1]===`
|
|
6
|
-
`?2:1,t())}},o=e=>{const l=u(e);if(l==="")return[[""]];const t=[];let n=[];a(l,r=>{n.push(r)},()=>{t.push(n),n=[]});let s=0;for(const r of t)r.length>s&&(s=r.length);return t.map(r=>r.length===s?r:[...r,...Array(s-r.length).fill("")])},g=e=>{const l=u(e);if(l==="")return{rows:1,cols:1};let t=0,n=0,s=0;return a(l,()=>{s+=1},()=>{t+=1,s>n&&(n=s),s=0}),{rows:t,cols:n}},p=(e,l)=>{let t="",n=l+1;for(;n<e.length;)if(e[n]==='"')if(e[n+1]==='"')t+='"',n+=2;else{const s=n+1;return(s>=e.length||e[s]===" "||e[s]===`
|
|
7
|
-
`||e[s]==="\r")&&/[\t\r\n]/.test(t)?{value:t,nextIndex:s}:null}else t+=e[n],n+=1;return null},c=(e,l)=>{for(let t=l;t<e.length;t+=1)if(e[t]===" "||e[t]===`
|
|
8
|
-
`||e[t]==="\r")return t;return e.length};exports.measureTsv=g;exports.parseTsv=o;exports.serializeTsv=h;
|