@threestarsoftware/tssgrid 0.1.35 → 0.1.36
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 +13 -0
- package/README.md +2 -0
- package/package.json +1 -1
- package/plugins/tss-calendar.js +9 -6
- package/plugins/tss-datetime.js +50 -9
- package/plugins/tss-time.js +23 -8
- package/src/tssgrid.js +11 -3
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,19 @@
|
|
|
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.36] - 2026-11-06
|
|
7
|
+
|
|
8
|
+
inline ピッカー3兄弟(**tss-time / tss-calendar / tss-datetime**)に共通して潜んでいた「ポップアップの `<select>` が使えない/確定がピッカー内部の値を書き戻す/編集中の表示が食い違う」を一掃。土台としてコアに2つの affordance を追加。
|
|
9
|
+
|
|
10
|
+
### 修正(コア)
|
|
11
|
+
- **inline エディタの補助ポップアップ(`<select>` 等 focus を奪う要素を含む)へフォーカスが移っても編集を閉じない**ようにした。従来は共有 input が blur した瞬間に確定+ポップアップ破棄だったため、**時/分/年/月のプルダウンを開こうとクリックすると閉じてしまい選べなかった**。inline ctx に `setPopup(el)` を追加し、`blur` の `relatedTarget` が登録ポップアップ内なら編集継続。
|
|
12
|
+
- **カスタム `editor` を付けた date/time 列はネイティブ `<input type="date|time">` を重ねない**ようにした(`_nativeInputType` が `cfg.editor` を見る)。従来は `picker:false` を書き忘れるとネイティブ入力が裏に残り、**ブラウザのロケールで `09:00 AM` のように AM/PM 表示が出てセル表示(24h)と食い違っていた**。`picker:false` が無くてもカスタムエディタが入力/表示を一貫して握る。
|
|
13
|
+
|
|
14
|
+
### 修正(プラグイン)
|
|
15
|
+
- **`tss-time`**: inline で**打鍵後にポップアップの[決定]/Enter を押すと、select が持つ元の値を書き戻して打鍵が消えていた**のを修正(inline は共有 input を確定・select を触ったら input へミラー+フォーカスを input へ戻す)。`hour12` 列で **AM/PM を切り替えると編集中だけ 24h 表記(`06:30`)がちらついた**のも、ミラーを 12h 表示形(`6:30 AM`・parser も受ける)にして解消。上記コア修正と合わせ、**時/分のプルダウンが選べる**ように。
|
|
16
|
+
- **`tss-calendar`**: **年/月のプルダウンを押すとポップアップが閉じて選べなかった**のを修正(`setPopup` 登録+選択後に共有 input へフォーカスを戻す)。値の確定(日クリック)・打鍵追従(`onInput`)は従来どおり。
|
|
17
|
+
- **`tss-datetime`**: 同じく**年/月/時/分のプルダウンが選べなかった**のに加え、**打鍵がピッカーに反映されない/ピッカー操作が共有 input に出ない/`<select>` 変更後にクリックアウトすると表示文字列がそのまま生保存されて保存値が壊れる**(`YYYY-MM-DDTHH:MM` にならない)という問題を一括修正。**`TssDatetime.parse`(表示形→保存形。`format` の逆)を新設**し、列に `parse` が無ければ自動付与=打鍵追従も確定(blur/Enter/決定)の round-trip も成立。ピッカー操作は表示形で共有 input にミラー+フォーカスを戻す。保存値は常に `YYYY-MM-DDTHH:MM`。
|
|
18
|
+
|
|
6
19
|
## [0.1.35] - 2026-11-06
|
|
7
20
|
|
|
8
21
|
### 追加(プラグイン)
|
package/README.md
CHANGED
|
@@ -562,6 +562,8 @@ new TssGrid(el, { columns: [{ type: 'time', editor: t }] });
|
|
|
562
562
|
```
|
|
563
563
|
- `opts`: `step`(分刻み, 既定1) / `hour12`(AM/PM 表示, 既定false) / `icon`(既定 `'🕐'`) / `openOnClick`(既定true) / `className`。
|
|
564
564
|
- 🕐 を**シングルクリックで開閉**、**現在**/**決定**ボタン、キーボード(Enter 決定 / Esc 取消)、外側クリックで採用、フィルで下へコピー。見た目は `--time-*` 変数と `tss-time-*` クラスで自由に。
|
|
565
|
+
- カスタム `editor` を付けた列は**ネイティブ `<input type="time">` を重ねません**(`picker:false` を書かなくてもOK)。`hour12` 列は編集中も **12h 表示**で一貫(保存は 24h)。
|
|
566
|
+
- **日付+時刻**は姉妹プラグイン `tss-datetime`(`editor: TssDatetime({ step: 5 }), format: TssDatetime.format`)。保存値は `'YYYY-MM-DDTHH:MM'`、表示は曜日つき。表示形→保存形の逆変換 **`TssDatetime.parse`**(列に `parse` 未指定なら自動付与)で、打鍵追従も確定の round-trip も成立。年/月/時/分のプルダウンは inline でもそのまま選べます。
|
|
565
567
|
|
|
566
568
|
### 同梱プラグイン: セルのコメント(tss-comment・Excel のメモ風)
|
|
567
569
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@threestarsoftware/tssgrid",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.36",
|
|
4
4
|
"description": "軽量・ノービルド・日本語IME堅牢な編集グリッド(vanilla JS / 依存なし / MIT)。Excel ライクな入力画面を <script> タグ1本で。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ThreeStarSoftware (https://www.threestarsoftware.co.jp/)",
|
package/plugins/tss-calendar.js
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
var ty = new Date().getFullYear();
|
|
35
35
|
var yrLo = opts.yearMin || (minD ? minD.getFullYear() : ty - 12);
|
|
36
36
|
var yrHi = opts.yearMax || (maxD ? maxD.getFullYear() : ty + 12);
|
|
37
|
-
var pop = null, view = null, sel = null, focus = null, ref = null, done = false, outside = null;
|
|
37
|
+
var pop = null, view = null, sel = null, focus = null, ref = null, done = false, outside = null, ictx = null;
|
|
38
38
|
var inline = opts.inline !== false; // true=共有 input に乗る(直打ち可・矢印は本体に返す)/false=従来のフォーカス奪取型
|
|
39
39
|
|
|
40
40
|
// ── 範囲選択(range): 連続した日を1回の編集で選ぶ(休暇申請の開始〜終了 等)───────────
|
|
@@ -134,6 +134,8 @@
|
|
|
134
134
|
var t = e.target;
|
|
135
135
|
if (t.classList && t.classList.contains('tss-cal-sel-yr')) { view = new Date(+t.value, view.getMonth(), 1); render(); }
|
|
136
136
|
else if (t.classList && t.classList.contains('tss-cal-sel-mon')) { view = new Date(view.getFullYear(), +t.value, 1); render(); }
|
|
137
|
+
// 年/月を選んだ後は共有 input にフォーカスを戻す(select は render で作り直され、残すと焦点が宙に浮く)。
|
|
138
|
+
if (ictx && ictx.inline && ictx.input) { try { ictx.input.focus(); } catch (err) {} }
|
|
137
139
|
}
|
|
138
140
|
function onKey(e) {
|
|
139
141
|
if (e.target && e.target.tagName === 'SELECT') return; // 年/月プルダウン操作中は日移動しない
|
|
@@ -161,9 +163,9 @@
|
|
|
161
163
|
try { rc.td.title = rangeTipOf(aIso, bIso); } catch (e) { /* noop */ }
|
|
162
164
|
}
|
|
163
165
|
function clearTip() { if (rc && rc.td) { try { rc.td.title = ''; } catch (e) { /* noop */ } } }
|
|
164
|
-
function show(anchor, value, onPick, onCancel, rangeCfg) {
|
|
166
|
+
function show(anchor, value, onPick, onCancel, rangeCfg, ctx) {
|
|
165
167
|
teardown(); // 既に開いていれば先に片付ける(前の pop 除去+outside リスナ解除・open-while-open の取り残し防止)
|
|
166
|
-
done = false; rc = rangeCfg || null; rangeStart = null; hoverEnd = null;
|
|
168
|
+
done = false; rc = rangeCfg || null; rangeStart = null; hoverEnd = null; ictx = ctx || null;
|
|
167
169
|
rangeActive = rc ? (isRangeValue(value) || !!rc.def) : false; // 既存値が範囲 or rangeDefault なら ON で開く
|
|
168
170
|
sel = parseISO(value);
|
|
169
171
|
// range の既存値('YYYY/MM/DD~…' 等)は parseISO では取れない=先頭日を緩く読んで月を合わせる。
|
|
@@ -181,13 +183,14 @@
|
|
|
181
183
|
ref = { pick: onPick, cancel: onCancel };
|
|
182
184
|
// inline は「外側クリック/フォーカス」を本体(_commitActive / blur)が持つ=二重に閉じない・共有 input から
|
|
183
185
|
// フォーカスを奪わない(=打ち続けられる)。矢印/Enter/Esc も本体の既定に返す(onKey は pop 未フォーカスなので発火しない)。
|
|
184
|
-
|
|
186
|
+
// ★ ポップアップを本体に登録=年/月の <select> へフォーカスが移っても編集を閉じない(②)。
|
|
187
|
+
if (inline) { if (ictx && ictx.inline && ictx.setPopup) ictx.setPopup(pop); return; }
|
|
185
188
|
// 外側クリックで閉じる。ただしアンカー(開いた元セル)は除外=再クリックはグリッド側でトグル処理させる
|
|
186
189
|
outside = function (e) { if (pop && !pop.contains(e.target) && !(anchor && anchor.contains && anchor.contains(e.target))) cancel(); };
|
|
187
190
|
setTimeout(function () { document.addEventListener('mousedown', outside, true); }, 0);
|
|
188
191
|
pop.focus();
|
|
189
192
|
}
|
|
190
|
-
function teardown() { clearTip(); if (outside) { document.removeEventListener('mousedown', outside, true); outside = null; } if (pop) { pop.remove(); pop = null; } }
|
|
193
|
+
function teardown() { clearTip(); if (outside) { document.removeEventListener('mousedown', outside, true); outside = null; } if (pop) { pop.remove(); pop = null; } ictx = null; }
|
|
191
194
|
function pick(d) {
|
|
192
195
|
if (done) return;
|
|
193
196
|
if (rc && rangeActive) { // トグル ON=2クリック範囲
|
|
@@ -206,7 +209,7 @@
|
|
|
206
209
|
var rangeMode = typeof rangeOpt === 'function' ? !!rangeOpt(ctx) : !!rangeOpt; // この行で範囲を使えるか(半休行は false=トグル無し)
|
|
207
210
|
var def = typeof rangeDefault === 'function' ? !!rangeDefault(ctx) : !!rangeDefault; // トグル初期ON/OFF(行ごと可)
|
|
208
211
|
var cfg = rangeMode ? { td: ctx.td, def: def } : null;
|
|
209
|
-
show(ctx.td, ctx.value, function (v) { ctx.commit(v); }, function () { ctx.cancel(); }, cfg);
|
|
212
|
+
show(ctx.td, ctx.value, function (v) { ctx.commit(v); }, function () { ctx.cancel(); }, cfg, ctx);
|
|
210
213
|
},
|
|
211
214
|
close: function () { teardown(); },
|
|
212
215
|
icon: opts.icon != null ? opts.icon : '📅', // セル右端に表示する合図(opts.icon:'' で消せる)
|
package/plugins/tss-datetime.js
CHANGED
|
@@ -38,8 +38,16 @@
|
|
|
38
38
|
var yrLo = opts.yearMin || (minD ? minD.getFullYear() : ty - 12);
|
|
39
39
|
var yrHi = opts.yearMax || (maxD ? maxD.getFullYear() : ty + 12);
|
|
40
40
|
|
|
41
|
-
var pop = null, view = null, selD = null, selH = null, selM = null, ref = null, done = false, outside = null;
|
|
41
|
+
var pop = null, view = null, selD = null, selH = null, selM = null, ref = null, done = false, outside = null, ictx = null;
|
|
42
42
|
var inline = opts.inline !== false; // true=共有 input に乗る(直打ち可・矢印は本体に返す)/false=従来のフォーカス奪取型
|
|
43
|
+
// inline: ピッカー操作の結果を共有 input に「表示形」で反映+フォーカスを input に戻す。
|
|
44
|
+
// これで select へ移っても閉じず(②)、編集中の表示も一致(④)、クリックアウト確定も壊れない。
|
|
45
|
+
function syncInput() {
|
|
46
|
+
if (!(ictx && ictx.inline && ictx.input)) return;
|
|
47
|
+
var v = value();
|
|
48
|
+
ictx.input.value = v === '' ? '' : TssDatetime.format(v, { weekLabels: weekLabels });
|
|
49
|
+
try { ictx.input.focus(); } catch (e) {}
|
|
50
|
+
}
|
|
43
51
|
|
|
44
52
|
function dDisabled(d) { if (minD && d < minD) return true; if (maxD && d > maxD) return true; return false; }
|
|
45
53
|
|
|
@@ -123,10 +131,10 @@
|
|
|
123
131
|
function onClick(e) {
|
|
124
132
|
var t = e.target;
|
|
125
133
|
var nav = t.getAttribute && t.getAttribute('data-nav');
|
|
126
|
-
if (nav) { view = new Date(view.getFullYear(), view.getMonth() + (+nav), 1); render(); return; }
|
|
134
|
+
if (nav) { view = new Date(view.getFullYear(), view.getMonth() + (+nav), 1); render(); syncInput(); return; }
|
|
127
135
|
var dd = t.getAttribute && t.getAttribute('data-d');
|
|
128
|
-
if (dd) { readTime(); var d = parseISOd(dd); if (!dDisabled(d)) { selD = d; render(); } return; } // 日付選択しても閉じない(時刻も決めるため)
|
|
129
|
-
if (t.classList.contains('tss-dt-now')) { var now = new Date(); selD = now; selH = now.getHours(); selM = Math.round(now.getMinutes() / step) * step; if (selM >= 60) selM = 60 - step; view = new Date(now.getFullYear(), now.getMonth(), 1); render(); return; }
|
|
136
|
+
if (dd) { readTime(); var d = parseISOd(dd); if (!dDisabled(d)) { selD = d; render(); syncInput(); } return; } // 日付選択しても閉じない(時刻も決めるため)
|
|
137
|
+
if (t.classList.contains('tss-dt-now')) { var now = new Date(); selD = now; selH = now.getHours(); selM = Math.round(now.getMinutes() / step) * step; if (selM >= 60) selM = 60 - step; view = new Date(now.getFullYear(), now.getMonth(), 1); render(); syncInput(); return; }
|
|
130
138
|
if (t.classList.contains('tss-dt-clear')) { commit(''); return; }
|
|
131
139
|
if (t.classList.contains('tss-dt-ok')) { commit(value()); return; }
|
|
132
140
|
}
|
|
@@ -135,14 +143,15 @@
|
|
|
135
143
|
if (t.classList.contains('tss-cal-sel-yr')) { view = new Date(+t.value, view.getMonth(), 1); render(); }
|
|
136
144
|
else if (t.classList.contains('tss-cal-sel-mon')) { view = new Date(view.getFullYear(), +t.value, 1); render(); }
|
|
137
145
|
else if (t.classList.contains('tss-time-h') || t.classList.contains('tss-time-m') || t.classList.contains('tss-time-ap')) { readTime(); var ro = pop.querySelector('.tss-dt-readout'); if (ro) ro.innerHTML = readoutHTML(); }
|
|
146
|
+
syncInput(); // 年/月/時/分/AM-PM いずれの変更も共有 input(表示形)へ反映+input へフォーカスを戻す
|
|
138
147
|
}
|
|
139
148
|
function onKey(e) {
|
|
140
149
|
if (e.key === 'Escape') { e.preventDefault(); cancel(); }
|
|
141
150
|
else if (e.key === 'Enter') { e.preventDefault(); commit(value()); }
|
|
142
151
|
}
|
|
143
|
-
function show(anchor, val, onPick, onCancel) {
|
|
152
|
+
function show(anchor, val, onPick, onCancel, ctx) {
|
|
144
153
|
teardown(); // 既に開いていれば先に片付ける(open-while-open の取り残し防止・tss-calendar と同方針)
|
|
145
|
-
done = false;
|
|
154
|
+
done = false; ictx = ctx || null;
|
|
146
155
|
var p = parseDT(val), tp = p ? null : parseTimeOnly(val); // 日付付き or 時刻のみ
|
|
147
156
|
selD = p ? p.d : null;
|
|
148
157
|
selH = p ? p.h : (tp ? tp.h : null);
|
|
@@ -156,21 +165,36 @@
|
|
|
156
165
|
pop.addEventListener('keydown', onKey);
|
|
157
166
|
render(); place(anchor);
|
|
158
167
|
ref = { pick: onPick, cancel: onCancel };
|
|
159
|
-
|
|
168
|
+
// ★ ポップアップを本体に登録=年/月/時/分の <select> へフォーカスが移っても編集を閉じない(②)。
|
|
169
|
+
if (inline) { if (ictx && ictx.inline && ictx.setPopup) ictx.setPopup(pop); return; } // inline は外側クリック/フォーカスを本体が持つ(打ち続けられる)
|
|
160
170
|
outside = function (e) { if (pop && !pop.contains(e.target) && !(anchor && anchor.contains && anchor.contains(e.target))) commit(value()); }; // 外側クリックは確定(time と同方針)
|
|
161
171
|
setTimeout(function () { document.addEventListener('mousedown', outside, true); }, 0);
|
|
162
172
|
pop.focus();
|
|
163
173
|
}
|
|
164
|
-
function teardown() { if (outside) { document.removeEventListener('mousedown', outside, true); outside = null; } if (pop) { pop.remove(); pop = null; } }
|
|
174
|
+
function teardown() { if (outside) { document.removeEventListener('mousedown', outside, true); outside = null; } if (pop) { pop.remove(); pop = null; } ictx = null; }
|
|
165
175
|
function commit(v) { if (done) return; done = true; var f = ref.pick; teardown(); f(v); }
|
|
166
176
|
function cancel() { if (done) return; done = true; var f = ref.cancel; teardown(); if (f) f(); }
|
|
167
177
|
|
|
168
178
|
return {
|
|
169
|
-
open: function (ctx) {
|
|
179
|
+
open: function (ctx) {
|
|
180
|
+
// 列に parse が無ければ付与=表示形↔保存形の round-trip(打鍵追従・blur/Enter 確定)を成立させる。
|
|
181
|
+
if (ctx.grid && typeof ctx.c === 'number') { var cc = ctx.grid.colCfg(ctx.c); if (cc && typeof cc.parse !== 'function') cc.parse = TssDatetime.parse; }
|
|
182
|
+
show(ctx.td, ctx.value, function (v) { ctx.commit(v); }, function () { ctx.cancel(); }, ctx);
|
|
183
|
+
},
|
|
170
184
|
close: function () { teardown(); },
|
|
171
185
|
icon: opts.icon != null ? opts.icon : '📅',
|
|
172
186
|
openOnClick: opts.openOnClick !== false,
|
|
173
187
|
inline: inline, // true=共有 input に乗る=日時をそのまま直打ちできる(矢印/Enter/Esc は本体の既定)
|
|
188
|
+
// inline のみ: 打った内容にピッカーが追従(ctx.parseCell=セルの確定と同じ読み方。上で付けた parse を使う)。
|
|
189
|
+
onInput: inline ? function (v, ctx) {
|
|
190
|
+
if (!pop) return;
|
|
191
|
+
var iso = null; try { iso = ctx.parseCell ? ctx.parseCell(v) : v; } catch (e) { iso = null; }
|
|
192
|
+
var p = parseDT(iso), tp = p ? null : parseTimeOnly(iso);
|
|
193
|
+
if (!p && !tp) return; // 打ちかけ/不正はピッカーを動かさない
|
|
194
|
+
if (p) { selD = p.d; if (p.h != null) selH = p.h; if (p.mi != null) selM = p.mi; view = new Date(p.d.getFullYear(), p.d.getMonth(), 1); }
|
|
195
|
+
else { selH = tp.h; selM = tp.m; }
|
|
196
|
+
render();
|
|
197
|
+
} : undefined,
|
|
174
198
|
openAt: function (anchorEl, val, onPick) { show(anchorEl, val, function (v) { onPick(v); }, null); },
|
|
175
199
|
};
|
|
176
200
|
}
|
|
@@ -185,6 +209,23 @@
|
|
|
185
209
|
return d.getFullYear() + '/' + pad(d.getMonth() + 1) + '/' + pad(d.getDate()) + wd + t;
|
|
186
210
|
};
|
|
187
211
|
|
|
212
|
+
// 表示形 → 保存形。format の逆。'YYYY/MM/DD(曜) HH:MM'・'YYYY-MM-DDTHH:MM'・'YYYY/MM/DD'・'HH:MM' を受け、
|
|
213
|
+
// 'YYYY-MM-DDTHH:MM'(または 'YYYY-MM-DD' / 'HH:MM')へ正規化。これを列 parse に付けると、打鍵追従も
|
|
214
|
+
// 確定(blur/Enter/決定)の round-trip も成立する(inline で editor が入力/表示を握るのに必須)。解釈不可は素通し。
|
|
215
|
+
TssDatetime.parse = function (v) {
|
|
216
|
+
var s = String(v == null ? '' : v).trim();
|
|
217
|
+
if (s === '') return '';
|
|
218
|
+
var m = /^(\d{4})[\/\-](\d{1,2})[\/\-](\d{1,2})(?:\s*\([^)]*\))?(?:[ T]+(\d{1,2}):(\d{2}))?/.exec(s);
|
|
219
|
+
if (m) {
|
|
220
|
+
var iso = m[1] + '-' + pad(+m[2]) + '-' + pad(+m[3]);
|
|
221
|
+
if (m[4] != null) iso += 'T' + pad(+m[4]) + ':' + pad(+m[5]);
|
|
222
|
+
return iso;
|
|
223
|
+
}
|
|
224
|
+
var t = /^(\d{1,2}):(\d{2})$/.exec(s);
|
|
225
|
+
if (t) return pad(+t[1]) + ':' + pad(+t[2]);
|
|
226
|
+
return s;
|
|
227
|
+
};
|
|
228
|
+
|
|
188
229
|
if (typeof module !== 'undefined' && module.exports) module.exports = TssDatetime;
|
|
189
230
|
else root.TssDatetime = TssDatetime;
|
|
190
231
|
})(typeof self !== 'undefined' ? self : this);
|
package/plugins/tss-time.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
opts = opts || {};
|
|
16
16
|
var step = Math.max(1, opts.step || 1);
|
|
17
17
|
var hour12 = !!opts.hour12;
|
|
18
|
-
var pop = null, ref = null, done = false, outside = null;
|
|
18
|
+
var pop = null, ref = null, done = false, outside = null, ictx = null; // ictx: inline の ctx(共有 input が真実)
|
|
19
19
|
var inline = opts.inline !== false; // true=共有 input に乗る(直打ち可・矢印は本体に返す)/false=従来のフォーカス奪取型
|
|
20
20
|
|
|
21
21
|
function opt(val, label, sel) { return '<option value="' + val + '"' + (sel ? ' selected' : '') + '>' + label + '</option>'; }
|
|
@@ -45,10 +45,24 @@
|
|
|
45
45
|
if (hour12 && aS) { var ap = aS.value; h = ap === 'PM' ? (h === 12 ? 12 : h + 12) : (h === 12 ? 0 : h); }
|
|
46
46
|
return pad(h) + ':' + pad(m);
|
|
47
47
|
}
|
|
48
|
+
// 共有 input に見せる文字列。hour12 は 12h 表示形("6:30 PM"=列のセル表示と一致・parser も受ける)、
|
|
49
|
+
// それ以外は 24h(= 保存値 = 表示)。これで編集中に 24h がちらっと出る食い違いを無くす。
|
|
50
|
+
function readDisplay() {
|
|
51
|
+
var hS = pop.querySelector('.tss-time-h'), mS = pop.querySelector('.tss-time-m'), aS = pop.querySelector('.tss-time-ap');
|
|
52
|
+
if (!hS || hS.value === '' || mS.value === '') return readValue();
|
|
53
|
+
if (hour12 && aS) return (+hS.value) + ':' + pad(+mS.value) + ' ' + aS.value;
|
|
54
|
+
return readValue();
|
|
55
|
+
}
|
|
48
56
|
function setNow() {
|
|
49
57
|
var d = new Date(), m = Math.round(d.getMinutes() / step) * step; if (m >= 60) m = 60 - step;
|
|
50
58
|
render({ h: d.getHours(), m: m });
|
|
59
|
+
mirrorToInput(); // inline: 現在ボタンの結果も共有 input へ
|
|
51
60
|
}
|
|
61
|
+
// inline のとき、select の内容を共有 input に流し込む(確定はしない)。これで「打つ」と「選ぶ」が input に集約。
|
|
62
|
+
// 選び終えたらフォーカスを共有 input に戻す=以降の「外クリックで確定」等が本体の既定どおり効く。
|
|
63
|
+
function mirrorToInput() { if (ictx && ictx.inline && ictx.input) { if (ictx.setValue) { var v = readDisplay(); if (v !== '') ictx.input.value = v; } try { ictx.input.focus(); } catch (e) {} } }
|
|
64
|
+
// 確定: inline は共有 input(打鍵 or ミラー済み)を真実として確定/非 inline は select を真実に。
|
|
65
|
+
function commitFinal() { if (ictx && ictx.inline && ictx.input) commit(ictx.input.value); else commit(readValue()); }
|
|
52
66
|
function place(anchor) {
|
|
53
67
|
var r = anchor.getBoundingClientRect();
|
|
54
68
|
pop.style.left = Math.max(2, Math.min(r.left, window.innerWidth - pop.offsetWidth - 6)) + 'px';
|
|
@@ -58,34 +72,35 @@
|
|
|
58
72
|
}
|
|
59
73
|
function onClick(e) {
|
|
60
74
|
if (e.target.classList.contains('tss-time-now')) { setNow(); return; }
|
|
61
|
-
if (e.target.classList.contains('tss-time-ok')) {
|
|
75
|
+
if (e.target.classList.contains('tss-time-ok')) { commitFinal(); }
|
|
62
76
|
}
|
|
63
77
|
function onKey(e) {
|
|
64
78
|
if (e.key === 'Escape') { e.preventDefault(); cancel(); }
|
|
65
|
-
else if (e.key === 'Enter') { e.preventDefault();
|
|
79
|
+
else if (e.key === 'Enter') { e.preventDefault(); commitFinal(); }
|
|
66
80
|
}
|
|
67
|
-
function show(anchor, value, onPick, onCancel) {
|
|
81
|
+
function show(anchor, value, onPick, onCancel, ctx) {
|
|
68
82
|
teardown(); // 既に開いていれば先に片付ける(open-while-open の取り残し防止・tss-calendar と同方針)
|
|
69
|
-
done = false;
|
|
83
|
+
done = false; ictx = ctx || null;
|
|
70
84
|
pop = document.createElement('div'); pop.className = 'tss-time' + (opts.className ? ' ' + opts.className : ''); pop.tabIndex = -1;
|
|
71
85
|
document.body.appendChild(pop);
|
|
72
86
|
pop.addEventListener('mousedown', function (e) { if (e.target.tagName !== 'SELECT') e.preventDefault(); });
|
|
73
87
|
pop.addEventListener('click', onClick);
|
|
74
88
|
pop.addEventListener('keydown', onKey);
|
|
89
|
+
pop.addEventListener('change', function (e) { if (e.target.tagName === 'SELECT') mirrorToInput(); }); // inline: select を選んだら共有 input に反映(打鍵と集約)
|
|
75
90
|
render(parseHM(value)); place(anchor);
|
|
76
91
|
ref = { pick: onPick, cancel: onCancel };
|
|
77
|
-
if (inline) return; // inline
|
|
92
|
+
if (inline) { if (ictx && ictx.inline && ictx.setPopup) ictx.setPopup(pop); return; } // inline: ポップアップを本体に登録=select へフォーカスが移っても編集を閉じない。外側クリック/フォーカスは本体が持つ(打ち続けられる)
|
|
78
93
|
// 外側クリックは「確定」(時/分を選んで離す=採用)。未変更なら同値 commit=実質no-op。アンカーは除外(再クリックはグリッドがトグル)
|
|
79
94
|
outside = function (e) { if (pop && !pop.contains(e.target) && !(anchor && anchor.contains && anchor.contains(e.target))) commit(readValue()); };
|
|
80
95
|
setTimeout(function () { document.addEventListener('mousedown', outside, true); }, 0);
|
|
81
96
|
pop.focus();
|
|
82
97
|
}
|
|
83
|
-
function teardown() { if (outside) { document.removeEventListener('mousedown', outside, true); outside = null; } if (pop) { pop.remove(); pop = null; } }
|
|
98
|
+
function teardown() { if (outside) { document.removeEventListener('mousedown', outside, true); outside = null; } if (pop) { pop.remove(); pop = null; } ictx = null; }
|
|
84
99
|
function commit(v) { if (done) return; done = true; var f = ref.pick; teardown(); f(v); }
|
|
85
100
|
function cancel() { if (done) return; done = true; var f = ref.cancel; teardown(); if (f) f(); }
|
|
86
101
|
|
|
87
102
|
return {
|
|
88
|
-
open: function (ctx) { show(ctx.td, ctx.value, function (v) { ctx.commit(v); }, function () { ctx.cancel(); }); },
|
|
103
|
+
open: function (ctx) { show(ctx.td, ctx.value, function (v) { ctx.commit(v); }, function () { ctx.cancel(); }, ctx); },
|
|
89
104
|
close: function () { teardown(); },
|
|
90
105
|
icon: opts.icon != null ? opts.icon : '🕐',
|
|
91
106
|
openOnClick: opts.openOnClick !== false,
|
package/src/tssgrid.js
CHANGED
|
@@ -159,7 +159,7 @@
|
|
|
159
159
|
this._headerSel = null; // 'col'/'row'/'all'=列/行/全選択モード(結合に拡張せず実列/行にクランプ=Excel流)。null=通常セル選択
|
|
160
160
|
this._selCols = new Set(); // 飛び飛びの列選択(Ctrl+ヘッダクリック)。_headerSel==='col' の時だけ有効。連続選択は矩形と一致
|
|
161
161
|
this._selRows = new Set(); // 飛び飛びの行選択(Ctrl+行ヘッダクリック)。_headerSel==='row' の時だけ有効
|
|
162
|
-
this._inlineEd = null; this._inlineCtx = null; // inline エディタ(editor.inline=true): 共有 input
|
|
162
|
+
this._inlineEd = null; this._inlineCtx = null; this._inlineEl = null; // inline エディタ(editor.inline=true): 共有 input に乗るエディタ。_inlineEl=補助ポップアップ参照
|
|
163
163
|
this.disjointSelect = opts.disjointSelect !== false; // Ctrl+ヘッダで行/列を飛び飛び複数選択(false で無効=Ctrl+クリックは単一選択に戻る)
|
|
164
164
|
// リサイズ(列幅・行高)
|
|
165
165
|
// 列幅 colW[c]=px(未指定は既定)。指定方法は3通り、優先度=実行時リサイズ/明示colW > columns[c].width > 既定:
|
|
@@ -1158,7 +1158,9 @@
|
|
|
1158
1158
|
// ネイティブ入力(date/time ピッカー)を使う列か。使わない=テキスト入力。
|
|
1159
1159
|
_nativeInputType(c) {
|
|
1160
1160
|
const cfg = this.colCfg(c), t = cfg.type;
|
|
1161
|
-
|
|
1161
|
+
// カスタム editor を付けた列は、そのエディタが入力/表示を握る=ネイティブ picker を重ねない
|
|
1162
|
+
// (重ねるとブラウザのロケールで AM/PM 等が出て、セル表示と食い違う)。picker:false を書き忘れても破綻しない。
|
|
1163
|
+
if ((t === 'date' || t === 'time') && cfg.picker !== false && !cfg.format && typeof cfg.parse !== 'function' && !cfg.hour12 && !cfg.editor) return t;
|
|
1162
1164
|
return null;
|
|
1163
1165
|
}
|
|
1164
1166
|
_usesTextEditor(c) {
|
|
@@ -2003,12 +2005,15 @@
|
|
|
2003
2005
|
setValue(v) { grid.editor.value = grid._cellStr(v); }, // 候補で置き換える用(確定はしない)
|
|
2004
2006
|
commit(v) { if (v != null) grid.editor.value = grid._cellStr(v); grid.commit(); grid.toNav(); grid.updateSelectionUI(); },
|
|
2005
2007
|
cancel() { grid.setActive(r, c); }, // 元値に戻して nav(Esc 相当)
|
|
2008
|
+
// inline 補助ポップアップ(<select> 等 focus を奪う要素を含む)を登録=その中へフォーカスが移っても編集を閉じない。
|
|
2009
|
+
setPopup(el) { grid._inlineEl = el || null; },
|
|
2006
2010
|
};
|
|
2007
2011
|
this._inlineEd = ed; this._inlineCtx = ctx;
|
|
2008
2012
|
try { ed.open(ctx); } catch (_) { this._inlineEd = null; this._inlineCtx = null; }
|
|
2009
2013
|
}
|
|
2010
2014
|
_closeInlineEditor() {
|
|
2011
2015
|
const ed = this._inlineEd;
|
|
2016
|
+
this._inlineEl = null; // 登録された補助ポップアップ参照を解除
|
|
2012
2017
|
if (!ed) return;
|
|
2013
2018
|
this._inlineEd = null; this._inlineCtx = null;
|
|
2014
2019
|
if (ed.close) { try { ed.close(); } catch (_) {} }
|
|
@@ -3095,8 +3100,11 @@
|
|
|
3095
3100
|
else if (t === 'date' || t === 'time') this.toEdit(this.data[this.active.r][c], true); // ネイティブピッカー
|
|
3096
3101
|
// checkbox は文字入力を無視(Space / クリックでトグル)
|
|
3097
3102
|
});
|
|
3098
|
-
ed.addEventListener('blur', () => {
|
|
3103
|
+
ed.addEventListener('blur', (e) => {
|
|
3099
3104
|
if (this._customEditor) return; // カスタムエディタが奪ったフォーカスでの blur は無視
|
|
3105
|
+
// inline エディタの補助ポップアップ(<select> 等)へフォーカスが移った blur は編集継続(閉じない)。
|
|
3106
|
+
const to = (e && e.relatedTarget) || document.activeElement;
|
|
3107
|
+
if (this._inlineEl && to && this._inlineEl.contains(to)) return;
|
|
3100
3108
|
if (this.mode === 'edit' && (!this.select || this.select.style.display === 'none')) { this.commit(); this.toNav(); this.updateSelectionUI(); }
|
|
3101
3109
|
});
|
|
3102
3110
|
});
|