@threestarsoftware/tssgrid 0.1.4 → 0.1.5
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 +15 -0
- package/README.md +28 -0
- package/package.json +1 -1
- package/plugins/tss-filter.css +9 -0
- package/plugins/tss-filter.js +48 -9
- package/src/tssgrid.css +16 -0
- package/src/tssgrid.d.ts +464 -450
- package/src/tssgrid.js +104 -15
package/src/tssgrid.js
CHANGED
|
@@ -202,6 +202,10 @@
|
|
|
202
202
|
this.rowReorder = !!opts.rowReorder;
|
|
203
203
|
this.rowReorderWhole = opts.rowReorder === 'header'; // 'header'=行ヘッダーセル全体が移動ハンドル(クリック=選択/ドラッグ=移動)/true=⠿アイコンのみ
|
|
204
204
|
this.rowReorderMark = opts.rowReorderMark === undefined ? 'before' : opts.rowReorderMark; // ⠿の位置: 'before'(番号の前/既定) | 'after'(番号の後) | false(出さない)
|
|
205
|
+
// 右端に非データの「削除列」(🗑)を1列出す。クリックで deleteRows(既存の remove_row と同一経路・1 history・undo で戻せる)。
|
|
206
|
+
// 非データ・非編集・非選択・幅固定・並べ替え/リサイズ対象外=getRows/toCSV/列インデックスに現れない。allowDeleteRows:false なら出さない。
|
|
207
|
+
this.rowDelete = !!opts.rowDelete;
|
|
208
|
+
this.rowDeleteW = (opts.rowDeleteWidth | 0) || 36; // 削除列の幅(px)
|
|
205
209
|
// 選択カーソル(緑の選択枠・範囲・セルハイライト・フィルハンドル)を表示するか。
|
|
206
210
|
// false で表示専用グリッド向けに非表示(内部の選択は保持=コピーは可)。
|
|
207
211
|
this.cursor = opts.cursor !== false;
|
|
@@ -227,6 +231,11 @@
|
|
|
227
231
|
this.onBeforeAutofill = opts.onBeforeAutofill || null; // (src,target)=>false で取消
|
|
228
232
|
this.onAfterAutofill = opts.onAfterAutofill || null; // (src,target)
|
|
229
233
|
this.copyPaste = opts.copyPaste !== false; // false でコピー/カット/ペースト無効
|
|
234
|
+
// 共有エディタへのブラウザ自動入力(Chrome のポイントカード/住所/支払い等)を強めに抑止する。
|
|
235
|
+
// 既定 false(name+autocomplete=off の通常抑止のみ)。true で autocomplete='new-password' に切替=
|
|
236
|
+
// Chrome の loyalty/payment autofill を確実に止める('off' は無視される)。副作用として環境により鍵アイコンや
|
|
237
|
+
// 生成PW提案が出ることがあるため opt-in。周囲に自前のラベル付き input(フォーム塊)を並べる埋め込みで有効。
|
|
238
|
+
this.suppressAutofill = !!opts.suppressAutofill;
|
|
230
239
|
// 列幅オートフィット(内容に合わせて自動調整)。境界ダブルクリック / autoSizeColumn でも。
|
|
231
240
|
this.autoColumnSize = !!opts.autoColumnSize;
|
|
232
241
|
// データ入力支援
|
|
@@ -382,6 +391,9 @@
|
|
|
382
391
|
const tbody = this.table.querySelector('tbody');
|
|
383
392
|
if (tbody) { let html = ''; for (let r = n0; r < this.ROWS; r++) html += this._rowHTML(r); tbody.insertAdjacentHTML('beforeend', html); }
|
|
384
393
|
}
|
|
394
|
+
// 追記前が floor 以下(最後の1行=クリア/minRows>1 ロック)だった時だけ、既存行の🗑 状態を再描画せず更新
|
|
395
|
+
// (appendRows は既存行を描き直さないため title/disabled が古いまま残るのを消す。floor 超過後は常態=更新不要)。
|
|
396
|
+
if (this._hasDelCol() && n0 <= Math.max(1, this.minRows)) this._refreshRowDel();
|
|
385
397
|
}
|
|
386
398
|
// 遅延ロード(push・Phase B): 総行数を total に確保する。現行の行はそのまま・不足分を PENDING 行で埋め、超過は切る。
|
|
387
399
|
// dataComplete=false になり、この間 minSpareRows は自動無効(末尾スペアと PENDING tail は両立不可)。
|
|
@@ -758,6 +770,23 @@
|
|
|
758
770
|
this.copybox = q('.tg-copybox'); this.fillhandle = q('.tg-fillhandle'); this.fillpreview = q('.tg-fillpreview');
|
|
759
771
|
this.resizeGuide = q('.tg-resizeguide');
|
|
760
772
|
this._edInput = q('input.tg-editor'); this._edMulti = q('.tg-editor-multi'); this.editor = this._edInput;
|
|
773
|
+
// ブラウザ/拡張の自動入力(Chrome の「ポイントカード/住所/支払い」等)抑止。
|
|
774
|
+
// name 無しだとブラウザが周囲のラベルからフィールド種別をML推論して誤爆する(会員番号扱い等)ため、
|
|
775
|
+
// 非意味的で一意な name を付けて種別マッチと入力記憶を避ける。autocomplete=off に加え各PWマネージャの無視属性も。
|
|
776
|
+
// ※Chrome のポイントカード/支払い系は autocomplete=off を無視することがあり100%ではない(確実に切るならブラウザ設定側)。
|
|
777
|
+
// suppressAutofill:true は autocomplete を 'new-password' に(Chrome の loyalty/payment は 'off' を無視するため)。
|
|
778
|
+
// 公式なフィールド無効化 API が将来出たら、この1行の値を差し替えれば全 TssGrid に波及する。
|
|
779
|
+
const _acVal = this.suppressAutofill ? 'new-password' : 'off';
|
|
780
|
+
const _eduid = 'tg-ed-' + (TssGrid._edSeq = (TssGrid._edSeq | 0) + 1);
|
|
781
|
+
[this._edInput, this._edMulti].forEach((el, i) => {
|
|
782
|
+
el.name = _eduid + '-' + i;
|
|
783
|
+
el.setAttribute('autocomplete', _acVal);
|
|
784
|
+
el.setAttribute('autocapitalize', 'off');
|
|
785
|
+
el.setAttribute('data-lpignore', 'true'); // LastPass
|
|
786
|
+
el.setAttribute('data-1p-ignore', 'true'); // 1Password
|
|
787
|
+
el.setAttribute('data-bwignore', 'true'); // Bitwarden
|
|
788
|
+
el.setAttribute('data-form-type', 'other'); // 一部ヒューリスティック向けヒント
|
|
789
|
+
});
|
|
761
790
|
this.select = q('.tg-select'); this.dumpEl = q('.tg-dump');
|
|
762
791
|
this.menuEl = q('.tg-menu');
|
|
763
792
|
this.root = q('.tssgrid');
|
|
@@ -1160,6 +1189,27 @@
|
|
|
1160
1189
|
_colWidth(c) { return this.colW[c] != null ? this.colW[c] : this.defColW; }
|
|
1161
1190
|
_rowHeight(r) { return this.rowH[r] != null ? this.rowH[r] : this.defRowH; }
|
|
1162
1191
|
_rhW() { return this.rowHeaders ? this.rowHeaderW : 0; } // 行ヘッダ列の幅(非表示なら 0)
|
|
1192
|
+
_hasDelCol() { return this.rowDelete && this.allowDeleteRows; } // 右端削除列を出すか(allowDeleteRows:false なら出さない)
|
|
1193
|
+
_delW() { return this._hasDelCol() ? this.rowDeleteW : 0; } // 削除列の幅(非表示なら 0)
|
|
1194
|
+
// 行末の削除セル(🗑)。data-r/data-c を持たない=各種 td[data-r] 探索に引っかからない(行番号は closest('tr[data-r]') から取る)。
|
|
1195
|
+
_delCellHTML(r) {
|
|
1196
|
+
if (!this._hasDelCol()) return '';
|
|
1197
|
+
const floor = Math.max(1, this.minRows);
|
|
1198
|
+
const canDelete = this.ROWS > floor;
|
|
1199
|
+
const soleClear = !canDelete && this.ROWS === 1; // 最後の1行=削除でなく「中身クリア」(行は残す)=🗑 は有効のまま
|
|
1200
|
+
const dis = (!canDelete && !soleClear) ? ' tg-disabled' : ''; // minRows>1 のロック時だけ無効(クリアもしない)
|
|
1201
|
+
const title = soleClear ? '内容をクリア' : '行を削除'; // 最後の1行は動作が「クリア」なのでツールチップも変える
|
|
1202
|
+
return '<td class="tg-rowdel-cell"><span class="tg-rowdel' + dis + '" title="' + title + '">🗑</span></td>';
|
|
1203
|
+
}
|
|
1204
|
+
// 🗑 の title/disabled は ROWS 依存(最後の1行=「内容をクリア」/minRows>1 ロック=無効)で全行一律。
|
|
1205
|
+
// 行数だけ変えて既存行を再描画しない経路(appendRows の非virtual)用に、描画済み🗑を現在の ROWS へ揃える。
|
|
1206
|
+
_refreshRowDel() {
|
|
1207
|
+
if (!this._hasDelCol() || !this.table) return;
|
|
1208
|
+
const canDelete = this.ROWS > Math.max(1, this.minRows);
|
|
1209
|
+
const soleClear = !canDelete && this.ROWS === 1;
|
|
1210
|
+
const dis = !canDelete && !soleClear, title = soleClear ? '内容をクリア' : '行を削除';
|
|
1211
|
+
this.table.querySelectorAll('tbody .tg-rowdel').forEach(el => { el.classList.toggle('tg-disabled', dis); el.setAttribute('title', title); });
|
|
1212
|
+
}
|
|
1163
1213
|
// 行ヘッダの表示内容: false=空(数字なし)/ 関数=カスタム(エスケープ)/ 既定=行番号(r+1)。
|
|
1164
1214
|
_rowHeadLabel(r) {
|
|
1165
1215
|
const L = this.rowHeaderLabel;
|
|
@@ -1228,7 +1278,7 @@
|
|
|
1228
1278
|
}
|
|
1229
1279
|
toggleColumn(c) { this.hiddenCols.has(this.clampC(c)) ? this.showColumn(c) : this.hideColumn(c); }
|
|
1230
1280
|
_placeholder(c) { const p = this.colCfg(c).placeholder; return p != null ? String(p) : this.placeholder; }
|
|
1231
|
-
_totalWidth() { let w = this._rhW(); for (let c = 0; c < this.COLS; c++) if (!this._isHidden(c)) w += this._colWidth(c); return w; }
|
|
1281
|
+
_totalWidth() { let w = this._rhW(); for (let c = 0; c < this.COLS; c++) if (!this._isHidden(c)) w += this._colWidth(c); return w + this._delW(); }
|
|
1232
1282
|
// テーブルに合計幅を明示。これが無いと狭い枠で fixed レイアウトが列を比例圧縮し、
|
|
1233
1283
|
// 保存値(colW)と実描画幅がズレてリサイズのプレビュー線が外れる。
|
|
1234
1284
|
_applyTableWidth() { this.table.style.width = this._totalWidth() + 'px'; }
|
|
@@ -1268,7 +1318,7 @@
|
|
|
1268
1318
|
// 上から n 行を固定(実行時変更可)
|
|
1269
1319
|
freezeRows(n) { this.frozenRows = Math.max(0, Math.min(this.ROWS, n | 0)); this.buildTable(); this.setActive(this.active.r, this.active.c); }
|
|
1270
1320
|
// リーフ(実列に対応する)ヘッダ行を生成。labels 指定時はその文字列/ {label} を見出しに使う。
|
|
1271
|
-
_leafHeaderTr(withCorner, labels) {
|
|
1321
|
+
_leafHeaderTr(withCorner, labels, delHdr) {
|
|
1272
1322
|
let html = '<tr>' + (withCorner ? '<th class="rowhead corner"></th>' : '');
|
|
1273
1323
|
for (let c = 0; c < this.COLS; c++) {
|
|
1274
1324
|
const cls = [];
|
|
@@ -1281,6 +1331,7 @@
|
|
|
1281
1331
|
if (lbl && typeof lbl === 'object') lbl = lbl.label != null ? lbl.label : '';
|
|
1282
1332
|
html += '<th data-c="' + c + '"' + a + '>' + headCb + TssGrid.esc(lbl != null ? lbl : '') + grip + '</th>';
|
|
1283
1333
|
}
|
|
1334
|
+
if (delHdr) html += '<th class="tg-rowdel-hdr"></th>'; // 右端削除列の見出し(無地・非データ)。ネストヘッダ時は角を rowspan で出すのでここでは出さない
|
|
1284
1335
|
return html + '</tr>';
|
|
1285
1336
|
}
|
|
1286
1337
|
// ネストヘッダ: 各ヘッダ行を異なる top で sticky(段が重ならないように積む)。
|
|
@@ -1298,7 +1349,8 @@
|
|
|
1298
1349
|
const rh = this.rowHeaders;
|
|
1299
1350
|
const head = rh ? '<th class="rowhead" data-r="' + r + '">' + this._rowHeadLabel(r) + '</th>' : '';
|
|
1300
1351
|
const cell = '<td class="tg-pending-cell" colspan="' + this.COLS + '" data-r="' + r + '" data-c="0"><span class="tg-pending-dots"></span></td>';
|
|
1301
|
-
|
|
1352
|
+
const del = this._hasDelCol() ? '<td class="tg-rowdel-cell"></td>' : ''; // 読込中行は🗑を出さない(値未取得)=列合わせの空セルのみ
|
|
1353
|
+
return '<tr data-r="' + r + '" class="tg-row tg-pending-row" style="height:' + this._rowHeight(r) + 'px">' + head + cell + del + '</tr>';
|
|
1302
1354
|
}
|
|
1303
1355
|
_rowHTML(r) {
|
|
1304
1356
|
if (this.data[r] === TssGrid.PENDING) return this._pendingRowHTML(r); // 未取得行=読込中プレースホルダ(値は読まない)
|
|
@@ -1346,7 +1398,7 @@
|
|
|
1346
1398
|
const csAttr = (cs > 1 ? ' colspan="' + cs + '"' : '') + (rs > 1 ? ' rowspan="' + rs + '"' : '');
|
|
1347
1399
|
html += '<td' + a + ico + dccls + sty + titleAttr + csAttr + ' data-r="' + r + '" data-c="' + c + '">' + this._cellHTML(r, c) + '</td>';
|
|
1348
1400
|
}
|
|
1349
|
-
return html + '</tr>';
|
|
1401
|
+
return html + this._delCellHTML(r) + '</tr>';
|
|
1350
1402
|
}
|
|
1351
1403
|
// 仮想スクロール: 現在の scrollTop から描画すべき窓 [start,end)(絶対行)を算出。バッファ込み。
|
|
1352
1404
|
_vWindow() {
|
|
@@ -1365,7 +1417,7 @@
|
|
|
1365
1417
|
if (!force && this._vRendered && win.start === this._vStart && win.end === this._vEnd) return false;
|
|
1366
1418
|
const tbody = this.table && this.table.querySelector('tbody');
|
|
1367
1419
|
if (!tbody) return false;
|
|
1368
|
-
const rh = this.rowHeaders, colsSpan = this.COLS + (rh ? 1 : 0);
|
|
1420
|
+
const rh = this.rowHeaders, colsSpan = this.COLS + (rh ? 1 : 0) + (this._hasDelCol() ? 1 : 0);
|
|
1369
1421
|
const topH = win.start * this.defRowH, botH = (this.ROWS - win.end) * this.defRowH;
|
|
1370
1422
|
let html = '';
|
|
1371
1423
|
if (topH > 0) html += '<tr class="tg-vspace" aria-hidden="true" style="height:' + topH + 'px"><td colspan="' + colsSpan + '"></td></tr>';
|
|
@@ -1412,6 +1464,7 @@
|
|
|
1412
1464
|
const rh = this.rowHeaders;
|
|
1413
1465
|
let html = '<colgroup>' + (rh ? '<col style="width:' + this.rowHeaderW + 'px">' : '');
|
|
1414
1466
|
for (let c = 0; c < this.COLS; c++) html += '<col data-c="' + c + '"' + (this._isHidden(c) ? ' class="tg-hidden"' : '') + ' style="width:' + (this._isHidden(c) ? 0 : this._colWidth(c)) + 'px">';
|
|
1467
|
+
if (this._hasDelCol()) html += '<col class="tg-rowdel-col" style="width:' + this.rowDeleteW + 'px">'; // 右端削除列(固定幅・システム列)
|
|
1415
1468
|
html += '</colgroup>';
|
|
1416
1469
|
if (this.colHeaders) {
|
|
1417
1470
|
html += '<thead>';
|
|
@@ -1430,18 +1483,19 @@
|
|
|
1430
1483
|
html += '<th class="tg-grouphdr' + froz + '" colspan="' + span + '" data-c0="' + c + '" data-c1="' + c1 + '">' + TssGrid.esc(label) + '</th>';
|
|
1431
1484
|
c += span;
|
|
1432
1485
|
}
|
|
1486
|
+
if (this._hasDelCol() && ri === 0) html += '<th class="tg-rowdel-hdr corner" rowspan="' + nRows + '"></th>'; // 右端削除列の角(全ヘッダ段を rowspan で覆う)
|
|
1433
1487
|
html += '</tr>';
|
|
1434
1488
|
});
|
|
1435
|
-
html += this._leafHeaderTr(false, leaf); //
|
|
1489
|
+
html += this._leafHeaderTr(false, leaf, false); // リーフ段(左角も右削除角も上段で rowspan 済み)
|
|
1436
1490
|
} else {
|
|
1437
|
-
html += this._leafHeaderTr(rh, null);
|
|
1491
|
+
html += this._leafHeaderTr(rh, null, this._hasDelCol());
|
|
1438
1492
|
}
|
|
1439
1493
|
html += '</thead>';
|
|
1440
1494
|
}
|
|
1441
1495
|
html += '<tbody>';
|
|
1442
1496
|
if (this.virtual) {
|
|
1443
1497
|
const win = this._vWindow(); this._vStart = win.start; this._vEnd = win.end; this._vRendered = true;
|
|
1444
|
-
const colsSpan = this.COLS + (rh ? 1 : 0);
|
|
1498
|
+
const colsSpan = this.COLS + (rh ? 1 : 0) + (this._hasDelCol() ? 1 : 0);
|
|
1445
1499
|
const topH = win.start * this.defRowH, botH = (this.ROWS - win.end) * this.defRowH;
|
|
1446
1500
|
if (topH > 0) html += '<tr class="tg-vspace" aria-hidden="true" style="height:' + topH + 'px"><td colspan="' + colsSpan + '"></td></tr>';
|
|
1447
1501
|
for (let r = win.start; r < win.end; r++) html += this._rowHTML(r);
|
|
@@ -1517,7 +1571,7 @@
|
|
|
1517
1571
|
if (this.stretchH === 'none' || !this.stretchH) return;
|
|
1518
1572
|
const avail = this.wrap ? this.wrap.clientWidth : 0;
|
|
1519
1573
|
if (!avail) return;
|
|
1520
|
-
const base = this._rhW(), widths = [];
|
|
1574
|
+
const base = this._rhW() + this._delW(), widths = []; // 削除列は固定幅=ストレッチ対象外(余白計算から差し引く)
|
|
1521
1575
|
let sum = 0;
|
|
1522
1576
|
for (let c = 0; c < this.COLS; c++) { const wd = this._colWidth(c); widths.push(wd); sum += wd; }
|
|
1523
1577
|
const extra = avail - base - sum;
|
|
@@ -2150,14 +2204,16 @@
|
|
|
2150
2204
|
}
|
|
2151
2205
|
this.renderDump();
|
|
2152
2206
|
const grid = this;
|
|
2207
|
+
// minSpareRows: この編集で末尾スペア行が自動追加された分を、履歴コマンドに畳み込む
|
|
2208
|
+
// (undo で編集値と一緒に追加行も外す/redo で戻す)=「自動行追加が undo で消えない」不具合の解消。
|
|
2209
|
+
const spareAdded = this._ensureSpare();
|
|
2153
2210
|
this.history.push({
|
|
2154
2211
|
label: this.name,
|
|
2155
|
-
apply() { grid._applyCells(changes, 'neu', 'redo'); grid.focusAndSelect(selAfter); },
|
|
2156
|
-
revert() { grid._applyCells(changes, 'old', 'undo'); grid.focusAndSelect(selBefore); },
|
|
2212
|
+
apply() { grid._applyCells(changes, 'neu', 'redo'); grid._addSpareRows(spareAdded); grid.focusAndSelect(selAfter); },
|
|
2213
|
+
revert() { grid._applyCells(changes, 'old', 'undo'); grid._removeSpareRows(spareAdded); grid.focusAndSelect(selBefore); },
|
|
2157
2214
|
});
|
|
2158
2215
|
if (this.onAfterChange) { try { this.onAfterChange(this._pubChanges(changes, source), source); } catch (_) {} }
|
|
2159
2216
|
this._syncHeaderCheckboxes(); // ヘッダ全選択チェックの見た目を同期
|
|
2160
|
-
this._ensureSpare(); // 末尾の空行確保(minSpareRows)
|
|
2161
2217
|
}
|
|
2162
2218
|
commit() {
|
|
2163
2219
|
const wasEditing = this.mode === 'edit';
|
|
@@ -2195,14 +2251,28 @@
|
|
|
2195
2251
|
}
|
|
2196
2252
|
}
|
|
2197
2253
|
// 末尾に常に minSpareRows 行の空行を確保(足りなければ追加。履歴には積まない)。
|
|
2254
|
+
// 追加した空行数を返す(pushCmd が履歴コマンドに畳み込んで undo/redo で戻す/足すため)。
|
|
2198
2255
|
_ensureSpare() {
|
|
2199
|
-
if (this._renderPaused) { this._renderDirty = true; return; } // バッチ描画中は保留→resume でまとめて確保
|
|
2200
|
-
if (!this.minSpareRows || this._allRows || !this.dataComplete) return; // フィルタ中/遅延ロード中(PENDING tail)は末尾の自動空行を足さない
|
|
2256
|
+
if (this._renderPaused) { this._renderDirty = true; return 0; } // バッチ描画中は保留→resume でまとめて確保
|
|
2257
|
+
if (!this.minSpareRows || this._allRows || !this.dataComplete) return 0; // フィルタ中/遅延ロード中(PENDING tail)は末尾の自動空行を足さない
|
|
2201
2258
|
let empties = 0;
|
|
2202
2259
|
for (let r = this.ROWS - 1; r >= 0; r--) { if (this._rowEmpty(r)) empties++; else break; }
|
|
2203
2260
|
let added = 0;
|
|
2204
2261
|
while (empties < this.minSpareRows && (!this.maxRows || this.ROWS < this.maxRows)) { this.data.push(new Array(this.COLS).fill('')); this.rowH.push(undefined); if (this._src) this._src.push({}); this.ROWS++; empties++; added++; } // maxRows で天井打ち(スペア行も超えない)
|
|
2205
2262
|
if (added && this.table) { this.buildTable(); this.setActive(this.active.r, this.active.c); }
|
|
2263
|
+
return added;
|
|
2264
|
+
}
|
|
2265
|
+
// pushCmd の undo/redo 用: minSpareRows で自動追加された末尾空行 n 行を、編集の取消/やり直しと一緒に外す/戻す。
|
|
2266
|
+
_addSpareRows(n) {
|
|
2267
|
+
if (!n) return;
|
|
2268
|
+
for (let k = 0; k < n; k++) { this.data.push(new Array(this.COLS).fill('')); this.rowH.push(undefined); if (this._src) this._src.push({}); }
|
|
2269
|
+
this.ROWS += n; this.buildTable();
|
|
2270
|
+
}
|
|
2271
|
+
_removeSpareRows(n) {
|
|
2272
|
+
if (!n) return;
|
|
2273
|
+
n = Math.min(n, this.ROWS - 1); // 最低1行は残す(防御)
|
|
2274
|
+
this.data.length -= n; this.rowH.length -= n; if (this._src) this._src.length -= n;
|
|
2275
|
+
this.ROWS -= n; this.buildTable();
|
|
2206
2276
|
}
|
|
2207
2277
|
// 構造変更を実行+履歴に積む共通処理。doIt/undoIt は冪等(redo で再実行される)。
|
|
2208
2278
|
_structCmd(doIt, undoIt, selAfterFn, selBefore, info) {
|
|
@@ -2289,6 +2359,15 @@
|
|
|
2289
2359
|
};
|
|
2290
2360
|
this._structCmd(doIt, undoIt, () => { const rr = Math.min(r0, grid.ROWS - 1); return { active: { r: rr, c }, extent: { r: rr, c } }; }, before, { type: 'deleteRows', r0, r1 });
|
|
2291
2361
|
}
|
|
2362
|
+
// 右端🗑の「最後の1行」用: 0行にはできない(削除ガード)ので、行は残して中身だけ空にする
|
|
2363
|
+
// (「消してから空行を足す」のと正味同じ結果を、0行に一瞬も落とさず 1 undo で)。readOnly セルはスキップ。
|
|
2364
|
+
clearRow(r) {
|
|
2365
|
+
if (r < 0 || r >= this.ROWS) return;
|
|
2366
|
+
if (this.mode === 'edit') this._commitActive();
|
|
2367
|
+
const before = this.snapSel(), changes = [];
|
|
2368
|
+
for (let c = 0; c < this.COLS; c++) { if (this._isReadOnly(r, c)) continue; this.setCell(r, c, '', changes); }
|
|
2369
|
+
this.pushCmd(changes, before, this.snapSel(), 'delete'); // changes 空(既に空/全 readOnly)なら pushCmd 側で no-op
|
|
2370
|
+
}
|
|
2292
2371
|
insertCol(ci, where = 'left') {
|
|
2293
2372
|
if (!this.allowInsertCols) return;
|
|
2294
2373
|
if (this._allRows) return; // v1: フィルタ中は構造変更ロック
|
|
@@ -2663,7 +2742,9 @@
|
|
|
2663
2742
|
for (let r = sel.r0; r <= sel.r1; r++) for (let c = sel.c0; c <= sel.c1; c++) if (!this._isHidden(c)) this.setCell(r, c, v, changes);
|
|
2664
2743
|
after = { active: { r: sel.r0, c: sel.c0 }, extent: { r: sel.r1, c: sel.c1 } };
|
|
2665
2744
|
} else {
|
|
2666
|
-
|
|
2745
|
+
// アクティブ列が隠し列だと(例: selectRow は active.c=0 を置く=先頭列が hiddenColumns の時)以下の indexOf が -1 になり、
|
|
2746
|
+
// 貼り付けが1列ずれて先頭値が落ちる。可視列へスナップしてから起点を取る(setActive と同じ「隠し列に置かない」規則)。
|
|
2747
|
+
const r0 = this.active.r, c0 = this._snapVisCol(this.active.c);
|
|
2667
2748
|
let maxw = 0; for (let i = 0; i < g.length; i++) maxw = Math.max(maxw, g[i].length);
|
|
2668
2749
|
const p0 = visAll.indexOf(c0), availCols = visAll.length - p0; // 貼り付けは可視列へ(隠し列をスキップ)
|
|
2669
2750
|
// オートグロー時に伸ばせる上限行数(maxRows があればそこまで/無ければ無制限)。非オートグローは現状の ROWS。
|
|
@@ -2835,6 +2916,7 @@
|
|
|
2835
2916
|
this.table.addEventListener('mousedown', (e) => {
|
|
2836
2917
|
if (e.button !== 0) return; // 右/中クリックは選択ドラッグを開始しない(右は contextmenu で処理)
|
|
2837
2918
|
const cls = e.target.classList;
|
|
2919
|
+
if (e.target.closest('.tg-rowdel-cell')) { e.preventDefault(); return; } // 削除列: 選択ドラッグを始めない(実削除は click で)。preventDefault で🗑のテキスト選択も抑止
|
|
2838
2920
|
if (cls && cls.contains('tg-colgrip')) { this._startColResize(e); return; } // 列幅リサイズ
|
|
2839
2921
|
if (cls && cls.contains('tg-rowgrip')) { this._startRowResize(e); return; } // 行高リサイズ
|
|
2840
2922
|
if (cls && cls.contains('tg-rowmove') && !this.rowReorderWhole) { this._startRowMove(e); return; } // ⠿つまみドラッグ移動('header'モードはセル全体扱い=_onHeaderMouseDown 経由で選択も伴う)
|
|
@@ -2858,6 +2940,13 @@
|
|
|
2858
2940
|
// ヘッダ全選択チェックは mousedown で処理済み。click のネイティブ・トグルを止めて二重反転を防ぐ
|
|
2859
2941
|
this.table.addEventListener('click', (e) => {
|
|
2860
2942
|
if (e.target.classList && e.target.classList.contains('tg-head-cb')) e.preventDefault();
|
|
2943
|
+
// 右端削除列の🗑クリック。通常は当該行を削除(remove_row と同一経路・1 undo で復活)。
|
|
2944
|
+
// 最後の1行(0行にはできない)は削除でなく「中身クリア(行は残す)」。minRows>1 のロック時は no-op(🗑 も無効表示)。
|
|
2945
|
+
const del = e.target.closest('.tg-rowdel-cell');
|
|
2946
|
+
if (del) {
|
|
2947
|
+
const tr = del.closest('tr[data-r]');
|
|
2948
|
+
if (tr) { const r = +tr.dataset.r; if (this.ROWS > Math.max(1, this.minRows)) this.deleteRows(r, r); else if (this.ROWS === 1) this.clearRow(r); }
|
|
2949
|
+
}
|
|
2861
2950
|
});
|
|
2862
2951
|
this.table.addEventListener('dblclick', (e) => {
|
|
2863
2952
|
// 列境界グリップのダブルクリック=オートフィット(Excel風)
|