@threestarsoftware/tssgrid 0.1.0 → 0.1.2
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 +19 -0
- package/README.md +15 -4
- package/package.json +1 -1
- package/plugins/tss-autocomplete.js +5 -4
- package/plugins/tss-calendar.js +24 -0
- package/plugins/tss-datetime.js +6 -0
- package/plugins/tss-time.js +6 -0
- package/src/tssgrid.d.ts +6 -0
- package/src/tssgrid.js +21 -11
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,25 @@
|
|
|
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.2] - 2026-07-17
|
|
7
|
+
|
|
8
|
+
### 修正
|
|
9
|
+
- **inline カレンダーが `format` だけの列(`parse` 無し)で月ジャンプしない不具合を修正**(プラグイン+コア)— `columns:[{type:'date', editor:TssCalendar(), format:'yyyy/mm/dd'}]` という**`parse` を書かない自然な使い方**で `2026/07/20` と打つと、**セルは受け付けるのにカレンダーだけ8月のまま**=同じ本体の中で入力の解釈が2つあった。原因は 0.1.1 の `onInput` が `col.parse` **だけ**を見て、無ければ生文字を ISO 厳格な自前 `parseISO` に渡していたこと。コアの確定は `col.parse` が無ければ**型ごとの組込パーサ**(`_parseDate` 等)に落ちるので、そこで食い違っていた(`2026-07-20` と ISO で打てば動く=症状が分かりにくい)。「休日が出ない」ようにも見えるが**休日機能は無事**で、休日のある月が表示されないだけだった。
|
|
10
|
+
- **`ctx.parseCell(v)` を追加**(コア/inline エディタの ctx)— 打たれた文字を**セルの確定と同じ読み方**で保存値に解釈する(`columns[c].parse` があればそれ、無ければ型ごとの組込。解釈できなければ `null`)。**エディタが独自パーサを持つと本体と食い違う**ため、打った内容に追従する実装はこれを使う。あわせてコア内部の「入力→保存値」の解釈を `_parseInput()` に括り出し、**確定の関所(`_coerceCell`)と inline エディタが同じ1箇所を共有**するようにした(=解釈の二重実装を構造的に防ぐ)。`TssCalendar` の `onInput` をこれに載せ替え。既存の列・`col.parse` を持つ列の挙動は不変。
|
|
11
|
+
|
|
12
|
+
## [0.1.1] - 2026-07-17
|
|
13
|
+
|
|
14
|
+
### 変更(既定の挙動が変わります)
|
|
15
|
+
- **同梱プラグインの `inline` を既定 ON に**(`TssAutocomplete` / `TssCalendar` / `TssDatetime` / `TssTime`)— **旧挙動に戻すには `inline: false` を明示**してください。
|
|
16
|
+
- **理由**: 0.1.0 で `inline` を opt-in として出したが、既定 false +**プラグイン自身のドキュメント例に `inline` が無い**ため、**「`inline: true` と書いた人だけが直る」**状態だった。例をコピペした人には修正が届かず、出荷されたのは修正ではなく**フラグ**だった。
|
|
17
|
+
- **既定 false で実際に失われていたもの**(正確に言うと): IME が使えなくなるわけではない。クリック/F2 でポップアップを開けば、その中の自前 input は普通のフォーカス済み input なので**日本語は普通に打てる**。失われるのは **「セルにいきなり打ち始めて上書き」=打ち始めの経路**だけ(1文字目が捨てられる/IME 直打ちで編集に入れない)。
|
|
18
|
+
- **なぜそれが重いか**: それは TssGrid が他ライブラリの回避策を批判した時に挙げた代償そのもの(「セルにいきなり打ち込んで上書きができなくなる。表計算で一番よく使う操作なのに」)。**批判した妥協を自分の既定で出荷していた**ので、既定を反転する。
|
|
19
|
+
- **代償(日付系)**: 既定では**矢印キーを奪いません**=カレンダー内の日移動が既定で無くなります(セル移動のまま)。「キー派は打つ/マウス派はクリック」の割り切り。矢印で日を送りたい場合のみ `inline: false`。
|
|
20
|
+
- **自作カスタムエディタの `editor.inline` は既定 false のまま**(共有 input に乗るには実装側の合意が要るため。宣言しない限り挙動不変)。
|
|
21
|
+
|
|
22
|
+
### 追加
|
|
23
|
+
- **日付/時刻ピッカーも `inline` 対応:`TssCalendar` / `TssDatetime` / `TssTime`**(プラグイン)— これらは**自前のテキスト入力を持たずポップアップにフォーカスする**型だったため、カスタムエディタとして付けると(`col.format`/`parse` 併用時は特に)**日付をそもそも打ち始められず、クリックで選ぶしかなかった**(=0.1.0 の `editor.inline` が解いた問題を、同梱の日付系も踏んでいた)。inline では本体の共有 input に乗り、**`6/20` のように直打ちでき(1文字目も落ちない)・IME 直打ちも効く**。ポップアップはフォーカスを奪わず"確認+クリック補助"に降りる。`TssCalendar` は打った内容に**追従してその月/日へジャンプ**(`onInput`+列の `parse` を解釈)。日をクリックで確定・Enter で打った値を確定・Esc で取消(本体既定)。
|
|
24
|
+
|
|
6
25
|
## [0.1.0] - 2026-07-17
|
|
7
26
|
|
|
8
27
|
### 追加
|
package/README.md
CHANGED
|
@@ -374,7 +374,11 @@ new TssGrid(el, { columns: [{ editor: myEditor }] });
|
|
|
374
374
|
const suggest = {
|
|
375
375
|
inline: true, icon: '🔍', // icon / openOnClick は inline でも使えます
|
|
376
376
|
open(ctx) { /* ctx.input = 打鍵/IMEが入る共有input。ここに候補ポップアップを添える(フォーカスは奪わない) */ },
|
|
377
|
-
onInput(value, ctx) {
|
|
377
|
+
onInput(value, ctx) {
|
|
378
|
+
// 打つたびに呼ばれる=候補を絞る。値として解釈したいなら ctx.parseCell(=セルと同じ読み方。
|
|
379
|
+
// 独自パーサを書くと本体と食い違う)。解釈できない打ちかけは null。
|
|
380
|
+
const v = ctx.parseCell(value); if (v == null) return;
|
|
381
|
+
},
|
|
378
382
|
onKeyDown(e, ctx) { // 本体より先に受ける。IME 変換中も呼ばれる
|
|
379
383
|
if (e.isComposing || e.keyCode === 229) return; // ★変換中は手を引く(↑↓を奪わない)
|
|
380
384
|
if (e.key === 'ArrowDown') { /* 候補を選ぶ */ return false; } // false で本体既定を止める
|
|
@@ -384,10 +388,15 @@ const suggest = {
|
|
|
384
388
|
};
|
|
385
389
|
new TssGrid(el, { columns: [{ editor: suggest }] });
|
|
386
390
|
```
|
|
387
|
-
- `ctx` は通常の `{grid,r,c,value,td,commit,cancel}` に加えて **`input`(共有input)/ `setValue(v)`(候補で置換・確定はしない)/ `inline:true`** が入ります。
|
|
391
|
+
- `ctx` は通常の `{grid,r,c,value,td,commit,cancel}` に加えて **`input`(共有input)/ `setValue(v)`(候補で置換・確定はしない)/ `parseCell(v)` / `inline:true`** が入ります。
|
|
392
|
+
- **打った内容に追従するなら `ctx.parseCell(v)` を使ってください**=**セルの確定と同じ読み方**(`columns[c].parse` があればそれ、無ければ型ごとの組込=date/time/number)で保存値に解釈し、解釈できなければ `null`(打ちかけ)。**エディタが独自パーサを持つと「セルは受け付けるのに UI は追従しない」食い違い**になります(実際 `TssCalendar` がこれを踏みました)。
|
|
388
393
|
- **確定は本体の `commit()`=関所を通る**ので `validator`/`parse`/`format`/読み取り専用/Undo はそのまま効きます。
|
|
389
394
|
- ポップアップ側は **`mousedown` を `preventDefault`** してフォーカスを奪わないこと(共有 input に打ち続けられる)。
|
|
390
|
-
- 対象は **text/number 系の列**(`dropdown`/`checkbox
|
|
395
|
+
- 対象は **text/number 系の列**(`dropdown`/`checkbox`/ネイティブ日付は従来どおり対象外)。
|
|
396
|
+
- **自作エディタの `inline` は既定 false**(宣言しない限り挙動は一切変わりません)。共有 input に乗るには実装側の合意が要るためで、ここは opt-in が正しい既定です。
|
|
397
|
+
- **同梱プラグインの `inline` は既定 true**:`TssAutocomplete`(打ちながら候補を絞る)/`TssCalendar`・`TssDatetime`・`TssTime`(**日付/時刻をそのまま直打ち**できる。カレンダーは打った内容に追従してその日へジャンプ)。**何も書かずにコピペした人が、看板どおりの「打ち始めれば書ける」を得ます**。
|
|
398
|
+
- 日付系は **「キー派は打つ/マウス派はクリック」** の割り切り=**矢印キーは奪いません**(文字カーソル/セル移動のまま)。伝票入力のように「`6/20` と打つ方が速い」場面向け。
|
|
399
|
+
- `inline: false` で旧型(ポップアップにフォーカスし ←→↑↓ で日移動)に戻せます。ただし **セルにいきなり打ち始めて上書き**(表計算で一番よく使う操作)ができなくなる=クリック/F2 で開いてから打つことになります。矢印での日送りが要る場面だけ選んでください。
|
|
391
400
|
|
|
392
401
|
### 同梱プラグイン: 休日カレンダー対応の日付ピッカー
|
|
393
402
|
|
|
@@ -405,7 +414,9 @@ const cal = TssCalendar({
|
|
|
405
414
|
});
|
|
406
415
|
new TssGrid(el, { columns: [{ type: 'date', editor: cal, format: 'yyyy/mm/dd' }] });
|
|
407
416
|
```
|
|
408
|
-
- セル右端に **📅**
|
|
417
|
+
- セル右端に **📅** が出て**シングルクリックで開閉**(再クリックで閉じる=トグル)、開いている間も**フィルハンドルで下へコピー可**(ドロップダウンと同じ操作感)。**年・月はプルダウン**で直接ジャンプ。祝日は赤+**名称ツールチップ**、土日は色分け。保存値は ISO のまま。
|
|
418
|
+
- **打つ人はそのまま打てます**(既定 `inline`): セルに `6/20` と打ち始めればそのまま編集開始(**1文字目も落ちない・IME 直打ちも効く**)、カレンダーは打った日へ追従。確定は Enter か日をクリック。**矢印はセル移動のまま**(カレンダーは奪いません)。
|
|
419
|
+
- 矢印/PageUp・Down でカレンダー内を移動する旧型が要るなら `inline: false`(→ [`editor.inline`](#editorinline-打鍵ime-直打ちを殺さないエディタサジェスト向け))。**セルに直接打ち始める操作は失われます**。
|
|
409
420
|
- 見た目は CSS 変数(`--cal-accent` など)と `tss-cal-*` クラスで自由に(依存ゼロ)。グリッド外でも `cal.openAt(anchorEl, isoValue, onPick)` で単体利用可。
|
|
410
421
|
- `opts`: `icon`(既定 `'📅'`, `''` で消す) / `openOnClick`(既定 true) / `yearMin`・`yearMax`(年プルダウンの範囲) も。
|
|
411
422
|
- 仕組み: カスタムエディタが **`icon` / `openOnClick` を申告**すると、グリッドが右端アイコン表示+シングルクリック起動を行います(自作エディタでも同様に使えます)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@threestarsoftware/tssgrid",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "軽量・ノービルド・日本語IME堅牢な編集グリッド(vanilla JS / 依存なし / MIT)。Excel ライクな入力画面を <script> タグ1本で。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ThreeStarSoftware (https://www.threestarsoftware.co.jp/)",
|
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
*
|
|
10
10
|
* opts:
|
|
11
11
|
* source : string[] か (query, ctx)=>string[]。必須。
|
|
12
|
-
* inline : true
|
|
13
|
-
* 素のテキスト列と同じに効く**(自前 input を作らず focus も奪わない)。
|
|
14
|
-
*
|
|
12
|
+
* inline : 「セルに打ちながら絞る」型(**既定 true**)。本体の共有 input に乗る=**打鍵の1文字目も IME 直打ちも
|
|
13
|
+
* 素のテキスト列と同じに効く**(自前 input を作らず focus も奪わない)。
|
|
14
|
+
* inline:false でポップアップの自前 input を開く旧型に戻せるが、**セルにいきなり打ち始めて上書き**が
|
|
15
|
+
* できなくなる(=クリック/F2 で開いてから打つ)。表計算で一番よく使う操作を捨てる選択なので非推奨。
|
|
15
16
|
* strict : true=候補から選んだ値のみ許可(既定 false=自由入力可)。
|
|
16
17
|
* minChars : この文字数以上で候補を出す(既定 0=開いた時点で全件)。
|
|
17
18
|
* max : 候補表示の最大件数(既定 20)。
|
|
@@ -25,7 +26,7 @@
|
|
|
25
26
|
|
|
26
27
|
function TssAutocomplete(opts) {
|
|
27
28
|
opts = opts || {};
|
|
28
|
-
var inline =
|
|
29
|
+
var inline = opts.inline !== false; // true=共有 input に乗る(打鍵/IME直打ちが効く)/false=従来の自前 input ポップアップ
|
|
29
30
|
var getSource = typeof opts.source === 'function' ? opts.source : function () { return opts.source || []; };
|
|
30
31
|
var strict = !!opts.strict;
|
|
31
32
|
var minChars = opts.minChars || 0;
|
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
|
+
// ★ 解釈は本体に訊く(ctx.parseCell=セルの確定と同じ読み方)。ここで自前パーサを使うと
|
|
146
|
+
// 「セルは受け付けるのにカレンダーは動かない」食い違いになる(col.parse を書かない列=format だけの
|
|
147
|
+
// 自然な使い方で 'yyyy/mm/dd' を打つと追従しなかった実バグ)。
|
|
148
|
+
try { d = parseISO(ctx.parseCell ? ctx.parseCell(v) : v); } 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.d.ts
CHANGED
|
@@ -222,6 +222,12 @@ export interface EditorContext {
|
|
|
222
222
|
input?: HTMLInputElement | HTMLTextAreaElement;
|
|
223
223
|
/** inline エディタのみ: 共有 input の値を差し替える(候補で置換する用・確定はしない)。 */
|
|
224
224
|
setValue?(value: any): void;
|
|
225
|
+
/**
|
|
226
|
+
* inline エディタのみ: 打たれた文字を**この列の保存値**に解釈する(=セルの確定と同じ読み方。
|
|
227
|
+
* `columns[c].parse` があればそれ、無ければ型ごとの組込=date/time/number)。解釈できなければ `null`。
|
|
228
|
+
* **エディタが独自パーサを持つと本体と食い違う**ので、打った内容に追従する実装は必ずこれを使うこと。
|
|
229
|
+
*/
|
|
230
|
+
parseCell?(value: any): any;
|
|
225
231
|
[k: string]: any;
|
|
226
232
|
}
|
|
227
233
|
/** カスタム編集UIの契約(`columns[c].editor`)。 */
|
package/src/tssgrid.js
CHANGED
|
@@ -861,22 +861,29 @@
|
|
|
861
861
|
.replace(/dd/g, d).replace(/d/g, String(+d));
|
|
862
862
|
}
|
|
863
863
|
// 1セルの型強制 / 検証。{ ok, value, message } を返す。入力→保存値の正規化(parse)もここで。
|
|
864
|
+
// 入力文字列 → 保存値。解釈できなければ null(=この列はその文字を受け付けない)。
|
|
865
|
+
// 「この列が入力をどう読むか」の唯一の定義。_coerceCell(確定の関所)と ctx.parseCell(inline エディタ)が
|
|
866
|
+
// ここを共有する=エディタ側が独自パーサを持って本体と食い違う事故を構造的に防ぐ。
|
|
867
|
+
_parseInput(r, c, v) {
|
|
868
|
+
const cfg = this.colCfg(c), t = cfg.type || 'text';
|
|
869
|
+
if (v === '') return '';
|
|
870
|
+
if (typeof cfg.parse === 'function') { // カスタム parse 優先
|
|
871
|
+
let p; try { p = cfg.parse(v, { r, c }); } catch (_) { p = null; }
|
|
872
|
+
return (p == null || p === false) ? null : String(p);
|
|
873
|
+
}
|
|
874
|
+
if (t === 'date') return TssGrid._parseDate(v); // parse を書かない列は型ごとの組込パーサに落ちる
|
|
875
|
+
if (t === 'time') return TssGrid._parseTime(v);
|
|
876
|
+
if (t === 'number') return TssGrid._parseNumber(v, cfg);
|
|
877
|
+
return v;
|
|
878
|
+
}
|
|
864
879
|
_coerceCell(r, c, value) {
|
|
865
880
|
const cfg = this.colCfg(c); let v = value;
|
|
866
881
|
const t = cfg.type || 'text';
|
|
867
882
|
// parse: 入力文字列 → 保存値(カスタム優先 / 無ければ date・time の組込)
|
|
868
883
|
if (v !== '') {
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
v = String(p);
|
|
873
|
-
} else if (t === 'date') {
|
|
874
|
-
const p = TssGrid._parseDate(v); if (p == null) return { ok: false }; v = p;
|
|
875
|
-
} else if (t === 'time') {
|
|
876
|
-
const p = TssGrid._parseTime(v); if (p == null) return { ok: false }; v = p;
|
|
877
|
-
} else if (t === 'number') {
|
|
878
|
-
const p = TssGrid._parseNumber(v, cfg); if (p == null) return { ok: false }; v = p;
|
|
879
|
-
}
|
|
884
|
+
const p = this._parseInput(r, c, v);
|
|
885
|
+
if (p == null) return { ok: false };
|
|
886
|
+
v = p;
|
|
880
887
|
}
|
|
881
888
|
if (t === 'dropdown') {
|
|
882
889
|
if (v === '') { if (cfg.allowEmpty === false) return { ok: false }; } // allowEmpty:false=必須選択(空は不可)
|
|
@@ -1639,6 +1646,9 @@
|
|
|
1639
1646
|
value: this.data[r][c],
|
|
1640
1647
|
td: this.cellEl(r, c),
|
|
1641
1648
|
input: this.editor, // 打鍵/IME が入る共有 input(読み取り・listen 用)
|
|
1649
|
+
// 打たれた文字を「この列の保存値」に解釈する=セルの確定と同じ読み方(col.parse があればそれ、無ければ型の組込)。
|
|
1650
|
+
// 解釈できなければ null(打ちかけ等)。エディタが独自パーサを持つと本体と食い違うので、必ずこれを使うこと。
|
|
1651
|
+
parseCell(val) { return grid._parseInput(r, c, val); },
|
|
1642
1652
|
setValue(v) { grid.editor.value = grid._cellStr(v); }, // 候補で置き換える用(確定はしない)
|
|
1643
1653
|
commit(v) { if (v != null) grid.editor.value = grid._cellStr(v); grid.commit(); grid.toNav(); grid.updateSelectionUI(); },
|
|
1644
1654
|
cancel() { grid.setActive(r, c); }, // 元値に戻して nav(Esc 相当)
|