@threestarsoftware/tssgrid 0.0.10 → 0.1.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 +23 -0
- package/README.md +32 -2
- package/package.json +1 -1
- package/plugins/tss-autocomplete.js +40 -16
- package/plugins/tss-calendar.js +24 -0
- package/plugins/tss-datetime.js +6 -0
- package/plugins/tss-time.js +6 -0
- package/src/tssgrid.css +1 -1
- package/src/tssgrid.d.ts +34 -1
- package/src/tssgrid.js +50 -4
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,29 @@
|
|
|
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.1] - 2026-07-17
|
|
7
|
+
|
|
8
|
+
### 変更(既定の挙動が変わります)
|
|
9
|
+
- **同梱プラグインの `inline` を既定 ON に**(`TssAutocomplete` / `TssCalendar` / `TssDatetime` / `TssTime`)— **旧挙動に戻すには `inline: false` を明示**してください。
|
|
10
|
+
- **理由**: 0.1.0 で `inline` を opt-in として出したが、既定 false +**プラグイン自身のドキュメント例に `inline` が無い**ため、**「`inline: true` と書いた人だけが直る」**状態だった。例をコピペした人には修正が届かず、出荷されたのは修正ではなく**フラグ**だった。
|
|
11
|
+
- **既定 false で実際に失われていたもの**(正確に言うと): IME が使えなくなるわけではない。クリック/F2 でポップアップを開けば、その中の自前 input は普通のフォーカス済み input なので**日本語は普通に打てる**。失われるのは **「セルにいきなり打ち始めて上書き」=打ち始めの経路**だけ(1文字目が捨てられる/IME 直打ちで編集に入れない)。
|
|
12
|
+
- **なぜそれが重いか**: それは TssGrid が他ライブラリの回避策を批判した時に挙げた代償そのもの(「セルにいきなり打ち込んで上書きができなくなる。表計算で一番よく使う操作なのに」)。**批判した妥協を自分の既定で出荷していた**ので、既定を反転する。
|
|
13
|
+
- **代償(日付系)**: 既定では**矢印キーを奪いません**=カレンダー内の日移動が既定で無くなります(セル移動のまま)。「キー派は打つ/マウス派はクリック」の割り切り。矢印で日を送りたい場合のみ `inline: false`。
|
|
14
|
+
- **自作カスタムエディタの `editor.inline` は既定 false のまま**(共有 input に乗るには実装側の合意が要るため。宣言しない限り挙動不変)。
|
|
15
|
+
|
|
16
|
+
### 追加
|
|
17
|
+
- **日付/時刻ピッカーも `inline` 対応:`TssCalendar` / `TssDatetime` / `TssTime`**(プラグイン)— これらは**自前のテキスト入力を持たずポップアップにフォーカスする**型だったため、カスタムエディタとして付けると(`col.format`/`parse` 併用時は特に)**日付をそもそも打ち始められず、クリックで選ぶしかなかった**(=0.1.0 の `editor.inline` が解いた問題を、同梱の日付系も踏んでいた)。inline では本体の共有 input に乗り、**`6/20` のように直打ちでき(1文字目も落ちない)・IME 直打ちも効く**。ポップアップはフォーカスを奪わず"確認+クリック補助"に降りる。`TssCalendar` は打った内容に**追従してその月/日へジャンプ**(`onInput`+列の `parse` を解釈)。日をクリックで確定・Enter で打った値を確定・Esc で取消(本体既定)。
|
|
18
|
+
|
|
19
|
+
## [0.1.0] - 2026-07-17
|
|
20
|
+
|
|
21
|
+
### 追加
|
|
22
|
+
- **`TssAutocomplete({ inline: true })`=「セルに打ちながら候補を絞る」サジェスト**(プラグイン)— 上記 `editor.inline` に載せた版。従来の同プラグインは**自前 input を持つポップアップ**(クリック/F2 で開き、その中に打つ)だったため、**セルに直接打ち始める/IME 直打ちで始める**経路が使えなかった(サンプルは「開いてから打つ」ので症状が見えていなかった)。`inline: true` を付けると自前 input を作らずフォーカスも奪わず、**打鍵の1文字目も IME 直打ちも素のテキスト列と同じ**に効き、候補リストだけを出す。**IME 変換中の ↑↓/Enter は奪わない**(変換確定が最優先=従来からの流儀を維持)。確定は本体 `commit()`=関所を通る。`examples/autocomplete.html` を inline 版に更新。**`inline` 無しは従来どおり(挙動不変・opt-in)**。
|
|
23
|
+
- **`editor.inline`=打鍵の1文字目・IME 直打ちを殺さないカスタムエディタ**(コア)— 従来 `columns[c].editor` を付けた列は**クリック/F2 でしか編集を開始できず**、打鍵は `preventDefault` されて1文字目が捨てられ、IME 直打ち(`compositionstart`)は素通りで編集にすら入れなかった(=TssGrid の看板である IME 堅牢な直打ちがカスタムエディタ列だけ効かない)。`editor.inline = true` を宣言すると **入力は本体の共有 input のまま**=**打鍵も IME も素のテキスト列と完全に同じ**になり、エディタは候補表示に徹する型になる(「セルに打ちながら候補を絞るサジェスト/ルックアップ」がこれで作れる)。エディタ側の口: `open(ctx)`(ctx に **`input`**=共有input / **`setValue(v)`** / `inline:true` を追加)・**`onKeyDown(e, ctx)`**(本体より先に受ける。**IME 変換中も呼ばれる**=変換中の↑↓を奪わない実装が書ける。`false` で本体既定を止める)・**`onInput(value, ctx)`**(候補の絞り込み用)・`close()`。`icon`/`openOnClick` は inline でもそのまま使える。確定は本体 `commit()`=関所を通るので validator/parse/format/readOnly/Undo はそのまま。対象は text/number 系(dropdown/checkbox/ネイティブ日付は従来どおり対象外)。**完全 opt-in=`inline` を書かない既存エディタ(TssCalendar 等)の挙動は一切変わらない**。tssgrid-kintone のルックアップ実装で必要になり起票。
|
|
24
|
+
|
|
25
|
+
### 修正
|
|
26
|
+
- **非仮想グリッドで↑キー等で先頭行へ移動すると、その行が sticky な列ヘッダの下に潜って読めなくなる不具合を修正**(コアCSS+JS)— キーボード移動の可視化に使う `scrollIntoView({block:'nearest'})` が、枠内に浮く sticky ヘッダ(≈29px)を考慮せず「一部でも見えていれば再スクロールしない」ため、先頭行がヘッダ下に潜ったまま止まっていた。`buildTable` でヘッダ高を CSS 変数 `--tg-thead-h` にセットし、`td { scroll-margin-top: var(--tg-thead-h) }` でヘッダ高ぶんの余白を確保(`scrollIntoView` がヘッダ下に潜らなくなる。ヘッダ高の変化にも追従・ヘッダ無しは0px)。tssgrid-kintone 実機(Chrome)で発覚・実測。
|
|
27
|
+
- **`filter({ 0: '営業' })` のように「列 index を数値キー」にした絞り込みが効かなかったのを修正**(コア)— `Object.keys` が数値キーを文字列化する(`{0:…}`→`'0'`)ため `_resolveCol('0')` が列 index に解決できず、条件が空=全行 true(絞り込まれない)になっていた。数値文字列キーは列 index として解決するフォールバックを追加(data キー/見出し名が優先=未解決の時だけ index 扱い)。関数述語・data キー・見出し名・関数値・AND は従来どおり。tss-filter の漏斗UI経由は元々無影響。
|
|
28
|
+
|
|
6
29
|
## [0.0.10] - 2026-07-15
|
|
7
30
|
|
|
8
31
|
### 修正
|
package/README.md
CHANGED
|
@@ -364,6 +364,34 @@ new TssGrid(el, { columns: [{ editor: myEditor }] });
|
|
|
364
364
|
- `open` が受け取る `{ grid, r, c, value, td, commit, cancel }` だけ知っていれば作れます。F2 / ダブルクリック / 文字入力で開きます。
|
|
365
365
|
- **`commit(value)` は通常の確定経路(pushCmd)を通る**ので、`validator`/`parse`/`format`/読み取り専用がそのまま効き、**Undo にも積まれます**。
|
|
366
366
|
- 編集中に別セルをクリックすると `cancel()` 相当で閉じます。描画(セルの見た目)は従来どおり `format` 等で制御。
|
|
367
|
+
- ⚠️ 上記の(フォーカスを奪う)エディタは **クリック / F2 で開く**型です。**打鍵の1文字目や IME 直打ちから始めたい**なら次の `inline` を使ってください。
|
|
368
|
+
|
|
369
|
+
### `editor.inline`: 打鍵・IME 直打ちを殺さないエディタ(サジェスト向け)
|
|
370
|
+
|
|
371
|
+
`editor.inline = true` を宣言すると、**入力は本体の IME 堅牢な共有 input のまま**で、エディタは「候補を描く」役に徹します。=**打った1文字目が残り、IME 直打ち(変換)も素のテキスト列と完全に同じ**に効きます。「セルに打ちながら候補を絞るサジェスト/ルックアップ」はこの型で作ります。
|
|
372
|
+
|
|
373
|
+
```js
|
|
374
|
+
const suggest = {
|
|
375
|
+
inline: true, icon: '🔍', // icon / openOnClick は inline でも使えます
|
|
376
|
+
open(ctx) { /* ctx.input = 打鍵/IMEが入る共有input。ここに候補ポップアップを添える(フォーカスは奪わない) */ },
|
|
377
|
+
onInput(value, ctx) { /* 打つたびに呼ばれる=候補を絞る */ },
|
|
378
|
+
onKeyDown(e, ctx) { // 本体より先に受ける。IME 変換中も呼ばれる
|
|
379
|
+
if (e.isComposing || e.keyCode === 229) return; // ★変換中は手を引く(↑↓を奪わない)
|
|
380
|
+
if (e.key === 'ArrowDown') { /* 候補を選ぶ */ return false; } // false で本体既定を止める
|
|
381
|
+
if (e.key === 'Enter') { ctx.commit(選んだ値); return false; } // commit=関所を通る
|
|
382
|
+
},
|
|
383
|
+
close() { /* 候補を片付ける */ },
|
|
384
|
+
};
|
|
385
|
+
new TssGrid(el, { columns: [{ editor: suggest }] });
|
|
386
|
+
```
|
|
387
|
+
- `ctx` は通常の `{grid,r,c,value,td,commit,cancel}` に加えて **`input`(共有input)/ `setValue(v)`(候補で置換・確定はしない)/ `inline:true`** が入ります。
|
|
388
|
+
- **確定は本体の `commit()`=関所を通る**ので `validator`/`parse`/`format`/読み取り専用/Undo はそのまま効きます。
|
|
389
|
+
- ポップアップ側は **`mousedown` を `preventDefault`** してフォーカスを奪わないこと(共有 input に打ち続けられる)。
|
|
390
|
+
- 対象は **text/number 系の列**(`dropdown`/`checkbox`/ネイティブ日付は従来どおり対象外)。
|
|
391
|
+
- **自作エディタの `inline` は既定 false**(宣言しない限り挙動は一切変わりません)。共有 input に乗るには実装側の合意が要るためで、ここは opt-in が正しい既定です。
|
|
392
|
+
- **同梱プラグインの `inline` は既定 true**:`TssAutocomplete`(打ちながら候補を絞る)/`TssCalendar`・`TssDatetime`・`TssTime`(**日付/時刻をそのまま直打ち**できる。カレンダーは打った内容に追従してその日へジャンプ)。**何も書かずにコピペした人が、看板どおりの「打ち始めれば書ける」を得ます**。
|
|
393
|
+
- 日付系は **「キー派は打つ/マウス派はクリック」** の割り切り=**矢印キーは奪いません**(文字カーソル/セル移動のまま)。伝票入力のように「`6/20` と打つ方が速い」場面向け。
|
|
394
|
+
- `inline: false` で旧型(ポップアップにフォーカスし ←→↑↓ で日移動)に戻せます。ただし **セルにいきなり打ち始めて上書き**(表計算で一番よく使う操作)ができなくなる=クリック/F2 で開いてから打つことになります。矢印での日送りが要る場面だけ選んでください。
|
|
367
395
|
|
|
368
396
|
### 同梱プラグイン: 休日カレンダー対応の日付ピッカー
|
|
369
397
|
|
|
@@ -381,7 +409,9 @@ const cal = TssCalendar({
|
|
|
381
409
|
});
|
|
382
410
|
new TssGrid(el, { columns: [{ type: 'date', editor: cal, format: 'yyyy/mm/dd' }] });
|
|
383
411
|
```
|
|
384
|
-
- セル右端に **📅**
|
|
412
|
+
- セル右端に **📅** が出て**シングルクリックで開閉**(再クリックで閉じる=トグル)、開いている間も**フィルハンドルで下へコピー可**(ドロップダウンと同じ操作感)。**年・月はプルダウン**で直接ジャンプ。祝日は赤+**名称ツールチップ**、土日は色分け。保存値は ISO のまま。
|
|
413
|
+
- **打つ人はそのまま打てます**(既定 `inline`): セルに `6/20` と打ち始めればそのまま編集開始(**1文字目も落ちない・IME 直打ちも効く**)、カレンダーは打った日へ追従。確定は Enter か日をクリック。**矢印はセル移動のまま**(カレンダーは奪いません)。
|
|
414
|
+
- 矢印/PageUp・Down でカレンダー内を移動する旧型が要るなら `inline: false`(→ [`editor.inline`](#editorinline-打鍵ime-直打ちを殺さないエディタサジェスト向け))。**セルに直接打ち始める操作は失われます**。
|
|
385
415
|
- 見た目は CSS 変数(`--cal-accent` など)と `tss-cal-*` クラスで自由に(依存ゼロ)。グリッド外でも `cal.openAt(anchorEl, isoValue, onPick)` で単体利用可。
|
|
386
416
|
- `opts`: `icon`(既定 `'📅'`, `''` で消す) / `openOnClick`(既定 true) / `yearMin`・`yearMax`(年プルダウンの範囲) も。
|
|
387
417
|
- 仕組み: カスタムエディタが **`icon` / `openOnClick` を申告**すると、グリッドが右端アイコン表示+シングルクリック起動を行います(自作エディタでも同様に使えます)。
|
|
@@ -769,7 +799,7 @@ new TssGrid(el, { hiddenColumns: [1] }); // 初期で隠す
|
|
|
769
799
|
| `onBeforeAutofill` / `onAfterAutofill` | `(src,target)=>…` | — | フィル前後。before は `false` で取消。`src`/`target` = `{r0,c0,r1,c1}` |
|
|
770
800
|
| `autoColumnSize` | `boolean \| number` | `false` | 初期化時に列幅を内容に合わせて自動調整(数値=上限px)。`autoSizeColumn`/`autoSizeAllColumns` API・列境界ダブルクリックでも |
|
|
771
801
|
| `columns[c].align` / `valign` | `string` | — | 列既定の整列(→ [整列](#整列--セル単位の読み取り専用実行時)) |
|
|
772
|
-
| `columns[c].editor` | `object \| ()=>object` | — | カスタム編集UI(→ [カスタムエディタ](#カスタムエディタ)) |
|
|
802
|
+
| `columns[c].editor` | `object \| ()=>object` | — | カスタム編集UI(→ [カスタムエディタ](#カスタムエディタ))。`editor.inline:true` で**打鍵の1文字目・IME 直打ちを殺さない**サジェスト型に(→ [`editor.inline`](#editorinline-打鍵ime-直打ちを殺さないエディタサジェスト向け)) |
|
|
773
803
|
| `copyPaste` | `boolean` | `true` | `false` でコピー/カット/ペーストを無効化 |
|
|
774
804
|
| `minSpareRows` | `number` | `0` | 末尾に常に確保する空行数(Excel風に下へ打ち足せる) |
|
|
775
805
|
| `autoWrapRow` / `autoWrapCol` | `boolean` | `false` | Tab で行末→次行頭 / Enter で列末→次列頭に折り返し |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@threestarsoftware/tssgrid",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "軽量・ノービルド・日本語IME堅牢な編集グリッド(vanilla JS / 依存なし / MIT)。Excel ライクな入力画面を <script> タグ1本で。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ThreeStarSoftware (https://www.threestarsoftware.co.jp/)",
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
*
|
|
10
10
|
* opts:
|
|
11
11
|
* source : string[] か (query, ctx)=>string[]。必須。
|
|
12
|
+
* inline : 「セルに打ちながら絞る」型(**既定 true**)。本体の共有 input に乗る=**打鍵の1文字目も IME 直打ちも
|
|
13
|
+
* 素のテキスト列と同じに効く**(自前 input を作らず focus も奪わない)。
|
|
14
|
+
* inline:false でポップアップの自前 input を開く旧型に戻せるが、**セルにいきなり打ち始めて上書き**が
|
|
15
|
+
* できなくなる(=クリック/F2 で開いてから打つ)。表計算で一番よく使う操作を捨てる選択なので非推奨。
|
|
12
16
|
* strict : true=候補から選んだ値のみ許可(既定 false=自由入力可)。
|
|
13
17
|
* minChars : この文字数以上で候補を出す(既定 0=開いた時点で全件)。
|
|
14
18
|
* max : 候補表示の最大件数(既定 20)。
|
|
@@ -22,6 +26,7 @@
|
|
|
22
26
|
|
|
23
27
|
function TssAutocomplete(opts) {
|
|
24
28
|
opts = opts || {};
|
|
29
|
+
var inline = opts.inline !== false; // true=共有 input に乗る(打鍵/IME直打ちが効く)/false=従来の自前 input ポップアップ
|
|
25
30
|
var getSource = typeof opts.source === 'function' ? opts.source : function () { return opts.source || []; };
|
|
26
31
|
var strict = !!opts.strict;
|
|
27
32
|
var minChars = opts.minChars || 0;
|
|
@@ -30,7 +35,7 @@
|
|
|
30
35
|
: (opts.match === 'startsWith' ? function (it, q) { return norm(it).indexOf(q) === 0; }
|
|
31
36
|
: function (it, q) { return norm(it).indexOf(q) >= 0; });
|
|
32
37
|
|
|
33
|
-
var pop, input, list, items = [], hi = -1, composing = false, ref = null, done = false, outside = null;
|
|
38
|
+
var pop, input, list, items = [], hi = -1, composing = false, ref = null, done = false, outside = null, unbindComp = null;
|
|
34
39
|
|
|
35
40
|
function filter(q) {
|
|
36
41
|
var ctx = ref ? ref.ctx : {};
|
|
@@ -67,11 +72,13 @@
|
|
|
67
72
|
if (strict) return null; // strict は候補必須
|
|
68
73
|
return input.value; // 自由入力を採用
|
|
69
74
|
}
|
|
75
|
+
// IME 変換中か(inline では本体の共有 input 上のキーを受けるので keyCode 229 も見る)
|
|
76
|
+
function imeOn(e) { return composing || e.isComposing || e.keyCode === 229; }
|
|
70
77
|
function onKey(e) {
|
|
71
|
-
if (e.key === 'ArrowDown') { if (
|
|
72
|
-
else if (e.key === 'ArrowUp') { if (
|
|
78
|
+
if (e.key === 'ArrowDown') { if (imeOn(e)) return; e.preventDefault(); moveHi(1); }
|
|
79
|
+
else if (e.key === 'ArrowUp') { if (imeOn(e)) return; e.preventDefault(); moveHi(-1); }
|
|
73
80
|
else if (e.key === 'Enter') {
|
|
74
|
-
if (
|
|
81
|
+
if (imeOn(e)) return; // ★ IME変換確定の Enter は奪わない
|
|
75
82
|
e.preventDefault();
|
|
76
83
|
var v = chosenValue(); if (v == null) return; // strict で候補なし=確定しない
|
|
77
84
|
commit(v);
|
|
@@ -91,30 +98,47 @@
|
|
|
91
98
|
teardown(); // 既に開いていれば先に片付ける(open-while-open の取り残し防止・tss-calendar と同方針)
|
|
92
99
|
done = false; ref = { ctx: ctx };
|
|
93
100
|
pop = document.createElement('div'); pop.className = 'tss-ac' + (opts.className ? ' ' + opts.className : ''); pop.tabIndex = -1;
|
|
94
|
-
|
|
101
|
+
// inline は候補リストだけ(自前 input を作らない=打鍵/IME は本体の共有 input のまま)
|
|
102
|
+
pop.innerHTML = (inline ? '' : '<input class="tss-ac-input" type="text">') + '<div class="tss-ac-list"></div>';
|
|
95
103
|
document.body.appendChild(pop);
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
104
|
+
list = pop.querySelector('.tss-ac-list');
|
|
105
|
+
if (inline) {
|
|
106
|
+
input = ctx.input; // ★ 打鍵/IME が入る本体の共有 input をそのまま参照(値は本体が持つ)
|
|
107
|
+
// 変換中は候補を更新しない(classic と同じ挙動)。確定後に更新。
|
|
108
|
+
var cs = function () { composing = true; }, ce = function () { composing = false; render(); };
|
|
109
|
+
input.addEventListener('compositionstart', cs);
|
|
110
|
+
input.addEventListener('compositionend', ce);
|
|
111
|
+
unbindComp = function () { input.removeEventListener('compositionstart', cs); input.removeEventListener('compositionend', ce); };
|
|
112
|
+
} else {
|
|
113
|
+
input = pop.querySelector('.tss-ac-input');
|
|
114
|
+
input.value = value == null ? '' : value;
|
|
115
|
+
input.addEventListener('compositionstart', function () { composing = true; });
|
|
116
|
+
input.addEventListener('compositionend', function () { composing = false; render(); }); // 変換確定後に候補更新
|
|
117
|
+
input.addEventListener('input', function () { if (!composing) render(); });
|
|
118
|
+
input.addEventListener('keydown', onKey);
|
|
119
|
+
}
|
|
120
|
+
list.addEventListener('mousedown', function (e) { var it = e.target.closest('.tss-ac-item'); if (it) { e.preventDefault(); commit(items[+it.dataset.i]); } }); // preventDefault=フォーカスを奪わない
|
|
103
121
|
render(); place(anchor);
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
122
|
+
if (!inline) { // inline は外側クリック/blur を本体(_commitActive / blur)が処理する=二重に閉じない
|
|
123
|
+
outside = function (e) { if (pop && !pop.contains(e.target) && !(anchor && anchor.contains && anchor.contains(e.target))) { var v = chosenOnOutside(); if (v == null) cancel(); else commit(v); } };
|
|
124
|
+
setTimeout(function () { document.addEventListener('mousedown', outside, true); }, 0);
|
|
125
|
+
input.focus(); input.setSelectionRange(input.value.length, input.value.length);
|
|
126
|
+
}
|
|
107
127
|
}
|
|
108
128
|
function chosenOnOutside() { if (strict) { var v = input.value; var src = getSource(v, {}) || []; return src.indexOf(v) >= 0 ? v : null; } return input.value; }
|
|
109
|
-
function teardown() { if (outside) { document.removeEventListener('mousedown', outside, true); outside = null; } if (pop) { pop.remove(); pop = null; } items = []; hi = -1; composing = false; }
|
|
129
|
+
function teardown() { if (unbindComp) { unbindComp(); unbindComp = null; } if (outside) { document.removeEventListener('mousedown', outside, true); outside = null; } if (pop) { pop.remove(); pop = null; } items = []; hi = -1; composing = false; }
|
|
110
130
|
function commit(v) { if (done) return; done = true; var f = ref.ctx.commit; teardown(); f(v); }
|
|
111
131
|
function cancel() { if (done) return; done = true; var f = ref.ctx.cancel; teardown(); if (f) f(); }
|
|
112
132
|
|
|
113
133
|
return {
|
|
134
|
+
inline: inline, // true=本体の共有 input に乗る(_usesTextEditor が見る)
|
|
114
135
|
openOnClick: opts.openOnClick !== false,
|
|
115
136
|
icon: opts.icon != null ? opts.icon : '▾',
|
|
116
137
|
open: function (ctx) { show(ctx.td, ctx.value, ctx); },
|
|
117
138
|
close: function () { teardown(); },
|
|
139
|
+
// ---- inline のみ: 入力/キーは本体の共有 input 経由で届く(自前 listener は張らない)----
|
|
140
|
+
onInput: inline ? function () { if (!composing) render(); } : undefined, // 打つたびに候補を絞る(変換中は据え置き)
|
|
141
|
+
onKeyDown: inline ? function (e) { onKey(e); } : undefined, // ↑↓/Enter/Tab/Esc。preventDefault で本体既定を止める
|
|
118
142
|
};
|
|
119
143
|
}
|
|
120
144
|
if (typeof module !== 'undefined' && module.exports) module.exports = TssAutocomplete;
|
package/plugins/tss-calendar.js
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
* 休日カレンダーを JSON で渡せる(勤怠・シフト等向け)。CSS は tss-calendar.css をクラス/変数で自由に。
|
|
3
3
|
* 使い方(グリッド): columns: [{ type:'date', editor: TssCalendar({ holidays }) }]
|
|
4
4
|
* 使い方(単体) : TssCalendar({holidays}).openAt(anchorEl, isoValue, onPick)
|
|
5
|
+
*
|
|
6
|
+
* opts.inline: 「キー派は打つ/マウス派はクリック」の型(**既定 true**)。
|
|
7
|
+
* 本体の共有 input に乗るので **日付をそのまま直打ちでき(1文字目も落ちない)・IME 直打ちも効く**。
|
|
8
|
+
* カレンダーはフォーカスを奪わず"確認+クリック補助"に降り、**矢印キーは奪わない**(=文字カーソル/セル移動のまま)。
|
|
9
|
+
* 打った内容に追従してカレンダーがその月/日へジャンプする。確定は Enter(打った値)or 日をクリック。
|
|
10
|
+
* inline:false で旧型(ポップアップにフォーカスし ←→↑↓ で日移動・Enter で選択)に戻せる。
|
|
11
|
+
* ただし **セルにいきなり打ち始めて上書き** ができなくなる(クリック/F2 で開いてから打つ)=矢印で日を送りたい時だけ。
|
|
5
12
|
*/
|
|
6
13
|
(function (root) {
|
|
7
14
|
'use strict';
|
|
@@ -26,6 +33,7 @@
|
|
|
26
33
|
var yrLo = opts.yearMin || (minD ? minD.getFullYear() : ty - 12);
|
|
27
34
|
var yrHi = opts.yearMax || (maxD ? maxD.getFullYear() : ty + 12);
|
|
28
35
|
var pop = null, view = null, sel = null, focus = null, ref = null, done = false, outside = null;
|
|
36
|
+
var inline = opts.inline !== false; // true=共有 input に乗る(直打ち可・矢印は本体に返す)/false=従来のフォーカス奪取型
|
|
29
37
|
|
|
30
38
|
function disabled(d) {
|
|
31
39
|
if (minD && d < minD) return true;
|
|
@@ -110,6 +118,9 @@
|
|
|
110
118
|
pop.addEventListener('keydown', onKey);
|
|
111
119
|
render(); place(anchor);
|
|
112
120
|
ref = { pick: onPick, cancel: onCancel };
|
|
121
|
+
// inline は「外側クリック/フォーカス」を本体(_commitActive / blur)が持つ=二重に閉じない・共有 input から
|
|
122
|
+
// フォーカスを奪わない(=打ち続けられる)。矢印/Enter/Esc も本体の既定に返す(onKey は pop 未フォーカスなので発火しない)。
|
|
123
|
+
if (inline) return;
|
|
113
124
|
// 外側クリックで閉じる。ただしアンカー(開いた元セル)は除外=再クリックはグリッド側でトグル処理させる
|
|
114
125
|
outside = function (e) { if (pop && !pop.contains(e.target) && !(anchor && anchor.contains && anchor.contains(e.target))) cancel(); };
|
|
115
126
|
setTimeout(function () { document.addEventListener('mousedown', outside, true); }, 0);
|
|
@@ -125,6 +136,19 @@
|
|
|
125
136
|
close: function () { teardown(); },
|
|
126
137
|
icon: opts.icon != null ? opts.icon : '📅', // セル右端に表示する合図(opts.icon:'' で消せる)
|
|
127
138
|
openOnClick: opts.openOnClick !== false, // シングルクリックで開く(既定 ON)
|
|
139
|
+
inline: inline, // true=共有 input に乗る(_usesTextEditor が見る)
|
|
140
|
+
// inline のみ: 打った内容にカレンダーが追従(列の parse があれば表示形→保存値に変換して解釈)。
|
|
141
|
+
// 矢印/Enter/Esc は本体に返す=onKeyDown は持たない(キー派は打つ・マウス派はクリック)。
|
|
142
|
+
onInput: inline ? function (v, ctx) {
|
|
143
|
+
if (!pop) return;
|
|
144
|
+
var d = null;
|
|
145
|
+
try {
|
|
146
|
+
var p = ctx.grid.colCfg(ctx.c).parse;
|
|
147
|
+
d = parseISO(typeof p === 'function' ? p(v, { r: ctx.r, c: ctx.c }) : v);
|
|
148
|
+
} catch (e) { d = null; }
|
|
149
|
+
if (!d) return; // 打ちかけ/不正はカレンダーを動かさない
|
|
150
|
+
sel = d; focus = new Date(d); view = new Date(d.getFullYear(), d.getMonth(), 1); render();
|
|
151
|
+
} : undefined,
|
|
128
152
|
// 単体ウィジェット: 任意要素にアンカーして開く
|
|
129
153
|
openAt: function (anchorEl, value, onPick) { show(anchorEl, value, function (v) { onPick(v); }, null); },
|
|
130
154
|
};
|
package/plugins/tss-datetime.js
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
* 使い方(グリッド): columns: [{ type:'text', editor: TssDatetime({ step:5 }), format: TssDatetime.format }]
|
|
4
4
|
* 使い方(単体) : TssDatetime({step:15}).openAt(anchorEl, 'YYYY-MM-DDTHH:MM', onPick)
|
|
5
5
|
* opts: holidays / weekend / weekLabels / min / max(日付) / step(分刻み,既定1) / hour12 / icon(既定 '📅') / openOnClick(既定true) / className
|
|
6
|
+
* opts.inline: 既定 true=本体の共有 input に乗る(日時をそのまま直打ちでき・1文字目も落ちない・IME直打ちも効く/
|
|
7
|
+
* ピッカーはフォーカスを奪わずクリック補助に降りる)。inline:false で旧型(ピッカーにフォーカス)に戻せるが、
|
|
8
|
+
* **セルにいきなり打ち始めて上書き** ができなくなる(クリック/F2 で開いてから打つ)。
|
|
6
9
|
*/
|
|
7
10
|
(function (root) {
|
|
8
11
|
'use strict';
|
|
@@ -36,6 +39,7 @@
|
|
|
36
39
|
var yrHi = opts.yearMax || (maxD ? maxD.getFullYear() : ty + 12);
|
|
37
40
|
|
|
38
41
|
var pop = null, view = null, selD = null, selH = null, selM = null, ref = null, done = false, outside = null;
|
|
42
|
+
var inline = opts.inline !== false; // true=共有 input に乗る(直打ち可・矢印は本体に返す)/false=従来のフォーカス奪取型
|
|
39
43
|
|
|
40
44
|
function dDisabled(d) { if (minD && d < minD) return true; if (maxD && d > maxD) return true; return false; }
|
|
41
45
|
|
|
@@ -152,6 +156,7 @@
|
|
|
152
156
|
pop.addEventListener('keydown', onKey);
|
|
153
157
|
render(); place(anchor);
|
|
154
158
|
ref = { pick: onPick, cancel: onCancel };
|
|
159
|
+
if (inline) return; // inline は外側クリック/フォーカスを本体が持つ=共有 input から奪わない(打ち続けられる)
|
|
155
160
|
outside = function (e) { if (pop && !pop.contains(e.target) && !(anchor && anchor.contains && anchor.contains(e.target))) commit(value()); }; // 外側クリックは確定(time と同方針)
|
|
156
161
|
setTimeout(function () { document.addEventListener('mousedown', outside, true); }, 0);
|
|
157
162
|
pop.focus();
|
|
@@ -165,6 +170,7 @@
|
|
|
165
170
|
close: function () { teardown(); },
|
|
166
171
|
icon: opts.icon != null ? opts.icon : '📅',
|
|
167
172
|
openOnClick: opts.openOnClick !== false,
|
|
173
|
+
inline: inline, // true=共有 input に乗る=日時をそのまま直打ちできる(矢印/Enter/Esc は本体の既定)
|
|
168
174
|
openAt: function (anchorEl, val, onPick) { show(anchorEl, val, function (v) { onPick(v); }, null); },
|
|
169
175
|
};
|
|
170
176
|
}
|
package/plugins/tss-time.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
* 時/分プルダウン方式(業務の任意時刻入力向き)。保存値は常に 24h 'HH:MM'(type:'time' と互換)。
|
|
3
3
|
* 使い方(グリッド): columns: [{ type:'time', editor: TssTime({ step:15 }) }]
|
|
4
4
|
* opts: step(分刻み,既定1) / hour12(AM/PM表示,既定false) / icon(既定 '🕐') / openOnClick(既定true) / className
|
|
5
|
+
* opts.inline: 既定 true=本体の共有 input に乗る(時刻をそのまま直打ちでき・1文字目も落ちない・IME直打ちも効く/
|
|
6
|
+
* ピッカーはフォーカスを奪わずクリック補助に降りる)。inline:false で旧型(ピッカーにフォーカス)に戻せるが、
|
|
7
|
+
* **セルにいきなり打ち始めて上書き** ができなくなる(クリック/F2 で開いてから打つ)。
|
|
5
8
|
*/
|
|
6
9
|
(function (root) {
|
|
7
10
|
'use strict';
|
|
@@ -13,6 +16,7 @@
|
|
|
13
16
|
var step = Math.max(1, opts.step || 1);
|
|
14
17
|
var hour12 = !!opts.hour12;
|
|
15
18
|
var pop = null, ref = null, done = false, outside = null;
|
|
19
|
+
var inline = opts.inline !== false; // true=共有 input に乗る(直打ち可・矢印は本体に返す)/false=従来のフォーカス奪取型
|
|
16
20
|
|
|
17
21
|
function opt(val, label, sel) { return '<option value="' + val + '"' + (sel ? ' selected' : '') + '>' + label + '</option>'; }
|
|
18
22
|
function render(cur) {
|
|
@@ -70,6 +74,7 @@
|
|
|
70
74
|
pop.addEventListener('keydown', onKey);
|
|
71
75
|
render(parseHM(value)); place(anchor);
|
|
72
76
|
ref = { pick: onPick, cancel: onCancel };
|
|
77
|
+
if (inline) return; // inline は外側クリック/フォーカスを本体が持つ=共有 input から奪わない(打ち続けられる)
|
|
73
78
|
// 外側クリックは「確定」(時/分を選んで離す=採用)。未変更なら同値 commit=実質no-op。アンカーは除外(再クリックはグリッドがトグル)
|
|
74
79
|
outside = function (e) { if (pop && !pop.contains(e.target) && !(anchor && anchor.contains && anchor.contains(e.target))) commit(readValue()); };
|
|
75
80
|
setTimeout(function () { document.addEventListener('mousedown', outside, true); }, 0);
|
|
@@ -84,6 +89,7 @@
|
|
|
84
89
|
close: function () { teardown(); },
|
|
85
90
|
icon: opts.icon != null ? opts.icon : '🕐',
|
|
86
91
|
openOnClick: opts.openOnClick !== false,
|
|
92
|
+
inline: inline, // true=共有 input に乗る=時刻をそのまま直打ちできる(矢印/Enter/Esc は本体の既定)
|
|
87
93
|
openAt: function (anchorEl, value, onPick) { show(anchorEl, value, function (v) { onPick(v); }, null); },
|
|
88
94
|
};
|
|
89
95
|
}
|
package/src/tssgrid.css
CHANGED
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
.tssgrid .tg-table thead th { height: 28px; position: sticky; top: 0; z-index: 6; }
|
|
78
78
|
/* ヘッダはクリックで行/列選択(角は全選択) */
|
|
79
79
|
.tssgrid .tg-table thead th[data-c], .tssgrid .tg-table th.rowhead[data-r], .tssgrid .tg-table th.corner { cursor: pointer; }
|
|
80
|
-
.tssgrid .tg-table td { background: var(--tg-cell-bg); cursor: cell; }
|
|
80
|
+
.tssgrid .tg-table td { background: var(--tg-cell-bg); cursor: cell; scroll-margin-top: var(--tg-thead-h, 0px); } /* キーボード移動の scrollIntoView が sticky ヘッダ下に潜らないよう、ヘッダ高ぶん余白を確保(--tg-thead-h は buildTable が設定) */
|
|
81
81
|
/* 表示専用(cursor:false): セルのカーソルを既定に(選択枠・ハイライトは JS 側で非表示) */
|
|
82
82
|
.tssgrid.tg-no-cursor .tg-table td { cursor: default; }
|
|
83
83
|
/* 非表示列: col を幅0にし、セルは padding/罫線を消して内容を overflow:hidden で畳む。
|
package/src/tssgrid.d.ts
CHANGED
|
@@ -60,7 +60,7 @@ export interface ColumnDef {
|
|
|
60
60
|
/** 入力検証。編集/貼付/フィルの全経路に効く。 */
|
|
61
61
|
validator?: (value: any, cell: CellCoord) => ValidatorResult;
|
|
62
62
|
/** カスタム編集UI(薄いオブジェクト契約 or それを返す関数)。 */
|
|
63
|
-
editor?:
|
|
63
|
+
editor?: EditorDef | (() => EditorDef);
|
|
64
64
|
/** 入力の正規化(保存値へ変換)。 */
|
|
65
65
|
parse?: (input: string, cell: CellCoord) => any;
|
|
66
66
|
/** 表示の整形(保存値→表示)。文字列 or 宣言的 CellFormat。 */
|
|
@@ -212,6 +212,39 @@ export interface CsvOptions {
|
|
|
212
212
|
delimiter?: string; [k: string]: any;
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
+
/** カスタム編集UIが `open` で受け取る ctx。確定は commit=本体の関所(検証/整形/readOnly/履歴)を通る。 */
|
|
216
|
+
export interface EditorContext {
|
|
217
|
+
grid: TssGrid; r: number; c: number; value: any; td: HTMLTableCellElement | null;
|
|
218
|
+
commit(value?: any): void; cancel(): void;
|
|
219
|
+
/** inline エディタのみ: true。 */
|
|
220
|
+
inline?: boolean;
|
|
221
|
+
/** inline エディタのみ: 打鍵/IME が入る本体の共有 input(読み取り・listen 用)。 */
|
|
222
|
+
input?: HTMLInputElement | HTMLTextAreaElement;
|
|
223
|
+
/** inline エディタのみ: 共有 input の値を差し替える(候補で置換する用・確定はしない)。 */
|
|
224
|
+
setValue?(value: any): void;
|
|
225
|
+
[k: string]: any;
|
|
226
|
+
}
|
|
227
|
+
/** カスタム編集UIの契約(`columns[c].editor`)。 */
|
|
228
|
+
export interface EditorDef {
|
|
229
|
+
open(ctx: EditorContext): void;
|
|
230
|
+
close?(): void;
|
|
231
|
+
/** セル右端に出すアイコン。 */
|
|
232
|
+
icon?: string;
|
|
233
|
+
/** シングルクリックで開く(ドロップダウン同様)。 */
|
|
234
|
+
openOnClick?: boolean;
|
|
235
|
+
/**
|
|
236
|
+
* `true` で「本体の共有 input に乗るエディタ」=**打鍵の1文字目・IME 直打ちが素のテキスト列と同じに効く**。
|
|
237
|
+
* 入力は本体(IME 堅牢な共有 input)に任せ、エディタは候補表示等だけを担当する型。text/number 系の列で有効
|
|
238
|
+
* (dropdown/checkbox/ネイティブ日付は従来どおり対象外)。未指定なら従来のフォーカスを奪うエディタ(挙動不変)。
|
|
239
|
+
*/
|
|
240
|
+
inline?: boolean;
|
|
241
|
+
/** inline のみ: 本体より先にキーを受ける(**IME 変換中も呼ばれる**=変換中の↑↓を奪わない実装が書ける)。`false` で本体既定を止める。 */
|
|
242
|
+
onKeyDown?(e: KeyboardEvent, ctx: EditorContext): boolean | void;
|
|
243
|
+
/** inline のみ: 共有 input の入力通知(候補の絞り込み用)。 */
|
|
244
|
+
onInput?(value: string, ctx: EditorContext): void;
|
|
245
|
+
[k: string]: any;
|
|
246
|
+
}
|
|
247
|
+
|
|
215
248
|
/** 右クリックの対象種別。カスタムメニュー項目の name/disabled/hidden の第2引数、callback の {ctx} で受け取る。 */
|
|
216
249
|
export interface MenuContext { on: 'col' | 'row' | 'cell' | 'corner'; r: number; c: number; }
|
|
217
250
|
|
package/src/tssgrid.js
CHANGED
|
@@ -154,6 +154,7 @@
|
|
|
154
154
|
this._headerSel = null; // 'col'/'row'/'all'=列/行/全選択モード(結合に拡張せず実列/行にクランプ=Excel流)。null=通常セル選択
|
|
155
155
|
this._selCols = new Set(); // 飛び飛びの列選択(Ctrl+ヘッダクリック)。_headerSel==='col' の時だけ有効。連続選択は矩形と一致
|
|
156
156
|
this._selRows = new Set(); // 飛び飛びの行選択(Ctrl+行ヘッダクリック)。_headerSel==='row' の時だけ有効
|
|
157
|
+
this._inlineEd = null; this._inlineCtx = null; // inline エディタ(editor.inline=true): 共有 input に乗るエディタの現在値
|
|
157
158
|
this.disjointSelect = opts.disjointSelect !== false; // Ctrl+ヘッダで行/列を飛び飛び複数選択(false で無効=Ctrl+クリックは単一選択に戻る)
|
|
158
159
|
// リサイズ(列幅・行高)
|
|
159
160
|
// 列幅 colW[c]=px(未指定は既定)。指定方法は3通り、優先度=実行時リサイズ/明示colW > columns[c].width > 既定:
|
|
@@ -495,6 +496,7 @@
|
|
|
495
496
|
if (typeof pred === 'object') {
|
|
496
497
|
const conds = Object.keys(pred).map(k => {
|
|
497
498
|
let ci = this._resolveCol(k); if (ci < 0) ci = this.headers.indexOf(k); // data キー → 見出し名でも解決
|
|
499
|
+
if (ci < 0 && /^\d+$/.test(k) && +k < this.COLS) ci = +k; // 数値文字列キー {0:…} は列 index として解決(Object.keys が数値キーを文字列化するため。data キー優先=上で未解決の時だけ)
|
|
498
500
|
const want = pred[k]; return { ci, test: typeof want === 'function' ? want : (v => String(v) === String(want)) };
|
|
499
501
|
}).filter(x => x.ci >= 0);
|
|
500
502
|
return row => conds.every(cd => cd.test(row[cd.ci]));
|
|
@@ -928,9 +930,12 @@
|
|
|
928
930
|
return null;
|
|
929
931
|
}
|
|
930
932
|
_usesTextEditor(c) {
|
|
931
|
-
|
|
933
|
+
const ed = this.colCfg(c).editor;
|
|
934
|
+
// カスタムエディタは共有 input を使わない(multiline は textarea を優先)。
|
|
935
|
+
// ただし editor.inline=true は「入力は本体の IME 堅牢な共有 input に任せ、エディタは候補を描くだけ」=共有 input に乗る。
|
|
936
|
+
if (ed && !ed.inline && !this.colCfg(c).multiline) return false;
|
|
932
937
|
const t = this.colType(c);
|
|
933
|
-
return t !== 'checkbox' && t !== 'dropdown' && !this._nativeInputType(c);
|
|
938
|
+
return t !== 'checkbox' && t !== 'dropdown' && !this._nativeInputType(c); // inline でも dropdown/checkbox/ネイティブ日付は対象外
|
|
934
939
|
}
|
|
935
940
|
// 保存値 → セル表示文字列(format 適用)。getData は保存値のまま。
|
|
936
941
|
_displayValue(r, c) {
|
|
@@ -1310,6 +1315,7 @@
|
|
|
1310
1315
|
if (this.onHeaderRender && this.colHeaders) this._applyHeaderRender(); // 外部がヘッダthを装飾(ソートアイコン等)。高さ確定前に走らせて幾何へ反映
|
|
1311
1316
|
const thd = this.table.querySelector('thead');
|
|
1312
1317
|
this._theadH = (this.colHeaders && thd) ? thd.offsetHeight : 0; // ヘッダ高をキャッシュ(クリップ計算用)
|
|
1318
|
+
this.wrap.style.setProperty('--tg-thead-h', this._theadH + 'px'); // scrollIntoView が sticky ヘッダに潜らないよう td の scroll-margin-top に使う(動的ヘッダ高に追従)
|
|
1313
1319
|
this._applyTableWidth();
|
|
1314
1320
|
this._applyStretch();
|
|
1315
1321
|
this._applyHeaderSticky();
|
|
@@ -1619,7 +1625,35 @@
|
|
|
1619
1625
|
const baseTop = parseFloat(el.style.top) || 0; // place がセル上端(content座標)を入れている
|
|
1620
1626
|
el.style.top = (up ? baseTop + cellH - h : baseTop) + 'px'; // up なら下端をセル下端に合わせて上へ伸ばす
|
|
1621
1627
|
}
|
|
1628
|
+
// inline エディタ(editor.inline=true)を開く。入力は共有 input のまま=打鍵の1文字目も IME 直打ちも殺さない。
|
|
1629
|
+
// エディタはフォーカスを持たない前提(ポップアップ側は mousedown を preventDefault すること)。
|
|
1630
|
+
// 確定は本体 commit=関所を通るので検証/整形/readOnly/履歴はそのまま効く。
|
|
1631
|
+
_openInlineEditor() {
|
|
1632
|
+
const r = this.active.r, c = this.active.c, def = this.colCfg(c).editor;
|
|
1633
|
+
if (!def || !def.inline || this._inlineEd) return;
|
|
1634
|
+
const ed = typeof def === 'function' ? def() : def;
|
|
1635
|
+
if (!ed || typeof ed.open !== 'function') return;
|
|
1636
|
+
const grid = this;
|
|
1637
|
+
const ctx = {
|
|
1638
|
+
grid, r, c, inline: true,
|
|
1639
|
+
value: this.data[r][c],
|
|
1640
|
+
td: this.cellEl(r, c),
|
|
1641
|
+
input: this.editor, // 打鍵/IME が入る共有 input(読み取り・listen 用)
|
|
1642
|
+
setValue(v) { grid.editor.value = grid._cellStr(v); }, // 候補で置き換える用(確定はしない)
|
|
1643
|
+
commit(v) { if (v != null) grid.editor.value = grid._cellStr(v); grid.commit(); grid.toNav(); grid.updateSelectionUI(); },
|
|
1644
|
+
cancel() { grid.setActive(r, c); }, // 元値に戻して nav(Esc 相当)
|
|
1645
|
+
};
|
|
1646
|
+
this._inlineEd = ed; this._inlineCtx = ctx;
|
|
1647
|
+
try { ed.open(ctx); } catch (_) { this._inlineEd = null; this._inlineCtx = null; }
|
|
1648
|
+
}
|
|
1649
|
+
_closeInlineEditor() {
|
|
1650
|
+
const ed = this._inlineEd;
|
|
1651
|
+
if (!ed) return;
|
|
1652
|
+
this._inlineEd = null; this._inlineCtx = null;
|
|
1653
|
+
if (ed.close) { try { ed.close(); } catch (_) {} }
|
|
1654
|
+
}
|
|
1622
1655
|
toNav() {
|
|
1656
|
+
this._closeInlineEditor(); // 編集終了の締めどころ(commit/Esc/blur/選択移動=全経路がここを通る)
|
|
1623
1657
|
this.mode = 'nav';
|
|
1624
1658
|
this.editor.className = (this.editor === this._edMulti) ? 'tg-editor tg-editor-multi nav' : 'tg-editor nav';
|
|
1625
1659
|
if (this.editor === this._edInput) this.editor.type = 'text'; // type は input 専用(textarea は readonly で throw する)
|
|
@@ -1642,6 +1676,7 @@
|
|
|
1642
1676
|
const _t = this.editor.type;
|
|
1643
1677
|
if (caretEnd && _t !== 'date' && _t !== 'time' && _t !== 'number') { const l = this.editor.value.length; this.editor.setSelectionRange(l, l); }
|
|
1644
1678
|
if (this.editor === this._edMulti) this._growMultiEditor(); // multiline は編集開始時に内容ぶん伸ばす
|
|
1679
|
+
if (starting) this._openInlineEditor(); // inline エディタ列なら ed.open(ctx)(共有 input はそのまま=打鍵/IME を維持)
|
|
1645
1680
|
if (starting && this.onEditStart) { try { this.onEditStart({ r: this.active.r, c: this.active.c, value: this.editor.value }); } catch (_) {} }
|
|
1646
1681
|
}
|
|
1647
1682
|
_fireSelection() {
|
|
@@ -2542,6 +2577,10 @@
|
|
|
2542
2577
|
// input と textarea(multiline) の両方に同じ編集入口リスナを張る(this.editor はアクティブ列で張り替わる)。
|
|
2543
2578
|
[this._edInput, this._edMulti].forEach((ed) => {
|
|
2544
2579
|
ed.addEventListener('keydown', (e) => this._onKey(e));
|
|
2580
|
+
// inline エディタへ入力を通知(打った/変換確定した内容で候補を絞る用)
|
|
2581
|
+
ed.addEventListener('input', () => {
|
|
2582
|
+
if (this._inlineEd && this._inlineEd.onInput) { try { this._inlineEd.onInput(this.editor.value, this._inlineCtx); } catch (_) {} }
|
|
2583
|
+
});
|
|
2545
2584
|
ed.addEventListener('compositionstart', () => {
|
|
2546
2585
|
if (this.mode !== 'nav') return;
|
|
2547
2586
|
if (this._usesTextEditor(this.active.c)) this.toEdit(null, false);
|
|
@@ -2596,7 +2635,7 @@
|
|
|
2596
2635
|
// ドロップダウンはシングルクリックで即開く
|
|
2597
2636
|
if (this.colType(c) === 'dropdown') { this.dragging = false; this._openSelect(); }
|
|
2598
2637
|
// カスタムエディタが openOnClick を申告していれば、ドロップダウン同様シングルクリックで開く(再クリックは開かない=トグル)
|
|
2599
|
-
else { const ed = this.colCfg(c).editor; if (ed && ed.openOnClick && !wasOpenHere) { this.dragging = false; this._openCustomEditor(r, c); } }
|
|
2638
|
+
else { const ed = this.colCfg(c).editor; if (ed && ed.openOnClick && !wasOpenHere) { this.dragging = false; if (ed.inline) this._beginEdit(r, c); else this._openCustomEditor(r, c); } } // inline は共有 input を開く(_beginEdit→toEdit→ed.open)
|
|
2600
2639
|
});
|
|
2601
2640
|
// ヘッダ全選択チェックは mousedown で処理済み。click のネイティブ・トグルを止めて二重反転を防ぐ
|
|
2602
2641
|
this.table.addEventListener('click', (e) => {
|
|
@@ -2733,7 +2772,8 @@
|
|
|
2733
2772
|
// ---- 型に応じた編集の入口 ----
|
|
2734
2773
|
_beginEdit(r, c) {
|
|
2735
2774
|
this.clearCopyMarquee(); // 編集開始でコピー/カットのマーキー(点線)を消す(dropdown/checkbox/カスタム含む)
|
|
2736
|
-
|
|
2775
|
+
const _ed = this.colCfg(c).editor;
|
|
2776
|
+
if (_ed && !_ed.inline) { this._openCustomEditor(r, c); return; } // カスタムエディタ(inline は共有 input の経路へ落として toEdit が open する)
|
|
2737
2777
|
const t = this.colType(c);
|
|
2738
2778
|
if (t === 'checkbox') { this._toggleCheckbox(r, c); return; }
|
|
2739
2779
|
if (t === 'dropdown') { this._openSelect(); return; }
|
|
@@ -2845,6 +2885,12 @@
|
|
|
2845
2885
|
const composing = e.isComposing || e.keyCode === 229;
|
|
2846
2886
|
// onBeforeKeyDown: グリッドより先にユーザーへ。false / preventDefault で既定を止める。
|
|
2847
2887
|
if (this.onBeforeKeyDown) { let r; try { r = this.onBeforeKeyDown(e); } catch (_) {} if (r === false || e.defaultPrevented) return; }
|
|
2888
|
+
// inline エディタにもキーを先に渡す(IME 変換中も渡す=「変換中の↑↓は奪わない」実装を書けるように。
|
|
2889
|
+
// 本体の `if (composing) return` より前=エディタ側で isComposing/keyCode 229 を見て手を引ける)。false で既定を止める。
|
|
2890
|
+
if (this._inlineEd && this._inlineEd.onKeyDown) {
|
|
2891
|
+
let r; try { r = this._inlineEd.onKeyDown(e, this._inlineCtx); } catch (_) {}
|
|
2892
|
+
if (r === false || e.defaultPrevented) return;
|
|
2893
|
+
}
|
|
2848
2894
|
// カスタムショートカット(IME 変換中は除く)
|
|
2849
2895
|
if (!composing && this._runShortcuts(e)) return;
|
|
2850
2896
|
if (this.mode === 'edit') {
|