@threestarsoftware/tssgrid 0.1.1 → 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 CHANGED
@@ -3,6 +3,12 @@
3
3
  TssGrid の各リリースの変更点。日付は JST。形式は [Keep a Changelog](https://keepachangelog.com/ja/1.1.0/) 準拠(ゆるめ)。
4
4
  数値は特記なき限り**自社調べ**(headless Chrome / Windows 11・環境差あり)。
5
5
 
6
+ ## [0.1.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
+
6
12
  ## [0.1.1] - 2026-07-17
7
13
 
8
14
  ### 変更(既定の挙動が変わります)
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,7 +388,8 @@ 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
395
  - 対象は **text/number 系の列**(`dropdown`/`checkbox`/ネイティブ日付は従来どおり対象外)。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@threestarsoftware/tssgrid",
3
- "version": "0.1.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/)",
@@ -142,10 +142,10 @@
142
142
  onInput: inline ? function (v, ctx) {
143
143
  if (!pop) return;
144
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; }
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
149
  if (!d) return; // 打ちかけ/不正はカレンダーを動かさない
150
150
  sel = d; focus = new Date(d); view = new Date(d.getFullYear(), d.getMonth(), 1); render();
151
151
  } : undefined,
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
- if (typeof cfg.parse === 'function') {
870
- let p; try { p = cfg.parse(v, { r, c }); } catch (_) { p = null; }
871
- if (p == null || p === false) return { ok: false };
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 相当)