@threestarsoftware/tssgrid 0.1.5 → 0.1.6
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 +6 -0
- package/README.md +30 -0
- package/package.json +1 -1
- package/src/tssgrid.d.ts +4 -2
- package/src/tssgrid.js +16 -7
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
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.6] - 2026-07-30
|
|
7
|
+
|
|
8
|
+
### 追加
|
|
9
|
+
- **依存ドロップダウン=`columns[c].options` に関数 `(r, row) => [...]`**(コア)— options を関数にすると**行ごとに違う候補**になり、「A 列で選んだ値に応じて B 列のリストが変わる」依存ドロップダウン(都道府県→市区町村 等)が作れる。`row` はその行の値配列(`grid.data[r]`)。開くたびに options を読み直すので、**列共有の静的配列では両立できない「行ごとに別リスト」が同時に成立**(`_optList(c,r)`/`_optLabel(r,c,v)` に r を通し、表示ラベル・保存値の検証もその行の候補で解決)。静的配列は従来どおり(後方互換)。動く例 `examples/dependent-dropdown.html`。
|
|
10
|
+
- **`html:true` 列は `format` を空セルでも実行**(コア)— 従来は空セルだと `_displayValue` が値を素通しして `format` が走らず、**ボタン/アイコン等の"飾り"を空セルに描けなかった**。`html:true` かつ `format` が関数のときは空でも走らせ、**全行にボタン列などを描ける**ように(素テキスト列・非空 html セルは不変)。セル内ボタンは専用 API を持たず `html:true`+`format`+`grid.table` への click 委譲で作る(動く例 `examples/cell-button.html`)。
|
|
11
|
+
|
|
6
12
|
## [0.1.5] - 2026-07-25
|
|
7
13
|
|
|
8
14
|
### 追加
|
package/README.md
CHANGED
|
@@ -269,6 +269,36 @@ new TssGrid(el, { suppressAutofill: true }); // 既定 false
|
|
|
269
269
|
- 副作用として環境により**鍵アイコン**や**生成パスワードの提案**が出ることがあるため **opt-in**(既定は付けません)。値はマスクされません(`type` は `text` のまま)。
|
|
270
270
|
- **あわせて、あなたのページ側の素の `<input>`(テーブル名欄など)にも同じ `autocomplete="new-password"` を付けてください**。誘発の主因はグリッドではなく周囲のフォーム塊のことが多いためです。
|
|
271
271
|
|
|
272
|
+
### 依存ドロップダウン(`options` を関数に=行ごとにリスト差替え)
|
|
273
|
+
|
|
274
|
+
`columns[c].options` は配列(列共通)のほか、**関数 `(r, row) => [...]`** を渡せます。すると**行ごとに違う候補**になり、「A 列で選んだ値に応じて B 列のリストが変わる」**依存ドロップダウン**(都道府県→市区町村 等)が作れます。`row` はその行の値配列(`grid.data[r]`)。開くたびに `options` を読み直すので、**列共有の静的配列では両立できない「行ごとに別リスト」が同時に成立**します(→ 動く例: [`examples/dependent-dropdown.html`](https://tssgrid.threestarsoftware.co.jp/examples/dependent-dropdown.html))。
|
|
275
|
+
|
|
276
|
+
```js
|
|
277
|
+
const CITY = { 東京都:['渋谷区','新宿区'], 大阪府:['大阪市','堺市'] };
|
|
278
|
+
columns: [
|
|
279
|
+
{ type:'dropdown', options: Object.keys(CITY) }, // 親(静的)
|
|
280
|
+
{ type:'dropdown', options: (r, row) => CITY[row[0]] || [] }, // 子(行依存)
|
|
281
|
+
]
|
|
282
|
+
// 親が変わったら子をクリア(古い値が残らないように)
|
|
283
|
+
onAfterChange: (chs) => chs.forEach(ch => { if (ch.c === 0 && ch.oldValue !== ch.newValue) grid.setValue(ch.r, 1, ''); });
|
|
284
|
+
```
|
|
285
|
+
- 行依存でも**表示ラベル・検証(保存値が候補にあるか)はその行の候補で解決**します。静的配列は従来どおり(後方互換)。
|
|
286
|
+
|
|
287
|
+
### セル内ボタン(`html:true` + クリック委譲)
|
|
288
|
+
|
|
289
|
+
専用のボタン列 API はありません。**`columns[c].html:true` + `format` が返す HTML** でセルにボタンを描き、**`grid.table` に click 委譲**して `data-*` で拾うのが定石です(→ 動く例: [`examples/cell-button.html`](https://tssgrid.threestarsoftware.co.jp/examples/cell-button.html))。
|
|
290
|
+
|
|
291
|
+
```js
|
|
292
|
+
columns: [ /* … */,
|
|
293
|
+
{ html:true, readOnly:true, format: () => '<button class="mybtn" data-act="del">削除</button>' } ]
|
|
294
|
+
grid.table.addEventListener('click', e => {
|
|
295
|
+
const b = e.target.closest('.mybtn'); if (!b) return;
|
|
296
|
+
const r = +b.closest('tr[data-r]').dataset.r;
|
|
297
|
+
if (b.dataset.act === 'del') grid.deleteRows(r, r);
|
|
298
|
+
});
|
|
299
|
+
```
|
|
300
|
+
- ボタン列は `readOnly:true` にして編集モードに入らないように。行番号は `closest('tr[data-r]')` から。**`html:true` はエスケープを外す**ので、ラベルにユーザー入力を入れる時は自分でエスケープを(XSS 責任は HTML 提供側)。空セルでも `format` は走ります(飾り列を全行に描ける)。
|
|
301
|
+
|
|
272
302
|
### 非破壊フィルタ(`filter` / `clearFilter`)
|
|
273
303
|
|
|
274
304
|
**コア内蔵**の非破壊フィルタ(行インデックスマッパ)。全行は内部の `_allRows`(マスタ)に保持したまま、**絞り込んだ可視部分集合だけを描画**します。要点は**絞り込み後の描画・ライブ検索が軽い**こと(自社調べ:10万行を保持して約200行に絞り込み → 初回描画 ≈ 0.5 秒、絞り込み済みのライブ検索 ≈ 数 ms)→ 動く例: [`examples/filter-live.html`](https://tssgrid.threestarsoftware.co.jp/examples/filter-live.html)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@threestarsoftware/tssgrid",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
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
|
@@ -46,8 +46,10 @@ export interface ColumnDef {
|
|
|
46
46
|
title?: string;
|
|
47
47
|
/** セルタイプ。既定 'text'。 */
|
|
48
48
|
type?: CellType;
|
|
49
|
-
/** dropdown
|
|
50
|
-
|
|
49
|
+
/** dropdown の選択肢。関数を渡すと**行ごとに異なる依存ドロップダウン**(例: 都道府県→市区町村)。
|
|
50
|
+
* `(r, row)=>[…]` の row はその行の値配列(`grid.data[r]`)。静的配列は全行共通(後方互換)。 */
|
|
51
|
+
options?: Array<string | { value: string; label?: string }>
|
|
52
|
+
| ((r: number, row: any[]) => Array<string | { value: string; label?: string }>);
|
|
51
53
|
/** 列ごとの初期幅(px)。列定義に同居=並べ替え/コピペで幅が追従。 */
|
|
52
54
|
width?: number;
|
|
53
55
|
/** false でこの列だけリサイズ禁止。 */
|
package/src/tssgrid.js
CHANGED
|
@@ -1031,7 +1031,7 @@
|
|
|
1031
1031
|
}
|
|
1032
1032
|
if (t === 'dropdown') {
|
|
1033
1033
|
if (v === '') { if (cfg.allowEmpty === false) return { ok: false }; } // allowEmpty:false=必須選択(空は不可)
|
|
1034
|
-
else if (!this._optList(c).some(o => o.value === v)) return { ok: false }; // 保存値は option の value
|
|
1034
|
+
else if (!this._optList(c, r).some(o => o.value === v)) return { ok: false }; // 保存値は option の value と照合(行依存 options も r で解決)
|
|
1035
1035
|
} else if (t === 'checkbox') {
|
|
1036
1036
|
v = this._isCheckedVal(c, v) ? this._cbChecked(c) : this._cbUnchecked(c);
|
|
1037
1037
|
}
|
|
@@ -1098,16 +1098,20 @@
|
|
|
1098
1098
|
if (typeof cfg.format === 'string') return TssGrid._applyDatePattern(v, cfg.format); // 日付パターン表示
|
|
1099
1099
|
if (cfg.type === 'number') return TssGrid._formatNumber(v, cfg); // 数値書式(カンマ/小数/前後綴り)
|
|
1100
1100
|
if (cfg.type === 'time' && cfg.hour12) return TssGrid._fmtTime12(v); // 組込 12時間表示
|
|
1101
|
-
if (cfg.type === 'dropdown') return this._optLabel(c, v); // options が {value,label}
|
|
1101
|
+
if (cfg.type === 'dropdown') return this._optLabel(r, c, v); // options が {value,label} なら表示はラベル(行依存 options も r で解決)
|
|
1102
1102
|
return v;
|
|
1103
1103
|
}
|
|
1104
1104
|
// dropdown の options を [{value,label}] に正規化(文字列なら value=label)。値↔表示を分けられる。
|
|
1105
|
-
|
|
1106
|
-
|
|
1105
|
+
// options が関数なら (r, row)=>[…] で行依存(依存ドロップダウン)。row はその行の値配列(this.data[r])。
|
|
1106
|
+
// 静的配列はそのまま(r は無視)=後方互換。
|
|
1107
|
+
_optList(c, r) {
|
|
1108
|
+
let opts = this.colCfg(c).options;
|
|
1109
|
+
if (typeof opts === 'function') { try { opts = opts(r, r != null ? this.data[r] : undefined) || []; } catch (_) { opts = []; } }
|
|
1110
|
+
return (opts || []).map(o =>
|
|
1107
1111
|
(o && typeof o === 'object') ? { value: String(o.value), label: String(o.label != null ? o.label : o.value) }
|
|
1108
1112
|
: { value: String(o), label: String(o) });
|
|
1109
1113
|
}
|
|
1110
|
-
_optLabel(c, value) { const o = this._optList(c).find(x => x.value === value); return o ? o.label : value; }
|
|
1114
|
+
_optLabel(r, c, value) { const o = this._optList(c, r).find(x => x.value === value); return o ? o.label : value; }
|
|
1111
1115
|
// 編集開始時にエディタへ入れる文字列。number は Excel風に「素の数値」を出す(書式は表示専用)。
|
|
1112
1116
|
// ネイティブ(date/time picker)は保存値、それ以外のテキスト編集は表示形。
|
|
1113
1117
|
_editText(r, c) {
|
|
@@ -1121,7 +1125,12 @@
|
|
|
1121
1125
|
return '<input type="checkbox" class="tg-cb" tabindex="-1"' + (this._isReadOnly(r, c) ? ' disabled' : '') + (this._isCheckedVal(c, this.data[r][c]) ? ' checked' : '') + '>';
|
|
1122
1126
|
if (this.data[r][c] === '' && this._placeholder(c) != null) return TssGrid.esc(this._placeholder(c));
|
|
1123
1127
|
// html:true の列は表示だけ生HTML(値はテキスト保存のまま)。エスケープを外す=XSS責任は format/値の提供側。
|
|
1124
|
-
|
|
1128
|
+
// 空セルでも format を走らせる=ボタン/アイコン等の"飾り"を全行に描ける(_displayValue は空を素通しするので html+関数formatだけ特別扱い)。
|
|
1129
|
+
if (this.colCfg(c).html) {
|
|
1130
|
+
const fmt = this.colCfg(c).format;
|
|
1131
|
+
if (typeof fmt === 'function' && this.data[r][c] === '') { try { const o = fmt('', { r, c }); return o == null ? '' : String(o); } catch (_) { return ''; } }
|
|
1132
|
+
return this._displayValue(r, c);
|
|
1133
|
+
}
|
|
1125
1134
|
const _h = TssGrid.esc(this._displayValue(r, c));
|
|
1126
1135
|
// 複数行セルは絶対配置のクリップ箱で包む=行を押し広げない(行高一定・仮想スクロール維持/行高ぶんだけ表示)
|
|
1127
1136
|
if (this.colCfg(c).multiline) return '<div class="tg-ml-clip">' + _h + '</div>';
|
|
@@ -3129,7 +3138,7 @@
|
|
|
3129
3138
|
_openSelect() {
|
|
3130
3139
|
const r = this.active.r, c = this.active.c, cfg = this.colCfg(c);
|
|
3131
3140
|
if (this._isReadOnly(r, c)) return; // 読み取り専用は <select> を開かない
|
|
3132
|
-
const opts = this._optList(c), values = opts.map(o => o.value), cur = this.data[r][c];
|
|
3141
|
+
const opts = this._optList(c, r), values = opts.map(o => o.value), cur = this.data[r][c];
|
|
3133
3142
|
const allowEmpty = cfg.allowEmpty !== false; // 既定: クリア用の空オプションを先頭に付ける
|
|
3134
3143
|
let html = allowEmpty ? '<option value=""></option>' : '';
|
|
3135
3144
|
for (const o of opts) html += '<option value="' + TssGrid.esc(o.value) + '">' + TssGrid.esc(o.label) + '</option>'; // value↔label 分離
|