@threestarsoftware/tssgrid 0.1.34 → 0.1.35

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,14 @@
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.35] - 2026-11-06
7
+
8
+ ### 追加(プラグイン)
9
+ - **`tss-sort`(見出しクリックで並べ替えUI)を追加**。`tss-filter` と同じ形のプラグインで、各リーフ見出しに **▲▼** を出し、押すたび **昇順 ▲ → 降順 ▼ → 元の順(`clearSort`)** を1列で循環。中身はコアの `sortBy` / `clearSort` を呼ぶだけ(多段は従来どおり `sortByCols` を直接)。`onHeaderClick` / `onHeaderRender` を**連鎖**(既存指定を保ちつつ上乗せ)。`columns`(対象列限定・既定=全リーフ列)/`initial:{col,dir}`(初期の並び)/`iconOnly`(▲▼ の所だけで並べ替え・本体クリックは純粋に列選択)。**本体クリックの列選択は従来どおり**活き、**空行(`minSpareRows`)はコア仕様どおり常に最下部**。`tss-filter` と併用しても ▲▼(ソート)と漏斗(フィルタ)は並ぶだけで**ぶつからない**。API: `getPlugin('sort').getState()`/`.sortBy(col,dir)`/`.clear()`。例 `examples/sort-plugin.html`。従来の手組み例(`examples/sort.html`・`multisort.html`・`sort-filter.html`)はそのまま有効。
10
+
11
+ ### 修正(プラグイン)
12
+ - **`tss-toggle` が関数の `readOnly` を見ていなかった不具合を修正**。列の `readOnly` が関数(`(val,{r})=>boolean`)だと、読取専用のセルでもセグメントを押せて値が変わってしまっていた(`cfg.readOnly === true` だけを見ていたため)。本体と同じ **`_isReadOnly(r, c)`** で判定するようにし、**関数 `readOnly`・セル単位RO・結合の従属・`grid.readOnly`** をすべて尊重。jimu で「直せないときはプラグインを付けない」でしのいでいた回避が不要に。
13
+
6
14
  ## [0.1.34] - 2026-11-05
7
15
 
8
16
  ### 改善(プラグイン)
