@threestarsoftware/tssgrid 0.1.25 → 0.1.27
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 +20 -0
- package/package.json +1 -1
- package/src/tssgrid.d.ts +33 -1
- package/src/tssgrid.js +58 -11
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,26 @@
|
|
|
3
3
|
TssGrid の各リリースの変更点。日付は JST。形式は [Keep a Changelog](https://keepachangelog.com/ja/1.1.0/) 準拠(ゆるめ)。
|
|
4
4
|
数値は特記なき限り**自社調べ**(headless Chrome / Windows 11・環境差あり)。
|
|
5
5
|
|
|
6
|
+
## [0.1.27] - 2026-10-14
|
|
7
|
+
|
|
8
|
+
### セキュリティ修正
|
|
9
|
+
- **`rowClass` / `cellClass` の戻り値がエスケープされず `class` 属性に差し込まれる XSS を修正**(コア)— `rowClass` が返すクラス名が `<tr class="...">` に生で入っており(`data-rcls` は esc 済みだが class 側は未処理)、利用者データ(行の値/`src`)由来のクラス名に `"` を含めると属性を差し込める(`onmouseover` 等=XSS)。`cellClass`→`class`/`classList.add` も同型。**`_cellClasses`/`_rowClassStr` が返すクラストークンを「安全なクラス名(空白や `" ' < > &` を含まない)」だけに絞る**ように修正(class 属性・`classList.add` の両経路に有効。Tailwind 風の `/` `:` `.` や Unicode クラスは保持)。あわせて **`TssGrid.esc` が `"` `'` も escape** するように(従来 `& < >` のみ)=`data-rcls`/`data-ccls`/`title` 等 esc 済み属性の `"` 抜けも根から解消。正規のクラスでは挙動不変。
|
|
10
|
+
|
|
11
|
+
## [0.1.26] - 2026-10-14
|
|
12
|
+
|
|
13
|
+
### 追加
|
|
14
|
+
- **公開APIの棚卸し=実務ギャップを一括で公開+型付け**(コア・TS/Vue 利用向け)。
|
|
15
|
+
- **`setColumns(columns, headers?)`**:実行時に列定義を差し替え(Vue の `:columns` 変更等)。object モードは元レコード(`_src`)から再展開、配列モードは行を新 COLS に詰める。
|
|
16
|
+
- **`setReadOnly(flag)`**:グリッド全体の読み取り専用を実行時に切替(`:readonly`)。
|
|
17
|
+
- **`getSelection()`** / **`rectRange()`**:現在の選択範囲を取得。
|
|
18
|
+
- **`setCells(cells)`**:`[{r,c,value}]`(c は index か data キー)を **1 undo でまとめ書き込み**(値コピー→再計算等の batch 書き込み)。
|
|
19
|
+
- **状態プロパティを `.d.ts` に宣言**:`name`/`active`/`extent`/`virtual`/`readOnly`(readonly。`ROWS`/`COLS`/`dataComplete` は既存)。
|
|
20
|
+
- **`colCfg`/`colType`/`setColumnChecked`/`toEdit`/`pasteGrid` を `.d.ts` に宣言**(元から public だが未宣言だったものを TS で型付きに)。
|
|
21
|
+
- examples `invoice`/`quote` の低レベル書き込みを `setCells` に移行。挙動は不変。
|
|
22
|
+
|
|
23
|
+
### 追加(サンプル)
|
|
24
|
+
- **遅延ロード例に「列に無い `src` 項目で行を色付け」を追加**(`examples/lazy-load.html`)— `rowClass:(r,row,src)=>src.retired?…` で退職者行を赤く=v0.1.25 修正(`data:[]` 遅延ロードでも src が届く)の動く例。
|
|
25
|
+
|
|
6
26
|
## [0.1.25] - 2026-10-13
|
|
7
27
|
|
|
8
28
|
### 修正
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@threestarsoftware/tssgrid",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.27",
|
|
4
4
|
"description": "軽量・ノービルド・日本語IME堅牢な編集グリッド(vanilla JS / 依存なし / MIT)。Excel ライクな入力画面を <script> タグ1本で。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ThreeStarSoftware (https://www.threestarsoftware.co.jp/)",
|
package/src/tssgrid.d.ts
CHANGED
|
@@ -342,6 +342,16 @@ export declare class TssGrid {
|
|
|
342
342
|
/** Enter/Tab 確定後の移動方向(代入で即反映)。 */
|
|
343
343
|
enterMoves: MoveDir;
|
|
344
344
|
tabMoves: MoveDir;
|
|
345
|
+
/** グリッド名(読み取り)。 */
|
|
346
|
+
readonly name: string;
|
|
347
|
+
/** アクティブセル(読み取り。移動は setActive)。 */
|
|
348
|
+
readonly active: { r: number; c: number };
|
|
349
|
+
/** 選択の伸長端(読み取り。setExtent で移動)。 */
|
|
350
|
+
readonly extent: { r: number; c: number };
|
|
351
|
+
/** 仮想スクロール有効か(読み取り)。 */
|
|
352
|
+
readonly virtual: boolean;
|
|
353
|
+
/** グリッド全体の読み取り専用(読み取り。実行時変更は setReadOnly)。 */
|
|
354
|
+
readonly readOnly: boolean;
|
|
345
355
|
|
|
346
356
|
// ---- データ -------------------------------------------------------------
|
|
347
357
|
/** 全データを2次元配列で取得(コピー)。 */
|
|
@@ -358,6 +368,12 @@ export declare class TssGrid {
|
|
|
358
368
|
getValue(r: number, cOrKey: number | string): any;
|
|
359
369
|
/** データを差し替え(履歴・フィルタ・整列状態はリセット)。 */
|
|
360
370
|
setData(rows: any[][] | Record<string, any>[]): void;
|
|
371
|
+
/** 実行時に列定義を差し替える(Vue の `:columns` 変更等)。`headers` 省略時は columns から再導出。
|
|
372
|
+
* object モードは元レコード(`_src`)から新 columns で再展開、配列モードは各行を新 COLS に詰める。
|
|
373
|
+
* 列構造が変わるので整列/セル単位 readOnly はクリア、frozen/選択は範囲内へクランプ。 */
|
|
374
|
+
setColumns(columns: ColumnDef[], headers?: (string | { label?: string })[]): void;
|
|
375
|
+
/** グリッド全体の読み取り専用を実行時に切替(Vue の `:readonly` 等)。 */
|
|
376
|
+
setReadOnly(flag: boolean): void;
|
|
361
377
|
/**
|
|
362
378
|
* 背景で"育てる"追記(progressive)。data 末尾へ rows を足し、可視窓+スペーサだけ再描画する。
|
|
363
379
|
* buildTable 全再描画をせず、スクロール位置・アクティブセル・フォーカス・選択・編集中のエディタと値を保ち、履歴にも積まない。
|
|
@@ -382,6 +398,12 @@ export declare class TssGrid {
|
|
|
382
398
|
setValue(r: number, cOrKey: number | string, val: any, force?: boolean): void;
|
|
383
399
|
/** 履歴に積まず・検証も通さず直接書き込み(計算列など派生値用。readOnly 貫通)。 */
|
|
384
400
|
setValueRaw(r: number, cOrKey: number | string, val: any): void;
|
|
401
|
+
/** 複数セルをまとめて書き込み、1 undo にする。`c` は列 index か data キー。値コピー→再計算等の batch 書き込みに。 */
|
|
402
|
+
setCells(cells: { r: number; c: number | string; value: any }[]): void;
|
|
403
|
+
/** アクティブセルを編集モードにする(低レベル)。`value` 指定でその値から開始、`caretEnd` でキャレット末尾。読取専用は無視。 */
|
|
404
|
+
toEdit(value?: any, caretEnd?: boolean): void;
|
|
405
|
+
/** アクティブセルを左上に、2次元配列を貼り付け(はみ出しは pasteOverflow ポリシー・1 undo)。 */
|
|
406
|
+
pasteGrid(grid: any[][]): void;
|
|
385
407
|
|
|
386
408
|
// ---- 並べ替え / フィルタ -------------------------------------------------
|
|
387
409
|
/** 列で並べ替え(c は index か data キー)。 */
|
|
@@ -418,6 +440,10 @@ export declare class TssGrid {
|
|
|
418
440
|
getSelectedColumns(): number[];
|
|
419
441
|
/** 選択中の行インデックス(Ctrl+行ヘッダクリックの飛び飛び選択に対応・昇順)。行選択モードでなければ `[]`。 */
|
|
420
442
|
getSelectedRows(): number[];
|
|
443
|
+
/** 現在の選択範囲(矩形+active/extent)を取得。 */
|
|
444
|
+
getSelection(): { r0: number; c0: number; r1: number; c1: number; active: { r: number; c: number }; extent: { r: number; c: number } };
|
|
445
|
+
/** 現在の選択矩形 `{r0,c0,r1,c1}`(active↔extent の正規化)。 */
|
|
446
|
+
rectRange(): { r0: number; c0: number; r1: number; c1: number };
|
|
421
447
|
/** 飛び飛びの複数列をまとめて削除(高index側から。右クリック「N 列を削除」の実体)。 */
|
|
422
448
|
deleteColsSet(cols: number[]): void;
|
|
423
449
|
/** 飛び飛びの複数行をまとめて削除(高index側から。右クリック「N 行を削除」の実体)。 */
|
|
@@ -443,6 +469,12 @@ export declare class TssGrid {
|
|
|
443
469
|
toggleColumn(c: number): void;
|
|
444
470
|
isColumnHidden(c: number): boolean;
|
|
445
471
|
getHiddenColumns(): number[];
|
|
472
|
+
/** 列 c の定義(columns[c])を取得(列の introspection 用)。 */
|
|
473
|
+
colCfg(c: number): ColumnDef;
|
|
474
|
+
/** 列 c の型('text'|'dropdown'|'checkbox'|'date'|'time'|'number')。 */
|
|
475
|
+
colType(c: number): string;
|
|
476
|
+
/** checkbox 列を全 ON/OFF(readOnly 行はスキップ・1 undo)。 */
|
|
477
|
+
setColumnChecked(c: number, flag: boolean): void;
|
|
446
478
|
setColWidth(c: number, px: number): void;
|
|
447
479
|
autoSizeColumn(c: number): void;
|
|
448
480
|
autoSizeAllColumns(): void;
|
|
@@ -493,7 +525,7 @@ export declare class TssGrid {
|
|
|
493
525
|
* number/date/time の組込 parse で使うほか、カスタム `parse`/`format` から `TssGrid.zen2han(v)` として呼べる。 */
|
|
494
526
|
static zen2han(s: string): string;
|
|
495
527
|
|
|
496
|
-
/** HTML エスケープ(`&` `<`
|
|
528
|
+
/** HTML エスケープ(`&` `<` `>` `"` `'`)。`format` で HTML/属性を組む時などに(属性値も安全)。 */
|
|
497
529
|
static esc(s: any): string;
|
|
498
530
|
|
|
499
531
|
[k: string]: any;
|
package/src/tssgrid.js
CHANGED
|
@@ -63,14 +63,7 @@
|
|
|
63
63
|
// 見出し: 明示 headers > columns の title/data 由来 > 既定 A,B,C
|
|
64
64
|
// 見出しの決定: 明示 title('' 含む)を最優先で尊重。checkbox 列は「ヘッダ=全選択チェック」なので
|
|
65
65
|
// title 未設定でも data 名('checked' 等)を出さない(空)。それ以外は title 未設定なら data 名→無ければ列ラベル。
|
|
66
|
-
this.headers = opts.headers
|
|
67
|
-
? this.columns.map((c, i) => {
|
|
68
|
-
if (!c) return undefined; // 列定義なし=従来どおり(空表示)
|
|
69
|
-
if (c.title != null) return c.title; // 明示 title('' で見出し無し)を尊重
|
|
70
|
-
if (c.type === 'checkbox') return ''; // checkbox は data 名を見出しに出さない
|
|
71
|
-
return c.data || TssGrid._colLabel(i); // 従来: title 未設定は data 名 → 無ければ列ラベル
|
|
72
|
-
})
|
|
73
|
-
: ['A', 'B', 'C']);
|
|
66
|
+
this.headers = this._deriveHeaders(opts.headers);
|
|
74
67
|
this.COLS = this.headers.length;
|
|
75
68
|
// データ取り込み: 配列(string[][]) と オブジェクト配列(object[]) を自動判別。
|
|
76
69
|
// オブジェクト時は columns[c].data をキーに 2次元へ展開し、元オブジェクトは _src に保持(非表示フィールド維持)。
|
|
@@ -374,6 +367,39 @@
|
|
|
374
367
|
return out;
|
|
375
368
|
}
|
|
376
369
|
setData(rows) { this._align.clear(); this._cellRO.clear(); this._unsortedData = null; this._allRows = this._allSrc = this._meta = this._filterFn = null; this.dataComplete = true; this._pendingCount = 0; if (this.history) this.history.clear(); this._ingest(rows); this._padToMin(); this.buildTable(); const ic = this._resolveInitialCell(); this.setActive(ic[0], ic[1]); this._ensureSpare(); }
|
|
370
|
+
// 見出し導出(constructor / setColumns で共用): 明示 headers > columns の title/data 由来 > A,B,C。
|
|
371
|
+
_deriveHeaders(headersOpt) {
|
|
372
|
+
if (headersOpt) return headersOpt.slice();
|
|
373
|
+
if (!this.columns.length) return ['A', 'B', 'C'];
|
|
374
|
+
return this.columns.map((c, i) => {
|
|
375
|
+
if (!c) return undefined;
|
|
376
|
+
if (c.title != null) return c.title;
|
|
377
|
+
if (c.type === 'checkbox') return ''; // checkbox は data 名を見出しに出さない
|
|
378
|
+
return c.data || TssGrid._colLabel(i);
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
// 実行時に列定義を差し替える(Vue の :columns 変更等)。headers 省略時は columns から再導出。
|
|
382
|
+
// データは object モードなら _src から新 columns で再展開、配列モードは各行を新 COLS に合わせて詰める。
|
|
383
|
+
// 列構造が変わるので per-cell 状態(整列/セル readOnly)はクリア、frozen/選択は範囲内へクランプ。
|
|
384
|
+
setColumns(columns, headers) {
|
|
385
|
+
this.columns = columns || [];
|
|
386
|
+
this.headers = this._deriveHeaders(headers);
|
|
387
|
+
this.COLS = this.headers.length;
|
|
388
|
+
if (this._src) {
|
|
389
|
+
this.data = this.data.map((row, r) => row === TssGrid.PENDING ? row : this._objToRow(this._src[r] || {}));
|
|
390
|
+
} else {
|
|
391
|
+
this.data = this.data.map(row => { const a = (Array.isArray(row) ? row : []).slice(0, this.COLS); while (a.length < this.COLS) a.push(''); return a; });
|
|
392
|
+
}
|
|
393
|
+
this._align.clear(); this._cellRO.clear(); // 旧列 index を指す状態は破棄
|
|
394
|
+
if (this.frozenCols > this.COLS) this.frozenCols = this.COLS;
|
|
395
|
+
this.active = { r: this.clampR(this.active.r), c: this.clampC(this.active.c) };
|
|
396
|
+
this.extent = { r: this.active.r, c: this.active.c };
|
|
397
|
+
this.buildTable(); this.setActive(this.active.r, this.active.c);
|
|
398
|
+
}
|
|
399
|
+
// グリッド全体の読み取り専用を実行時に切替(Vue の :readonly 等)。
|
|
400
|
+
setReadOnly(flag) { this.readOnly = !!flag; this.buildTable(); this.setActive(this.active.r, this.active.c); }
|
|
401
|
+
// 現在の選択範囲を取得(矩形+active/extent)。rectRange の公開版。
|
|
402
|
+
getSelection() { const g = this.rectRange(); return { r0: g.r0, c0: g.c0, r1: g.r1, c1: g.c1, active: { ...this.active }, extent: { ...this.extent } }; }
|
|
377
403
|
// 背景で"育てる"追記(progressive): data 末尾へ rows を足し ROWS を更新、可視窓+上下スペーサだけ再描画する。
|
|
378
404
|
// buildTable 全再描画をしない=スクロール位置・アクティブセル・フォーカス・選択範囲・編集中のエディタと値を保つ。
|
|
379
405
|
// history には積まない(背景成長は undo 対象外)。既存の setData/insertRows 経路は不変=完全 opt-in。
|
|
@@ -517,6 +543,19 @@
|
|
|
517
543
|
this.setCell(r, ci, this._cellStr(val), changes);
|
|
518
544
|
this.pushCmd(changes, before, this.snapSel(), 'edit', !!force);
|
|
519
545
|
}
|
|
546
|
+
// 複数セルをまとめて書き込み、1 undo にする。cells = [{ r, c, value }](c は index か data キー)。
|
|
547
|
+
// 「値コピー→再計算」等の batch 書き込み向け=raw な setCell/pushCmd を外から触らずに済む公開プリミティブ。
|
|
548
|
+
setCells(cells) {
|
|
549
|
+
if (!Array.isArray(cells) || !cells.length) return;
|
|
550
|
+
const before = this.snapSel(), changes = [];
|
|
551
|
+
for (const it of cells) {
|
|
552
|
+
if (!it) continue;
|
|
553
|
+
const ci = this._resolveCol(it.c);
|
|
554
|
+
if (ci < 0 || it.r < 0 || it.r >= this.ROWS) continue;
|
|
555
|
+
this.setCell(it.r, ci, this._cellStr(it.value), changes);
|
|
556
|
+
}
|
|
557
|
+
if (changes.length) this.pushCmd(changes, before, this.snapSel(), 'edit');
|
|
558
|
+
}
|
|
520
559
|
// 履歴に積まず・検証も通さず値を直接セット(readOnly も貫通)。表示書式は描画時に適用。
|
|
521
560
|
// 計算列など「入力から導く派生値」向け。undo/redo では入力セルが履歴復元され、onAfterChange で
|
|
522
561
|
// これを呼んで再計算する流儀にする=派生値が独立した undo ステップにならず入力に追従する。
|
|
@@ -828,7 +867,14 @@
|
|
|
828
867
|
if (this.height) this.wrap.style.maxHeight = this.height; // 高さ制約(縦スクロール用)
|
|
829
868
|
}
|
|
830
869
|
|
|
831
|
-
static esc(s) { return String(s).replace(/[&<>]/g, m => ({ '&': '&', '<': '<', '>': '>' }[m])); }
|
|
870
|
+
static esc(s) { return String(s).replace(/[&<>"']/g, m => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[m])); }
|
|
871
|
+
// rowClass/cellClass が返す値を「CSS クラス名として安全なトークン配列」に。class 属性へ生で入る+classList.add
|
|
872
|
+
// するため、空白や " ' < > & を含むトークンは捨てる=属性抜け(XSS)や classList.add の例外を防ぐ。
|
|
873
|
+
static _clsSafe(x) {
|
|
874
|
+
if (!x) return [];
|
|
875
|
+
const toks = (Array.isArray(x) ? x : [x]).filter(Boolean).map(String).join(' ').split(/\s+/);
|
|
876
|
+
return toks.filter(t => t && /^[^\s"'<>&]+$/.test(t));
|
|
877
|
+
}
|
|
832
878
|
cellEl(r, c) { if (this._merges) { const a = this._anchorOf(r, c); r = a.r; c = a.c; } return this.table.querySelector('td[data-r="' + r + '"][data-c="' + c + '"]'); }
|
|
833
879
|
activeTd() { return this.cellEl(this.active.r, this.active.c); }
|
|
834
880
|
clampR(r) { return Math.max(0, Math.min(this.ROWS - 1, r)); }
|
|
@@ -1176,7 +1222,7 @@
|
|
|
1176
1222
|
const cc = this.colCfg(c).cellClass;
|
|
1177
1223
|
if (typeof cc === 'function') { try { push(cc(v, { r, c, row: this.data[r], src })); } catch (_) {} }
|
|
1178
1224
|
if (typeof this.cellClass === 'function') { try { push(this.cellClass(r, c, v, this.data[r], src)); } catch (_) {} } // row=値配列, src=元レコード
|
|
1179
|
-
return
|
|
1225
|
+
return TssGrid._clsSafe(out); // クラス名として安全なトークンのみ(class 属性/classList.add へ生で入るため)
|
|
1180
1226
|
}
|
|
1181
1227
|
// 条件付きクラスをセルに反映(前回分 data-ccls を外してから付け直す=値変更で追従)。
|
|
1182
1228
|
_applyCellClass(td, r, c) {
|
|
@@ -1189,7 +1235,8 @@
|
|
|
1189
1235
|
// 行クラス: rowClass(r, row, src) → 空白区切り文字列(配列は join)。無効/例外は ''。
|
|
1190
1236
|
_rowClassStr(r) {
|
|
1191
1237
|
if (typeof this.rowClass !== 'function') return '';
|
|
1192
|
-
try {
|
|
1238
|
+
let x; try { x = this.rowClass(r, this.data[r], this._src ? this._src[r] : null); } catch (_) { return ''; }
|
|
1239
|
+
return TssGrid._clsSafe(x).join(' '); // クラス名として安全なトークンのみ(<tr class>/classList.add へ生で入るため)
|
|
1193
1240
|
}
|
|
1194
1241
|
// <tr> の行クラスを反映(前回分 data-rcls を外して付け直す=セル再描画で追従)。
|
|
1195
1242
|
_applyRowClass(r) {
|