@threestarsoftware/tssgrid 0.1.20 → 0.1.22
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 +12 -0
- package/README.md +19 -3
- package/package.json +1 -1
- package/src/tssgrid.d.ts +3 -0
- package/src/tssgrid.js +19 -6
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,18 @@
|
|
|
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.22] - 2026-10-07
|
|
7
|
+
|
|
8
|
+
### 追加
|
|
9
|
+
- **`renderCell(r, cOrKey)` を公開**(コア)— セル1つだけ表示を描き直す(テキスト/HTML・条件付き書式〔cellClass/cellStyle/rowClass〕・無効表示を最新データで)。全体 `redraw()` より軽い。`format` が外部状態に依存する/セル内ボタンの状態を更新した等で「このセルだけ」描き直したい時に。`c` は列 index か data キー、範囲外 `r`・未解決 key・窓外/非表示は安全に no-op。**TypeScript の型定義(`.d.ts`)にも追加**。
|
|
10
|
+
- **`date` / `time` 列が IME ON の全角入力を受理**(コア)— 従来は半角しか受け付けず弾いていた全角の日付/時刻(`2026/01/05`・`9:30` 等)を、**半角化して受理**するように(`number` 列と同じく `_zen2han` を通す。全角スラッシュ `/`・全角コロン `:` も対応)。全角の日付/時刻は元々不正なので常に正規化=受理が広がるだけ(半角は従来どおり・実在しない日付/時刻は引き続き拒否)。
|
|
11
|
+
- **ページネーション(サーバ側ページング)のサンプルを追加** — `examples/pagination.html`。ページネーションはサーバ API 形に依存するため**本体は「渡された1ページ分を描く」だけ**に徹する方針を、モックサーバ `fetchPage(offset,limit)→{rows,total}` + `grid.setData(rows)` + グリッド外の前/次/ページサイズ/「N件中 M〜」で提示。README に「ページネーション(サーバ側ページング)」節も追加。
|
|
12
|
+
|
|
13
|
+
## [0.1.21] - 2026-10-06
|
|
14
|
+
|
|
15
|
+
### 修正
|
|
16
|
+
- **チェックボックス列のヘッダ全選択が、read-only 行のせいで「選べる行は全部 ON なのに − になる」のを修正**(コア)— v0.1.20 で全選択の無効判定(`_columnAllReadOnly`)は read-only 行を除外したが、**状態判定(`_columnCheckState`=✓/−/☐)は read-only 行を total に数えたまま**だった。全選択(`setColumnChecked`)は read-only をスキップするので、状態判定も**「操作できる行(非空かつ非 read-only)」だけで数える**よう統一。選べる行が全部 ON なら **✓**(押せば全部外れる)。あわせて**全 read-only 列では右クリックメニュー「すべてチェック/外す」を出さない**(no-op なので・ヘッダ無効と整合)。これでチェックボックス×read-only の全経路(状態判定/無効判定/全選択書込/body トグル/コンテキストメニュー)が「操作できる集合」で揃った。
|
|
17
|
+
|
|
6
18
|
## [0.1.20] - 2026-10-06
|
|
7
19
|
|
|
8
20
|
### 修正
|
package/README.md
CHANGED
|
@@ -351,6 +351,22 @@ new TssGrid(el, { virtual: { buffer: 10 }, data }); // 窓の上下バッフ
|
|
|
351
351
|
- 制約(v1): **行高一定**/**固定行列・折り返し・セル結合は非対応**(指定時は自動で無効化+警告)。行番号の桁数が増える場合は `rowHeaderWidth` で行ヘッダ幅を広げられます。
|
|
352
352
|
- フィルタとの関係: `filter()` は「絞り込んだ部分集合だけ描く」軽さ、`virtual` は「絞らず全件をスクロールする」軽さ。用途で使い分け(v1 では併用は未対応)。
|
|
353
353
|
|
|
354
|
+
### ページネーション(サーバ側ページング)
|
|
355
|
+
|
|
356
|
+
ページネーションは**サーバの API 形(offset/limit・`total` 件数の有無)に依存**するので、**TssGrid 本体は「渡された1ページ分を描く」だけ**に徹します(コアに作り込みません)。ロジックはアプリ側で、**ページ変更 → fetch → `grid.setData(pageRows)`**、ページ番号ボタンや「N 件中 M〜」は**グリッドの外**に置きます。動く例: [`examples/pagination.html`](https://tssgrid.threestarsoftware.co.jp/examples/pagination.html)。
|
|
357
|
+
|
|
358
|
+
```js
|
|
359
|
+
async function load() {
|
|
360
|
+
const { rows, total } = await fetchPage((page - 1) * size, size); // サーバの offset/limit
|
|
361
|
+
grid.setData(rows); // ← TssGrid は1ページ分を描くだけ
|
|
362
|
+
renderCount(total); // 「N 件中 M〜」はグリッドの外で
|
|
363
|
+
}
|
|
364
|
+
// 前/次/ページ番号は <button> で外に置き、go(p) → load() を呼ぶ
|
|
365
|
+
```
|
|
366
|
+
- **`total` が返るサーバ**: 「全何件・何ページ」まで出せる(上の例)。**`total` が無い(cursor のみ)**: ページ番号は出せないので **`appendRows` で「もっと読む」=無限スクロール型**に。
|
|
367
|
+
- **1本の長いスクロールで必要分だけ取得**したいなら、ページ送りでなく `setRowCount(total)` + `onViewportChange` + `fillRows(start, rows)`(AG Grid の Infinite Row Model 相当・上の「メソッド」参照)。
|
|
368
|
+
- sort/filter をサーバ側でやる場合も同じ=**操作 → 再 fetch → `setData`**(遅延スクロール型なら `onSortFilterChange` で委譲)。
|
|
369
|
+
|
|
354
370
|
### 同梱プラグイン: ヘッダ オートフィルタ(filter UI)
|
|
355
371
|
|
|
356
372
|
`plugins/tss-filter.js`+`.css`= 各見出しの**ロート(漏斗)アイコン**をクリックで**値チェックリスト+検索**のポップアップ(Excel風。ソートの▲▼と区別できるアイコン)。複数列の条件は **AND**。中身は上の**コア `filter()` を呼ぶだけ**。動く例: [`examples/filter-ui.html`](https://tssgrid.threestarsoftware.co.jp/examples/filter-ui.html)。
|
|
@@ -972,7 +988,7 @@ new TssGrid(el, {
|
|
|
972
988
|
});
|
|
973
989
|
```
|
|
974
990
|
|
|
975
|
-
メソッド: `getData()` / `getRows()` / `getRow(r)` / `getColumn(cOrKey)` / `getColumns()` / `setData(rows)` / `appendRows(rows,opts?)`(**背景で"育てる"追記**=data末尾に足し可視窓だけ再描画。buildTable全再描画せずスクロール位置・選択・編集中エディタを保ち履歴にも積まない。「先頭ページを描く→残りをidleでappendRows」でfirst-paintを早める土台。既存経路は不変のopt-in) / `setRowCount(total)` + `fillRows(start,rows)`(**遅延ロード=on-demand**=virtual前提。総件数を確保し未取得ぶんは「読込中」行、`onViewportChange(start,end)` で要る範囲を受け `fillRows` で実データを流し込む。sort/filterは遅延中ローカル計算せず `onSortFilterChange` でサーバ委譲、未取得セルは編集不可、export(getData/toCSV)はロード済みのみ=AG Grid の Infinite Row Model 相当を push で) / `getValue(r,cOrKey)` / `setValue(r,cOrKey,val,force?)` / `setValueRaw(r,cOrKey,val)`(履歴に積まない派生値書き込み・計算列用) / `hideColumn(c)` / `showColumn(c)` / `toggleColumn(c)` / `isColumnHidden(c)` / `getHiddenColumns()` / `setAlignment(align,range?)` / `getAlignment(r,c)` / `setCellReadOnly(flag,range?)` / `autoSizeColumn(c)` / `autoSizeAllColumns()` / `sortBy(cOrKey, dir)` / `sortRows(cmp)` / `addShortcut(s)` / `removeShortcut(name)` / `usePlugin(fn)` / `getPlugin(name)` / `destroy()` / `setActive(r,c)` / `scrollToRow(r, align?)`(**指定行までスクロール=HTML の `#` アンカー相当**。`align` は `'start'`〔既定=先頭・sticky ヘッダ直下〕/`'center'`/`'end'`/`'nearest'`〔可視ならそのまま〕。仮想・非仮想どちらでも動き、範囲外 `r` は自動クランプ。**選択は変えない**ので目印は `rowClass` 等を併用。例 `examples/scroll-to-row.html`) / `selectRow(r)` / `selectCol(c)` / `selectAll()` / `setColWidth(c,px)` / `setRowHeight(r,px)` / `freezeCols(n)` / `freezeRows(n)` / `moveRow(from,to)` / `moveRows(r0,r1,to)` / `clearSort()` / `toCSV(opts)` / `downloadCSV(name,opts)` / `insertRow(ri,'above'|'below')` / `insertRows(ri,count?,'above'|'below')`(count 行を1回の再描画・1 undo で一括挿入=大量挿入が O(N)) / `deleteRows(r0,r1?)` / `insertCol(ci,'left'|'right')` / `deleteCols(c0,c1?)` / `redraw()`(列定義変更後の再描画,
|
|
991
|
+
メソッド: `getData()` / `getRows()` / `getRow(r)` / `getColumn(cOrKey)` / `getColumns()` / `setData(rows)` / `appendRows(rows,opts?)`(**背景で"育てる"追記**=data末尾に足し可視窓だけ再描画。buildTable全再描画せずスクロール位置・選択・編集中エディタを保ち履歴にも積まない。「先頭ページを描く→残りをidleでappendRows」でfirst-paintを早める土台。既存経路は不変のopt-in) / `setRowCount(total)` + `fillRows(start,rows)`(**遅延ロード=on-demand**=virtual前提。総件数を確保し未取得ぶんは「読込中」行、`onViewportChange(start,end)` で要る範囲を受け `fillRows` で実データを流し込む。sort/filterは遅延中ローカル計算せず `onSortFilterChange` でサーバ委譲、未取得セルは編集不可、export(getData/toCSV)はロード済みのみ=AG Grid の Infinite Row Model 相当を push で) / `getValue(r,cOrKey)` / `setValue(r,cOrKey,val,force?)` / `setValueRaw(r,cOrKey,val)`(履歴に積まない派生値書き込み・計算列用) / `hideColumn(c)` / `showColumn(c)` / `toggleColumn(c)` / `isColumnHidden(c)` / `getHiddenColumns()` / `setAlignment(align,range?)` / `getAlignment(r,c)` / `setCellReadOnly(flag,range?)` / `autoSizeColumn(c)` / `autoSizeAllColumns()` / `sortBy(cOrKey, dir)` / `sortRows(cmp)` / `addShortcut(s)` / `removeShortcut(name)` / `usePlugin(fn)` / `getPlugin(name)` / `destroy()` / `setActive(r,c)` / `scrollToRow(r, align?)`(**指定行までスクロール=HTML の `#` アンカー相当**。`align` は `'start'`〔既定=先頭・sticky ヘッダ直下〕/`'center'`/`'end'`/`'nearest'`〔可視ならそのまま〕。仮想・非仮想どちらでも動き、範囲外 `r` は自動クランプ。**選択は変えない**ので目印は `rowClass` 等を併用。例 `examples/scroll-to-row.html`) / `selectRow(r)` / `selectCol(c)` / `selectAll()` / `setColWidth(c,px)` / `setRowHeight(r,px)` / `freezeCols(n)` / `freezeRows(n)` / `moveRow(from,to)` / `moveRows(r0,r1,to)` / `clearSort()` / `toCSV(opts)` / `downloadCSV(name,opts)` / `insertRow(ri,'above'|'below')` / `insertRows(ri,count?,'above'|'below')`(count 行を1回の再描画・1 undo で一括挿入=大量挿入が O(N)) / `deleteRows(r0,r1?)` / `insertCol(ci,'left'|'right')` / `deleteCols(c0,c1?)` / `redraw()`(列定義変更後の再描画, データ・選択は保持) / `renderCell(r, cOrKey)`(**セル1つだけ表示を描き直す**=テキスト/HTML・条件付き書式・無効表示を最新データで。全体 `redraw()` より軽い。`format` が外部状態に依存する/セル内ボタンの状態を更新した等に。窓外/非表示は no-op)ほか。
|
|
976
992
|
|
|
977
993
|
## イベント
|
|
978
994
|
|
|
@@ -1046,8 +1062,8 @@ new TssGrid(el, {
|
|
|
1046
1062
|
| `'text'`(既定) | テキスト入力 | そのまま |
|
|
1047
1063
|
| `'dropdown'` | `<select>`(`options` 必須)。セルに ▾ を表示、**1クリックで一覧が開き**、選ぶと即確定 | 選択肢 or 空のみ許可。範囲外は拒否。**クリア用の空オプションは自動で先頭に付く**ので `options` に `''` を入れない(入れると空が二重)。`allowEmpty:false` で**空オプションを出さず空値も弾く**=ラジオ的な必須選択。**`options` は `string[]` または `{value,label}[]`=保存値(value=内部コード)と表示(label)を分離**(`getData`/`getRows` は value で返る・検証も value で照合) |
|
|
1048
1064
|
| `'checkbox'` | Space / クリックでトグル | `checked`(既定 `'1'`) / `unchecked`(既定 `''`)。貼付値も正規化 |
|
|
1049
|
-
| `'date'` | `<input type="date">` | `YYYY-MM-DD` のみ。実在しない日付(例 `2026-02-30
|
|
1050
|
-
| `'time'` | `<input type="time">` / テキスト | `HH:MM[:SS]`(24時間制)で保存。`hour12:true` で AM/PM
|
|
1065
|
+
| `'date'` | `<input type="date">` | `YYYY-MM-DD` のみ。実在しない日付(例 `2026-02-30`)は拒否。**全角→半角を正規化**(IME ON のまま `2026/01/05` と打っても受理。区切りは `-` `/` `.` + 8桁 `20260105` も可) |
|
|
1066
|
+
| `'time'` | `<input type="time">` / テキスト | `HH:MM[:SS]`(24時間制)で保存。`hour12:true` で AM/PM 表示・入力。**全角→半角を正規化**(IME ON のまま `9:30` と打っても受理) |
|
|
1051
1067
|
| `'number'` | テキスト(編集時は素の数値) | 数値のみ。`decimals`/`thousands`/`prefix`/`suffix` で表示書式(→ [CellFormat](#cellformat宣言的な表示書式数値--日付))。**全角→半角を既定で正規化**(`123`→`123`、`zenkaku:false`で無効化)。右寄せ |
|
|
1052
1068
|
|
|
1053
1069
|
- **`validator(value, { r, c })`**: `true`/`undefined` で許可、`false` で拒否、**文字列を返すとエラーメッセージ付きで拒否**。型と併用可(型 → validator の順で評価)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@threestarsoftware/tssgrid",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22",
|
|
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
|
@@ -462,6 +462,9 @@ export declare class TssGrid {
|
|
|
462
462
|
downloadCSV(filename: string, opts?: CsvOptions): void;
|
|
463
463
|
/** 列定義変更後などの再描画(データ・選択は保持)。 */
|
|
464
464
|
redraw(): void;
|
|
465
|
+
/** セル1つだけ表示を描き直す(テキスト/HTML・条件付き書式・無効表示を最新データで)。全体 redraw より軽い。
|
|
466
|
+
* format が外部状態に依存する/セル内ボタンの状態を更新した等に。`c` は列 index か data キー。窓外/非表示は no-op。 */
|
|
467
|
+
renderCell(r: number, cOrKey: number | string): void;
|
|
465
468
|
/** 画像/フォント等の遅延レイアウト後に幾何キャッシュを再構築する保険。 */
|
|
466
469
|
refreshGeometry(): void;
|
|
467
470
|
/** グリッドを破棄(リスナ除去・DOM 撤去・idempotent)。フレームワークの teardown で呼ぶ。 */
|
package/src/tssgrid.js
CHANGED
|
@@ -526,6 +526,13 @@
|
|
|
526
526
|
}
|
|
527
527
|
// 列定義(format/hour12 等)を変えた後の再描画。データと選択は保持。
|
|
528
528
|
redraw() { this.buildTable(); this.setActive(this.active.r, this.active.c); }
|
|
529
|
+
// セル1つだけ表示を描き直す(テキスト/HTML・条件付き書式〔cellClass/cellStyle/rowClass〕・無効表示を最新データで)。
|
|
530
|
+
// 全体 redraw() より軽い。format が外部状態に依存する/セル内ボタンの状態を更新した等で「このセルだけ」を描き直す用。
|
|
531
|
+
// c は列 index か data キー。非表示・仮想で窓外のセルは安全に no-op(描画対象の DOM が無いだけ)。
|
|
532
|
+
renderCell(r, c) {
|
|
533
|
+
const ci = this._resolveCol(c); if (ci < 0 || r < 0 || r >= this.ROWS) return;
|
|
534
|
+
this._renderCell(r, ci);
|
|
535
|
+
}
|
|
529
536
|
|
|
530
537
|
// ---- 並べ替え(A方式: データ実体を並べ替え。Undo には積まない/状態は行に追従) ----
|
|
531
538
|
// 行を order(=新しい並びの「元index配列」)で再配置。data/rowH/_src と セル状態(整列/cellRO)も行ごと追従。
|
|
@@ -954,7 +961,8 @@
|
|
|
954
961
|
static _colLabel(n) { let s = ''; n = n | 0; do { s = String.fromCharCode(65 + (n % 26)) + s; n = Math.floor(n / 26) - 1; } while (n >= 0); return s; }
|
|
955
962
|
// 入力文字列 → 'yyyy-mm-dd'(不正は null)。yyyymmdd / yyyy-mm-dd / yyyy/mm/dd / yyyy.mm.dd を許容。
|
|
956
963
|
static _parseDate(s) {
|
|
957
|
-
s = String(s).trim();
|
|
964
|
+
s = TssGrid._zen2han(String(s).trim()); // IME ON の全角(2026/01/01 等)も半角化して受理
|
|
965
|
+
if (s === '') return null;
|
|
958
966
|
let y, m, d;
|
|
959
967
|
const sep = /^(\d{4})[-/.](\d{1,2})[-/.](\d{1,2})$/.exec(s);
|
|
960
968
|
if (sep) { y = +sep[1]; m = +sep[2]; d = +sep[3]; }
|
|
@@ -965,7 +973,8 @@
|
|
|
965
973
|
}
|
|
966
974
|
// 入力文字列 → 24時間制 'HH:MM' / 'HH:MM:SS'(不正は null)。24h と 12h(AM/PM) の両方を受理。
|
|
967
975
|
static _parseTime(s) {
|
|
968
|
-
|
|
976
|
+
// IME ON の全角(9:30 等)も半角化して受理
|
|
977
|
+
const m = /^(\d{1,2}):(\d{2})(?::(\d{2}))?\s*([AaPp][.]?[Mm][.]?)?$/.exec(TssGrid._zen2han(String(s).trim()));
|
|
969
978
|
if (!m) return null;
|
|
970
979
|
let h = +m[1]; const mi = +m[2], se = m[3] == null ? null : +m[3];
|
|
971
980
|
const ap = m[4] ? m[4][0].toLowerCase() : null; // 'a' | 'p' | null
|
|
@@ -982,12 +991,14 @@
|
|
|
982
991
|
return h + ':' + m[2] + (m[3] != null ? ':' + m[3] : '') + ' ' + ap;
|
|
983
992
|
}
|
|
984
993
|
// CellFormat: 入力(グルーピング/前後綴り混じり) → 正規化した数値文字列(保存値)。不正は null。
|
|
985
|
-
//
|
|
994
|
+
// 全角→半角(数値・日付・時刻文脈)。数字・符号・小数点・カンマ・%・¥・空白+日付/時刻の区切り(/:)を半角化。
|
|
995
|
+
// 業務フォームの IME 誤入力対策(IME ON のまま「2026/01/01」「9:30」等を打っても弾かず半角で受理)。
|
|
986
996
|
static _zen2han(s) {
|
|
987
997
|
return String(s)
|
|
988
998
|
.replace(/[0-9]/g, ch => String.fromCharCode(ch.charCodeAt(0) - 0xFEE0))
|
|
989
999
|
.replace(/+/g, '+').replace(/[-−ー―]/g, '-')
|
|
990
1000
|
.replace(/./g, '.').replace(/,/g, ',')
|
|
1001
|
+
.replace(///g, '/').replace(/:/g, ':')
|
|
991
1002
|
.replace(/%/g, '%').replace(/¥/g, '¥').replace(/ /g, ' ');
|
|
992
1003
|
}
|
|
993
1004
|
static _parseNumber(v, cfg) {
|
|
@@ -1611,7 +1622,9 @@
|
|
|
1611
1622
|
_columnCheckState(c) {
|
|
1612
1623
|
let checked = 0, total = 0;
|
|
1613
1624
|
for (let r = 0; r < this.ROWS; r++) {
|
|
1614
|
-
|
|
1625
|
+
// 全選択は readOnly をスキップする(setColumnChecked と同じ)ので、状態判定も「操作できる行」だけで数える。
|
|
1626
|
+
// これを入れないと「選べる行は全部 ON なのに、押せない行のせいで − 表示」になる。
|
|
1627
|
+
if (this._rowBlankExcept(r, c) || this._isReadOnly(r, c)) continue;
|
|
1615
1628
|
total++; if (this._isCheckedVal(c, this.data[r][c])) checked++;
|
|
1616
1629
|
}
|
|
1617
1630
|
return total === 0 ? 'none' : (checked === total ? 'all' : (checked === 0 ? 'none' : 'some'));
|
|
@@ -2642,8 +2655,8 @@
|
|
|
2642
2655
|
case 'cut': return { label: '切り取り', disabled: !this.copyPaste, act: () => this.copy(true) };
|
|
2643
2656
|
case 'paste': return { label: '貼り付け', disabled: !this.copyPaste, act: () => this.paste() };
|
|
2644
2657
|
case 'clear': return { label: '内容をクリア', act: () => this.clearRange() };
|
|
2645
|
-
case 'check_column': return this.colType(s.c0) === 'checkbox' ? { label: 'すべてチェック', act: () => this.setColumnChecked(s.c0, true) } : null;
|
|
2646
|
-
case 'uncheck_column': return this.colType(s.c0) === 'checkbox' ? { label: 'すべて外す', act: () => this.setColumnChecked(s.c0, false) } : null;
|
|
2658
|
+
case 'check_column': return (this.colType(s.c0) === 'checkbox' && !this._columnAllReadOnly(s.c0)) ? { label: 'すべてチェック', act: () => this.setColumnChecked(s.c0, true) } : null; // 全 read-only 列は no-op なので出さない(ヘッダ無効と整合)
|
|
2659
|
+
case 'uncheck_column': return (this.colType(s.c0) === 'checkbox' && !this._columnAllReadOnly(s.c0)) ? { label: 'すべて外す', act: () => this.setColumnChecked(s.c0, false) } : null;
|
|
2647
2660
|
case 'undo': return { label: '元に戻す', disabled: !this.history.canUndo(), act: () => this.history.undo() };
|
|
2648
2661
|
case 'redo': return { label: 'やり直し', disabled: !this.history.canRedo(), act: () => this.history.redo() };
|
|
2649
2662
|
default: return /^-+$/.test(key) || key === 'separator' ? { sep: true } : null;
|