@threestarsoftware/tssgrid 0.0.5 → 0.0.7

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 ADDED
@@ -0,0 +1,86 @@
1
+ # 変更履歴 (Changelog)
2
+
3
+ TssGrid の各リリースの変更点。日付は JST。形式は [Keep a Changelog](https://keepachangelog.com/ja/1.1.0/) 準拠(ゆるめ)。
4
+ 数値は特記なき限り**自社調べ**(headless Chrome / Windows 11・環境差あり)。
5
+
6
+ ## [0.0.7] - 2026-07-10
7
+
8
+ ### 修正
9
+ - カスタムエディタ(`tss-calendar` / `tss-datetime` 等、`body` 直下にポップアップを出す型)を**開いたままフィルタ/ソート/フリーズ/再描画**すると、ポップアップが画面に取り残されて浮く不具合を修正(`buildTable()` が開いているエディタを閉じていなかった)。1修正で全カスタムエディタに効く。
10
+ - ポップアップ型エディタ(`tss-calendar` / `tss-time` / `tss-datetime` / `tss-autocomplete`)で、**別のセルを続けて開く**と前のポップアップと `document` リスナが片付かず取り残し/リークする不具合を修正(各 `show()` 冒頭で `teardown()` するよう統一)。
11
+
12
+ ### 補足(挙動変化・影響範囲)
13
+ - カスタムエディタを開いたまま再描画/フィルタ/ソートすると、そのエディタは**キャンセル確定**となり `onEditEnd({ canceled: true })` が発火するようになりました(従来は発火せずポップアップだけ取り残していた)。
14
+ - ネイティブのドロップダウン(`<select>`)は永続要素のため上記いずれの影響も受けません(確認済み)。
15
+
16
+ ## [0.0.6] - 2026-06-30
17
+
18
+ ### 追加
19
+ - ドラッグ選択・フィルハンドルが**表示領域の外(上下左右)まで自動スクロールしながら伸びる**ように。ビューも一緒にスクロールし、速度は端からのはみ出し量に比例(少し超え=ゆっくり/大きく超え=速く)。仮想スクロール時・非仮想時の両方で動作。
20
+ - `rowHeaderLabel` オプション: 行ヘッダの表示を指定(`false`=数字なしの空ヘッダ/関数 `(r)=>文字列`=カスタム表示/未指定=従来の行番号)。空にしても溝は残り、クリックでの行選択は効く。
21
+ - `rowReorder: 'header'`: 行ヘッダーセル(左端の行番号の列)**全体**を行移動の掴み手に(クリック=行選択/ドラッグ=移動)。従来の `rowReorder: true` は ⠿ アイコンのみ掴める(不変)。
22
+ - `rowReorderMark` オプション: 行ヘッダーの ⠿ マークの位置を `'before'`(番号の前・既定)/ `'after'`(番号の後)/ `false`(出さない)から選べる。
23
+
24
+ ### 修正
25
+ - 仮想スクロールでドラッグ選択/フィルが窓の端で止まっていた問題。
26
+ - フィルが端を1行超えただけで大量に伸びてしまう暴走を修正(伸ばす先を常にポインタ位置のセルに)。
27
+ - `examples/virtual.html`: 部署ドロップダウンの選択肢が出ない不具合(`options` 指定漏れ)を修正/表示中インジケータの更新でツールバーがずれる問題を修正/パネルを縦リサイズ可能に。
28
+
29
+ ### ドキュメント
30
+ - 比較表(`examples/compare.html`): 仮想スクロールの行を「対応(`virtual:true`・opt-in)」に更新し、デモリンクに `virtual.html` を追加。
31
+
32
+ ## [0.0.5] - 2026-06-25
33
+
34
+ ### 追加
35
+ - **仮想スクロール `virtual: true`**(コア)— 大量行を「画面に見える窓ぶん+バッファだけ」描画。全件を一度に DOM 化しないので、初期描画が一瞬・スクロールも軽い。有効化はオプション一行。
36
+ - 自社調べ: 10万行で初回描画 ≈ 46ms(全件描画 ≈ 10.5秒に対して)。デモ: `examples/virtual.html`。
37
+ - 制約(v1): 行高一定/固定行列・折り返し・セル結合は非対応(指定時は自動無効化)。
38
+ - **`rowHeaderWidth` オプション**(コア)— 大量行で行番号の桁数が増える時に行ヘッダ幅を調整。
39
+
40
+ ### ドキュメント
41
+ - README: 非破壊フィルタの説明を実測値に正確化(「絞り込み後が軽い」を自社調べ値で)+仮想スクロール節を追加。
42
+ - 比較表(`examples/compare.html`)に Tabulator を追加(公式デモ準拠)。
43
+
44
+ ## [0.0.4] - 2026-06-24
45
+
46
+ ### 追加
47
+ - 見積書デモ(`examples/quote.html`)— 印刷/日付ピッカー/列幅固定/Enter 横送り(`nextCell`)。
48
+ - `navSkipReadOnly` オプション(Tab/Enter 送りで readonly セルを飛ばす=伝票入力向け)。
49
+ - プラグイン `tss-datetime`(日付+時刻の同時ピッカー・曜日つき・別フィールド結合・時刻のみ対応)。
50
+
51
+ ### 修正
52
+ - 編集中の `Ctrl+Z` で元の値に戻せない不具合(input 編集を Esc 同等でキャンセル)。
53
+ - グリッド外周の右・下が二重線になる不具合(最終行/列のセル罫線を枠線に一本化)。
54
+ - 全幅見出し帯の結合セルで列/行選択が他列へ広がる不具合(Excel 流にクランプ)。
55
+ - 結合解除で見出しラベルが消える不具合。
56
+
57
+ ## [0.0.3] - 2026-06-23
58
+
59
+ ### 追加
60
+ - プラグイン `tss-sum`(条件付き合計)+請求書デモで採用。
61
+
62
+ ### 修正
63
+ - 請求書デモの合計欄を別グリッド化して金額列に完全整列/No. 列見出しが選択色になる不具合/印刷で選択枠を非表示。
64
+
65
+ ## [0.0.2] - 2026-06-22
66
+
67
+ ### 追加
68
+ - **npm パッケージ化**(`@threestarsoftware/tssgrid`)+公開 API の型定義 `tssgrid.d.ts`。
69
+ - 請求書(インボイス対応)デモ。
70
+ - サイト: `/docs` ドキュメント・`/api` リファレンス(`.d.ts` から自動生成)・会社名フッター。
71
+
72
+ ### 修正
73
+ - 行/列ヘッダ選択中の右クリックで複数選択が解除される不具合。
74
+
75
+ ## [0.0.1] - 2026-06-19
76
+
77
+ - 初回公開。コア(編集グリッド/日本語 IME 堅牢・全角半角・セル結合・固定行列・非破壊フィルタ・条件付き書式・コピー/貼付・Undo/Redo ほか)+同梱プラグイン+サンプル一式。
78
+ - 目玉デモ(方眼ガント/在庫管理)をサイトでライブ化。
79
+
80
+ [0.0.7]: https://github.com/threestarsoftware/TssGrid/releases/tag/v0.0.7
81
+ [0.0.6]: https://github.com/threestarsoftware/TssGrid/releases/tag/v0.0.6
82
+ [0.0.5]: https://github.com/threestarsoftware/TssGrid/releases/tag/v0.0.5
83
+ [0.0.4]: https://github.com/threestarsoftware/TssGrid/releases/tag/v0.0.4
84
+ [0.0.3]: https://github.com/threestarsoftware/TssGrid/releases/tag/v0.0.3
85
+ [0.0.2]: https://github.com/threestarsoftware/TssGrid/releases/tag/v0.0.2
86
+ [0.0.1]: https://github.com/threestarsoftware/TssGrid/releases/tag/v0.0.1
package/README.md CHANGED
@@ -269,7 +269,7 @@ new TssGrid(el, { virtual: { buffer: 10 }, data }); // 窓の上下バッフ
269
269
 
270
270
  ### 同梱プラグイン: ヘッダ オートフィルタ(filter UI)
271
271
 
272
- `plugins/tss-filter.js`+`.css`<!--DEV-->(**無依存・コア無改変**)<!--/DEV-->= 各見出しの**ロート(漏斗)アイコン**をクリックで**値チェックリスト+検索**のポップアップ(Excel風。ソートの▲▼と区別できるアイコン)。複数列の条件は **AND**。中身は上の**コア `filter()` を呼ぶだけ**。動く例: [`examples/filter-ui.html`](https://tssgrid.threestarsoftware.co.jp/examples/filter-ui.html)。
272
+ `plugins/tss-filter.js`+`.css`= 各見出しの**ロート(漏斗)アイコン**をクリックで**値チェックリスト+検索**のポップアップ(Excel風。ソートの▲▼と区別できるアイコン)。複数列の条件は **AND**。中身は上の**コア `filter()` を呼ぶだけ**。動く例: [`examples/filter-ui.html`](https://tssgrid.threestarsoftware.co.jp/examples/filter-ui.html)。
273
273
 
274
274
  ```html
275
275
  <link rel="stylesheet" href="plugins/tss-filter.css">
@@ -284,7 +284,7 @@ grid.getPlugin('filter').clearAll(); // 全条件クリア
284
284
 
285
285
  ### 同梱プラグイン: 共有マスタ(複数グリッドで1データ)
286
286
 
287
- `plugins/tss-shared.js`<!--DEV-->(**無依存・コア無改変**)<!--/DEV-->= **1つのマスタ(同じ行データ)を複数グリッドで共有**。左右を**別フィルタ/別ソート**で表示でき、**片方の編集がもう片方の同じ行へ即反映**(行参照を共有)。A系/B系を並べて突き合わせ編集する用途。動く例: [`examples/shared-master.html`](https://tssgrid.threestarsoftware.co.jp/examples/shared-master.html)。
287
+ `plugins/tss-shared.js`= **1つのマスタ(同じ行データ)を複数グリッドで共有**。左右を**別フィルタ/別ソート**で表示でき、**片方の編集がもう片方の同じ行へ即反映**(行参照を共有)。A系/B系を並べて突き合わせ編集する用途。動く例: [`examples/shared-master.html`](https://tssgrid.threestarsoftware.co.jp/examples/shared-master.html)。
288
288
 
289
289
  ```html
290
290
  <script src="plugins/tss-shared.js"></script>
@@ -335,7 +335,7 @@ TssGrid.registerPlugin('autosave', autoSavePlugin); // 名前登録 → plugin
335
335
 
336
336
  ## カスタムエディタ
337
337
 
338
- 組込エディタ(text/dropdown/checkbox/date/time/number)以外に、**独自の編集UI**を列に差せます。`columns[c].editor` に**薄いオブジェクト契約**を渡すだけ<!--DEV-->(無依存)<!--/DEV-->。
338
+ 組込エディタ(text/dropdown/checkbox/date/time/number)以外に、**独自の編集UI**を列に差せます。`columns[c].editor` に**薄いオブジェクト契約**を渡すだけ。
339
339
 
340
340
  ```js
341
341
  const myEditor = {
@@ -362,7 +362,7 @@ new TssGrid(el, { columns: [{ editor: myEditor }] });
362
362
 
363
363
  ### 同梱プラグイン: 休日カレンダー対応の日付ピッカー
364
364
 
365
- `plugins/tss-calendar.js`<!--DEV-->(**無依存**)<!--/DEV-->= ネイティブ `<input type=date>` では出来ない**会社の休日カレンダー(JSON)対応**の日付ピッカー。勤怠・シフト・予約などで効きます(動く例: [`examples/calendar.html`](https://tssgrid.threestarsoftware.co.jp/examples/calendar.html))。
365
+ `plugins/tss-calendar.js`= ネイティブ `<input type=date>` では出来ない**会社の休日カレンダー(JSON)対応**の日付ピッカー。勤怠・シフト・予約などで効きます(動く例: [`examples/calendar.html`](https://tssgrid.threestarsoftware.co.jp/examples/calendar.html))。
366
366
 
367
367
  ```html
368
368
  <link rel="stylesheet" href="plugins/tss-calendar.css">
@@ -383,7 +383,7 @@ new TssGrid(el, { columns: [{ type: 'date', editor: cal, format: 'yyyy/mm/dd' }]
383
383
 
384
384
  ### 同梱プラグイン: 時刻ピッカー(時/分プルダウン)
385
385
 
386
- `plugins/tss-time.js`<!--DEV-->(**無依存**)<!--/DEV-->= 業務の時刻入力向けの**時/分プルダウン**ピッカー(クロックフェイスのようなリッチさは省き、任意時刻を素早く)。保存値は常に **24h `'HH:MM'`**(組込 `type:'time'` と互換)。動く例: [`examples/time.html`](https://tssgrid.threestarsoftware.co.jp/examples/time.html)。
386
+ `plugins/tss-time.js`= 業務の時刻入力向けの**時/分プルダウン**ピッカー(クロックフェイスのようなリッチさは省き、任意時刻を素早く)。保存値は常に **24h `'HH:MM'`**(組込 `type:'time'` と互換)。動く例: [`examples/time.html`](https://tssgrid.threestarsoftware.co.jp/examples/time.html)。
387
387
 
388
388
  ```html
389
389
  <link rel="stylesheet" href="plugins/tss-time.css">
@@ -398,7 +398,7 @@ new TssGrid(el, { columns: [{ type: 'time', editor: t }] });
398
398
 
399
399
  ### 同梱プラグイン: 派生列(formula / computed column)
400
400
 
401
- `plugins/tss-formula.js`<!--DEV-->(**無依存・コア無改変**)<!--/DEV-->=「その行の値だけから計算する列」を `columns[c].formula` 関数で書けるようにする。金額=数量×単価、税込=金額×1.1 のような**派生列**を宣言的に。動く例: [`examples/formula.html`](https://tssgrid.threestarsoftware.co.jp/examples/formula.html)。
401
+ `plugins/tss-formula.js`=「その行の値だけから計算する列」を `columns[c].formula` 関数で書けるようにする。金額=数量×単価、税込=金額×1.1 のような**派生列**を宣言的に。動く例: [`examples/formula.html`](https://tssgrid.threestarsoftware.co.jp/examples/formula.html)。
402
402
 
403
403
  ```html
404
404
  <script src="plugins/tss-formula.js"></script>
@@ -421,7 +421,7 @@ new TssGrid(el, {
421
421
 
422
422
  ### 同梱プラグイン: 列の集計(totals / 縦の SUM・平均…)
423
423
 
424
- `plugins/tss-totals.js`<!--DEV-->(**無依存・コア無改変**)<!--/DEV-->= 列方向の集計(`sum`/`avg`/`count`/`min`/`max`/独自関数)を担う。**計算だけで描画はしない**=結果を `onTotals` で受けて好きな場所へ(フッタ・ラベル・**別の TssGrid の合計表**でも)。`formula` と併用すると派生列の後に集計が連動。動く例: [`examples/formula.html`](https://tssgrid.threestarsoftware.co.jp/examples/formula.html)(明細=formula/合計=totals)。
424
+ `plugins/tss-totals.js`= 列方向の集計(`sum`/`avg`/`count`/`min`/`max`/独自関数)を担う。**計算だけで描画はしない**=結果を `onTotals` で受けて好きな場所へ(フッタ・ラベル・**別の TssGrid の合計表**でも)。`formula` と併用すると派生列の後に集計が連動。動く例: [`examples/formula.html`](https://tssgrid.threestarsoftware.co.jp/examples/formula.html)(明細=formula/合計=totals)。
425
425
 
426
426
  ```html
427
427
  <script src="plugins/tss-totals.js"></script>
@@ -442,7 +442,7 @@ new TssGrid(el, {
442
442
 
443
443
  ### 同梱プラグイン: 条件付き合計(tss-sum / ラベル付き合計行・整列表示)
444
444
 
445
- `plugins/tss-sum.js`<!--DEV-->(**無依存・コア無改変**/ラベル横結合に TssMerge を任意利用)<!--/DEV-->= **ラベル付きの"合計行"**(小計・消費税10%・消費税8%・合計 など)を、**条件(`when`)+対象(`of`)を宣言**で定義して作る。`tss-totals` が「列ごと1値」なのに対し、こちらは**同じ列を条件別に何本でも集計**でき(消費税10%と8%は両方 amount 由来=per-column では衝突する)、**ソースと同一列構成の readonly 集計グリッドを生成**するので**金額が縦にピッタリ揃う**(画面でも印刷でも)。請求書・見積などの合計欄に。動く例: [`examples/invoice.html`](https://tssgrid.threestarsoftware.co.jp/examples/invoice.html)(インボイス請求書の合計欄)。
445
+ `plugins/tss-sum.js`= **ラベル付きの"合計行"**(小計・消費税10%・消費税8%・合計 など)を、**条件(`when`)+対象(`of`)を宣言**で定義して作る。`tss-totals` が「列ごと1値」なのに対し、こちらは**同じ列を条件別に何本でも集計**でき(消費税10%と8%は両方 amount 由来=per-column では衝突する)、**ソースと同一列構成の readonly 集計グリッドを生成**するので**金額が縦にピッタリ揃う**(画面でも印刷でも)。請求書・見積などの合計欄に。動く例: [`examples/invoice.html`](https://tssgrid.threestarsoftware.co.jp/examples/invoice.html)(インボイス請求書の合計欄)。
446
446
 
447
447
  ```html
448
448
  <script src="plugins/tss-merge.js"></script> <!-- ラベルの横結合に利用 -->
@@ -466,7 +466,7 @@ const sum = TssSum.attach(grid, document.getElementById('sumgrid'), {
466
466
 
467
467
  ### 同梱プラグイン: 残高/累計(running total・縦の連鎖)
468
468
 
469
- `plugins/tss-running.js`<!--DEV-->(**無依存・コア無改変**)<!--/DEV-->=「**前の行の結果 + この行の増減**」で縦に積む列。`formula`(行内)でも `totals`(全体合計)でも出せない**縦方向の連鎖**を担う。出納帳/通帳の**残高**、在庫受払の**在庫数**、経費・ポイントの**累計**など。動く例: [`examples/running.html`](https://tssgrid.threestarsoftware.co.jp/examples/running.html)(現金出納帳=入金・出金→残高)。
469
+ `plugins/tss-running.js`=「**前の行の結果 + この行の増減**」で縦に積む列。`formula`(行内)でも `totals`(全体合計)でも出せない**縦方向の連鎖**を担う。出納帳/通帳の**残高**、在庫受払の**在庫数**、経費・ポイントの**累計**など。動く例: [`examples/running.html`](https://tssgrid.threestarsoftware.co.jp/examples/running.html)(現金出納帳=入金・出金→残高)。
470
470
 
471
471
  ```html
472
472
  <script src="plugins/tss-running.js"></script>
@@ -485,11 +485,11 @@ new TssGrid(el, {
485
485
  - `column`(残高列) / `initial`(前残) / `delta` か `step` / `skipEmpty`(全列空の行は連鎖を進めず空表示、既定true)。
486
486
  - **行ドラッグ移動(rowReorder)併用時**は順序が変わるので `onAfterRowMove` で `handle.recompute()` を呼ぶ。
487
487
 
488
- > 「**行=formula/列=totals/縦=running**」の3点が揃う。いずれも `setValueRaw`+`onAfterChange` ラップで動く**<!--DEV-->コア無改変<!--/DEV-->プラグイン**。
488
+ > 「**行=formula/列=totals/縦=running**」の3点が揃う。いずれも `setValueRaw`+`onAfterChange` ラップで動く**プラグイン**。
489
489
 
490
490
  ### 同梱プラグイン: 氏名→フリガナ 自動入力(furigana・辞書不要)
491
491
 
492
- `plugins/tss-furigana.js`(**<!--DEV-->無依存・<!--/DEV-->辞書不要<!--DEV-->・コア無改変<!--/DEV-->**)= 漢字を入力(IME変換)すると**読みが別列に自動で入る**(スマホ連絡先の よみがな 自動入力と同じ)。辞書引きではなく、**IME変換の composition イベントから「変換前のかな」を拾う**方式。TssGrid は**アクティブセルに本物の `<input>` を重ねる**設計なので composition が確実に取れる=「**IMEに強い**」を機能で体現。動く例: [`examples/furigana.html`](https://tssgrid.threestarsoftware.co.jp/examples/furigana.html)。
492
+ `plugins/tss-furigana.js`(**辞書不要**)= 漢字を入力(IME変換)すると**読みが別列に自動で入る**(スマホ連絡先の よみがな 自動入力と同じ)。辞書引きではなく、**IME変換の composition イベントから「変換前のかな」を拾う**方式。TssGrid は**アクティブセルに本物の `<input>` を重ねる**設計なので composition が確実に取れる=「**IMEに強い**」を機能で体現。動く例: [`examples/furigana.html`](https://tssgrid.threestarsoftware.co.jp/examples/furigana.html)。
493
493
 
494
494
  ```html
495
495
  <script src="plugins/tss-furigana.js"></script>
@@ -506,7 +506,7 @@ new TssGrid(el, {
506
506
 
507
507
  ### 同梱プラグイン: セル内ミニ計算式(calc・=12*3)
508
508
 
509
- `plugins/tss-calc.js`(**<!--DEV-->無依存・<!--/DEV-->eval/Function 不使用<!--DEV-->・コア無改変<!--/DEV-->**)= セルに `=12*3+5` と打つと確定時に **41** に。四則 `+ - * /`・括弧・小数対応の**自前再帰下降パーサ**(CSP安全)。`columns[c].parse`(入力変換)seam に乗るだけ。動く例: [`examples/calc.html`](https://tssgrid.threestarsoftware.co.jp/examples/calc.html)。
509
+ `plugins/tss-calc.js`(**eval/Function 不使用**)= セルに `=12*3+5` と打つと確定時に **41** に。四則 `+ - * /`・括弧・小数対応の**自前再帰下降パーサ**(CSP安全)。`columns[c].parse`(入力変換)seam に乗るだけ。動く例: [`examples/calc.html`](https://tssgrid.threestarsoftware.co.jp/examples/calc.html)。
510
510
 
511
511
  ```html
512
512
  <script src="plugins/tss-calc.js"></script>
@@ -522,7 +522,7 @@ plugins: [ TssCalc.plugin({ columns: ['quantity', 'price'] }) ] // columns 省
522
522
 
523
523
  ### 同梱プラグイン: オートコンプリート(autocomplete エディタ)
524
524
 
525
- `plugins/tss-autocomplete.js`<!--DEV-->(**無依存・コア無改変**)<!--/DEV-->= 自由入力+**候補の絞り込み**。固定 dropdown の上位版で「だいたい決まってるが新規もある」項目(取引先・商品名)向け。**IME変換中の Enter は奪わない**(変換確定が最優先)。動く例: [`examples/autocomplete.html`](https://tssgrid.threestarsoftware.co.jp/examples/autocomplete.html)。
525
+ `plugins/tss-autocomplete.js`= 自由入力+**候補の絞り込み**。固定 dropdown の上位版で「だいたい決まってるが新規もある」項目(取引先・商品名)向け。**IME変換中の Enter は奪わない**(変換確定が最優先)。動く例: [`examples/autocomplete.html`](https://tssgrid.threestarsoftware.co.jp/examples/autocomplete.html)。
526
526
 
527
527
  ```html
528
528
  <link rel="stylesheet" href="plugins/tss-autocomplete.css">
@@ -540,7 +540,7 @@ columns: [
540
540
 
541
541
  ### 同梱プラグイン: セル内スパークライン(sparkline・ミニグラフ)
542
542
 
543
- `plugins/tss-sparkline.js`(**<!--DEV-->無依存・<!--/DEV-->SVG自前描画<!--DEV-->・コア無改変<!--/DEV-->**)= セルの数列を**ミニ折れ線/棒**で描く。`columns[c].html:true` + `format`(format が返す SVG がセルに入る)seam に乗るだけ。Chart.js 等は積まない。動く例: [`examples/sparkline.html`](https://tssgrid.threestarsoftware.co.jp/examples/sparkline.html)(月次売上→動向グラフ・編集でライブ更新)。
543
+ `plugins/tss-sparkline.js`(**SVG自前描画**)= セルの数列を**ミニ折れ線/棒**で描く。`columns[c].html:true` + `format`(format が返す SVG がセルに入る)seam に乗るだけ。Chart.js 等は積まない。動く例: [`examples/sparkline.html`](https://tssgrid.threestarsoftware.co.jp/examples/sparkline.html)(月次売上→動向グラフ・編集でライブ更新)。
544
544
 
545
545
  ```html
546
546
  <script src="plugins/tss-sparkline.js"></script>
@@ -554,11 +554,11 @@ columns: [
554
554
  formula: (row) => ['m4','m5','m6','m7','m8','m9'].map(k=>row[k]).filter(v=>v!=='').join(',') }
555
555
  ```
556
556
  - `type`('line'/'bar') / `width` / `height` / `color` / `strokeWidth` / `fill`(折れ線下の塗り) / `min`・`max`(既定は自動)。
557
- - 数値しか SVG に入れない=安全。`formula`/`html-cells` と同じ仕組みで、**有料グリッドの "セル内グラフ" を無料<!--DEV-->・無依存<!--/DEV-->で**。
557
+ - 数値しか SVG に入れない=安全。`formula`/`html-cells` と同じ仕組みで、**有料グリッドの "セル内グラフ" を無料で**。
558
558
 
559
559
  ### 同梱プラグイン: セル内 横棒グラフ(bar・データバー)
560
560
 
561
- `plugins/tss-bar.js`<!--DEV-->(**無依存・コア無改変**)<!--/DEV-->= セルの数値を**横棒**で描く(認知率・進捗%・スコア)。`html:true`+`format` seam。`type:'number'` 併用で**バーのまま編集**(値を打つと伸びる)。動く例: [`examples/bar.html`](https://tssgrid.threestarsoftware.co.jp/examples/bar.html)。
561
+ `plugins/tss-bar.js`= セルの数値を**横棒**で描く(認知率・進捗%・スコア)。`html:true`+`format` seam。`type:'number'` 併用で**バーのまま編集**(値を打つと伸びる)。動く例: [`examples/bar.html`](https://tssgrid.threestarsoftware.co.jp/examples/bar.html)。
562
562
 
563
563
  ```html
564
564
  <script src="plugins/tss-bar.js"></script>
@@ -573,7 +573,7 @@ columns: [
573
573
 
574
574
  ### 同梱プラグイン: ヒートマップ / 背景データバー(cellStyle)
575
575
 
576
- `plugins/tss-heatmap.js`<!--DEV-->(**無依存・コア無改変**)<!--/DEV-->= コアの `cellStyle` フックに乗せる**連続色(ヒートマップ)**と**背景データバー**。`tss-bar.js`(前景の棒+ラベル)と違い、**セル背景**を塗る=**数値はそのまま見せて編集できる**。動く例: [`examples/heatmap.html`](https://tssgrid.threestarsoftware.co.jp/examples/heatmap.html)。
576
+ `plugins/tss-heatmap.js`= コアの `cellStyle` フックに乗せる**連続色(ヒートマップ)**と**背景データバー**。`tss-bar.js`(前景の棒+ラベル)と違い、**セル背景**を塗る=**数値はそのまま見せて編集できる**。動く例: [`examples/heatmap.html`](https://tssgrid.threestarsoftware.co.jp/examples/heatmap.html)。
577
577
 
578
578
  ```html
579
579
  <script src="plugins/tss-heatmap.js"></script>
@@ -587,7 +587,7 @@ columns: [
587
587
  - `TssHeatmap`: `min`/`max`(明示。セル単位呼び出しで列範囲を知らないため)/ `colors`(2色以上のストップ) or `low`/`high` / `text`(自動文字色, 既定on)。
588
588
  - `TssDataBar`: `min`/`max` / `color` / `align`('left'/'right')。編集で即更新。
589
589
 
590
- > 「セル内グラフ」3点: **前景の折れ線/棒=`html:true`+`format`(tss-sparkline / tss-bar)**、**背景の連続色/バー=`cellStyle`(tss-heatmap)**。どれも<!--DEV-->無依存・<!--/DEV-->ノービルド。
590
+ > 「セル内グラフ」3点: **前景の折れ線/棒=`html:true`+`format`(tss-sparkline / tss-bar)**、**背景の連続色/バー=`cellStyle`(tss-heatmap)**。どれもノービルド。
591
591
 
592
592
  ### グラフ連携(Chart.js などと)— 編集でライブ更新
593
593
 
@@ -601,7 +601,7 @@ new TssGrid(el, { columns, data, onAfterChange: () => { chart.data = toChartData
601
601
 
602
602
  ### 同梱プラグイン: 2択トグル(toggle・有効|無効)
603
603
 
604
- `plugins/tss-toggle.js`+`.css`<!--DEV-->(**無依存・コア無改変**)<!--/DEV-->= ラジオの2択を**並んだセグメント(有効|無効)**で表示、クリックで切替。チェックボックスより**2ラベルが見える**。値は dropdown 同様 **value を保存**。動く例: [`examples/toggle.html`](https://tssgrid.threestarsoftware.co.jp/examples/toggle.html)。
604
+ `plugins/tss-toggle.js`+`.css`= ラジオの2択を**並んだセグメント(有効|無効)**で表示、クリックで切替。チェックボックスより**2ラベルが見える**。値は dropdown 同様 **value を保存**。動く例: [`examples/toggle.html`](https://tssgrid.threestarsoftware.co.jp/examples/toggle.html)。
605
605
 
606
606
  ```html
607
607
  <link rel="stylesheet" href="plugins/tss-toggle.css">
@@ -618,7 +618,7 @@ plugins: [ TssToggle.plugin({ columns: {
618
618
 
619
619
  ### 同梱プラグイン: セル結合(merge・横/縦/矩形)
620
620
 
621
- `plugins/tss-merge.js`<!--DEV-->(**無依存**)<!--/DEV-->= データセルの**矩形結合**(`colspan`×`rowspan`)。左上の**アンカーが値・編集・選択の対象**で、覆われた従属セルは**描画スキップ+読み取り専用**(貼り付け・フィルは弾く)。動く例: [`examples/merge.html`](https://tssgrid.threestarsoftware.co.jp/examples/merge.html)。(見出しの結合は `nestedHeaders` を参照)
621
+ `plugins/tss-merge.js`= データセルの**矩形結合**(`colspan`×`rowspan`)。左上の**アンカーが値・編集・選択の対象**で、覆われた従属セルは**描画スキップ+読み取り専用**(貼り付け・フィルは弾く)。動く例: [`examples/merge.html`](https://tssgrid.threestarsoftware.co.jp/examples/merge.html)。(見出しの結合は `nestedHeaders` を参照)
622
622
 
623
623
  ```html
624
624
  <script src="plugins/tss-merge.js"></script>
@@ -644,7 +644,7 @@ plugins: [ TssMerge.plugin({ merges: [
644
644
 
645
645
  ### 同梱プラグイン: 同期スクロール(syncscroll・左右2表を並べて比較)
646
646
 
647
- `plugins/tss-syncscroll.js`<!--DEV-->(**無依存・コア無改変**)<!--/DEV-->= 並べた複数グリッドのスクロールを連動(Excel「並べて比較+同時にスクロール」相当)。A系/B系で別々に絞り込んだ表を**突き合わせながら編集**する用途に。動く例: [`examples/sync-scroll.html`](https://tssgrid.threestarsoftware.co.jp/examples/sync-scroll.html)。
647
+ `plugins/tss-syncscroll.js`= 並べた複数グリッドのスクロールを連動(Excel「並べて比較+同時にスクロール」相当)。A系/B系で別々に絞り込んだ表を**突き合わせながら編集**する用途に。動く例: [`examples/sync-scroll.html`](https://tssgrid.threestarsoftware.co.jp/examples/sync-scroll.html)。
648
648
 
649
649
  ```html
650
650
  <script src="plugins/tss-syncscroll.js"></script>
@@ -911,7 +911,7 @@ new TssGrid(el, {
911
911
 
912
912
  ### 入力の正規化 / 表示の整形(`parse` / `format`)
913
913
 
914
- **保存値**(`getData()` が返す内部値)と、**入力**・**表示**を分離できます。外部ライブラリは同梱せず、必要なら列のフックに差し込む方針です<!--DEV-->(コアは無依存のまま)<!--/DEV-->。
914
+ **保存値**(`getData()` が返す内部値)と、**入力**・**表示**を分離できます。外部ライブラリは同梱せず、必要なら列のフックに差し込む方針です。
915
915
 
916
916
  | フック | 向き | 役割 |
917
917
  |---|---|---|
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@threestarsoftware/tssgrid",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "軽量・ノービルド・日本語IME堅牢な編集グリッド(vanilla JS / 依存なし / MIT)。Excel ライクな入力画面を <script> タグ1本で。",
5
5
  "license": "MIT",
6
6
  "author": "ThreeStarSoftware (https://www.threestarsoftware.co.jp/)",
@@ -52,6 +52,7 @@
52
52
  "src/tssgrid.d.ts",
53
53
  "plugins/",
54
54
  "README.md",
55
+ "CHANGELOG.md",
55
56
  "LICENSE"
56
57
  ]
57
58
  }
@@ -1,5 +1,4 @@
1
1
  /*! TssAutocomplete — 候補サジェスト付きテキスト入力(TssGrid カスタムエディタ)。
2
- * DEV: 無依存・コア無改変(dev専用メモ・公開時strip)
3
2
  *
4
3
  * 自由入力+候補の絞り込み。固定 dropdown の上位版(取引先名・商品名など「だいたい決まってるが新規もある」項目向け)。
5
4
  * **IME変換中の Enter は奪わない**(変換確定が最優先=TssGrid の流儀)。
@@ -89,6 +88,7 @@
89
88
  pop.style.minWidth = r.width + 'px';
90
89
  }
91
90
  function show(anchor, value, ctx) {
91
+ teardown(); // 既に開いていれば先に片付ける(open-while-open の取り残し防止・tss-calendar と同方針)
92
92
  done = false; ref = { ctx: ctx };
93
93
  pop = document.createElement('div'); pop.className = 'tss-ac' + (opts.className ? ' ' + opts.className : ''); pop.tabIndex = -1;
94
94
  pop.innerHTML = '<input class="tss-ac-input" type="text"><div class="tss-ac-list"></div>';
@@ -1,5 +1,4 @@
1
1
  /*! TssBar — セル内 横棒グラフ(データバー)。
2
- * DEV: 無依存・コア無改変
3
2
  *
4
3
  * セルの数値を**横棒**で描く(アンケートの認知率バー、進捗%、スコアなど)。
5
4
  * `columns[c].html:true` + `format` 関数 seam に乗るだけ(format が返す <div> がセルに入る)。
@@ -1,5 +1,4 @@
1
1
  /*! TssCalc — セル内ミニ計算式(=12*3+5 → 41)。**eval/Function 不使用**。
2
- * DEV: 無依存・コア無改変
3
2
  *
4
3
  * セルに数式を打つと確定時に計算結果へ。四則 + - * / と括弧 () と小数に対応。
5
4
  * パーサは再帰下降の自前実装=**eval を使わない**(CSP安全)。`columns[c].parse` seam に乗るだけ。
@@ -1,5 +1,4 @@
1
1
  /*! TssCalendar — 日付ピッカー(TssGrid のカスタムエディタ / 単体ウィジェット両用)
2
- * DEV: 無依存
3
2
  * 休日カレンダーを JSON で渡せる(勤怠・シフト等向け)。CSS は tss-calendar.css をクラス/変数で自由に。
4
3
  * 使い方(グリッド): columns: [{ type:'date', editor: TssCalendar({ holidays }) }]
5
4
  * 使い方(単体) : TssCalendar({holidays}).openAt(anchorEl, isoValue, onPick)
@@ -99,6 +98,7 @@
99
98
  if (k === 'PageUp' || k === 'PageDown') { e.preventDefault(); view = new Date(view.getFullYear(), view.getMonth() + (k === 'PageUp' ? -1 : 1), 1); render(); }
100
99
  }
101
100
  function show(anchor, value, onPick, onCancel) {
101
+ teardown(); // 既に開いていれば先に片付ける(前の pop 除去+outside リスナ解除・open-while-open の取り残し防止)
102
102
  done = false; sel = parseISO(value);
103
103
  view = sel ? new Date(sel.getFullYear(), sel.getMonth(), 1) : new Date();
104
104
  focus = sel ? new Date(sel) : new Date();
@@ -1,5 +1,4 @@
1
1
  /*! TssDatetime — 日付+時刻を同時に設定するピッカー(TssGrid カスタムエディタ / 単体ウィジェット両用)
2
- * DEV: 無依存。カレンダー部は tss-calendar.css、時刻部は tss-time.css のクラスを再利用+薄い tss-datetime.css。
3
2
  * 保存値は常に 'YYYY-MM-DDTHH:MM'(kintone DATETIME と相互変換しやすい正規形。表示は列 format で整形)。
4
3
  * 使い方(グリッド): columns: [{ type:'text', editor: TssDatetime({ step:5 }), format: TssDatetime.format }]
5
4
  * 使い方(単体) : TssDatetime({step:15}).openAt(anchorEl, 'YYYY-MM-DDTHH:MM', onPick)
@@ -138,6 +137,7 @@
138
137
  else if (e.key === 'Enter') { e.preventDefault(); commit(value()); }
139
138
  }
140
139
  function show(anchor, val, onPick, onCancel) {
140
+ teardown(); // 既に開いていれば先に片付ける(open-while-open の取り残し防止・tss-calendar と同方針)
141
141
  done = false;
142
142
  var p = parseDT(val), tp = p ? null : parseTimeOnly(val); // 日付付き or 時刻のみ
143
143
  selD = p ? p.d : null;
@@ -1,5 +1,4 @@
1
1
  /*! TssFilter — ヘッダのオートフィルタUI(Excel風)。コアの非破壊 filter() を呼ぶだけ。
2
- * DEV: 無依存・コア無改変
3
2
  *
4
3
  * 各リーフ見出しにロート(漏斗)アイコンを出し(ソートの▲▼と区別)、クリックで「値チェックリスト+絞り込み検索」ポップアップ。
5
4
  * 複数列の条件は AND。適用は grid.filter(pred)/全解除で grid.clearFilter()。フィルタ済み列は印が付く。
@@ -1,5 +1,4 @@
1
1
  /*! TssFormula — 行スコープの派生列(computed column)プラグイン。
2
- * DEV: コア無改変
3
2
  *
4
3
  * 「その行の値だけから計算する列」を columns 定義に formula 関数で書けるようにする。
5
4
  * 例: 金額 = 数量 × 単価 / 税込 = 金額 × 1.1 / 勤怠の勤務時間 など。
@@ -1,5 +1,4 @@
1
1
  /*! TssFurigana — 氏名→フリガナ 自動入力プラグイン。**辞書不要**。
2
- * DEV: コア無改変
3
2
  *
4
3
  * スマホの連絡先のように「漢字を入力すると よみ が別列に自動で入る」。仕組みは**辞書引きではなく**、
5
4
  * IME変換の **composition イベントから「変換前の読み(かな)」を拾う**だけ。だから辞書データを持たない。
@@ -1,5 +1,4 @@
1
1
  /*! TssHeatmap / TssDataBar — セル背景の連続ビジュアライズ(cellStyle フック用)。
2
- * DEV: 無依存・コア無改変
3
2
  *
4
3
  * コアの `cellStyle`(セルにインラインCSSを差すフック)に乗せる。cellClass(クラス=離散色)では出せない
5
4
  * **値に応じた連続色(ヒートマップ)/背景データバー**を、**編集できる数値セルのまま**実現する。
@@ -1,5 +1,4 @@
1
1
  /*! TssMerge — 矩形セル結合(colspan×rowspan)の軽量プラグイン。
2
- * DEV: コアの薄い継ぎ目を使う
3
2
  *
4
3
  * アンカー=(r,c)=左上が値・編集・選択の対象。覆われた従属セルは描画スキップ+読取専用(貼付/フィルは弾く)。
5
4
  * 行/列の挿入削除では結合座標がコア側で縦横対称に追従(undo 対応)。
@@ -1,5 +1,4 @@
1
1
  /*! TssMessages — システム共通メッセージのカタログ作成ヘルパー(CSV/行配列 → grid.messages 用マップ)。
2
- * DEV: 無依存
3
2
  *
4
3
  * エラー本文をコードで一元管理(トーン統一・多言語・差し替え)。validator は { code, params } を返すだけにし、
5
4
  * 本文は外部の設定(CSV/JSON/XML→行配列)に置く。ここはその設定を grid.messages の形に変換する係。
@@ -1,5 +1,4 @@
1
1
  /*! TssRunningTotal — 残高/累計(running total)プラグイン。
2
- * DEV: コア無改変
3
2
  *
4
3
  * 「前の行の結果 + この行の増減」で縦に積む列を作る。formula(行内) でも totals(全体合計) でも出せない
5
4
  * **縦方向の連鎖**を担当。出納帳/通帳の残高、在庫受払の在庫数、経費・ポイントの累計など。
@@ -1,5 +1,4 @@
1
1
  /*! TssShared — 1つのマスタ(行データ)を複数グリッドで共有(A系/B系を別フィルタ・別ソートで並べて同時編集)。
2
- * DEV: 無依存・コア無改変
3
2
  * 各ビューは同じ「行参照」を持つ=片方の編集がもう片方へ即反映(該当行を再描画)。
4
3
  * **行の挿入/削除も伝播**(マスタ&他ビューに反映)。**配列データ/オブジェクト配列データ どちらも対応**。
5
4
  *
@@ -1,5 +1,4 @@
1
1
  /*! TssSparkline — セル内スパークライン(ミニ折れ線/棒)。**SVG自前描画**。
2
- * DEV: 無依存・コア無改変
3
2
  *
4
3
  * セルの値(カンマ/空白区切りの数列 "388,329,220,348,323,165")を**小さなグラフ**として描く。
5
4
  * 仕組みは `columns[c].html:true` + `format` 関数=format が返す SVG 文字列がそのままセルに入る(html-cells と同じ seam)。
@@ -1,5 +1,4 @@
1
1
  /*! TssSum — 条件付き合計グリッドの軽量プラグイン。
2
- * DEV: コア無改変・無依存(ラベルの横結合に TssMerge を任意利用)
3
2
  *
4
3
  * tss-totals との住み分け: tss-totals は「列ごとの集計(sum/avg/min/max/count)」を onTotals で返す。
5
4
  * tss-sum は「同じ列を条件別に複数集計=ラベル付きの"合計行"(小計/消費税10%/消費税8%/合計 など)」+
@@ -1,5 +1,4 @@
1
1
  /*! TssSyncScroll — 複数グリッドの同期スクロール(Excel「並べて比較+同時にスクロール」相当)。
2
- * DEV: 無依存・コア無改変
3
2
  *
4
3
  * 左右(or 上下)に並べた 2枚以上のグリッドのスクロールを連動。A系/B系で別々に絞り込んだ表を
5
4
  * 突き合わせながら編集する用途に。`grid.wrap`(overflow:auto のスクロール枠) の scroll を相手へ転写。
@@ -1,5 +1,4 @@
1
1
  /*! TssTime — 時刻ピッカー(TssGrid のカスタムエディタ / 単体ウィジェット両用)
2
- * DEV: 無依存
3
2
  * 時/分プルダウン方式(業務の任意時刻入力向き)。保存値は常に 24h 'HH:MM'(type:'time' と互換)。
4
3
  * 使い方(グリッド): columns: [{ type:'time', editor: TssTime({ step:15 }) }]
5
4
  * opts: step(分刻み,既定1) / hour12(AM/PM表示,既定false) / icon(既定 '🕐') / openOnClick(既定true) / className
@@ -62,6 +61,7 @@
62
61
  else if (e.key === 'Enter') { e.preventDefault(); commit(readValue()); }
63
62
  }
64
63
  function show(anchor, value, onPick, onCancel) {
64
+ teardown(); // 既に開いていれば先に片付ける(open-while-open の取り残し防止・tss-calendar と同方針)
65
65
  done = false;
66
66
  pop = document.createElement('div'); pop.className = 'tss-time' + (opts.className ? ' ' + opts.className : ''); pop.tabIndex = -1;
67
67
  document.body.appendChild(pop);
@@ -1,5 +1,4 @@
1
1
  /*! TssToggle — 2択セグメント・トグル(有効|無効・公開|非公開・男|女 …)。
2
- * DEV: 無依存・コア無改変
3
2
  *
4
3
  * ラジオの2択を「並んだピル(セグメント)」で表示し、クリックでどちらかに切替。値は dropdown と同じく
5
4
  * 選んだ option の value を保存(getData も value)。html:true+format で描画、クリックは委譲で拾う。
@@ -1,5 +1,4 @@
1
1
  /*! TssTotals — 列方向の集計(合計/平均/件数/最小/最大)プラグイン。**計算だけ=描画は利用者側**。
2
- * DEV: コア無改変
3
2
  *
4
3
  * 「縦の合計」をコアに埋めると合計行の置き場が minSpareRows/ソート/空行処理と衝突して肥大化する。
5
4
  * そこで **集計ロジックだけ** を担い、結果は onTotals コールバック / getTotals() で返す。
package/src/tssgrid.css CHANGED
@@ -134,6 +134,11 @@
134
134
  .tssgrid .tg-rowmove { cursor: grab; color: #b3b6c8; margin-right: 2px; font-size: 11px; line-height: 1; user-select: none; vertical-align: 1px; }
135
135
  .tssgrid .tg-rowmove:hover { color: var(--tg-accent); }
136
136
  .tssgrid .tg-rowmove:active { cursor: grabbing; }
137
+ .tssgrid .tg-rowmove.tg-mark-after { margin-right: 0; margin-left: 4px; } /* ⠿ を番号の後ろに置く時の間隔 */
138
+ .tssgrid .tg-rowmove.tg-mark-only { margin: 0; } /* 番号なし=⠿だけ=前後の区別をやめて中央に */
139
+ /* rowReorder:'header' = 行ヘッダーセル全体が移動ハンドル(クリック=選択/ドラッグ=移動) */
140
+ .tssgrid .tg-table th.rowhead.tg-rowmove-cell { cursor: grab; }
141
+ .tssgrid .tg-table th.rowhead.tg-rowmove-cell:active { cursor: grabbing; }
137
142
  .tssgrid .tg-colgrip:hover { background: var(--tg-edit-accent); opacity: .5; }
138
143
  .tssgrid .tg-rowgrip:hover { background: var(--tg-edit-accent); opacity: .5; }
139
144
  /* リサイズのプレビュー線(ドラッグ中だけ表示、離して確定) */
package/src/tssgrid.d.ts CHANGED
@@ -128,6 +128,12 @@ export interface TssGridOptions {
128
128
  rowHeaders?: boolean;
129
129
  /** 行番号列の幅(px)。既定 40。大量行で桁数が増える時に広げる。 */
130
130
  rowHeaderWidth?: number;
131
+ /** 行ヘッダの表示内容。未指定=行番号(r+1) / false=数字なし(空) / (r)=>文字列=カスタム表示。 */
132
+ rowHeaderLabel?: false | ((r: number) => string | number);
133
+ /** 行のドラッグ並べ替え。true=行ヘッダーセルの ⠿ アイコンを掴んで移動 / 'header'=行ヘッダーセル全体が移動ハンドル(クリック=選択/ドラッグ=移動)。 */
134
+ rowReorder?: boolean | 'header';
135
+ /** 行ヘッダーの ⠿ マークの位置。'before'=番号の前(既定)/ 'after'=番号の後 / false=マークを出さない。 */
136
+ rowReorderMark?: 'before' | 'after' | false;
131
137
  /** 仮想スクロール(固定行高ウィンドウイング)。大量行を可視窓だけ描画。true か { buffer:窓上下バッファ行数(既定6) }。
132
138
  * 制約(v1): 行高一定/固定行列・折り返し・セル結合は非対応(指定時は自動無効化)。 */
133
139
  virtual?: boolean | { buffer?: number };
package/src/tssgrid.js CHANGED
@@ -146,6 +146,7 @@
146
146
 
147
147
  this.active = { r: 0, c: 0 }; this.extent = { r: 0, c: 0 };
148
148
  this.mode = 'nav'; this.dragging = false; this.filling = null;
149
+ this._asRAF = 0; this._dragDir = 0; this._fillRow = null; // ドラッグ/フィルの端自動スクロール状態
149
150
  this.pendingCut = null;
150
151
  this.headerDrag = null; this._colAnchor = 0; this._rowAnchor = 0; // 行/列ヘッダ選択
151
152
  this._headerSel = null; // 'col'/'row'/'all'=列/行/全選択モード(結合に拡張せず実列/行にクランプ=Excel流)。null=通常セル選択
@@ -184,9 +185,12 @@
184
185
  // 行/列ヘッダの表示(false で行番号列・ヘッダ行を隠す)
185
186
  this.rowHeaders = opts.rowHeaders !== false;
186
187
  this.rowHeaderW = (opts.rowHeaderWidth | 0) || 40; // 行番号列の幅(px)。大量行で桁数が増える時に広げる
188
+ this.rowHeaderLabel = opts.rowHeaderLabel; // 行ヘッダ表示: 未指定=行番号(r+1) / false=数字なし(空) / (r)=>文字列=カスタム
187
189
  this.colHeaders = opts.colHeaders !== false;
188
190
  // 行ヘッダの「つまみ」をドラッグして行を並べ替える(行番号列が必要)
189
191
  this.rowReorder = !!opts.rowReorder;
192
+ this.rowReorderWhole = opts.rowReorder === 'header'; // 'header'=行ヘッダーセル全体が移動ハンドル(クリック=選択/ドラッグ=移動)/true=⠿アイコンのみ
193
+ this.rowReorderMark = opts.rowReorderMark === undefined ? 'before' : opts.rowReorderMark; // ⠿の位置: 'before'(番号の前/既定) | 'after'(番号の後) | false(出さない)
190
194
  // 選択カーソル(緑の選択枠・範囲・セルハイライト・フィルハンドル)を表示するか。
191
195
  // false で表示専用グリッド向けに非表示(内部の選択は保持=コピーは可)。
192
196
  this.cursor = opts.cursor !== false;
@@ -522,6 +526,7 @@
522
526
  if (this._destroyed) return;
523
527
  this._destroyed = true;
524
528
  if (this._vRAF) { try { cancelAnimationFrame(this._vRAF); } catch (_) {} this._vRAF = 0; } // 仮想スクロールの保留フレームを破棄
529
+ this._stopAutoScroll(); // ドラッグ自動スクロールの保留フレームを破棄
525
530
  for (const p of this._plugins) { if (p.handle && typeof p.handle.destroy === 'function') { try { p.handle.destroy(); } catch (_) {} } }
526
531
  this._plugins = [];
527
532
  if (this._ro) { try { this._ro.disconnect(); } catch (_) {} this._ro = null; }
@@ -983,6 +988,13 @@
983
988
  _colWidth(c) { return this.colW[c] != null ? this.colW[c] : this.defColW; }
984
989
  _rowHeight(r) { return this.rowH[r] != null ? this.rowH[r] : this.defRowH; }
985
990
  _rhW() { return this.rowHeaders ? this.rowHeaderW : 0; } // 行ヘッダ列の幅(非表示なら 0)
991
+ // 行ヘッダの表示内容: false=空(数字なし)/ 関数=カスタム(エスケープ)/ 既定=行番号(r+1)。
992
+ _rowHeadLabel(r) {
993
+ const L = this.rowHeaderLabel;
994
+ if (L === false) return '';
995
+ if (typeof L === 'function') { let v; try { v = L(r); } catch (_) {} return TssGrid.esc(v == null ? '' : v); }
996
+ return (r + 1);
997
+ }
986
998
  // ---- 列の非表示 ----
987
999
  _isHidden(c) { return this.hiddenCols.has(c); }
988
1000
  _visCols() { const a = []; for (let c = 0; c < this.COLS; c++) if (!this.hiddenCols.has(c)) a.push(c); return a; }
@@ -1112,9 +1124,15 @@
1112
1124
  _rowHTML(r) {
1113
1125
  const rh = this.rowHeaders;
1114
1126
  const rgrip = (rh && this.resizeRows) ? '<div class="tg-rowgrip"></div>' : '';
1115
- const dgrip = (rh && this.rowReorder && !this._hasVMerge()) ? '<span class="tg-rowmove" title="ドラッグで行を移動">⠿</span>' : ''; // 横結合は行と一緒に移動可/縦結合中は不可
1127
+ const canMove = rh && this.rowReorder && !this._hasVMerge();
1128
+ const lbl = this._rowHeadLabel(r);
1129
+ const after = this.rowReorderMark === 'after' && lbl !== ''; // 番号が無いなら「前/後」は無意味=中央に固定(マージンも消す)
1130
+ const markCls = 'tg-rowmove' + (lbl === '' ? ' tg-mark-only' : (after ? ' tg-mark-after' : ''));
1131
+ const mark = (canMove && this.rowReorderMark !== false) ? '<span class="' + markCls + '" title="ドラッグで行を移動">⠿</span>' : '';
1132
+ const inner = after ? (lbl + mark) : (mark + lbl); // ⠿ を番号の前/後に(rowReorderMark)。番号なしは前後どちらでも中央
1133
+ const rhCls = (canMove && this.rowReorderWhole) ? ' tg-rowmove-cell' : ''; // 'header'=行ヘッダーセル全体を掴み手に(grab カーソル)。番号/⠿の表示は独立
1116
1134
  const rcls = (r < this.frozenRows) ? (' tg-frozen-row' + (r === this.frozenRows - 1 ? ' tg-frozen-row-edge' : '')) : ''; // 行固定
1117
- let html = '<tr data-r="' + r + '" class="tg-row' + rcls + '" style="height:' + this._rowHeight(r) + 'px">' + (rh ? '<th class="rowhead" data-r="' + r + '">' + dgrip + (r + 1) + rgrip + '</th>' : '');
1135
+ let html = '<tr data-r="' + r + '" class="tg-row' + rcls + '" style="height:' + this._rowHeight(r) + 'px">' + (rh ? '<th class="rowhead' + rhCls + '" data-r="' + r + '">' + inner + rgrip + '</th>' : '');
1118
1136
  for (let c = 0; c < this.COLS; c++) {
1119
1137
  if (this._merges && this._isCovered(r, c)) continue; // 結合に覆われた従属セルは td を出さない(アンカーが colspan で覆う)
1120
1138
  const cs = this._merges ? this._colspanAt(r, c) : 1; // 結合アンカーは colspan で従属列ぶんを覆う
@@ -1200,6 +1218,7 @@
1200
1218
  this._renderWindow(this._vWindow(), false); // 窓を即同期(place 前に DOM を確定)
1201
1219
  }
1202
1220
  buildTable() {
1221
+ if (this._customEditor && this._customCancel) this._customCancel(); // 開いているカスタムエディタを閉じる(body直下ポップアップの取り残し防止・_commitActive と同じ扱い)
1203
1222
  this._selhdrFg = null; // 選択ヘッダ文字色キャッシュ破棄(テーマ変更に追従)
1204
1223
  const rh = this.rowHeaders;
1205
1224
  let html = '<colgroup>' + (rh ? '<col style="width:' + this.rowHeaderW + 'px">' : '');
@@ -1698,7 +1717,10 @@
1698
1717
  } else if (th.dataset.r != null) { // 行ヘッダ
1699
1718
  const r = +th.dataset.r;
1700
1719
  if (e.shiftKey) this._extendRow(r); else this.selectRow(r);
1701
- this.editor.focus(); this.headerDrag = 'row';
1720
+ this.editor.focus();
1721
+ // 'header'モード: 行ヘッダーセル全体が移動ハンドル=行を選択した上で移動を armする(クリック=選択のまま/ドラッグで移動)。縦結合/フィルタ中は不可。
1722
+ if (this.rowReorderWhole && !e.shiftKey && !this._hasVMerge() && !this._allRows) this._startRowMove(e);
1723
+ else this.headerDrag = 'row'; // 通常: ドラッグで複数行選択
1702
1724
  } else { // 左上の角 → 全選択
1703
1725
  this.selectAll(); this.editor.focus();
1704
1726
  }
@@ -2197,6 +2219,66 @@
2197
2219
  clearCopyMarquee() { this.copybox.style.display = 'none'; this.pendingCut = null; }
2198
2220
 
2199
2221
  _tdAtPoint(e) { const el = document.elementFromPoint(e.clientX, e.clientY); return el && el.closest ? el.closest('td[data-r]') : null; }
2222
+ // ポインタ Y から絶対行を算出(td が無い窓外/スペーサ上でも使える=仮想スクロールのドラッグ用。一定行高モデル)。
2223
+ _rowFromPointerY(clientY) {
2224
+ const wr = this.wrap.getBoundingClientRect();
2225
+ const contentY = clientY - wr.top + this.wrap.scrollTop - (this.colHeaders ? (this._theadH || 0) : 0);
2226
+ return Math.max(0, Math.min(this.ROWS - 1, Math.floor(contentY / this.defRowH)));
2227
+ }
2228
+ // ポインタ X から列を算出(_colLefts 累積から。隠し列は飛ばす)。
2229
+ _colFromPointerX(clientX) {
2230
+ const wr = this.wrap.getBoundingClientRect(), x = clientX - wr.left + this.wrap.scrollLeft, L = this._colLefts;
2231
+ if (L) for (let c = this.COLS - 1; c >= 0; c--) if (!this._isHidden(c) && x >= L[c]) return c;
2232
+ return this.extent.c;
2233
+ }
2234
+ _stopAutoScroll() { if (this._asRAF) { try { cancelAnimationFrame(this._asRAF); } catch (_) {} this._asRAF = 0; } this._dragDir = 0; }
2235
+ _updateFillPreview(r, c, alt) {
2236
+ this._fillRow = r;
2237
+ const fr = this.fillRectFromPointer(r, c, this.filling.src, alt);
2238
+ if (fr) { this.placeRect(this.fillpreview, fr.r0, fr.c0, fr.r1, fr.c1); this.fillpreview.style.display = 'block'; }
2239
+ else this.fillpreview.style.display = 'none';
2240
+ }
2241
+ // ドラッグ/フィル中に上下端を越えたら自動スクロールしながら選択/プレビューを伸ばす。
2242
+ // 重要: 伸ばす先は「カウンタ加算」でなく常に【ポインタ位置の行】=端を1pxだけ超えたら1行しか伸びない(暴走しない)。
2243
+ // 自動スクロール速度は端からのはみ出し量に比例(少し超えた=ゆっくり / 大きく超えた=速く)。
2244
+ _edgeAutoScroll(e, mode) {
2245
+ this._dragY = e.clientY; this._dragX = e.clientX; this._dragAlt = !!e.altKey; this._dragMode = mode;
2246
+ const r = this._rowFromPointerY(e.clientY), c = this._colFromPointerX(e.clientX);
2247
+ if (mode === 'drag') this._extendTo(r, c); else this._updateFillPreview(r, c, this._dragAlt); // まずポインタ位置のセルへ
2248
+ const wr = this.wrap.getBoundingClientRect();
2249
+ const headB = wr.top + (this.colHeaders ? (this._theadH || 0) : 0); // 上端=ヘッダ下
2250
+ const leftB = wr.left + this._frozenPaneW(); // 左端=固定ペイン(行番号+固定列)の右
2251
+ const past = e.clientY > wr.bottom || e.clientY < headB || e.clientX > wr.right || e.clientX < leftB;
2252
+ if (past && !this._asRAF) this._asRAF = requestAnimationFrame(() => this._autoScrollTick());
2253
+ }
2254
+ _autoScrollTick() {
2255
+ this._asRAF = 0;
2256
+ if (!this.dragging && !this.filling) return;
2257
+ const wr = this.wrap.getBoundingClientRect();
2258
+ const headB = wr.top + (this.colHeaders ? (this._theadH || 0) : 0), leftB = wr.left + this._frozenPaneW();
2259
+ const amp = d => Math.min(24, 2 + d * 0.2); // px/フレーム=はみ出し量に比例(上限24・暴走しない)
2260
+ let v = 0, vh = 0;
2261
+ if (this._dragY > wr.bottom) v = amp(this._dragY - wr.bottom); else if (this._dragY < headB) v = -amp(headB - this._dragY);
2262
+ if (this._dragX > wr.right) vh = amp(this._dragX - wr.right); else if (this._dragX < leftB) vh = -amp(leftB - this._dragX);
2263
+ if (v !== 0) {
2264
+ const before = this.wrap.scrollTop;
2265
+ this.wrap.scrollTop = Math.max(0, before + v);
2266
+ if (this.wrap.scrollTop !== before && this.virtual) this._renderWindow(this._vWindow(), false); // 縦は仮想時のみ再窓(非仮想で呼ぶと tbody を壊す)
2267
+ }
2268
+ if (vh !== 0) this.wrap.scrollLeft = Math.max(0, this.wrap.scrollLeft + vh); // 横は全列描画なので scrollLeft だけ
2269
+ const r = this._rowFromPointerY(this._dragY), c = this._colFromPointerX(this._dragX); // 伸ばす先は常にポインタのセル
2270
+ if (this._dragMode === 'drag') this._extendTo(r, c); else this._updateFillPreview(r, c, this._dragAlt);
2271
+ const maxTop = this.wrap.scrollHeight - this.wrap.clientHeight, maxLeft = this.wrap.scrollWidth - this.wrap.clientWidth;
2272
+ const vStuck = (v > 0 && this.wrap.scrollTop >= maxTop) || (v < 0 && this.wrap.scrollTop <= 0);
2273
+ const hStuck = (vh > 0 && this.wrap.scrollLeft >= maxLeft) || (vh < 0 && this.wrap.scrollLeft <= 0);
2274
+ if ((v !== 0 && !vStuck) || (vh !== 0 && !hStuck)) this._asRAF = requestAnimationFrame(() => this._autoScrollTick());
2275
+ }
2276
+ // setExtent の自動可視化(_vEnsureVisible)を伴わない版=自動スクロールは _autoScrollTick が手動制御するため、二重スクロールを避ける。
2277
+ _extendTo(r, c) {
2278
+ const e = { r: this.clampR(r), c: this.clampC(c) };
2279
+ if (!this._beforeSel(this.active, e)) return;
2280
+ this.extent = e; this.updateSelectionUI(); this._fireSelection();
2281
+ }
2200
2282
 
2201
2283
  // スクロール / リサイズでセル位置が動いた時、エディタと選択枠を貼り直す
2202
2284
  _reflow() {
@@ -2248,7 +2330,7 @@
2248
2330
  const cls = e.target.classList;
2249
2331
  if (cls && cls.contains('tg-colgrip')) { this._startColResize(e); return; } // 列幅リサイズ
2250
2332
  if (cls && cls.contains('tg-rowgrip')) { this._startRowResize(e); return; } // 行高リサイズ
2251
- if (cls && cls.contains('tg-rowmove')) { this._startRowMove(e); return; } // 行つまみドラッグ移動
2333
+ if (cls && cls.contains('tg-rowmove') && !this.rowReorderWhole) { this._startRowMove(e); return; } // ⠿つまみドラッグ移動('header'モードはセル全体扱い=_onHeaderMouseDown 経由で選択も伴う)
2252
2334
  const th = e.target.closest('th');
2253
2335
  if (th) { this._onHeaderMouseDown(e, th); return; } // 行/列/全体 選択
2254
2336
  const td = e.target.closest('td[data-r]'); if (!td) return;
@@ -2278,7 +2360,7 @@
2278
2360
  const td = e.target.closest('td[data-r]'); if (!td) return;
2279
2361
  this.setActive(+td.dataset.r, +td.dataset.c); this._beginEdit(this.active.r, this.active.c);
2280
2362
  });
2281
- this.fillhandle.addEventListener('mousedown', (e) => { e.preventDefault(); e.stopPropagation(); this.filling = { src: this.rectRange() }; });
2363
+ this.fillhandle.addEventListener('mousedown', (e) => { e.preventDefault(); e.stopPropagation(); this.filling = { src: this.rectRange() }; this._fillRow = null; });
2282
2364
  // 右クリックメニュー(行/列の挿入・削除)
2283
2365
  this.table.addEventListener('contextmenu', (e) => this._onContextMenu(e));
2284
2366
  this.menuEl.addEventListener('mousedown', (e) => {
@@ -2346,12 +2428,15 @@
2346
2428
  }
2347
2429
  return;
2348
2430
  }
2349
- if (this.dragging) { const td = this._tdAtPoint(e); if (td) this.setExtent(+td.dataset.r, +td.dataset.c); }
2431
+ if (this.dragging) {
2432
+ const td = this._tdAtPoint(e);
2433
+ if (td) { this._stopAutoScroll(); this.setExtent(+td.dataset.r, +td.dataset.c); }
2434
+ else this._edgeAutoScroll(e, 'drag'); // 窓外/端=自動スクロール+モデル幾何で伸ばす
2435
+ }
2350
2436
  else if (this.filling) {
2351
- const td = this._tdAtPoint(e); if (!td) return;
2352
- const fr = this.fillRectFromPointer(+td.dataset.r, +td.dataset.c, this.filling.src, e.altKey);
2353
- if (fr) { this.placeRect(this.fillpreview, fr.r0, fr.c0, fr.r1, fr.c1); this.fillpreview.style.display = 'block'; }
2354
- else this.fillpreview.style.display = 'none';
2437
+ const td = this._tdAtPoint(e);
2438
+ if (td) { this._stopAutoScroll(); this._updateFillPreview(+td.dataset.r, +td.dataset.c, e.altKey); }
2439
+ else this._edgeAutoScroll(e, 'fill');
2355
2440
  }
2356
2441
  });
2357
2442
  this._on(document, 'mouseup', (e) => {
@@ -2370,8 +2455,9 @@
2370
2455
  return;
2371
2456
  }
2372
2457
  if (this.headerDrag) { this.headerDrag = null; return; }
2373
- if (this.dragging) { this.dragging = false; return; }
2458
+ if (this.dragging) { this.dragging = false; this._stopAutoScroll(); return; }
2374
2459
  if (this.filling) {
2460
+ this._stopAutoScroll();
2375
2461
  // autoInsertRow: フィルハンドルをテーブル下端より下へドロップしたら、足りない行を追加(縦フィル時)
2376
2462
  if (this.fillAutoInsertRow && this.fillDirection !== 'horizontal' && !this._tdAtPoint(e)) {
2377
2463
  const tb = this.table.getBoundingClientRect();
@@ -2381,8 +2467,11 @@
2381
2467
  this.buildTable();
2382
2468
  }
2383
2469
  }
2384
- const td = this._tdAtPoint(e) || this.cellEl(this.filling.src.r1, this.filling.src.c1);
2385
- const fr = this.fillRectFromPointer(+td.dataset.r, +td.dataset.c, this.filling.src, e.altKey);
2470
+ // 確定の対象セル: td があればそれ、無ければ(窓外)モデル幾何=直近のフィル行/ポインタ位置から。
2471
+ const td = this._tdAtPoint(e);
2472
+ const tr = td ? +td.dataset.r : (this._fillRow != null ? this._fillRow : this._rowFromPointerY(e.clientY));
2473
+ const tc = td ? +td.dataset.c : this._colFromPointerX(e.clientX);
2474
+ const fr = this.fillRectFromPointer(tr, tc, this.filling.src, e.altKey);
2386
2475
  this.fillpreview.style.display = 'none';
2387
2476
  const src = this.filling.src; this.filling = null;
2388
2477
  if (fr) { this.doFill(src, fr, e.ctrlKey || e.metaKey); this.selectRect(fr.r0, fr.c0, fr.r1, fr.c1); }