@threestarsoftware/tssgrid 0.1.11 → 0.1.13
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 +10 -0
- package/README.md +35 -0
- package/package.json +25 -1
- package/plugins/tss-autocomplete.d.ts +32 -0
- package/plugins/tss-calendar.d.ts +44 -0
- package/plugins/tss-cascade.d.ts +26 -0
- package/plugins/tss-combo.css +26 -0
- package/plugins/tss-combo.d.ts +37 -0
- package/plugins/tss-combo.js +182 -0
- package/plugins/tss-datetime.d.ts +42 -0
- package/plugins/tss-time.d.ts +29 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,16 @@
|
|
|
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.13] - 2026-09-24
|
|
7
|
+
|
|
8
|
+
### 追加
|
|
9
|
+
- **エディタ系プラグイン6つに型定義 `.d.ts` を同梱(TypeScript ファースト)** — `columns[c].editor` に入れるエディタ工場プラグインを **`import` するだけで型が効く**ように。対象は `tss-combo` / `tss-autocomplete` / `tss-cascade` / `tss-calendar` / `tss-datetime` / `tss-time`。UMD 形(`export =` + `export as namespace`)なので **module import でも `<script>` グローバルでも**型が効く。戻り値は `EditorDef` を満たし、静的メソッド(`TssCombo.codeOf` / `TssCascade.pathFormat`・`pathOf` / `TssDatetime.format`)や単体 `openAt` も型付け。`package.json` の `exports` に6プラグインの `types` エントリを追加(その他の `usePlugin`/`format` 系プラグインは従来どおり untyped=必要なら5行のアンビエント宣言で補える。README 参照)。コア無改変。
|
|
10
|
+
|
|
11
|
+
## [0.1.12] - 2026-09-24
|
|
12
|
+
|
|
13
|
+
### 追加
|
|
14
|
+
- **コード付きプルダウン+「その他」で自由入力 プラグイン `tss-combo`**(プラグイン・無依存・コア無改変)— 「**選択肢から選ぶ。ただし『その他』を選んだら自由入力**」を、業務側で**その他かどうかを分岐せず** **(コード, 値) のペア**で扱えるカスタムエディタ(`columns[c].editor` 契約に乗る)。**コードはそのまま**(選んだ項目の code/その他は固定 code)、**値だけ**が「選択ラベル or 入力文字列」に変わる。値列にエディタを付け `codeField` で**別のコード列へ code を書き戻す**(`setValueRaw`=派生値・`readOnly` 列にも書ける)。`options` は `{code,label}` か文字列配列、`other` 省略で自由入力なし(素のコード選択)。既存の「その他」値(選択肢に無い値)は開くと**入力欄に復元**。undo/redo・貼付で code を追従させたい時用に `TssCombo.codeOf({options,other})` ヘルパ。動く例 `examples/combo-other.html`(tss-combo の各パターン=コード+名称両表示/hidden/その他なし/文字列options + autocomplete・2セル方式の比較も)。
|
|
15
|
+
|
|
6
16
|
## [0.1.11] - 2026-09-16
|
|
7
17
|
|
|
8
18
|
### 修正
|
package/README.md
CHANGED
|
@@ -56,6 +56,18 @@ import '@threestarsoftware/tssgrid/tssgrid.css';
|
|
|
56
56
|
```
|
|
57
57
|
**型定義 (`.d.ts`) 同梱**なので TypeScript ではそのまま補完が効きます(APIリファレンス → [tssgrid…/api](https://tssgrid.threestarsoftware.co.jp/api.html))。
|
|
58
58
|
|
|
59
|
+
**プラグインの型(TypeScript)**: エディタ系プラグインは **`.d.ts` 同梱で `import` するだけで型が効きます**(`.js` 付きで import):
|
|
60
|
+
```ts
|
|
61
|
+
import TssCombo from '@threestarsoftware/tssgrid/plugins/tss-combo.js'; // ← 型が効く
|
|
62
|
+
// 対応: tss-combo / tss-autocomplete / tss-cascade / tss-calendar / tss-datetime / tss-time
|
|
63
|
+
columns: [{ data: 'pay', editor: TssCombo({ options: [{ code: '01', label: '現金' }], other: { code: '99', label: 'その他' }, codeField: 'payCode' }) }];
|
|
64
|
+
```
|
|
65
|
+
それ以外のプラグイン(`usePlugin` 系・`format` ヘルパ系)は型定義がまだ無いので、必要なら5行のアンビエント宣言で補えます(`editor.open/close` を持つ戻り値は `EditorDef`):
|
|
66
|
+
```ts
|
|
67
|
+
import type { EditorDef } from '@threestarsoftware/tssgrid';
|
|
68
|
+
declare function TssXxx(opts: any): EditorDef; // 未同梱プラグインの暫定宣言
|
|
69
|
+
```
|
|
70
|
+
|
|
59
71
|
**Node / CommonJS**:
|
|
60
72
|
|
|
61
73
|
```js
|
|
@@ -647,6 +659,29 @@ columns: [
|
|
|
647
659
|
- ↑↓で候補移動・Enter確定・Esc取消・クリック選択。`source`(配列 or 関数) / `strict`(候補のみ) / `minChars` / `max` / `match`(`includes`/`startsWith`/関数) / `openOnClick`。
|
|
648
660
|
- カスタムエディタ契約(`editor.open/close`)に乗るだけ。見た目は `tss-ac-*` クラスと `--ac-*` 変数で。
|
|
649
661
|
|
|
662
|
+
### 同梱プラグイン: コード付きプルダウン+その他自由入力(tss-combo)
|
|
663
|
+
|
|
664
|
+
`plugins/tss-combo.js`= 「**選択肢から選ぶ。ただし『その他』を選んだら自由入力**」を、業務側で**その他かどうかを分岐せず** **(コード, 値) のペア**で扱えるようにするエディタ。**コードはそのまま**(選んだ項目の code/その他は固定 code)、**値だけ**が「選択ラベル or 入力文字列」に変わる。動く例: [`examples/combo-other.html`](https://tssgrid.threestarsoftware.co.jp/examples/combo-other.html)(tss-combo の各パターン+ autocomplete / 2セル方式の比較も)。
|
|
665
|
+
|
|
666
|
+
```html
|
|
667
|
+
<link rel="stylesheet" href="plugins/tss-combo.css">
|
|
668
|
+
<script src="plugins/tss-combo.js"></script>
|
|
669
|
+
```
|
|
670
|
+
```js
|
|
671
|
+
columns: [
|
|
672
|
+
{ data: 'payCode', readOnly: true }, // コード列(hidden 運用も可=hiddenColumns:[…])
|
|
673
|
+
{ data: 'payName', editor: TssCombo({
|
|
674
|
+
options: [ { code:'01', label:'現金' }, { code:'02', label:'振込' } ],
|
|
675
|
+
other: { code:'99', label:'その他' }, // 選ぶと入力欄に切替(省略で自由入力なし)
|
|
676
|
+
codeField: 'payCode', // コードの書き戻し先(data キー or 列 index)
|
|
677
|
+
}) },
|
|
678
|
+
];
|
|
679
|
+
// 通常項目 → (payCode, payName) = (その code, label)/その他 → ('99', 入力文字列)
|
|
680
|
+
```
|
|
681
|
+
- 値列にエディタを付け、`codeField` で**別のコード列へ code を書き戻す**(`setValueRaw`=派生値・`readOnly` 列にも書ける)。`options` は `{code,label}` か文字列配列(code=label)。既存の「その他」値(選択肢に無い値)は開くと**入力欄に復元**。
|
|
682
|
+
- undo/redo・貼付で code を値に追従させたい時は `TssCombo.codeOf({options, other})` を `onAfterChange` に噛ませる(任意)。
|
|
683
|
+
- 見た目は `tg-combo*` クラス、色は `--tg-accent` を流用。`openOnClick` / `otherPlaceholder` / `icon` / `className`。
|
|
684
|
+
|
|
650
685
|
### 同梱プラグイン: セル内スパークライン(sparkline・ミニグラフ)
|
|
651
686
|
|
|
652
687
|
`plugins/tss-sparkline.js`(**SVG自前描画**)= セルの数列を**ミニ折れ線/棒**で描く。`columns[c].html:true` + `format`(format が返す SVG がセルに入る)seam に乗るだけ。Chart.js 等は積まない。動く例: [`examples/sparkline.html`](https://tssgrid.threestarsoftware.co.jp/examples/sparkline.html)(月次売上→動向グラフ・編集でライブ更新)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@threestarsoftware/tssgrid",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "軽量・ノービルド・日本語IME堅牢な編集グリッド(vanilla JS / 依存なし / MIT)。Excel ライクな入力画面を <script> タグ1本で。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ThreeStarSoftware (https://www.threestarsoftware.co.jp/)",
|
|
@@ -35,6 +35,30 @@
|
|
|
35
35
|
"default": "./src/tssgrid.js"
|
|
36
36
|
},
|
|
37
37
|
"./tssgrid.css": "./src/tssgrid.css",
|
|
38
|
+
"./plugins/tss-combo.js": {
|
|
39
|
+
"types": "./plugins/tss-combo.d.ts",
|
|
40
|
+
"default": "./plugins/tss-combo.js"
|
|
41
|
+
},
|
|
42
|
+
"./plugins/tss-autocomplete.js": {
|
|
43
|
+
"types": "./plugins/tss-autocomplete.d.ts",
|
|
44
|
+
"default": "./plugins/tss-autocomplete.js"
|
|
45
|
+
},
|
|
46
|
+
"./plugins/tss-cascade.js": {
|
|
47
|
+
"types": "./plugins/tss-cascade.d.ts",
|
|
48
|
+
"default": "./plugins/tss-cascade.js"
|
|
49
|
+
},
|
|
50
|
+
"./plugins/tss-calendar.js": {
|
|
51
|
+
"types": "./plugins/tss-calendar.d.ts",
|
|
52
|
+
"default": "./plugins/tss-calendar.js"
|
|
53
|
+
},
|
|
54
|
+
"./plugins/tss-datetime.js": {
|
|
55
|
+
"types": "./plugins/tss-datetime.d.ts",
|
|
56
|
+
"default": "./plugins/tss-datetime.js"
|
|
57
|
+
},
|
|
58
|
+
"./plugins/tss-time.js": {
|
|
59
|
+
"types": "./plugins/tss-time.d.ts",
|
|
60
|
+
"default": "./plugins/tss-time.js"
|
|
61
|
+
},
|
|
38
62
|
"./plugins/*": "./plugins/*",
|
|
39
63
|
"./package.json": "./package.json"
|
|
40
64
|
},
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// TssAutocomplete — 候補サジェスト付きテキスト入力(TssGrid カスタムエディタ)。
|
|
2
|
+
import { EditorDef } from '../src/tssgrid';
|
|
3
|
+
|
|
4
|
+
export = TssAutocomplete;
|
|
5
|
+
export as namespace TssAutocomplete;
|
|
6
|
+
|
|
7
|
+
declare function TssAutocomplete(opts: TssAutocomplete.Options): EditorDef;
|
|
8
|
+
|
|
9
|
+
declare namespace TssAutocomplete {
|
|
10
|
+
interface SourceCtx { row: any; r: number; c: number; grid: any; }
|
|
11
|
+
|
|
12
|
+
interface Options {
|
|
13
|
+
/** 候補。配列 or `(query, ctx) => string[]`(動的候補)。 */
|
|
14
|
+
source: string[] | ((query: string, ctx: SourceCtx) => string[]);
|
|
15
|
+
/** 共有 input に乗る型(既定 true)=打鍵の1文字目・IME 直打ちが効く。false で自前 input ポップアップ。 */
|
|
16
|
+
inline?: boolean;
|
|
17
|
+
/** true=候補から選んだ値のみ許可(既定 false=自由入力可)。 */
|
|
18
|
+
strict?: boolean;
|
|
19
|
+
/** この文字数以上で候補を出す(既定 0=開いた時点で全件)。 */
|
|
20
|
+
minChars?: number;
|
|
21
|
+
/** 候補表示の最大件数(既定 20)。 */
|
|
22
|
+
max?: number;
|
|
23
|
+
/** 照合方法(既定 'includes')。大文字小文字は無視。 */
|
|
24
|
+
match?: 'includes' | 'startsWith' | ((item: string, query: string) => boolean);
|
|
25
|
+
/** シングルクリックで開く(既定 true)。 */
|
|
26
|
+
openOnClick?: boolean;
|
|
27
|
+
/** セル右端の目印(既定 '▾')。 */
|
|
28
|
+
icon?: string;
|
|
29
|
+
/** ポップアップに付ける任意クラス。 */
|
|
30
|
+
className?: string;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// TssCalendar — 休日対応カレンダー(TssGrid カスタムエディタ/単体ピッカーとしても)。保存値は 'YYYY-MM-DD'。
|
|
2
|
+
import { EditorDef } from '../src/tssgrid';
|
|
3
|
+
|
|
4
|
+
export = TssCalendar;
|
|
5
|
+
export as namespace TssCalendar;
|
|
6
|
+
|
|
7
|
+
declare function TssCalendar(opts?: TssCalendar.Options): TssCalendar.Editor;
|
|
8
|
+
|
|
9
|
+
declare namespace TssCalendar {
|
|
10
|
+
/** 休日マップ。`'YYYY-MM-DD'` → 名称文字列 or `{name, type?}`。 */
|
|
11
|
+
type Holidays = Record<string, string | { name: string; type?: string }>;
|
|
12
|
+
|
|
13
|
+
interface Options {
|
|
14
|
+
holidays?: Holidays;
|
|
15
|
+
/** 週末とみなす曜日(0=日, 6=土。既定 [0,6])。 */
|
|
16
|
+
weekend?: number[];
|
|
17
|
+
/** 曜日ラベル(既定 ['日','月',…,'土'])。 */
|
|
18
|
+
weekLabels?: string[];
|
|
19
|
+
/** 月見出し(既定 `y年(m+1)月`)。 */
|
|
20
|
+
monthLabel?: (year: number, month: number) => string;
|
|
21
|
+
/** 選択可能範囲('YYYY-MM-DD')。 */
|
|
22
|
+
min?: string;
|
|
23
|
+
max?: string;
|
|
24
|
+
/** 年プルダウンの範囲。 */
|
|
25
|
+
yearMin?: number;
|
|
26
|
+
yearMax?: number;
|
|
27
|
+
/** 選択不可にする種別(例 ['holiday','weekend'])。 */
|
|
28
|
+
disable?: Array<'holiday' | 'weekend' | string>;
|
|
29
|
+
/** 共有 input に乗る型(既定 true)=直打ち可・矢印は本体へ。false でフォーカス奪取型。 */
|
|
30
|
+
inline?: boolean;
|
|
31
|
+
/** シングルクリックで開く(既定 true)。 */
|
|
32
|
+
openOnClick?: boolean;
|
|
33
|
+
/** セル右端の目印(既定 '📅'。'' で消す)。 */
|
|
34
|
+
icon?: string;
|
|
35
|
+
/** ポップアップに付ける任意クラス。 */
|
|
36
|
+
className?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** グリッドの `editor` として使えるほか、単体で `openAt` を呼べる。 */
|
|
40
|
+
interface Editor extends EditorDef {
|
|
41
|
+
/** アンカー要素の下に開き、日付選択で `onPick(iso)` を呼ぶ(単体ピッカー用)。 */
|
|
42
|
+
openAt(anchorEl: Element, isoValue: string, onPick: (iso: string) => void): void;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// TssCascade — 多段(フライアウト)プルダウン(TssGrid カスタムエディタ・ポップアップ型)。
|
|
2
|
+
import { EditorDef } from '../src/tssgrid';
|
|
3
|
+
|
|
4
|
+
export = TssCascade;
|
|
5
|
+
export as namespace TssCascade;
|
|
6
|
+
|
|
7
|
+
declare function TssCascade(opts: TssCascade.Options): EditorDef;
|
|
8
|
+
|
|
9
|
+
declare namespace TssCascade {
|
|
10
|
+
/** ノード。枝は `children`、葉は `value`(未指定なら `label` を保存)。 */
|
|
11
|
+
interface Node { label: string; value?: string | number; children?: Node[]; }
|
|
12
|
+
|
|
13
|
+
interface Options {
|
|
14
|
+
/** 入れ子の選択肢。 */
|
|
15
|
+
options: Node[];
|
|
16
|
+
/** シングルクリックで開く(既定 true)。 */
|
|
17
|
+
openOnClick?: boolean;
|
|
18
|
+
/** ポップアップに付ける任意クラス。 */
|
|
19
|
+
className?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** 列 format 用: 値 → 「大 / 中 / 小」のパス文字列を返す関数。 */
|
|
23
|
+
function pathFormat(options: Node[], opts?: { separator?: string }): (value: any) => string;
|
|
24
|
+
/** 値 → パス文字列(単発)。 */
|
|
25
|
+
function pathOf(options: Node[], value: any, separator?: string): string;
|
|
26
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/* TssCombo — コード付きプルダウン+その他自由入力(plugins/tss-combo.js)。色は --tg-accent を流用。 */
|
|
2
|
+
.tg-combo {
|
|
3
|
+
position: fixed; z-index: 1000; min-width: 150px; max-height: 60vh; overflow: auto;
|
|
4
|
+
background: #fff; color: #1f2733; border: 1px solid #c4ccd6; border-radius: 8px;
|
|
5
|
+
box-shadow: 0 6px 22px rgba(20, 40, 80, .22);
|
|
6
|
+
font: 13px "Meiryo UI", "Segoe UI", sans-serif; padding: 4px; box-sizing: border-box;
|
|
7
|
+
}
|
|
8
|
+
.tg-combo .tg-combo-item {
|
|
9
|
+
padding: 5px 10px; border-radius: 5px; cursor: pointer; white-space: nowrap; outline: none;
|
|
10
|
+
}
|
|
11
|
+
.tg-combo .tg-combo-item:hover,
|
|
12
|
+
.tg-combo .tg-combo-item.active { background: var(--tg-accent, #1573c0); color: #fff; }
|
|
13
|
+
.tg-combo .tg-combo-other { font-style: italic; opacity: .95; }
|
|
14
|
+
.tg-combo .tg-combo-sep { height: 1px; background: #e2e7ee; margin: 4px 6px; }
|
|
15
|
+
/* その他モードの入力欄 */
|
|
16
|
+
.tg-combo .tg-combo-inputwrap { padding: 4px; min-width: 180px; }
|
|
17
|
+
.tg-combo .tg-combo-input {
|
|
18
|
+
width: 100%; box-sizing: border-box; padding: 5px 8px; font: inherit;
|
|
19
|
+
border: 1px solid #c4ccd6; border-radius: 5px; outline: none;
|
|
20
|
+
}
|
|
21
|
+
.tg-combo .tg-combo-input:focus { border-color: var(--tg-accent, #1573c0); }
|
|
22
|
+
.tg-combo .tg-combo-actions { display: flex; gap: 6px; margin-top: 6px; justify-content: flex-end; }
|
|
23
|
+
.tg-combo .tg-combo-actions button {
|
|
24
|
+
padding: 3px 12px; font: inherit; cursor: pointer; border-radius: 5px; border: 1px solid #c4ccd6; background: #f5f7fa;
|
|
25
|
+
}
|
|
26
|
+
.tg-combo .tg-combo-actions .tg-combo-ok { background: var(--tg-accent, #1573c0); color: #fff; border-color: transparent; }
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// TssCombo — コード付きプルダウン+「その他」で自由入力(TssGrid カスタムエディタ)。
|
|
2
|
+
// UMD: `import TssCombo from '@threestarsoftware/tssgrid/plugins/tss-combo.js'` でも、<script> タグのグローバル `TssCombo` でも使える。
|
|
3
|
+
import { EditorDef } from '../src/tssgrid';
|
|
4
|
+
|
|
5
|
+
export = TssCombo;
|
|
6
|
+
export as namespace TssCombo;
|
|
7
|
+
|
|
8
|
+
declare function TssCombo(opts: TssCombo.Options): EditorDef;
|
|
9
|
+
|
|
10
|
+
declare namespace TssCombo {
|
|
11
|
+
/** 選択肢1件。`code`(別名 `value`)と表示 `label`。文字列を渡すと code=label。 */
|
|
12
|
+
interface Option { code?: string | number; value?: string | number; label?: string; }
|
|
13
|
+
|
|
14
|
+
interface Options {
|
|
15
|
+
/** 選択肢。`{code,label}` か文字列(code=label)の配列。 */
|
|
16
|
+
options: Array<Option | string>;
|
|
17
|
+
/** 「その他」を有効化。`true` で `{code:'99', label:'その他'}`。選ぶと入力欄に切替。省略で自由入力なし。 */
|
|
18
|
+
other?: boolean | Option;
|
|
19
|
+
/** コードの書き戻し先の列(data キー or 列 index)。省略時はコードを書かず値だけ確定。 */
|
|
20
|
+
codeField?: string | number;
|
|
21
|
+
/** その他モードの入力欄プレースホルダ(既定 '自由入力')。 */
|
|
22
|
+
otherPlaceholder?: string;
|
|
23
|
+
/** シングルクリックで開く(既定 true)。 */
|
|
24
|
+
openOnClick?: boolean;
|
|
25
|
+
/** セル右端の目印(既定 '▾')。 */
|
|
26
|
+
icon?: string;
|
|
27
|
+
/** ポップアップに付ける任意クラス。 */
|
|
28
|
+
className?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 値 → コードを引く関数を返す(同じ options/other から)。undo/redo・貼付でコードを値に追従させたい時、
|
|
33
|
+
* `onAfterChange` の中で `grid.setValueRaw(r, コード列, codeOf(grid.getValue(r, 値列)))` のように使う。
|
|
34
|
+
* 値が選択肢に無ければ other の code、空なら ''。
|
|
35
|
+
*/
|
|
36
|
+
function codeOf(opts: { options: Array<Option | string>; other?: boolean | Option }): (value: any) => string;
|
|
37
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/*! TssCombo — コード付きプルダウン+「その他」で自由入力。TssGrid カスタムエディタ(ポップアップ型)。
|
|
2
|
+
*
|
|
3
|
+
* ねらい: 「選択肢から選ぶ。ただし『その他』を選んだら自由入力」を **1操作で (コード, 値) のペア**にする。
|
|
4
|
+
* - 通常項目を選ぶ → 値セル=そのラベル / コードセル=その code
|
|
5
|
+
* - 「その他」を選ぶ → 入力欄に切替 → 値セル=入力文字列 / コードセル=その他の code(固定)
|
|
6
|
+
* 業務側は「その他かどうか」を分岐せず (コード, 値) を読むだけでよい(コードはそのまま・値だけが選択/入力で変わる)。
|
|
7
|
+
*
|
|
8
|
+
* 使い方(値列にエディタを付け、codeField でコードの書き戻し先=別列を指す):
|
|
9
|
+
* columns: [
|
|
10
|
+
* { data:'payCode' }, // コード列(そのまま保存: 01/02/…/99)。hidden:true でも可
|
|
11
|
+
* { data:'payName', editor: TssCombo({
|
|
12
|
+
* options: [
|
|
13
|
+
* { code:'01', label:'現金' },
|
|
14
|
+
* { code:'02', label:'振込' },
|
|
15
|
+
* { code:'03', label:'クレジット' },
|
|
16
|
+
* ],
|
|
17
|
+
* other: { code:'99', label:'その他' }, // これを選ぶと入力欄に切替(省略で「その他」無効=素のコード選択)
|
|
18
|
+
* codeField: 'payCode', // コードの書き戻し先(data キー or 列 index)
|
|
19
|
+
* }) },
|
|
20
|
+
* ]
|
|
21
|
+
*
|
|
22
|
+
* opts:
|
|
23
|
+
* options : [{ code, label }](`value` も code の別名として可/文字列は code=label)。必須。
|
|
24
|
+
* other : { code, label } でその他を有効化。`other:true` なら {code:'99', label:'その他'}。省略で無効。
|
|
25
|
+
* codeField : コードの書き戻し先の列(data キー or index)。省略時はコードを書かず値だけ確定。
|
|
26
|
+
* otherPlaceholder : 入力欄のプレースホルダ(既定 '自由入力')。
|
|
27
|
+
* openOnClick: 既定 true(プルダウン同様シングルクリックで開く)。
|
|
28
|
+
* icon : セル右の目印(既定 '▾')。className: ポップアップに付ける任意クラス。
|
|
29
|
+
*/
|
|
30
|
+
(function (root) {
|
|
31
|
+
'use strict';
|
|
32
|
+
function esc(s) { return String(s == null ? '' : s).replace(/[&<>"]/g, function (c) { return { '&': '&', '<': '<', '>': '>', '"': '"' }[c]; }); }
|
|
33
|
+
// options を [{code,label}] に正規化(value を code の別名に/文字列は両方同じ)。
|
|
34
|
+
function normOpt(o) {
|
|
35
|
+
if (o && typeof o === 'object') {
|
|
36
|
+
var code = o.code != null ? o.code : (o.value != null ? o.value : o.label);
|
|
37
|
+
return { code: String(code == null ? '' : code), label: String(o.label != null ? o.label : code) };
|
|
38
|
+
}
|
|
39
|
+
return { code: String(o), label: String(o) };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function TssCombo(opts) {
|
|
43
|
+
opts = opts || {};
|
|
44
|
+
var list = (opts.options || []).map(normOpt);
|
|
45
|
+
var other = opts.other === true ? { code: '99', label: 'その他' }
|
|
46
|
+
: (opts.other && typeof opts.other === 'object') ? normOpt(opts.other) : null;
|
|
47
|
+
var codeField = opts.codeField;
|
|
48
|
+
var placeholder = opts.otherPlaceholder != null ? opts.otherPlaceholder : '自由入力';
|
|
49
|
+
|
|
50
|
+
var pop = null, ctxRef = null, done = false, onDoc = null, onKey = null;
|
|
51
|
+
var items = [], hi = -1, composing = false;
|
|
52
|
+
|
|
53
|
+
function teardown() {
|
|
54
|
+
if (onDoc) document.removeEventListener('mousedown', onDoc, true);
|
|
55
|
+
if (onKey) document.removeEventListener('keydown', onKey, true);
|
|
56
|
+
onDoc = onKey = null;
|
|
57
|
+
if (pop && pop.parentNode) pop.parentNode.removeChild(pop);
|
|
58
|
+
pop = null; items = []; hi = -1; composing = false;
|
|
59
|
+
}
|
|
60
|
+
// (コード, 値) を確定=値セルは commit(履歴に積む)、コードセルは別列へ setValueRaw で書き戻す。
|
|
61
|
+
// ★ setValueRaw を使う理由: コードは「値から導く派生値」なのでコード列を readOnly:true にしても貫通して書け、
|
|
62
|
+
// 履歴に独立ステップを作らない(=値のセルだけが 1 undo 単位)。undo/redo でコードも値に追従させたい場合は
|
|
63
|
+
// 下の TssCombo.codeOf を onAfterChange に噛ませる(任意)。
|
|
64
|
+
function commitPair(code, value) {
|
|
65
|
+
if (done) return; done = true;
|
|
66
|
+
var c = ctxRef; teardown();
|
|
67
|
+
c.commit(value); // 値列(このセル)=履歴に積む
|
|
68
|
+
if (codeField != null && c.grid && c.r != null) {
|
|
69
|
+
try { c.grid.setValueRaw(c.r, codeField, code); } catch (e) {} // コード列(別列・派生値・readOnly 貫通)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function doCancel() { if (done) return; done = true; var c = ctxRef; teardown(); if (c.cancel) c.cancel(); }
|
|
73
|
+
|
|
74
|
+
function position(anchor) {
|
|
75
|
+
var r = anchor.getBoundingClientRect(), pw = pop.offsetWidth, ph = pop.offsetHeight;
|
|
76
|
+
var left = Math.max(6, Math.min(r.left, window.innerWidth - pw - 6));
|
|
77
|
+
var top = r.bottom + 2;
|
|
78
|
+
if (top + ph > window.innerHeight - 6) top = Math.max(6, r.top - 2 - ph); // 下にはみ出すなら上へ
|
|
79
|
+
pop.style.left = left + 'px'; pop.style.top = top + 'px'; pop.style.minWidth = r.width + 'px';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ---- リスト表示(選択モード)----
|
|
83
|
+
function renderList(highlight) {
|
|
84
|
+
var rows = list.map(function (o, i) {
|
|
85
|
+
return '<div class="tg-combo-item" data-i="' + i + '" role="option">' + esc(o.label) + '</div>';
|
|
86
|
+
});
|
|
87
|
+
if (other) rows.push('<div class="tg-combo-sep"></div>' +
|
|
88
|
+
'<div class="tg-combo-item tg-combo-other" data-other="1" role="option">' + esc(other.label) + '…</div>');
|
|
89
|
+
pop.innerHTML = rows.join('');
|
|
90
|
+
items = pop.querySelectorAll('.tg-combo-item');
|
|
91
|
+
hi = -1;
|
|
92
|
+
Array.prototype.forEach.call(items, function (el, k) {
|
|
93
|
+
el.addEventListener('mouseenter', function () { activate(k); });
|
|
94
|
+
el.addEventListener('mousedown', function (e) { e.preventDefault(); }); // フォーカスを奪わない
|
|
95
|
+
el.addEventListener('click', function (e) { e.stopPropagation(); choose(k); });
|
|
96
|
+
});
|
|
97
|
+
// 現在値に一致する項目をハイライト(無ければ先頭)
|
|
98
|
+
var want = 0;
|
|
99
|
+
if (highlight != null) for (var i = 0; i < list.length; i++) if (list[i].label === highlight) { want = i; break; }
|
|
100
|
+
activate(want);
|
|
101
|
+
}
|
|
102
|
+
function activate(k) {
|
|
103
|
+
if (k < 0 || k >= items.length) return;
|
|
104
|
+
Array.prototype.forEach.call(items, function (el, i) { el.classList.toggle('active', i === k); });
|
|
105
|
+
hi = k; if (items[k]) items[k].scrollIntoView({ block: 'nearest' });
|
|
106
|
+
}
|
|
107
|
+
function choose(k) {
|
|
108
|
+
var el = items[k]; if (!el) return;
|
|
109
|
+
if (el.dataset.other) return showInput(''); // 「その他」→入力モードへ
|
|
110
|
+
var o = list[k]; commitPair(o.code, o.label); // 通常項目→(code, label) 確定
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ---- 入力表示(その他モード)----
|
|
114
|
+
function showInput(initial) {
|
|
115
|
+
pop.innerHTML = '<div class="tg-combo-inputwrap">' +
|
|
116
|
+
'<input class="tg-combo-input" type="text" placeholder="' + esc(placeholder) + '">' +
|
|
117
|
+
'<div class="tg-combo-actions"><button type="button" class="tg-combo-ok">確定</button>' +
|
|
118
|
+
'<button type="button" class="tg-combo-back">戻る</button></div></div>';
|
|
119
|
+
var input = pop.querySelector('.tg-combo-input');
|
|
120
|
+
input.value = initial == null ? '' : initial;
|
|
121
|
+
input.addEventListener('compositionstart', function () { composing = true; });
|
|
122
|
+
input.addEventListener('compositionend', function () { composing = false; });
|
|
123
|
+
input.addEventListener('keydown', function (e) {
|
|
124
|
+
if (e.key === 'Enter') { if (composing || e.isComposing || e.keyCode === 229) return; e.preventDefault(); confirmInput(input.value); }
|
|
125
|
+
else if (e.key === 'Escape') { e.preventDefault(); if (list.length) renderListWithHighlight(); else doCancel(); }
|
|
126
|
+
});
|
|
127
|
+
pop.querySelector('.tg-combo-ok').addEventListener('mousedown', function (e) { e.preventDefault(); confirmInput(input.value); });
|
|
128
|
+
pop.querySelector('.tg-combo-back').addEventListener('mousedown', function (e) { e.preventDefault(); renderListWithHighlight(); });
|
|
129
|
+
setTimeout(function () { input.focus(); input.setSelectionRange(input.value.length, input.value.length); }, 0);
|
|
130
|
+
}
|
|
131
|
+
function confirmInput(v) { commitPair(other ? other.code : '', v); } // (その他code, 入力値) 確定
|
|
132
|
+
function renderListWithHighlight() { renderList(ctxRef ? ctxRef.value : null); }
|
|
133
|
+
|
|
134
|
+
function handleKey(e) {
|
|
135
|
+
if (!pop || pop.querySelector('.tg-combo-input')) return; // 入力モードのキーは input 側で処理
|
|
136
|
+
if (e.key === 'ArrowDown') { e.preventDefault(); activate((hi + 1) % items.length); }
|
|
137
|
+
else if (e.key === 'ArrowUp') { e.preventDefault(); activate((hi - 1 + items.length) % items.length); }
|
|
138
|
+
else if (e.key === 'Enter') { e.preventDefault(); if (hi >= 0) choose(hi); }
|
|
139
|
+
else if (e.key === 'Escape') { e.preventDefault(); doCancel(); }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
openOnClick: opts.openOnClick !== false,
|
|
144
|
+
icon: opts.icon != null ? opts.icon : '▾',
|
|
145
|
+
open: function (ctx) {
|
|
146
|
+
ctxRef = ctx; done = false;
|
|
147
|
+
pop = document.createElement('div');
|
|
148
|
+
pop.className = 'tg-combo' + (opts.className ? ' ' + opts.className : '');
|
|
149
|
+
pop.setAttribute('role', 'listbox');
|
|
150
|
+
document.body.appendChild(pop);
|
|
151
|
+
// 現在値が選択肢に無い=過去に「その他」で入れた値 → いきなり入力モードで復元
|
|
152
|
+
var cur = ctx.value, known = cur != null && cur !== '' && list.some(function (o) { return o.label === String(cur); });
|
|
153
|
+
if (other && cur != null && cur !== '' && !known) showInput(String(cur));
|
|
154
|
+
else renderList(cur);
|
|
155
|
+
position(ctx.td);
|
|
156
|
+
onDoc = function (e) { if (pop && !pop.contains(e.target) && !(ctx.td && ctx.td.contains(e.target))) doCancel(); };
|
|
157
|
+
onKey = handleKey;
|
|
158
|
+
document.addEventListener('mousedown', onDoc, true);
|
|
159
|
+
document.addEventListener('keydown', onKey, true);
|
|
160
|
+
},
|
|
161
|
+
close: function () { teardown(); },
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 値 → コードを引く関数を返す(同じ options/other から)。undo/redo・貼付でコードを値に追従させたい時、
|
|
166
|
+
// onAfterChange: (chs) => chs.forEach(ch => { if (ch.c === 値列) grid.setValueRaw(ch.r, コード列, codeOf(grid.getValue(ch.r, 値列))); })
|
|
167
|
+
// のように噛ませる(任意)。値が選択肢に無ければ other の code、空なら ''。
|
|
168
|
+
TssCombo.codeOf = function (opts) {
|
|
169
|
+
opts = opts || {};
|
|
170
|
+
var list = (opts.options || []).map(normOpt);
|
|
171
|
+
var other = opts.other === true ? { code: '99' }
|
|
172
|
+
: (opts.other && typeof opts.other === 'object') ? normOpt(opts.other) : null;
|
|
173
|
+
return function (value) {
|
|
174
|
+
if (value == null || value === '') return '';
|
|
175
|
+
for (var i = 0; i < list.length; i++) if (list[i].label === String(value)) return list[i].code;
|
|
176
|
+
return other ? other.code : '';
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
if (typeof module !== 'undefined' && module.exports) module.exports = TssCombo;
|
|
181
|
+
else root.TssCombo = TssCombo;
|
|
182
|
+
})(typeof self !== 'undefined' ? self : this);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// TssDatetime — 日付+時刻ピッカー(TssGrid カスタムエディタ/単体でも)。保存値は 'YYYY-MM-DDTHH:MM'。
|
|
2
|
+
import { EditorDef } from '../src/tssgrid';
|
|
3
|
+
|
|
4
|
+
export = TssDatetime;
|
|
5
|
+
export as namespace TssDatetime;
|
|
6
|
+
|
|
7
|
+
declare function TssDatetime(opts?: TssDatetime.Options): TssDatetime.Editor;
|
|
8
|
+
|
|
9
|
+
declare namespace TssDatetime {
|
|
10
|
+
type Holidays = Record<string, string | { name: string; type?: string }>;
|
|
11
|
+
|
|
12
|
+
interface Options {
|
|
13
|
+
holidays?: Holidays;
|
|
14
|
+
weekend?: number[];
|
|
15
|
+
weekLabels?: string[];
|
|
16
|
+
/** 選択可能範囲('YYYY-MM-DD')。 */
|
|
17
|
+
min?: string;
|
|
18
|
+
max?: string;
|
|
19
|
+
yearMin?: number;
|
|
20
|
+
yearMax?: number;
|
|
21
|
+
/** 分刻み(既定 1)。 */
|
|
22
|
+
step?: number;
|
|
23
|
+
/** AM/PM 表示(既定 false=24h)。 */
|
|
24
|
+
hour12?: boolean;
|
|
25
|
+
/** 共有 input に乗る型(既定 true)=直打ち可。false でフォーカス奪取型。 */
|
|
26
|
+
inline?: boolean;
|
|
27
|
+
/** シングルクリックで開く(既定 true)。 */
|
|
28
|
+
openOnClick?: boolean;
|
|
29
|
+
/** セル右端の目印(既定 '📅')。 */
|
|
30
|
+
icon?: string;
|
|
31
|
+
/** ポップアップに付ける任意クラス。 */
|
|
32
|
+
className?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface Editor extends EditorDef {
|
|
36
|
+
/** アンカー要素の下に開き、選択で `onPick('YYYY-MM-DDTHH:MM')` を呼ぶ(単体用)。 */
|
|
37
|
+
openAt(anchorEl: Element, value: string, onPick: (value: string) => void): void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** 列 format 用: 保存値 'YYYY-MM-DDTHH:MM' を表示整形。 */
|
|
41
|
+
function format(value: any, opts?: { hour12?: boolean }): string;
|
|
42
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// TssTime — 時/分プルダウン方式の時刻エディタ(TssGrid カスタムエディタ/単体でも)。保存値は 24h 'HH:MM'。
|
|
2
|
+
import { EditorDef } from '../src/tssgrid';
|
|
3
|
+
|
|
4
|
+
export = TssTime;
|
|
5
|
+
export as namespace TssTime;
|
|
6
|
+
|
|
7
|
+
declare function TssTime(opts?: TssTime.Options): TssTime.Editor;
|
|
8
|
+
|
|
9
|
+
declare namespace TssTime {
|
|
10
|
+
interface Options {
|
|
11
|
+
/** 分刻み(既定 1)。 */
|
|
12
|
+
step?: number;
|
|
13
|
+
/** AM/PM 表示(既定 false=24h)。 */
|
|
14
|
+
hour12?: boolean;
|
|
15
|
+
/** 共有 input に乗る型(既定 true)=直打ち可。false でフォーカス奪取型。 */
|
|
16
|
+
inline?: boolean;
|
|
17
|
+
/** シングルクリックで開く(既定 true)。 */
|
|
18
|
+
openOnClick?: boolean;
|
|
19
|
+
/** セル右端の目印(既定 '🕐')。 */
|
|
20
|
+
icon?: string;
|
|
21
|
+
/** ポップアップに付ける任意クラス。 */
|
|
22
|
+
className?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface Editor extends EditorDef {
|
|
26
|
+
/** アンカー要素の下に開き、選択で `onPick('HH:MM')` を呼ぶ(単体用)。 */
|
|
27
|
+
openAt(anchorEl: Element, value: string, onPick: (value: string) => void): void;
|
|
28
|
+
}
|
|
29
|
+
}
|