@threestarsoftware/tssgrid 0.1.28 → 0.1.29-beta.0
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 +5 -0
- package/package.json +1 -1
- package/plugins/tss-calendar.css +7 -0
- package/plugins/tss-calendar.d.ts +11 -0
- package/plugins/tss-calendar.js +67 -10
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
TssGrid の各リリースの変更点。日付は JST。形式は [Keep a Changelog](https://keepachangelog.com/ja/1.1.0/) 準拠(ゆるめ)。
|
|
4
4
|
数値は特記なき限り**自社調べ**(headless Chrome / Windows 11・環境差あり)。
|
|
5
5
|
|
|
6
|
+
## [Unreleased]
|
|
7
|
+
|
|
8
|
+
### 追加(プラグイン)
|
|
9
|
+
- **`tss-calendar` に範囲選択 `range` を追加** — 連続した日(休暇申請の開始〜終了など)を**1回の編集で**選べる。`range:true` で有効、`range:(ctx)=>boolean` で**行ごとにON/OFF**(半休の行は範囲を選ばせない等)。未指定は単日=**従来動作**。1クリック目=開始・2クリック目=終了(**開始より前なら入替・同じ日を2回で単日確定**)、間の日に色が付く。1クリック目だけで閉じたら(Esc/外クリック)**値は変えない**。確定値は既定で `2026/10/13~2026/10/16`(単日は `2026/10/13`/区切りは `rangeSeparator`・既定 '~'/`rangeValue(startISO,endISO)` で完全差替可)。**入力中のセルに範囲ツールチップ**を出し、`rangeTooltip(startISO,endISO)` で文面を差し替え可能(休日を除いた日数などを外から足す口)。**休日スキップは利用側**=カレンダーは範囲を返すだけ。範囲列は `type:'date'` を付けない素の列にする(範囲文字列を日付パーサに通さないため)。例 `examples/calendar-range.html`。
|
|
10
|
+
|
|
6
11
|
## [0.1.28] - 2026-10-20
|
|
7
12
|
|
|
8
13
|
### 追加(プラグイン)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@threestarsoftware/tssgrid",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.29-beta.0",
|
|
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.css
CHANGED
|
@@ -37,4 +37,11 @@
|
|
|
37
37
|
.tss-cal-day.tss-cal-today { box-shadow: inset 0 0 0 1px var(--cal-accent); }
|
|
38
38
|
.tss-cal-day.tss-cal-focus { box-shadow: inset 0 0 0 2px var(--cal-accent); }
|
|
39
39
|
.tss-cal-day.tss-cal-sel { background: var(--cal-accent); color: #fff; }
|
|
40
|
+
/* 範囲選択(range:true)。間の日は淡色、両端は濃色。 */
|
|
41
|
+
.tss-cal-day.tss-cal-inrange { background: var(--cal-hover); border-radius: 0; }
|
|
42
|
+
.tss-cal-day.tss-cal-range-start,
|
|
43
|
+
.tss-cal-day.tss-cal-range-end { background: var(--cal-accent); color: #fff; }
|
|
44
|
+
.tss-cal-day.tss-cal-range-start { border-radius: 6px 0 0 6px; }
|
|
45
|
+
.tss-cal-day.tss-cal-range-end { border-radius: 0 6px 6px 0; }
|
|
46
|
+
.tss-cal-day.tss-cal-range-start.tss-cal-range-end { border-radius: 6px; } /* 開始=終端(1日だけ選択中) */
|
|
40
47
|
.tss-cal-day.tss-cal-disabled { color: var(--cal-muted); background: repeating-linear-gradient(45deg, transparent, transparent 4px, rgba(0,0,0,.04) 4px, rgba(0,0,0,.04) 8px); cursor: not-allowed; }
|
|
@@ -28,6 +28,17 @@ declare namespace TssCalendar {
|
|
|
28
28
|
disable?: Array<'holiday' | 'weekend' | string>;
|
|
29
29
|
/** 共有 input に乗る型(既定 true)=直打ち可・矢印は本体へ。false でフォーカス奪取型。 */
|
|
30
30
|
inline?: boolean;
|
|
31
|
+
/** 範囲選択(開始〜終了を1回の編集で)。`true` で全行有効/`(ctx)=>boolean` で行ごとに切替
|
|
32
|
+
* (例: 半休の行は範囲を選ばせない)。未指定は単日=従来動作。1クリック目=開始・2クリック目=終了
|
|
33
|
+
* (開始より前なら入替・同日2回は単日確定)。確定値・ツールチップは下記で差し替え可。休日スキップは利用側。 */
|
|
34
|
+
range?: boolean | ((ctx: any) => boolean);
|
|
35
|
+
/** 範囲値・ツールチップの日付区切り(既定 '~')。 */
|
|
36
|
+
rangeSeparator?: string;
|
|
37
|
+
/** 範囲の確定値を作る(既定 `'YYYY/MM/DD~YYYY/MM/DD'`/同日は `'YYYY/MM/DD'`)。
|
|
38
|
+
* 引数は開始・終了の ISO('YYYY-MM-DD'・開始<=終了に整列済み)。別フィールド分割などはこの口で。 */
|
|
39
|
+
rangeValue?: (startISO: string, endISO: string) => string;
|
|
40
|
+
/** 編集中セルに出す範囲ツールチップの文面(既定は範囲文字列)。休日を除いた日数などを外から足す口。 */
|
|
41
|
+
rangeTooltip?: (startISO: string, endISO: string) => string;
|
|
31
42
|
/** シングルクリックで開く(既定 true)。 */
|
|
32
43
|
openOnClick?: boolean;
|
|
33
44
|
/** セル右端の目印(既定 '📅'。'' で消す)。 */
|
package/plugins/tss-calendar.js
CHANGED
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
function parseISO(s) { var m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(s || '')); return m ? new Date(+m[1], +m[2] - 1, +m[3]) : null; }
|
|
18
18
|
function sameDay(a, b) { return !!a && !!b && a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate(); }
|
|
19
19
|
function esc(s) { return String(s).replace(/[&<>"]/g, function (c) { return { '&': '&', '<': '<', '>': '>', '"': '"' }[c]; }); }
|
|
20
|
+
function fmtSlash(isoStr) { return String(isoStr).replace(/-/g, '/'); } // 'YYYY-MM-DD' → 'YYYY/MM/DD'(range の既定表示)
|
|
21
|
+
function parseLoose(s) { var m = /^(\d{4})[/-](\d{1,2})[/-](\d{1,2})/.exec(String(s || '')); return m ? new Date(+m[1], +m[2] - 1, +m[3]) : null; } // slash/dash どちらも
|
|
20
22
|
|
|
21
23
|
function TssCalendar(opts) {
|
|
22
24
|
opts = opts || {};
|
|
@@ -35,6 +37,22 @@
|
|
|
35
37
|
var pop = null, view = null, sel = null, focus = null, ref = null, done = false, outside = null;
|
|
36
38
|
var inline = opts.inline !== false; // true=共有 input に乗る(直打ち可・矢印は本体に返す)/false=従来のフォーカス奪取型
|
|
37
39
|
|
|
40
|
+
// ── 範囲選択(range): 連続した日を1回の編集で選ぶ(休暇申請の開始〜終了 等)───────────
|
|
41
|
+
// range:true で有効(行ごとに切替えたい時は range:(ctx)=>boolean)。未指定は単日=従来動作。
|
|
42
|
+
// 1クリック目=開始、2クリック目=終了(開始より前なら入替・同日2回は単日確定)。間の日に色。
|
|
43
|
+
// 確定値は rangeValue(開始ISO,終了ISO)(既定 'YYYY/MM/DD~YYYY/MM/DD'・単日は 'YYYY/MM/DD')。
|
|
44
|
+
// 入力中はセルに range のツールチップ(rangeTooltip で文面差替=休日除く日数を外から足せる口)。
|
|
45
|
+
// 休日スキップは利用側(返すのは範囲だけ)。
|
|
46
|
+
var rangeOpt = opts.range;
|
|
47
|
+
var rangeSep = opts.rangeSeparator != null ? opts.rangeSeparator : '~';
|
|
48
|
+
var rangeValueFn = typeof opts.rangeValue === 'function' ? opts.rangeValue : null;
|
|
49
|
+
var rangeTooltipFn = typeof opts.rangeTooltip === 'function' ? opts.rangeTooltip : null;
|
|
50
|
+
function defRangeStr(aIso, bIso) { return aIso === bIso ? fmtSlash(aIso) : fmtSlash(aIso) + rangeSep + fmtSlash(bIso); }
|
|
51
|
+
function rangeValOf(aIso, bIso) { return (aIso !== bIso && rangeValueFn) ? rangeValueFn(aIso, bIso) : defRangeStr(aIso, bIso); }
|
|
52
|
+
function rangeTipOf(aIso, bIso) { return rangeTooltipFn ? rangeTooltipFn(aIso, bIso) : defRangeStr(aIso, bIso); }
|
|
53
|
+
var rc = null; // 現在の編集が range なら { td } を持つ(open(ctx) で ctx 判定して設定)
|
|
54
|
+
var rangeStart = null, hoverEnd = null; // 範囲選択の途中状態(1クリック目=rangeStart、プレビュー終端=hoverEnd)
|
|
55
|
+
|
|
38
56
|
function disabled(d) {
|
|
39
57
|
if (minD && d < minD) return true;
|
|
40
58
|
if (maxD && d > maxD) return true;
|
|
@@ -71,6 +89,12 @@
|
|
|
71
89
|
if (sameDay(d, today)) cls += ' tss-cal-today';
|
|
72
90
|
if (sameDay(d, sel)) cls += ' tss-cal-sel';
|
|
73
91
|
if (sameDay(d, focus)) cls += ' tss-cal-focus';
|
|
92
|
+
if (rc && rangeStart) { // 範囲の途中=開始〜(プレビュー)終端を塗る
|
|
93
|
+
var ra = rangeStart, rb = hoverEnd || rangeStart, lo = ra <= rb ? ra : rb, hi = ra <= rb ? rb : ra;
|
|
94
|
+
if (d >= lo && d <= hi) cls += ' tss-cal-inrange';
|
|
95
|
+
if (sameDay(d, ra)) cls += ' tss-cal-range-start';
|
|
96
|
+
if (sameDay(d, rb)) cls += ' tss-cal-range-end';
|
|
97
|
+
}
|
|
74
98
|
if (disabled(d)) cls += ' tss-cal-disabled';
|
|
75
99
|
html += '<div class="' + cls + '" data-d="' + k + '"' + (nm ? ' title="' + esc(nm) + '"' : '') + '>' + d.getDate() + '</div>';
|
|
76
100
|
d.setDate(d.getDate() + 1);
|
|
@@ -102,20 +126,40 @@
|
|
|
102
126
|
if (k === 'Escape') { e.preventDefault(); cancel(); return; }
|
|
103
127
|
if (k === 'Enter') { e.preventDefault(); if (!disabled(focus)) pick(focus); return; }
|
|
104
128
|
var dx = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 }[k];
|
|
105
|
-
if (dx != null) { e.preventDefault(); focus = new Date(focus); focus.setDate(focus.getDate() + dx); view = new Date(focus.getFullYear(), focus.getMonth(), 1); render(); return; }
|
|
129
|
+
if (dx != null) { e.preventDefault(); focus = new Date(focus); focus.setDate(focus.getDate() + dx); view = new Date(focus.getFullYear(), focus.getMonth(), 1); if (rc && rangeStart) { hoverEnd = new Date(focus); updateTip(); } render(); return; }
|
|
106
130
|
if (k === 'PageUp' || k === 'PageDown') { e.preventDefault(); view = new Date(view.getFullYear(), view.getMonth() + (k === 'PageUp' ? -1 : 1), 1); render(); }
|
|
107
131
|
}
|
|
108
|
-
|
|
132
|
+
// range 途中: 日の上をなぞると終端プレビューを更新(間の色+ツールチップ)。
|
|
133
|
+
function onOver(e) {
|
|
134
|
+
if (!(rc && rangeStart)) return;
|
|
135
|
+
var dd = e.target.getAttribute && e.target.getAttribute('data-d');
|
|
136
|
+
if (!dd) return;
|
|
137
|
+
var d = parseISO(dd); if (!d || disabled(d)) return;
|
|
138
|
+
if (!hoverEnd || !sameDay(hoverEnd, d)) { hoverEnd = d; render(); updateTip(); }
|
|
139
|
+
}
|
|
140
|
+
// 編集中セルに範囲ツールチップを出す(rangeTooltip で文面差替可)。
|
|
141
|
+
function updateTip() {
|
|
142
|
+
if (!(rc && rc.td)) return;
|
|
143
|
+
var aIso = iso(rangeStart), bIso = iso(hoverEnd || rangeStart);
|
|
144
|
+
if (bIso < aIso) { var t = aIso; aIso = bIso; bIso = t; }
|
|
145
|
+
try { rc.td.title = rangeTipOf(aIso, bIso); } catch (e) { /* noop */ }
|
|
146
|
+
}
|
|
147
|
+
function clearTip() { if (rc && rc.td) { try { rc.td.title = ''; } catch (e) { /* noop */ } } }
|
|
148
|
+
function show(anchor, value, onPick, onCancel, rangeCfg) {
|
|
109
149
|
teardown(); // 既に開いていれば先に片付ける(前の pop 除去+outside リスナ解除・open-while-open の取り残し防止)
|
|
110
|
-
done = false;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
150
|
+
done = false; rc = rangeCfg || null; rangeStart = null; hoverEnd = null;
|
|
151
|
+
sel = parseISO(value);
|
|
152
|
+
// range の既存値('YYYY/MM/DD~…' 等)は parseISO では取れない=先頭日を緩く読んで月を合わせる。
|
|
153
|
+
var initD = sel || (rc ? parseLoose(value) : null);
|
|
154
|
+
view = initD ? new Date(initD.getFullYear(), initD.getMonth(), 1) : new Date();
|
|
155
|
+
focus = initD ? new Date(initD) : new Date();
|
|
156
|
+
pop = document.createElement('div'); pop.className = 'tss-cal' + (opts.className ? ' ' + opts.className : '') + (rc ? ' tss-cal-range' : '');
|
|
114
157
|
pop.tabIndex = -1; document.body.appendChild(pop);
|
|
115
158
|
pop.addEventListener('mousedown', function (e) { if (e.target.tagName !== 'SELECT') e.preventDefault(); }); // フォーカスを奪わない(select は除く)
|
|
116
159
|
pop.addEventListener('click', onClick);
|
|
117
160
|
pop.addEventListener('change', onChange);
|
|
118
161
|
pop.addEventListener('keydown', onKey);
|
|
162
|
+
if (rc) pop.addEventListener('mouseover', onOver);
|
|
119
163
|
render(); place(anchor);
|
|
120
164
|
ref = { pick: onPick, cancel: onCancel };
|
|
121
165
|
// inline は「外側クリック/フォーカス」を本体(_commitActive / blur)が持つ=二重に閉じない・共有 input から
|
|
@@ -126,13 +170,26 @@
|
|
|
126
170
|
setTimeout(function () { document.addEventListener('mousedown', outside, true); }, 0);
|
|
127
171
|
pop.focus();
|
|
128
172
|
}
|
|
129
|
-
function teardown() { if (outside) { document.removeEventListener('mousedown', outside, true); outside = null; } if (pop) { pop.remove(); pop = null; } }
|
|
130
|
-
function pick(d) {
|
|
131
|
-
|
|
173
|
+
function teardown() { clearTip(); if (outside) { document.removeEventListener('mousedown', outside, true); outside = null; } if (pop) { pop.remove(); pop = null; } }
|
|
174
|
+
function pick(d) {
|
|
175
|
+
if (done) return;
|
|
176
|
+
if (rc) {
|
|
177
|
+
if (!rangeStart) { rangeStart = d; hoverEnd = d; focus = new Date(d); render(); updateTip(); return; } // 1クリック目=開始(閉じない)
|
|
178
|
+
var aIso = iso(rangeStart), bIso = iso(d); // 2クリック目=終了
|
|
179
|
+
if (bIso < aIso) { var t = aIso; aIso = bIso; bIso = t; } // 逆順は入替/同日は単日
|
|
180
|
+
done = true; var fr = ref.pick; teardown(); fr(rangeValOf(aIso, bIso)); return;
|
|
181
|
+
}
|
|
182
|
+
done = true; var f = ref.pick; teardown(); f(iso(d));
|
|
183
|
+
}
|
|
184
|
+
function cancel() { if (done) return; done = true; var f = ref.cancel; teardown(); if (f) f(); } // range 途中の取消も値は変えない(ref.cancel)
|
|
132
185
|
|
|
133
186
|
return {
|
|
134
187
|
// TssGrid カスタムエディタ契約
|
|
135
|
-
open: function (ctx) {
|
|
188
|
+
open: function (ctx) {
|
|
189
|
+
var rangeMode = typeof rangeOpt === 'function' ? !!rangeOpt(ctx) : !!rangeOpt; // 行ごとに range を切替(半休行は単日)
|
|
190
|
+
var cfg = rangeMode ? { td: ctx.td } : null;
|
|
191
|
+
show(ctx.td, ctx.value, function (v) { ctx.commit(v); }, function () { ctx.cancel(); }, cfg);
|
|
192
|
+
},
|
|
136
193
|
close: function () { teardown(); },
|
|
137
194
|
icon: opts.icon != null ? opts.icon : '📅', // セル右端に表示する合図(opts.icon:'' で消せる)
|
|
138
195
|
openOnClick: opts.openOnClick !== false, // シングルクリックで開く(既定 ON)
|