package/README.md CHANGED
@@ -382,6 +382,24 @@ grid.getPlugin('filter').clearAll(); // 全条件クリア
382
382
  ```
383
383
  - 値候補は**マスタ全行から集計**(絞り込み中でも全値が出る)。フィルタ済み列に印。値は HTML エスケープ。
384
384
 
385
+ ### 同梱プラグイン: 見出しクリックで並べ替え(sort UI)
386
+
387
+ `plugins/tss-sort.js`+`.css`= 各見出しに **▲▼** を出し、押すたび **昇順 ▲ → 降順 ▼ → 元の順** を1列で循環。中身はコアの **`sortBy` / `clearSort` を呼ぶだけ**。**列選択は今までどおり**効き、**空行(`minSpareRows`)は常に最下部**。`tss-filter` と併用しても ▲▼(ソート)と漏斗(フィルタ)は**並ぶだけでぶつからない**。動く例: [`examples/sort-plugin.html`](https://tssgrid.threestarsoftware.co.jp/examples/sort-plugin.html)。
388
+
389
+ ```html
390
+ <link rel="stylesheet" href="plugins/tss-sort.css">
391
+ <script src="plugins/tss-sort.js"></script>
392
+ ```
393
+ ```js
394
+ plugins: [ TssSort.plugin() ] // 全リーフ列に ▲▼
395
+ plugins: [ TssSort.plugin({ columns:['createdAt', 'workDate'] }) ] // 対象列を限定
396
+ plugins: [ TssSort.plugin({ initial:{ col:'createdAt', dir:'desc' } }) ] // 初期の並び
397
+ plugins: [ TssSort.plugin({ iconOnly:true }) ] // ▲▼ の所だけ(本体クリックは純粋に選択)
398
+ grid.getPlugin('sort').getState(); // { col, dir } または null
399
+ grid.getPlugin('sort').clear(); // 入力順へ戻す
400
+ ```
401
+ - 単一列ソート(多段は `grid.sortByCols(specs)` を直接)。`onHeaderClick` / `onHeaderRender` フックを**連鎖**して使う(既存指定を保ちつつ上乗せ)。`sortBy`→再描画→`onHeaderRender` 再実行で ▲▼ の点灯も自動更新。
402
+
385
403
  ### 同梱プラグイン: 共有マスタ(複数グリッドで1データ)
386
404
 
387
405
  `plugins/tss-shared.js`= **1つのマスタ(同じ行データ)を複数グリッドで共有**。左右を**別フィルタ/別ソート**で表示でき、**片方の編集がもう片方の同じ行へ即反映**(行参照を共有)。A系/B系を並べて突き合わせ編集する用途。動く例: [`examples/shared-master.html`](https://tssgrid.threestarsoftware.co.jp/examples/shared-master.html)。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@threestarsoftware/tssgrid",
3
- "version": "0.1.34",
3
+ "version": "0.1.35",
4
4
  "description": "軽量・ノービルド・日本語IME堅牢な編集グリッド(vanilla JS / 依存なし / MIT)。Excel ライクな入力画面を <script> タグ1本で。",
5
5
  "license": "MIT",
6
6
  "author": "ThreeStarSoftware (https://www.threestarsoftware.co.jp/)",
@@ -0,0 +1,21 @@
1
+ /* TssSort — ヘッダの並べ替えUI(plugins/tss-sort.js)。▲▼ は小さな三角を縦2段。色は --tg-accent を流用。
2
+ 既定は薄いグレー(常時表示=出入りのピコピコ無し)、点灯中の向きだけアクセント色。漏斗(filter)の右に並ぶ。 */
3
+ .tssgrid .tg-table thead th .tg-sort-ind {
4
+ display: inline-flex; flex-direction: column; justify-content: center; gap: 2px;
5
+ margin-left: 5px; vertical-align: middle; line-height: 0; user-select: none; cursor: pointer;
6
+ }
7
+ .tssgrid .tg-table thead th .tg-sort-ind .tg-sort-up,
8
+ .tssgrid .tg-table thead th .tg-sort-ind .tg-sort-dn {
9
+ width: 0; height: 0; border-left: 4px solid transparent; border-right: 4px solid transparent;
10
+ }
11
+ .tssgrid .tg-table thead th .tg-sort-ind .tg-sort-up { border-bottom: 5px solid var(--tg-sort-dim, #9aa6b4); }
12
+ .tssgrid .tg-table thead th .tg-sort-ind .tg-sort-dn { border-top: 5px solid var(--tg-sort-dim, #9aa6b4); }
13
+ .tssgrid .tg-table thead th .tg-sort-ind:hover .tg-sort-up { border-bottom-color: #cdd6e2; }
14
+ .tssgrid .tg-table thead th .tg-sort-ind:hover .tg-sort-dn { border-top-color: #cdd6e2; }
15
+ /* 点灯:確定した向きだけアクセント色、反対側は薄く落とす */
16
+ .tssgrid .tg-table thead th .tg-sort-ind.asc .tg-sort-up { border-bottom-color: var(--tg-sort-accent, var(--tg-accent, #2f6fed)); }
17
+ .tssgrid .tg-table thead th .tg-sort-ind.desc .tg-sort-dn { border-top-color: var(--tg-sort-accent, var(--tg-accent, #2f6fed)); }
18
+ .tssgrid .tg-table thead th .tg-sort-ind.asc .tg-sort-dn,
19
+ .tssgrid .tg-table thead th .tg-sort-ind.desc .tg-sort-up { opacity: .35; }
20
+ /* 本体クリックでも並べ替える既定モードでは見出し全体にカーソルを出す(iconOnly は ▲▼ だけ) */
21
+ .tssgrid:not(.tg-sort-icononly) .tg-table thead th.tg-sortable { cursor: pointer; }
@@ -0,0 +1,105 @@
1
+ /*! TssSort — ヘッダクリックで並べ替えUI。コアの sortBy / clearSort を呼ぶだけ。
2
+ *
3
+ * 各リーフ見出しに ▲▼ インジケータを出し、クリックで「昇順 → 降順 → 元の順(clearSort)」を1列で循環。
4
+ * 値の並べ替え自体は grid.sortBy(c,'asc'|'desc') / grid.clearSort()(コア機能)。空行(minSpareRows)は
5
+ * コア仕様どおり常に最下部のまま。本体クリックの列選択は従来どおり活きる(選択を奪わない)。
6
+ * tss-filter と同時に付けても、▲▼(ソート)と漏斗(フィルタ)は別アイコンで並ぶ=ぶつからない。
7
+ *
8
+ * 使い方:
9
+ * const grid = new TssGrid(el, { plugins: [ TssSort.plugin() ] }); // 全リーフ列に ▲▼
10
+ * TssSort.plugin({ columns: ['createdAt', 'workDate'] }); // 対象列を限定(data キー or index)
11
+ * TssSort.plugin({ initial: { col: 'createdAt', dir: 'desc' } }); // 初期の並び
12
+ * TssSort.plugin({ iconOnly: true }); // ▲▼ の所だけで並べ替え(本体クリックは純粋に選択)
13
+ * grid.getPlugin('sort').clear(); // 並べ替え解除(入力順へ)
14
+ * grid.getPlugin('sort').getState(); // { col, dir } または null
15
+ *
16
+ * opts: columns(対象列の配列・省略=全リーフ列) / initial({col, dir:'asc'|'desc'}) / iconOnly(既定 false)。
17
+ * メモ: 単一列ソート(多段は grid.sortByCols を直接)。onHeaderClick / onHeaderRender フックを「連鎖」して使う
18
+ * (既存の指定があれば保ちつつ上乗せ)。sortBy→再描画→onHeaderRender 再実行で ▲▼ の点灯も自動更新。
19
+ */
20
+ (function (root) {
21
+ 'use strict';
22
+
23
+ function plugin(opts) {
24
+ opts = opts || {};
25
+ const iconOnly = !!opts.iconOnly;
26
+ return function (grid) {
27
+ const cur = { col: -1, dir: null }; // dir: 'asc' | 'desc' | null(唯一の状態)
28
+
29
+ // 対象列 index の集合(省略時は全リーフ列)
30
+ const targets = (() => {
31
+ if (!opts.columns) return null; // null = 全列
32
+ const s = new Set();
33
+ opts.columns.forEach(k => { const ci = grid._resolveCol ? grid._resolveCol(k) : (typeof k === 'number' ? k : -1); if (ci >= 0) s.add(ci); });
34
+ return s;
35
+ })();
36
+ const isTarget = c => !targets || targets.has(c);
37
+
38
+ // 昇順 → 降順 → 元の順(clearSort)を1列で循環。
39
+ function cycle(c) {
40
+ if (cur.col !== c) { cur.col = c; cur.dir = 'asc'; grid.sortBy(c, 'asc'); }
41
+ else if (cur.dir === 'asc') { cur.dir = 'desc'; grid.sortBy(c, 'desc'); }
42
+ else { cur.col = -1; cur.dir = null; if (!grid.clearSort()) grid.redraw(); }
43
+ }
44
+
45
+ // ---- クリック(onHeaderClick を連鎖)----
46
+ const prevClick = grid.onHeaderClick;
47
+ grid.onHeaderClick = function (c, e) {
48
+ // ソート対象で、漏斗(filter)以外のクリック。iconOnly なら ▲▼ の上だけ。
49
+ const onFunnel = e && e.target && e.target.closest && e.target.closest('.tg-filter-btn');
50
+ const onInd = e && e.target && e.target.closest && e.target.closest('.tg-sort-ind');
51
+ if (isTarget(c) && !onFunnel && (!iconOnly || onInd)) cycle(c);
52
+ // 既存の onHeaderClick を尊重(false なら列選択を抑止)。ソート自身は選択を奪わない。
53
+ if (prevClick) { let r; try { r = prevClick.call(grid, c, e); } catch (_) {} if (r === false) return false; }
54
+ return undefined; // 列選択は今までどおり通す
55
+ };
56
+
57
+ // ---- ▲▼ インジケータ(onHeaderRender を連鎖。再描画毎に自動で付け直し)----
58
+ const prevRender = grid.onHeaderRender;
59
+ grid.onHeaderRender = function (th, c) {
60
+ if (prevRender) { try { prevRender.call(grid, th, c); } catch (_) {} }
61
+ if (!isTarget(c)) return;
62
+ th.classList.add('tg-sortable');
63
+ const dir = (c === cur.col) ? cur.dir : null;
64
+ const ind = document.createElement('span');
65
+ ind.className = 'tg-sort-ind' + (dir ? ' ' + dir : '');
66
+ ind.innerHTML = '<span class="tg-sort-up"></span><span class="tg-sort-dn"></span>';
67
+ th.appendChild(ind);
68
+ };
69
+ grid.root.classList.add('tg-has-sort');
70
+ if (iconOnly) grid.root.classList.add('tg-sort-icononly');
71
+
72
+ // 初期の並び
73
+ if (opts.initial && opts.initial.col != null) {
74
+ const ci = grid._resolveCol ? grid._resolveCol(opts.initial.col) : (typeof opts.initial.col === 'number' ? opts.initial.col : -1);
75
+ if (ci >= 0 && isTarget(ci)) { cur.col = ci; cur.dir = opts.initial.dir === 'desc' ? 'desc' : 'asc'; grid.sortBy(ci, cur.dir); }
76
+ }
77
+ grid.buildTable(); // インジケータを反映(onHeaderRender 実行)
78
+
79
+ return {
80
+ name: 'sort',
81
+ // 現在の並び { col:(dataキー or index), dir } または null
82
+ getState: () => (cur.col >= 0 ? { col: (grid.columns[cur.col] && grid.columns[cur.col].data != null ? grid.columns[cur.col].data : cur.col), dir: cur.dir } : null),
83
+ // プログラムから並べ替え(UIの点灯も同期)
84
+ sortBy: (c, dir) => {
85
+ const ci = grid._resolveCol ? grid._resolveCol(c) : (typeof c === 'number' ? c : -1);
86
+ if (ci < 0 || !isTarget(ci)) return;
87
+ cur.col = ci; cur.dir = dir === 'desc' ? 'desc' : 'asc'; grid.sortBy(ci, cur.dir);
88
+ },
89
+ clear: () => { cur.col = -1; cur.dir = null; if (!grid.clearSort()) grid.redraw(); },
90
+ destroy: function () {
91
+ grid.onHeaderClick = prevClick || null;
92
+ grid.onHeaderRender = prevRender || null;
93
+ grid.root.classList.remove('tg-has-sort', 'tg-sort-icononly');
94
+ if (grid.table) grid.table.querySelectorAll('.tg-sort-ind').forEach(el => el.remove());
95
+ if (grid.table) grid.table.querySelectorAll('th.tg-sortable').forEach(th => th.classList.remove('tg-sortable'));
96
+ grid.buildTable();
97
+ },
98
+ };
99
+ };
100
+ }
101
+
102
+ const TssSort = { plugin: plugin };
103
+ if (typeof module !== 'undefined' && module.exports) module.exports = TssSort;
104
+ else root.TssSort = TssSort;
105
+ })(typeof self !== 'undefined' ? self : this);
@@ -55,7 +55,8 @@
55
55
  if (byCol[c]) return byCol[c];
56
56
  return null; // 設定外の列は対象外
57
57
  }
58
- function editable(r, c) { var cfg = grid.columns[c]; return !grid.readOnly && !(cfg && cfg.readOnly === true); }
58
+ // 本体と同じ判定=grid.readOnly / 関数readOnly / セル単位RO / 結合の従属 すべて尊重(cfg.readOnly===true だけ見ていた不具合を修正)。
59
+ function editable(r, c) { return grid._isReadOnly ? !grid._isReadOnly(r, c) : (!grid.readOnly && !(grid.columns[c] && grid.columns[c].readOnly === true)); }
59
60
 
60
61
  function onClick(e) {
61
62
  var seg = e.target.closest && e.target.closest('.tg-seg[data-tg-v]'); if (!seg) return;