@threestarsoftware/tssgrid 0.1.29-beta.1 → 0.1.29-beta.3

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
@@ -7,6 +7,7 @@ TssGrid の各リリースの変更点。日付は JST。形式は [Keep a Chang
7
7
 
8
8
  ### 追加(プラグイン)
9
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.1",
3
+ "version": "0.1.29-beta.3",
4
4
  "description": "軽量・ノービルド・日本語IME堅牢な編集グリッド(vanilla JS / 依存なし / MIT)。Excel ライクな入力画面を <script> タグ1本で。",
5
5
  "license": "MIT",
6
6
  "author": "ThreeStarSoftware (https://www.threestarsoftware.co.jp/)",
@@ -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,10 +28,16 @@ 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回は単日確定)。確定値・ツールチップは下記で差し替え可。休日スキップは利用側。 */
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
43
  /** 範囲の確定値を作る。**同日(単日)のときも呼ばれる**。引数は開始・終了の ISO
@@ -38,12 +38,15 @@
38
38
  var inline = opts.inline !== false; // true=共有 input に乗る(直打ち可・矢印は本体に返す)/false=従来のフォーカス奪取型
39
39
 
40
40
  // ── 範囲選択(range): 連続した日を1回の編集で選ぶ(休暇申請の開始〜終了 等)───────────
41
- // range:true で有効(行ごとに切替えたい時は range:(ctx)=>boolean)。未指定は単日=従来動作。
42
- // 1クリック目=開始、2クリック目=終了(開始より前なら入替・同日2回は単日確定)。間の日に色。
43
- // 確定値は rangeValue(開始ISO,終了ISO)(既定: 範囲 'YYYY/MM/DD~YYYY/MM/DD'/単日は ISO 'YYYY-MM-DD'=範囲なしと同形。rangeValue は同日も呼ぶ)。
44
- // 入力中はセルに range のツールチップ(rangeTooltip で文面差替=休日除く日数を外から足せる口)。
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;
@@ -53,8 +56,11 @@
53
56
  return aIso === bIso ? aIso : defRangeStr(aIso, bIso); // 既定: 単日=ISO(範囲なしと同形)/範囲='YYYY/MM/DD~YYYY/MM/DD'
54
57
  }
55
58
  function rangeTipOf(aIso, bIso) { return rangeTooltipFn ? rangeTooltipFn(aIso, bIso) : (aIso === bIso ? fmtSlash(aIso) : defRangeStr(aIso, bIso)); }
56
- var rc = null; // 現在の編集が range なら { td } を持つ(open(ctx) で ctx 判定して設定)
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
57
62
  var rangeStart = null, hoverEnd = null; // 範囲選択の途中状態(1クリック目=rangeStart、プレビュー終端=hoverEnd)
63
+ function setRangeActive(on) { rangeActive = !!on; rangeStart = null; hoverEnd = null; clearTip(); render(); } // トグル切替=途中選択はリセット
58
64
 
59
65
  function disabled(d) {
60
66
  if (minD && d < minD) return true;
@@ -74,7 +80,12 @@
74
80
  var monSel = '<select class="tss-cal-sel-mon" aria-label="月">';
75
81
  for (var mm = 0; mm < 12; mm++) monSel += '<option value="' + mm + '"' + (mm === m ? ' selected' : '') + '>' + (mm + 1) + '月</option>';
76
82
  monSel += '</select>';
77
- var html = '<div class="tss-cal-head">'
83
+ // range が使える行はトグル(範囲で選ぶ)を上部に。OFF=単日1クリック/ON=2クリック範囲。
84
+ // ★<label>+ネイティブ checkbox だと click で input にフォーカスが移り、inline 時に共有 input が blur→
85
+ // 本体が commit+close してカレンダーが閉じてしまう。so 非フォーカスな span で包み、checked は render が持ち、
86
+ // 切替は pop の click(mousedown は preventDefault 済=フォーカスを奪わない)で処理する(onClick 参照)。
87
+ var toggle = rc ? '<span class="tss-cal-toggle" data-toggle="1"><input type="checkbox" class="tss-cal-range-cb" tabindex="-1"' + (rangeActive ? ' checked' : '') + '><span>' + esc(rangeToggleLabel) + '</span></span>' : '';
88
+ var html = toggle + '<div class="tss-cal-head">'
78
89
  + '<button type="button" class="tss-cal-nav" data-nav="-1" aria-label="前の月">‹</button>'
79
90
  + '<span class="tss-cal-sels">' + yrSel + monSel + '</span>'
80
91
  + '<button type="button" class="tss-cal-nav" data-nav="1" aria-label="次の月">›</button></div>'
@@ -92,7 +103,7 @@
92
103
  if (sameDay(d, today)) cls += ' tss-cal-today';
93
104
  if (sameDay(d, sel)) cls += ' tss-cal-sel';
94
105
  if (sameDay(d, focus)) cls += ' tss-cal-focus';
95
- if (rc && rangeStart) { // 範囲の途中=開始〜(プレビュー)終端を塗る
106
+ if (rc && rangeActive && rangeStart) { // 範囲の途中=開始〜(プレビュー)終端を塗る(ON のときだけ)
96
107
  var ra = rangeStart, rb = hoverEnd || rangeStart, lo = ra <= rb ? ra : rb, hi = ra <= rb ? rb : ra;
97
108
  if (d >= lo && d <= hi) cls += ' tss-cal-inrange';
98
109
  if (sameDay(d, ra)) cls += ' tss-cal-range-start';
@@ -112,6 +123,8 @@
112
123
  pop.style.top = top + 'px';
113
124
  }
114
125
  function onClick(e) {
126
+ // 「範囲で選ぶ」トグル: ネイティブ checkbox の toggle/フォーカスには頼らず click で反転(フォーカス不動=inline でも閉じない)。
127
+ if (e.target.closest && e.target.closest('.tss-cal-toggle')) { e.preventDefault(); setRangeActive(!rangeActive); return; }
115
128
  var nav = e.target.getAttribute && e.target.getAttribute('data-nav');
116
129
  if (nav) { view = new Date(view.getFullYear(), view.getMonth() + (+nav), 1); render(); return; }
117
130
  var dd = e.target.getAttribute && e.target.getAttribute('data-d');
@@ -129,12 +142,12 @@
129
142
  if (k === 'Escape') { e.preventDefault(); cancel(); return; }
130
143
  if (k === 'Enter') { e.preventDefault(); if (!disabled(focus)) pick(focus); return; }
131
144
  var dx = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 }[k];
132
- 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; }
145
+ 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; }
133
146
  if (k === 'PageUp' || k === 'PageDown') { e.preventDefault(); view = new Date(view.getFullYear(), view.getMonth() + (k === 'PageUp' ? -1 : 1), 1); render(); }
134
147
  }
135
148
  // range 途中: 日の上をなぞると終端プレビューを更新(間の色+ツールチップ)。
136
149
  function onOver(e) {
137
- if (!(rc && rangeStart)) return;
150
+ if (!(rc && rangeActive && rangeStart)) return;
138
151
  var dd = e.target.getAttribute && e.target.getAttribute('data-d');
139
152
  if (!dd) return;
140
153
  var d = parseISO(dd); if (!d || disabled(d)) return;
@@ -151,6 +164,7 @@
151
164
  function show(anchor, value, onPick, onCancel, rangeCfg) {
152
165
  teardown(); // 既に開いていれば先に片付ける(前の pop 除去+outside リスナ解除・open-while-open の取り残し防止)
153
166
  done = false; rc = rangeCfg || null; rangeStart = null; hoverEnd = null;
167
+ rangeActive = rc ? (isRangeValue(value) || !!rc.def) : false; // 既存値が範囲 or rangeDefault なら ON で開く
154
168
  sel = parseISO(value);
155
169
  // range の既存値('YYYY/MM/DD~…' 等)は parseISO では取れない=先頭日を緩く読んで月を合わせる。
156
170
  var initD = sel || (rc ? parseLoose(value) : null);
@@ -176,21 +190,22 @@
176
190
  function teardown() { clearTip(); if (outside) { document.removeEventListener('mousedown', outside, true); outside = null; } if (pop) { pop.remove(); pop = null; } }
177
191
  function pick(d) {
178
192
  if (done) return;
179
- if (rc) {
193
+ if (rc && rangeActive) { // トグル ON=2クリック範囲
180
194
  if (!rangeStart) { rangeStart = d; hoverEnd = d; focus = new Date(d); render(); updateTip(); return; } // 1クリック目=開始(閉じない)
181
195
  var aIso = iso(rangeStart), bIso = iso(d); // 2クリック目=終了
182
196
  if (bIso < aIso) { var t = aIso; aIso = bIso; bIso = t; } // 逆順は入替/同日は単日
183
197
  done = true; var fr = ref.pick; teardown(); fr(rangeValOf(aIso, bIso)); return;
184
198
  }
185
- done = true; var f = ref.pick; teardown(); f(iso(d));
199
+ done = true; var f = ref.pick; teardown(); f(iso(d)); // トグル OFF/range 無しの行=単日1クリック(ISO)
186
200
  }
187
201
  function cancel() { if (done) return; done = true; var f = ref.cancel; teardown(); if (f) f(); } // range 途中の取消も値は変えない(ref.cancel)
188
202
 
189
203
  return {
190
204
  // TssGrid カスタムエディタ契約
191
205
  open: function (ctx) {
192
- var rangeMode = typeof rangeOpt === 'function' ? !!rangeOpt(ctx) : !!rangeOpt; // 行ごとに range を切替(半休行は単日)
193
- var cfg = rangeMode ? { td: ctx.td } : null;
206
+ var rangeMode = typeof rangeOpt === 'function' ? !!rangeOpt(ctx) : !!rangeOpt; // この行で範囲を使えるか(半休行は false=トグル無し)
207
+ var def = typeof rangeDefault === 'function' ? !!rangeDefault(ctx) : !!rangeDefault; // トグル初期ON/OFF(行ごと可)
208
+ var cfg = rangeMode ? { td: ctx.td, def: def } : null;
194
209
  show(ctx.td, ctx.value, function (v) { ctx.commit(v); }, function () { ctx.cancel(); }, cfg);
195
210
  },
196
211
  close: function () { teardown(); },