@threestarsoftware/tssgrid 0.1.29-beta.0 → 0.1.29-beta.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 +2 -1
- package/package.json +1 -1
- package/plugins/tss-calendar.css +4 -0
- package/plugins/tss-calendar.d.ts +12 -5
- package/plugins/tss-calendar.js +31 -17
package/CHANGELOG.md
CHANGED
|
@@ -6,7 +6,8 @@ TssGrid の各リリースの変更点。日付は JST。形式は [Keep a Chang
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
8
|
### 追加(プラグイン)
|
|
9
|
-
- **`tss-calendar` に範囲選択 `range` を追加** — 連続した日(休暇申請の開始〜終了など)を**1回の編集で**選べる。`range:true` で有効、`range:(ctx)=>boolean` で**行ごとにON/OFF**(半休の行は範囲を選ばせない等)。未指定は単日=**従来動作**。1クリック目=開始・2クリック目=終了(**開始より前なら入替・同じ日を2回で単日確定**)、間の日に色が付く。1クリック目だけで閉じたら(Esc/外クリック)**値は変えない**。確定値は既定で `2026/10/13~2026/10/16
|
|
9
|
+
- **`tss-calendar` に範囲選択 `range` を追加** — 連続した日(休暇申請の開始〜終了など)を**1回の編集で**選べる。`range:true` で有効、`range:(ctx)=>boolean` で**行ごとにON/OFF**(半休の行は範囲を選ばせない等)。未指定は単日=**従来動作**。1クリック目=開始・2クリック目=終了(**開始より前なら入替・同じ日を2回で単日確定**)、間の日に色が付く。1クリック目だけで閉じたら(Esc/外クリック)**値は変えない**。確定値は既定で `2026/10/13~2026/10/16`(**単日は ISO `2026-10-13`=範囲なしの単日と同じ形**で保存値が混ざらない/区切りは `rangeSeparator`・既定 '~'/`rangeValue(startISO,endISO)` で完全差替可・**同日も呼ばれる**)。**入力中のセルに範囲ツールチップ**を出し、`rangeTooltip(startISO,endISO)` で文面を差し替え可能(休日を除いた日数などを外から足す口)。**休日スキップは利用側**=カレンダーは範囲を返すだけ。範囲列は `type:'date'` を付けない素の列にする(範囲文字列を日付パーサに通さないため)。例 `examples/calendar-range.html`。
|
|
10
|
+
- **`tss-calendar` 範囲に「範囲で選ぶ」トグルを追加** — `range` が有効な行のカレンダー上部にトグル(チェックボックス)を出し、**OFF=従来の単日**(1クリック確定・値 ISO・ツールチップ無し)/**ON=範囲**(2クリック・間の色・ツールチップ)を編集中に切替可能。休暇申請はほとんどが1日なので**既定 OFF**(1クリックで決まる)。初期状態は `rangeDefault`(`true`/`(ctx)=>boolean`・既定 `false`)で、**どちらでもセル値が既に範囲なら ON で開く**。`range:(ctx)=>false` の行はトグルを出さない。文言は `rangeToggleLabel`(既定 `'範囲で選ぶ'`)。
|
|
10
11
|
|
|
11
12
|
## [0.1.28] - 2026-10-20
|
|
12
13
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@threestarsoftware/tssgrid",
|
|
3
|
-
"version": "0.1.29-beta.
|
|
3
|
+
"version": "0.1.29-beta.2",
|
|
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
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
border: 1px solid var(--cal-border); border-radius: var(--cal-radius);
|
|
19
19
|
box-shadow: 0 6px 24px rgba(0,0,0,.18); padding: 8px; font: var(--cal-font); user-select: none; outline: none;
|
|
20
20
|
}
|
|
21
|
+
/* 範囲選択トグル(range 有効行のみ上部に表示)。 */
|
|
22
|
+
.tss-cal-toggle { display: flex; align-items: center; gap: 6px; margin: 0 2px 6px; padding: 3px 4px; font-size: 12px; color: var(--cal-fg); cursor: pointer; border-bottom: 1px solid var(--cal-border); user-select: none; }
|
|
23
|
+
.tss-cal-toggle input { cursor: pointer; margin: 0; }
|
|
24
|
+
.tss-cal-toggle:hover { color: var(--cal-accent); }
|
|
21
25
|
.tss-cal-head { display: flex; align-items: center; justify-content: space-between; margin: 2px 2px 6px; }
|
|
22
26
|
.tss-cal-title { font-weight: 600; }
|
|
23
27
|
.tss-cal-sels { display: flex; gap: 4px; }
|
|
@@ -28,14 +28,21 @@ declare namespace TssCalendar {
|
|
|
28
28
|
disable?: Array<'holiday' | 'weekend' | string>;
|
|
29
29
|
/** 共有 input に乗る型(既定 true)=直打ち可・矢印は本体へ。false でフォーカス奪取型。 */
|
|
30
30
|
inline?: boolean;
|
|
31
|
-
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
31
|
+
/** 範囲選択を**使える行**にする。`true` で全行/`(ctx)=>boolean` で行ごと(例: 半休の行は `false`=単日のみ)。
|
|
32
|
+
* 有効な行では上部に「範囲で選ぶ」トグルが出る: **OFF=従来の単日**(1クリック確定・値 ISO・ツールチップ無し)/
|
|
33
|
+
* **ON=範囲**(1クリック目=開始・2クリック目=終了・開始より前なら入替・同日2回は単日・間の色・ツールチップ)。
|
|
34
|
+
* 未指定は常に単日=従来動作。初期ON/OFFは `rangeDefault`、確定値・ツールチップ・文言は下記で差替可。休日スキップは利用側。 */
|
|
34
35
|
range?: boolean | ((ctx: any) => boolean);
|
|
36
|
+
/** 「範囲で選ぶ」トグルの初期状態(`true`=ON/`(ctx)=>boolean` で行ごと・既定 `false`=OFF)。
|
|
37
|
+
* いずれの場合も **セルの値が既に範囲なら ON で開く**。`range` が無効な行では無視(トグル自体が出ない)。 */
|
|
38
|
+
rangeDefault?: boolean | ((ctx: any) => boolean);
|
|
39
|
+
/** トグルの文言(既定 `'範囲で選ぶ'`)。 */
|
|
40
|
+
rangeToggleLabel?: string;
|
|
35
41
|
/** 範囲値・ツールチップの日付区切り(既定 '~')。 */
|
|
36
42
|
rangeSeparator?: string;
|
|
37
|
-
/**
|
|
38
|
-
*
|
|
43
|
+
/** 範囲の確定値を作る。**同日(単日)のときも呼ばれる**。引数は開始・終了の ISO
|
|
44
|
+
* ('YYYY-MM-DD'・開始<=終了に整列済み)。未指定の既定は 範囲=`'YYYY/MM/DD~YYYY/MM/DD'`/
|
|
45
|
+
* **単日=ISO `'YYYY-MM-DD'`(範囲なしの単日と同じ形)**。別フィールド分割などはこの口で。 */
|
|
39
46
|
rangeValue?: (startISO: string, endISO: string) => string;
|
|
40
47
|
/** 編集中セルに出す範囲ツールチップの文面(既定は範囲文字列)。休日を除いた日数などを外から足す口。 */
|
|
41
48
|
rangeTooltip?: (startISO: string, endISO: string) => string;
|
package/plugins/tss-calendar.js
CHANGED
|
@@ -38,20 +38,29 @@
|
|
|
38
38
|
var inline = opts.inline !== false; // true=共有 input に乗る(直打ち可・矢印は本体に返す)/false=従来のフォーカス奪取型
|
|
39
39
|
|
|
40
40
|
// ── 範囲選択(range): 連続した日を1回の編集で選ぶ(休暇申請の開始〜終了 等)───────────
|
|
41
|
-
// range:true
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
41
|
+
// range:true で「範囲も選べる」行になる(行ごとに切替えたい時は range:(ctx)=>boolean)。未指定は常に単日=従来動作。
|
|
42
|
+
// range が有効な行では、カレンダー上部に「範囲で選ぶ」トグルが出る(文言は rangeToggleLabel):
|
|
43
|
+
// OFF=従来の単日(1クリックで確定・値は ISO・ツールチップ無し)/ON=2クリック(開始→終了・間の色・ツールチップ)。
|
|
44
|
+
// トグルの初期状態は rangeDefault(true/関数(ctx)=>boolean・既定 OFF)。ただし **セル値が既に範囲なら ON で開く**。
|
|
45
|
+
// ON 時の確定値は rangeValue(開始ISO,終了ISO)(既定: 範囲 'YYYY/MM/DD~YYYY/MM/DD'/同日は ISO 'YYYY-MM-DD'。rangeValue は同日も呼ぶ)。
|
|
45
46
|
// 休日スキップは利用側(返すのは範囲だけ)。
|
|
46
47
|
var rangeOpt = opts.range;
|
|
48
|
+
var rangeDefault = opts.rangeDefault; // トグルの初期ON/OFF(bool or (ctx)=>bool・既定 false=OFF)
|
|
49
|
+
var rangeToggleLabel = opts.rangeToggleLabel != null ? opts.rangeToggleLabel : '範囲で選ぶ';
|
|
47
50
|
var rangeSep = opts.rangeSeparator != null ? opts.rangeSeparator : '~';
|
|
48
51
|
var rangeValueFn = typeof opts.rangeValue === 'function' ? opts.rangeValue : null;
|
|
49
52
|
var rangeTooltipFn = typeof opts.rangeTooltip === 'function' ? opts.rangeTooltip : null;
|
|
50
|
-
function defRangeStr(aIso, bIso) { return
|
|
51
|
-
function rangeValOf(aIso, bIso) {
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
function defRangeStr(aIso, bIso) { return fmtSlash(aIso) + rangeSep + fmtSlash(bIso); } // 表示用(両端 slash・範囲)
|
|
54
|
+
function rangeValOf(aIso, bIso) {
|
|
55
|
+
if (rangeValueFn) return rangeValueFn(aIso, bIso); // rangeValue は同日も必ず呼ぶ(利用側で形を決められる)
|
|
56
|
+
return aIso === bIso ? aIso : defRangeStr(aIso, bIso); // 既定: 単日=ISO(範囲なしと同形)/範囲='YYYY/MM/DD~YYYY/MM/DD'
|
|
57
|
+
}
|
|
58
|
+
function rangeTipOf(aIso, bIso) { return rangeTooltipFn ? rangeTooltipFn(aIso, bIso) : (aIso === bIso ? fmtSlash(aIso) : defRangeStr(aIso, bIso)); }
|
|
59
|
+
function isRangeValue(v) { v = String(v == null ? '' : v); return !!rangeSep && v.indexOf(rangeSep) >= 0; } // 既定書式の範囲値か(区切りを含む)
|
|
60
|
+
var rc = null; // この行で範囲が使えるなら { td, def } を持つ=トグルを出す(open(ctx) で ctx 判定)
|
|
61
|
+
var rangeActive = false; // トグルの現在値(ON=2クリック範囲/OFF=単日)。rc 無しの行は常に false
|
|
54
62
|
var rangeStart = null, hoverEnd = null; // 範囲選択の途中状態(1クリック目=rangeStart、プレビュー終端=hoverEnd)
|
|
63
|
+
function setRangeActive(on) { rangeActive = !!on; rangeStart = null; hoverEnd = null; clearTip(); render(); } // トグル切替=途中選択はリセット
|
|
55
64
|
|
|
56
65
|
function disabled(d) {
|
|
57
66
|
if (minD && d < minD) return true;
|
|
@@ -71,7 +80,9 @@
|
|
|
71
80
|
var monSel = '<select class="tss-cal-sel-mon" aria-label="月">';
|
|
72
81
|
for (var mm = 0; mm < 12; mm++) monSel += '<option value="' + mm + '"' + (mm === m ? ' selected' : '') + '>' + (mm + 1) + '月</option>';
|
|
73
82
|
monSel += '</select>';
|
|
74
|
-
|
|
83
|
+
// range が使える行はトグル(範囲で選ぶ)を上部に。OFF=単日1クリック/ON=2クリック範囲。
|
|
84
|
+
var toggle = rc ? '<label class="tss-cal-toggle"><input type="checkbox" class="tss-cal-range-cb"' + (rangeActive ? ' checked' : '') + '><span>' + esc(rangeToggleLabel) + '</span></label>' : '';
|
|
85
|
+
var html = toggle + '<div class="tss-cal-head">'
|
|
75
86
|
+ '<button type="button" class="tss-cal-nav" data-nav="-1" aria-label="前の月">‹</button>'
|
|
76
87
|
+ '<span class="tss-cal-sels">' + yrSel + monSel + '</span>'
|
|
77
88
|
+ '<button type="button" class="tss-cal-nav" data-nav="1" aria-label="次の月">›</button></div>'
|
|
@@ -89,7 +100,7 @@
|
|
|
89
100
|
if (sameDay(d, today)) cls += ' tss-cal-today';
|
|
90
101
|
if (sameDay(d, sel)) cls += ' tss-cal-sel';
|
|
91
102
|
if (sameDay(d, focus)) cls += ' tss-cal-focus';
|
|
92
|
-
if (rc && rangeStart) {
|
|
103
|
+
if (rc && rangeActive && rangeStart) { // 範囲の途中=開始〜(プレビュー)終端を塗る(ON のときだけ)
|
|
93
104
|
var ra = rangeStart, rb = hoverEnd || rangeStart, lo = ra <= rb ? ra : rb, hi = ra <= rb ? rb : ra;
|
|
94
105
|
if (d >= lo && d <= hi) cls += ' tss-cal-inrange';
|
|
95
106
|
if (sameDay(d, ra)) cls += ' tss-cal-range-start';
|
|
@@ -116,7 +127,8 @@
|
|
|
116
127
|
}
|
|
117
128
|
function onChange(e) {
|
|
118
129
|
var t = e.target;
|
|
119
|
-
if (t.classList && t.classList.contains('tss-cal-
|
|
130
|
+
if (t.classList && t.classList.contains('tss-cal-range-cb')) { setRangeActive(t.checked); } // 「範囲で選ぶ」トグル
|
|
131
|
+
else if (t.classList && t.classList.contains('tss-cal-sel-yr')) { view = new Date(+t.value, view.getMonth(), 1); render(); }
|
|
120
132
|
else if (t.classList && t.classList.contains('tss-cal-sel-mon')) { view = new Date(view.getFullYear(), +t.value, 1); render(); }
|
|
121
133
|
}
|
|
122
134
|
function onKey(e) {
|
|
@@ -126,12 +138,12 @@
|
|
|
126
138
|
if (k === 'Escape') { e.preventDefault(); cancel(); return; }
|
|
127
139
|
if (k === 'Enter') { e.preventDefault(); if (!disabled(focus)) pick(focus); return; }
|
|
128
140
|
var dx = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 }[k];
|
|
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; }
|
|
141
|
+
if (dx != null) { e.preventDefault(); focus = new Date(focus); focus.setDate(focus.getDate() + dx); view = new Date(focus.getFullYear(), focus.getMonth(), 1); if (rc && rangeActive && rangeStart) { hoverEnd = new Date(focus); updateTip(); } render(); return; }
|
|
130
142
|
if (k === 'PageUp' || k === 'PageDown') { e.preventDefault(); view = new Date(view.getFullYear(), view.getMonth() + (k === 'PageUp' ? -1 : 1), 1); render(); }
|
|
131
143
|
}
|
|
132
144
|
// range 途中: 日の上をなぞると終端プレビューを更新(間の色+ツールチップ)。
|
|
133
145
|
function onOver(e) {
|
|
134
|
-
if (!(rc && rangeStart)) return;
|
|
146
|
+
if (!(rc && rangeActive && rangeStart)) return;
|
|
135
147
|
var dd = e.target.getAttribute && e.target.getAttribute('data-d');
|
|
136
148
|
if (!dd) return;
|
|
137
149
|
var d = parseISO(dd); if (!d || disabled(d)) return;
|
|
@@ -148,6 +160,7 @@
|
|
|
148
160
|
function show(anchor, value, onPick, onCancel, rangeCfg) {
|
|
149
161
|
teardown(); // 既に開いていれば先に片付ける(前の pop 除去+outside リスナ解除・open-while-open の取り残し防止)
|
|
150
162
|
done = false; rc = rangeCfg || null; rangeStart = null; hoverEnd = null;
|
|
163
|
+
rangeActive = rc ? (isRangeValue(value) || !!rc.def) : false; // 既存値が範囲 or rangeDefault なら ON で開く
|
|
151
164
|
sel = parseISO(value);
|
|
152
165
|
// range の既存値('YYYY/MM/DD~…' 等)は parseISO では取れない=先頭日を緩く読んで月を合わせる。
|
|
153
166
|
var initD = sel || (rc ? parseLoose(value) : null);
|
|
@@ -173,21 +186,22 @@
|
|
|
173
186
|
function teardown() { clearTip(); if (outside) { document.removeEventListener('mousedown', outside, true); outside = null; } if (pop) { pop.remove(); pop = null; } }
|
|
174
187
|
function pick(d) {
|
|
175
188
|
if (done) return;
|
|
176
|
-
if (rc) {
|
|
189
|
+
if (rc && rangeActive) { // トグル ON=2クリック範囲
|
|
177
190
|
if (!rangeStart) { rangeStart = d; hoverEnd = d; focus = new Date(d); render(); updateTip(); return; } // 1クリック目=開始(閉じない)
|
|
178
191
|
var aIso = iso(rangeStart), bIso = iso(d); // 2クリック目=終了
|
|
179
192
|
if (bIso < aIso) { var t = aIso; aIso = bIso; bIso = t; } // 逆順は入替/同日は単日
|
|
180
193
|
done = true; var fr = ref.pick; teardown(); fr(rangeValOf(aIso, bIso)); return;
|
|
181
194
|
}
|
|
182
|
-
done = true; var f = ref.pick; teardown(); f(iso(d));
|
|
195
|
+
done = true; var f = ref.pick; teardown(); f(iso(d)); // トグル OFF/range 無しの行=単日1クリック(ISO)
|
|
183
196
|
}
|
|
184
197
|
function cancel() { if (done) return; done = true; var f = ref.cancel; teardown(); if (f) f(); } // range 途中の取消も値は変えない(ref.cancel)
|
|
185
198
|
|
|
186
199
|
return {
|
|
187
200
|
// TssGrid カスタムエディタ契約
|
|
188
201
|
open: function (ctx) {
|
|
189
|
-
var rangeMode = typeof rangeOpt === 'function' ? !!rangeOpt(ctx) : !!rangeOpt; //
|
|
190
|
-
var
|
|
202
|
+
var rangeMode = typeof rangeOpt === 'function' ? !!rangeOpt(ctx) : !!rangeOpt; // この行で範囲を使えるか(半休行は false=トグル無し)
|
|
203
|
+
var def = typeof rangeDefault === 'function' ? !!rangeDefault(ctx) : !!rangeDefault; // トグル初期ON/OFF(行ごと可)
|
|
204
|
+
var cfg = rangeMode ? { td: ctx.td, def: def } : null;
|
|
191
205
|
show(ctx.td, ctx.value, function (v) { ctx.commit(v); }, function () { ctx.cancel(); }, cfg);
|
|
192
206
|
},
|
|
193
207
|
close: function () { teardown(); },
|