@kupola/kupola 1.4.2 → 1.4.4
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/README.md +28 -3
- package/css/components-ext.css +1 -1
- package/css/kupola.css +3 -2
- package/css/table.css +74 -0
- package/dist/css/components-ext.css +158 -1
- package/dist/css/components.css +93 -1
- package/dist/css/kupola.css +13 -1
- package/dist/css/table.css +74 -0
- package/dist/css/theme-dark.css +5 -5
- package/dist/css/utilities.css +159 -1
- package/dist/kupola.cjs.js +214 -17409
- package/dist/kupola.cjs.js.map +1 -1
- package/dist/kupola.css +101 -4
- package/dist/kupola.esm.js +8196 -17225
- package/dist/kupola.esm.js.map +1 -1
- package/dist/kupola.min.css +1 -1
- package/dist/kupola.umd.js +214 -17415
- package/dist/kupola.umd.js.map +1 -1
- package/dist/types/kupola.d.ts +41 -25
- package/js/calendar.js +3 -10
- package/js/carousel.js +3 -10
- package/js/collapse.js +3 -10
- package/js/color-picker.js +5 -16
- package/js/component.js +4 -18
- package/js/countdown.js +3 -10
- package/js/data-bind.js +56 -36
- package/js/datepicker.js +16 -24
- package/js/depends.js +27 -16
- package/js/dialog.js +1 -5
- package/js/drawer.js +3 -11
- package/js/dropdown.js +11 -18
- package/js/dynamic-tags.js +3 -10
- package/js/fileupload.js +10 -12
- package/js/form.js +26 -20
- package/js/global-events.js +1 -14
- package/js/heatmap.js +3 -12
- package/js/i18n.js +1 -14
- package/js/icons.js +2 -2
- package/js/image-preview.js +1 -7
- package/js/initializer.js +0 -43
- package/js/kupola-core.js +1 -13
- package/js/kupola-lifecycle.js +1 -9
- package/js/message.js +1 -6
- package/js/modal.js +22 -20
- package/js/notification.js +1 -6
- package/js/numberinput.js +3 -10
- package/js/pagination.js +0 -5
- package/js/registry.js +0 -4
- package/js/select.js +5 -16
- package/js/slide-captcha.js +8 -21
- package/js/slider.js +3 -10
- package/js/statcard.js +3 -11
- package/js/table.js +240 -262
- package/js/tag.js +3 -10
- package/js/theme.js +11 -16
- package/js/timepicker.js +6 -19
- package/js/tooltip.js +3 -10
- package/js/utils.js +281 -1439
- package/js/validation.js +2 -7
- package/js/virtual-list.js +13 -38
- package/js/web-components.js +0 -9
- package/package.json +12 -7
- package/scripts/build-css.cjs +3 -2
- package/types/kupola.d.ts +41 -25
- package/version.json +10 -0
- package/dist/css/kupola.min.css +0 -1
- package/dist/icons.svg +0 -284
- package/dist/kupola.min.js +0 -2
- package/dist/kupola.min.js.map +0 -1
- /package/css/{colors_and_type.css → colors-and-type.css} +0 -0
- /package/dist/css/{colors_and_type.css → colors-and-type.css} +0 -0
package/js/table.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { ref } from './data-bind.js';
|
|
9
|
+
import { kupolaInitializer } from './initializer.js';
|
|
9
10
|
|
|
10
11
|
class KupolaTable {
|
|
11
12
|
constructor(element, options = {}) {
|
|
@@ -70,6 +71,10 @@ class KupolaTable {
|
|
|
70
71
|
// Virtual scroll
|
|
71
72
|
this.virtualScroll = options.virtualScroll || null; // { rowHeight: 40, overscan: 5 }
|
|
72
73
|
this._scrollContainer = null;
|
|
74
|
+
this._scrollHandler = null; // 保存引用以便 destroy 移除
|
|
75
|
+
this._resizeCleanups = []; // 列宽拖拽清理函数
|
|
76
|
+
this._filterDebounceTimer = null;
|
|
77
|
+
this._reactiveCleanups = []; // ref() subscribe 清理
|
|
73
78
|
|
|
74
79
|
// Merge cells
|
|
75
80
|
this.mergeCells = options.mergeCells || null; // function(data) => [{row, col, rowSpan, colSpan}]
|
|
@@ -109,11 +114,15 @@ class KupolaTable {
|
|
|
109
114
|
setData(data) {
|
|
110
115
|
if (data && typeof data === 'object' && 'value' in data) {
|
|
111
116
|
this._data = Array.isArray(data.value) ? data.value : [];
|
|
112
|
-
data.
|
|
113
|
-
this.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
+
if (data.subscribe) {
|
|
118
|
+
this._reactiveCleanups.push(
|
|
119
|
+
data.subscribe((newVal) => {
|
|
120
|
+
this._data = Array.isArray(newVal) ? newVal : [];
|
|
121
|
+
this._total = this._data.length;
|
|
122
|
+
this.render();
|
|
123
|
+
})
|
|
124
|
+
);
|
|
125
|
+
}
|
|
117
126
|
} else if (Array.isArray(data)) {
|
|
118
127
|
this._data = data;
|
|
119
128
|
} else {
|
|
@@ -129,7 +138,11 @@ class KupolaTable {
|
|
|
129
138
|
setLoading(loading) {
|
|
130
139
|
if (loading && typeof loading === 'object' && 'value' in loading) {
|
|
131
140
|
this._loading = loading.value;
|
|
132
|
-
loading.
|
|
141
|
+
if (loading.subscribe) {
|
|
142
|
+
this._reactiveCleanups.push(
|
|
143
|
+
loading.subscribe((val) => { this._loading = val; this.render(); })
|
|
144
|
+
);
|
|
145
|
+
}
|
|
133
146
|
} else {
|
|
134
147
|
this._loading = !!loading;
|
|
135
148
|
}
|
|
@@ -305,67 +318,15 @@ class KupolaTable {
|
|
|
305
318
|
const thead = document.createElement('thead');
|
|
306
319
|
const tr = document.createElement('tr');
|
|
307
320
|
|
|
308
|
-
|
|
309
|
-
if (this.selection) {
|
|
310
|
-
const th = document.createElement('th');
|
|
311
|
-
th.className = 'kupola-table-col-selection';
|
|
312
|
-
if (this.selection === 'checkbox') {
|
|
313
|
-
const cb = document.createElement('input');
|
|
314
|
-
cb.type = 'checkbox';
|
|
315
|
-
const pageData = this.getProcessedData();
|
|
316
|
-
const allKeys = pageData.map(r => r[this.rowKey]);
|
|
317
|
-
cb.checked = allKeys.length > 0 && allKeys.every(k => this._selectedKeys.has(k));
|
|
318
|
-
cb.addEventListener('change', () => cb.checked ? this.selectAll() : this.deselectAll());
|
|
319
|
-
th.appendChild(cb);
|
|
320
|
-
}
|
|
321
|
-
tr.appendChild(th);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// Expand column
|
|
321
|
+
if (this.selection) this._renderSelectionHeader(tr);
|
|
325
322
|
if (this.expandable) {
|
|
326
323
|
const th = document.createElement('th');
|
|
327
324
|
th.className = 'kupola-table-col-expand';
|
|
328
325
|
tr.appendChild(th);
|
|
329
326
|
}
|
|
330
327
|
|
|
331
|
-
// Data columns
|
|
332
328
|
this.columns.forEach(col => {
|
|
333
|
-
const th =
|
|
334
|
-
th.textContent = col.title || col.key;
|
|
335
|
-
if (col.width) th.style.width = typeof col.width === 'number' ? col.width + 'px' : col.width;
|
|
336
|
-
if (col.minWidth) th.style.minWidth = typeof col.minWidth === 'number' ? col.minWidth + 'px' : col.minWidth;
|
|
337
|
-
if (col.align) th.style.textAlign = col.align;
|
|
338
|
-
if (col.fixed) th.setAttribute('data-fixed', col.fixed);
|
|
339
|
-
|
|
340
|
-
// Sortable
|
|
341
|
-
if (col.sortable) {
|
|
342
|
-
th.classList.add('kupola-table-sortable');
|
|
343
|
-
const sortInfo = this._sorts.find(s => s.key === col.key);
|
|
344
|
-
if (sortInfo) th.classList.add(`kupola-table-sort-${sortInfo.order}`);
|
|
345
|
-
th.addEventListener('click', (e) => {
|
|
346
|
-
if (this.resizable && e.target.classList.contains('kupola-table-resize-handle')) return;
|
|
347
|
-
this._handleSort(col.key);
|
|
348
|
-
});
|
|
349
|
-
const indicator = document.createElement('span');
|
|
350
|
-
indicator.className = 'kupola-table-sort-icon';
|
|
351
|
-
if (sortInfo) {
|
|
352
|
-
indicator.textContent = this.multiSort
|
|
353
|
-
? ` ${this._sorts.indexOf(sortInfo) + 1}${sortInfo.order === 'asc' ? '▲' : '▼'}`
|
|
354
|
-
: (sortInfo.order === 'asc' ? ' ▲' : ' ▼');
|
|
355
|
-
} else {
|
|
356
|
-
indicator.textContent = ' ⇅';
|
|
357
|
-
}
|
|
358
|
-
th.appendChild(indicator);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
// Resize handle
|
|
362
|
-
if (this.resizable && col.key !== this.columns[this.columns.length - 1]?.key) {
|
|
363
|
-
const handle = document.createElement('span');
|
|
364
|
-
handle.className = 'kupola-table-resize-handle';
|
|
365
|
-
handle.setAttribute('data-col-key', col.key);
|
|
366
|
-
th.appendChild(handle);
|
|
367
|
-
}
|
|
368
|
-
|
|
329
|
+
const th = this._renderColumnHeader(col);
|
|
369
330
|
tr.appendChild(th);
|
|
370
331
|
});
|
|
371
332
|
|
|
@@ -373,6 +334,62 @@ class KupolaTable {
|
|
|
373
334
|
return thead;
|
|
374
335
|
}
|
|
375
336
|
|
|
337
|
+
_renderSelectionHeader(tr) {
|
|
338
|
+
const th = document.createElement('th');
|
|
339
|
+
th.className = 'kupola-table-col-selection';
|
|
340
|
+
if (this.selection === 'checkbox') {
|
|
341
|
+
const cb = document.createElement('input');
|
|
342
|
+
cb.type = 'checkbox';
|
|
343
|
+
const pageData = this.getProcessedData();
|
|
344
|
+
const allKeys = pageData.map(r => r[this.rowKey]);
|
|
345
|
+
cb.checked = allKeys.length > 0 && allKeys.every(k => this._selectedKeys.has(k));
|
|
346
|
+
cb.addEventListener('change', () => cb.checked ? this.selectAll() : this.deselectAll());
|
|
347
|
+
th.appendChild(cb);
|
|
348
|
+
}
|
|
349
|
+
tr.appendChild(th);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
_renderColumnHeader(col) {
|
|
353
|
+
const th = document.createElement('th');
|
|
354
|
+
th.textContent = col.title || col.key;
|
|
355
|
+
if (col.width) th.style.width = typeof col.width === 'number' ? col.width + 'px' : col.width;
|
|
356
|
+
if (col.minWidth) th.style.minWidth = typeof col.minWidth === 'number' ? col.minWidth + 'px' : col.minWidth;
|
|
357
|
+
if (col.align) th.style.textAlign = col.align;
|
|
358
|
+
if (col.fixed) th.setAttribute('data-fixed', col.fixed);
|
|
359
|
+
|
|
360
|
+
if (col.sortable) this._renderSortIndicator(th, col);
|
|
361
|
+
|
|
362
|
+
// Resize handle
|
|
363
|
+
if (this.resizable && col.key !== this.columns[this.columns.length - 1]?.key) {
|
|
364
|
+
const handle = document.createElement('span');
|
|
365
|
+
handle.className = 'kupola-table-resize-handle';
|
|
366
|
+
handle.setAttribute('data-col-key', col.key);
|
|
367
|
+
th.appendChild(handle);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return th;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
_renderSortIndicator(th, col) {
|
|
374
|
+
th.classList.add('kupola-table-sortable');
|
|
375
|
+
const sortInfo = this._sorts.find(s => s.key === col.key);
|
|
376
|
+
if (sortInfo) th.classList.add(`kupola-table-sort-${sortInfo.order}`);
|
|
377
|
+
th.addEventListener('click', (e) => {
|
|
378
|
+
if (this.resizable && e.target.classList.contains('kupola-table-resize-handle')) return;
|
|
379
|
+
this._handleSort(col.key);
|
|
380
|
+
});
|
|
381
|
+
const indicator = document.createElement('span');
|
|
382
|
+
indicator.className = 'kupola-table-sort-icon';
|
|
383
|
+
if (sortInfo) {
|
|
384
|
+
indicator.textContent = this.multiSort
|
|
385
|
+
? ` ${this._sorts.indexOf(sortInfo) + 1}${sortInfo.order === 'asc' ? '▲' : '▼'}`
|
|
386
|
+
: (sortInfo.order === 'asc' ? ' ▲' : ' ▼');
|
|
387
|
+
} else {
|
|
388
|
+
indicator.textContent = ' ⇅';
|
|
389
|
+
}
|
|
390
|
+
th.appendChild(indicator);
|
|
391
|
+
}
|
|
392
|
+
|
|
376
393
|
_renderTbody(data) {
|
|
377
394
|
const tbody = document.createElement('tbody');
|
|
378
395
|
|
|
@@ -424,122 +441,129 @@ class KupolaTable {
|
|
|
424
441
|
tr.classList.add('kupola-table-draggable');
|
|
425
442
|
}
|
|
426
443
|
|
|
427
|
-
|
|
428
|
-
if (this.
|
|
429
|
-
const td = document.createElement('td');
|
|
430
|
-
td.className = 'kupola-table-col-selection';
|
|
431
|
-
const input = document.createElement('input');
|
|
432
|
-
input.type = this.selection;
|
|
433
|
-
input.checked = isSelected;
|
|
434
|
-
input.addEventListener('change', () => {
|
|
435
|
-
if (this.selection === 'radio') {
|
|
436
|
-
this._selectedKeys.clear();
|
|
437
|
-
this._selectedKeys.add(key);
|
|
438
|
-
} else {
|
|
439
|
-
isSelected ? this._selectedKeys.delete(key) : this._selectedKeys.add(key);
|
|
440
|
-
}
|
|
441
|
-
this.selectedKeys.value = [...this._selectedKeys];
|
|
442
|
-
if (this.onSelect) this.onSelect([...this._selectedKeys], this.getSelectedRows());
|
|
443
|
-
this.render();
|
|
444
|
-
});
|
|
445
|
-
td.appendChild(input);
|
|
446
|
-
tr.appendChild(td);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// Expand cell
|
|
450
|
-
if (this.expandable) {
|
|
451
|
-
const td = document.createElement('td');
|
|
452
|
-
td.className = 'kupola-table-col-expand';
|
|
453
|
-
const btn = document.createElement('button');
|
|
454
|
-
btn.className = 'kupola-table-expand-btn';
|
|
455
|
-
const isExpanded = this._expandedKeys.has(key);
|
|
456
|
-
btn.textContent = isExpanded ? '▼' : '▶';
|
|
457
|
-
btn.type = 'button';
|
|
458
|
-
btn.addEventListener('click', () => this._toggleExpand(key));
|
|
459
|
-
td.appendChild(btn);
|
|
460
|
-
tr.appendChild(td);
|
|
461
|
-
}
|
|
444
|
+
if (this.selection) this._renderSelectionCell(tr, key, isSelected);
|
|
445
|
+
if (this.expandable) this._renderExpandCell(tr, key);
|
|
462
446
|
|
|
463
447
|
// Data cells
|
|
464
448
|
this.columns.forEach((col, colIndex) => {
|
|
465
|
-
// Skip merged cells
|
|
466
449
|
if (skipCells.has(`${rowIndex}-${colIndex}`)) return;
|
|
450
|
+
const td = this._renderDataCell(row, rowIndex, key, col, colIndex, skipCells, mergeMap);
|
|
451
|
+
tr.appendChild(td);
|
|
452
|
+
});
|
|
467
453
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
}
|
|
454
|
+
if (this.onRowClick) {
|
|
455
|
+
tr.style.cursor = 'pointer';
|
|
456
|
+
tr.addEventListener('click', (e) => {
|
|
457
|
+
if (e.target.closest('.kupola-table-expand-btn, .kupola-table-tree-toggle, input, button')) return;
|
|
458
|
+
this.onRowClick(row, rowIndex, e);
|
|
459
|
+
});
|
|
460
|
+
}
|
|
474
461
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
462
|
+
return tr;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
_renderSelectionCell(tr, key, isSelected) {
|
|
466
|
+
const td = document.createElement('td');
|
|
467
|
+
td.className = 'kupola-table-col-selection';
|
|
468
|
+
const input = document.createElement('input');
|
|
469
|
+
input.type = this.selection;
|
|
470
|
+
input.checked = isSelected;
|
|
471
|
+
input.addEventListener('change', () => {
|
|
472
|
+
if (this.selection === 'radio') {
|
|
473
|
+
this._selectedKeys.clear();
|
|
474
|
+
this._selectedKeys.add(key);
|
|
475
|
+
} else {
|
|
476
|
+
isSelected ? this._selectedKeys.delete(key) : this._selectedKeys.add(key);
|
|
486
477
|
}
|
|
478
|
+
this.selectedKeys.value = [...this._selectedKeys];
|
|
479
|
+
if (this.onSelect) this.onSelect([...this._selectedKeys], this.getSelectedRows());
|
|
480
|
+
this.render();
|
|
481
|
+
});
|
|
482
|
+
td.appendChild(input);
|
|
483
|
+
tr.appendChild(td);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
_renderExpandCell(tr, key) {
|
|
487
|
+
const td = document.createElement('td');
|
|
488
|
+
td.className = 'kupola-table-col-expand';
|
|
489
|
+
const btn = document.createElement('button');
|
|
490
|
+
btn.className = 'kupola-table-expand-btn';
|
|
491
|
+
btn.textContent = this._expandedKeys.has(key) ? '▼' : '▶';
|
|
492
|
+
btn.type = 'button';
|
|
493
|
+
btn.addEventListener('click', () => this._toggleExpand(key));
|
|
494
|
+
td.appendChild(btn);
|
|
495
|
+
tr.appendChild(td);
|
|
496
|
+
}
|
|
487
497
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
const spacer = document.createElement('span');
|
|
506
|
-
spacer.className = 'kupola-table-tree-toggle-placeholder';
|
|
507
|
-
td.appendChild(spacer);
|
|
498
|
+
_renderDataCell(row, rowIndex, key, col, colIndex, skipCells, mergeMap) {
|
|
499
|
+
const td = document.createElement('td');
|
|
500
|
+
if (col.align) td.style.textAlign = col.align;
|
|
501
|
+
if (col.fixed) {
|
|
502
|
+
td.setAttribute('data-fixed', col.fixed);
|
|
503
|
+
td.classList.add(`kupola-table-fixed-${col.fixed}`);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Merge cells
|
|
507
|
+
const mergeInfo = mergeMap.get(`${rowIndex}-${colIndex}`);
|
|
508
|
+
if (mergeInfo) {
|
|
509
|
+
if (mergeInfo.rowSpan > 1) td.rowSpan = mergeInfo.rowSpan;
|
|
510
|
+
if (mergeInfo.colSpan > 1) td.colSpan = mergeInfo.colSpan;
|
|
511
|
+
for (let r = 0; r < (mergeInfo.rowSpan || 1); r++) {
|
|
512
|
+
for (let c = 0; c < (mergeInfo.colSpan || 1); c++) {
|
|
513
|
+
if (r === 0 && c === 0) continue;
|
|
514
|
+
skipCells.add(`${rowIndex + r}-${colIndex + c}`);
|
|
508
515
|
}
|
|
509
516
|
}
|
|
517
|
+
}
|
|
510
518
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
519
|
+
// Tree indent (first column only)
|
|
520
|
+
if (this.tree && colIndex === 0 && row._level > 0) {
|
|
521
|
+
this._renderTreeIndent(td, row);
|
|
522
|
+
}
|
|
514
523
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
const content = col.render(row[col.key], row, rowIndex);
|
|
519
|
-
if (typeof content === 'string') td.innerHTML = content;
|
|
520
|
-
else if (content instanceof HTMLElement) td.appendChild(content);
|
|
521
|
-
} else {
|
|
522
|
-
td.textContent = row[col.key] ?? '';
|
|
523
|
-
}
|
|
524
|
+
// Cell content: editing / custom render / plain text
|
|
525
|
+
const isEditing = this._editingCell &&
|
|
526
|
+
this._editingCell.rowKey === key && this._editingCell.colKey === col.key;
|
|
524
527
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
528
|
+
if (isEditing) {
|
|
529
|
+
td.appendChild(this._renderEditCell(col, row));
|
|
530
|
+
} else if (col.render) {
|
|
531
|
+
const content = col.render(row[col.key], row, rowIndex);
|
|
532
|
+
if (typeof content === 'string') td.innerHTML = content;
|
|
533
|
+
else if (content instanceof HTMLElement) td.appendChild(content);
|
|
534
|
+
} else {
|
|
535
|
+
td.textContent = row[col.key] ?? '';
|
|
536
|
+
}
|
|
530
537
|
|
|
531
|
-
|
|
532
|
-
|
|
538
|
+
// Double-click to edit
|
|
539
|
+
if (this.editable && !isEditing && col.editable !== false) {
|
|
540
|
+
td.classList.add('kupola-table-editable-cell');
|
|
541
|
+
td.addEventListener('dblclick', () => this._startEdit(key, col.key, row[col.key]));
|
|
542
|
+
}
|
|
533
543
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
544
|
+
return td;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
_renderTreeIndent(td, row) {
|
|
548
|
+
const indent = document.createElement('span');
|
|
549
|
+
indent.className = 'kupola-table-tree-indent';
|
|
550
|
+
indent.style.paddingLeft = (row._level * 20) + 'px';
|
|
551
|
+
td.appendChild(indent);
|
|
552
|
+
if (row._hasChildren) {
|
|
553
|
+
const toggle = document.createElement('button');
|
|
554
|
+
toggle.className = 'kupola-table-tree-toggle';
|
|
555
|
+
toggle.textContent = this._treeExpandedKeys.has(row[this.rowKey]) ? '▼' : '▶';
|
|
556
|
+
toggle.type = 'button';
|
|
557
|
+
toggle.addEventListener('click', (e) => {
|
|
558
|
+
e.stopPropagation();
|
|
559
|
+
this._toggleTreeExpand(row[this.rowKey]);
|
|
539
560
|
});
|
|
561
|
+
td.appendChild(toggle);
|
|
562
|
+
} else {
|
|
563
|
+
const spacer = document.createElement('span');
|
|
564
|
+
spacer.className = 'kupola-table-tree-toggle-placeholder';
|
|
565
|
+
td.appendChild(spacer);
|
|
540
566
|
}
|
|
541
|
-
|
|
542
|
-
return tr;
|
|
543
567
|
}
|
|
544
568
|
|
|
545
569
|
_renderStatusRow(text, className) {
|
|
@@ -583,12 +607,16 @@ class KupolaTable {
|
|
|
583
607
|
bottomSpacer.style.height = '0px';
|
|
584
608
|
tbody.appendChild(bottomSpacer);
|
|
585
609
|
|
|
586
|
-
// Attach scroll listener
|
|
610
|
+
// Attach scroll listener (移除旧的再绑新的,避免重复)
|
|
587
611
|
const wrapper = this.element.querySelector('.kupola-table-container');
|
|
588
612
|
if (wrapper) {
|
|
589
613
|
wrapper.style.maxHeight = this.virtualScroll.maxHeight || '400px';
|
|
590
614
|
wrapper.style.overflowY = 'auto';
|
|
591
|
-
|
|
615
|
+
if (this._scrollHandler) {
|
|
616
|
+
wrapper.removeEventListener('scroll', this._scrollHandler);
|
|
617
|
+
}
|
|
618
|
+
this._scrollHandler = () => this._updateVirtualScroll();
|
|
619
|
+
wrapper.addEventListener('scroll', this._scrollHandler);
|
|
592
620
|
}
|
|
593
621
|
|
|
594
622
|
return tbody;
|
|
@@ -807,6 +835,8 @@ class KupolaTable {
|
|
|
807
835
|
};
|
|
808
836
|
document.addEventListener('mousemove', onMouseMove);
|
|
809
837
|
document.addEventListener('mouseup', onMouseUp);
|
|
838
|
+
// 注册清理函数,destroy 时可强制移除
|
|
839
|
+
this._resizeCleanups.push(onMouseUp);
|
|
810
840
|
});
|
|
811
841
|
});
|
|
812
842
|
}
|
|
@@ -829,21 +859,7 @@ class KupolaTable {
|
|
|
829
859
|
row.classList.add('kupola-table-drag-over');
|
|
830
860
|
});
|
|
831
861
|
row.addEventListener('dragleave', () => row.classList.remove('kupola-table-drag-over'));
|
|
832
|
-
row.addEventListener('drop', (e) =>
|
|
833
|
-
e.preventDefault();
|
|
834
|
-
row.classList.remove('kupola-table-drag-over');
|
|
835
|
-
if (!this._dragState) return;
|
|
836
|
-
const toKey = row.getAttribute('data-row-key');
|
|
837
|
-
if (this._dragState.fromKey === toKey) return;
|
|
838
|
-
const fromIdx = this._data.findIndex(r => String(r[this.rowKey]) === this._dragState.fromKey);
|
|
839
|
-
const toIdx = this._data.findIndex(r => String(r[this.rowKey]) === toKey);
|
|
840
|
-
if (fromIdx >= 0 && toIdx >= 0) {
|
|
841
|
-
const [moved] = this._data.splice(fromIdx, 1);
|
|
842
|
-
this._data.splice(toIdx, 0, moved);
|
|
843
|
-
if (this.onRowDragEnd) this.onRowDragEnd(moved, fromIdx, toIdx, this._data);
|
|
844
|
-
this.render();
|
|
845
|
-
}
|
|
846
|
-
});
|
|
862
|
+
row.addEventListener('drop', (e) => this._handleRowDrop(e, row));
|
|
847
863
|
row.addEventListener('dragend', () => {
|
|
848
864
|
row.classList.remove('kupola-table-dragging');
|
|
849
865
|
this._dragState = null;
|
|
@@ -851,6 +867,22 @@ class KupolaTable {
|
|
|
851
867
|
});
|
|
852
868
|
}
|
|
853
869
|
|
|
870
|
+
_handleRowDrop(e, targetRow) {
|
|
871
|
+
e.preventDefault();
|
|
872
|
+
targetRow.classList.remove('kupola-table-drag-over');
|
|
873
|
+
if (!this._dragState) return;
|
|
874
|
+
const toKey = targetRow.getAttribute('data-row-key');
|
|
875
|
+
if (this._dragState.fromKey === toKey) return;
|
|
876
|
+
const fromIdx = this._data.findIndex(r => String(r[this.rowKey]) === this._dragState.fromKey);
|
|
877
|
+
const toIdx = this._data.findIndex(r => String(r[this.rowKey]) === toKey);
|
|
878
|
+
if (fromIdx >= 0 && toIdx >= 0) {
|
|
879
|
+
const [moved] = this._data.splice(fromIdx, 1);
|
|
880
|
+
this._data.splice(toIdx, 0, moved);
|
|
881
|
+
if (this.onRowDragEnd) this.onRowDragEnd(moved, fromIdx, toIdx, this._data);
|
|
882
|
+
this.render();
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
854
886
|
// ================================================================
|
|
855
887
|
// STICKY COLUMNS
|
|
856
888
|
// ================================================================
|
|
@@ -914,10 +946,9 @@ class KupolaTable {
|
|
|
914
946
|
filterInput.className = 'ds-input kupola-table-filter-input';
|
|
915
947
|
filterInput.placeholder = this.options.filterPlaceholder || '搜索...';
|
|
916
948
|
filterInput.value = this._filterText;
|
|
917
|
-
let debounceTimer;
|
|
918
949
|
filterInput.addEventListener('input', () => {
|
|
919
|
-
clearTimeout(
|
|
920
|
-
|
|
950
|
+
clearTimeout(this._filterDebounceTimer);
|
|
951
|
+
this._filterDebounceTimer = setTimeout(() => {
|
|
921
952
|
this._filterText = filterInput.value;
|
|
922
953
|
this._currentPage = 1;
|
|
923
954
|
this.filterText.value = this._filterText;
|
|
@@ -1105,91 +1136,41 @@ class KupolaTable {
|
|
|
1105
1136
|
}
|
|
1106
1137
|
|
|
1107
1138
|
destroy() {
|
|
1139
|
+
// 移除虚拟滚动 scroll listener
|
|
1140
|
+
if (this._scrollHandler) {
|
|
1141
|
+
const wrapper = this.element.querySelector('.kupola-table-container');
|
|
1142
|
+
if (wrapper) wrapper.removeEventListener('scroll', this._scrollHandler);
|
|
1143
|
+
this._scrollHandler = null;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
// 清除 filter debounce timer
|
|
1147
|
+
if (this._filterDebounceTimer) {
|
|
1148
|
+
clearTimeout(this._filterDebounceTimer);
|
|
1149
|
+
this._filterDebounceTimer = null;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
// 清理列宽拖拽残留的 document listener
|
|
1153
|
+
this._resizeCleanups.forEach(fn => fn());
|
|
1154
|
+
this._resizeCleanups = [];
|
|
1155
|
+
|
|
1156
|
+
// 清理 ref() 订阅
|
|
1157
|
+
this._reactiveCleanups.forEach(sub => sub.unsubscribe());
|
|
1158
|
+
this._reactiveCleanups = [];
|
|
1159
|
+
|
|
1160
|
+
// 清理 DOM 和 class
|
|
1108
1161
|
this.element.innerHTML = '';
|
|
1109
1162
|
this.element.classList.remove('kupola-table-wrapper', 'kupola-table-virtual-wrapper');
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
1163
|
|
|
1113
|
-
//
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
const style = document.createElement('style');
|
|
1121
|
-
style.textContent = `
|
|
1122
|
-
.kupola-table-wrapper { width: 100%; }
|
|
1123
|
-
.kupola-table-container { overflow-x: auto; }
|
|
1124
|
-
.kupola-table { width: 100%; border-collapse: collapse; font-size: 14px; }
|
|
1125
|
-
.kupola-table th, .kupola-table td { padding: 12px 16px; text-align: left; border-bottom: 1px solid #e8e8e8; }
|
|
1126
|
-
.kupola-table th { background: #fafafa; font-weight: 600; color: #333; white-space: nowrap; }
|
|
1127
|
-
.kupola-table-striped tbody tr:nth-child(even) { background: #fafafa; }
|
|
1128
|
-
.kupola-table-hover tbody tr:hover { background: #f0f7ff; }
|
|
1129
|
-
.kupola-table-bordered { border: 1px solid #e8e8e8; }
|
|
1130
|
-
.kupola-table-bordered th, .kupola-table-bordered td { border: 1px solid #e8e8e8; }
|
|
1131
|
-
.kupola-table-compact th, .kupola-table-compact td { padding: 8px 12px; }
|
|
1132
|
-
.kupola-table-sortable { cursor: pointer; user-select: none; position: relative; }
|
|
1133
|
-
.kupola-table-sortable:hover { background: #f0f0f0; }
|
|
1134
|
-
.kupola-table-sort-icon { font-size: 12px; opacity: 0.5; margin-left: 4px; }
|
|
1135
|
-
.kupola-table-sort-asc .kupola-table-sort-icon,
|
|
1136
|
-
.kupola-table-sort-desc .kupola-table-sort-icon { opacity: 1; color: #1890ff; }
|
|
1137
|
-
.kupola-table-empty, .kupola-table-loading { text-align: center; padding: 40px 16px !important; color: #999; }
|
|
1138
|
-
.kupola-table-toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; flex-wrap: wrap; gap: 8px; }
|
|
1139
|
-
.kupola-table-toolbar-right { display: flex; align-items: center; gap: 8px; }
|
|
1140
|
-
.kupola-table-filter-input { padding: 6px 12px; border: 1px solid #d9d9d9; border-radius: 4px; font-size: 14px; width: 240px; }
|
|
1141
|
-
.kupola-table-filter-input:focus { outline: none; border-color: #1890ff; box-shadow: 0 0 0 2px rgba(24,144,255,0.1); }
|
|
1142
|
-
.kupola-table-info { color: #999; font-size: 13px; }
|
|
1143
|
-
.kupola-table-selection-info { color: #1890ff; font-size: 13px; font-weight: 500; }
|
|
1144
|
-
.kupola-table-col-selection { width: 40px; text-align: center; }
|
|
1145
|
-
.kupola-table-col-expand { width: 40px; text-align: center; }
|
|
1146
|
-
.kupola-table-expand-btn { background: none; border: none; cursor: pointer; font-size: 12px; padding: 2px 6px; color: #666; }
|
|
1147
|
-
.kupola-table-expand-btn:hover { color: #1890ff; }
|
|
1148
|
-
.kupola-table-expand-row td { background: #fafafa; padding: 16px; }
|
|
1149
|
-
.kupola-table-row-selected { background: #e6f7ff !important; }
|
|
1150
|
-
.kupola-table-row-selected:hover { background: #bae7ff !important; }
|
|
1151
|
-
/* Resize */
|
|
1152
|
-
.kupola-table-resize-handle { position: absolute; right: 0; top: 0; bottom: 0; width: 6px; cursor: col-resize; background: transparent; }
|
|
1153
|
-
.kupola-table-resize-handle:hover { background: #1890ff; opacity: 0.3; }
|
|
1154
|
-
/* Drag */
|
|
1155
|
-
.kupola-table-draggable { transition: opacity 0.2s; }
|
|
1156
|
-
.kupola-table-dragging { opacity: 0.4; }
|
|
1157
|
-
.kupola-table-drag-over { border-top: 2px solid #1890ff !important; }
|
|
1158
|
-
/* Edit */
|
|
1159
|
-
.kupola-table-editable-cell { cursor: text; }
|
|
1160
|
-
.kupola-table-editable-cell:hover { background: #e6f7ff; }
|
|
1161
|
-
.kupola-table-edit-cell { display: flex; gap: 4px; align-items: center; }
|
|
1162
|
-
.kupola-table-edit-input { flex: 1; padding: 2px 6px; font-size: 13px; }
|
|
1163
|
-
.kupola-table-edit-actions { display: flex; gap: 2px; }
|
|
1164
|
-
.kupola-table-edit-save, .kupola-table-edit-cancel { background: none; border: 1px solid #d9d9d9; border-radius: 3px; cursor: pointer; padding: 2px 6px; font-size: 12px; }
|
|
1165
|
-
.kupola-table-edit-save { color: #52c41a; border-color: #52c41a; }
|
|
1166
|
-
.kupola-table-edit-cancel { color: #ff4d4f; border-color: #ff4d4f; }
|
|
1167
|
-
.kupola-table-edit-save:hover { background: #f6ffed; }
|
|
1168
|
-
.kupola-table-edit-cancel:hover { background: #fff2f0; }
|
|
1169
|
-
/* Tree */
|
|
1170
|
-
.kupola-table-tree-indent { display: inline-block; }
|
|
1171
|
-
.kupola-table-tree-toggle { background: none; border: none; cursor: pointer; font-size: 10px; padding: 0 4px; color: #666; }
|
|
1172
|
-
.kupola-table-tree-toggle:hover { color: #1890ff; }
|
|
1173
|
-
.kupola-table-tree-toggle-placeholder { display: inline-block; width: 18px; }
|
|
1174
|
-
/* Virtual */
|
|
1175
|
-
.kupola-table-virtual-wrapper .kupola-table-container { overflow-y: auto; }
|
|
1176
|
-
/* Pagination */
|
|
1177
|
-
.kupola-table-pagination { display: flex; justify-content: flex-end; align-items: center; gap: 12px; margin-top: 16px; padding: 8px 0; }
|
|
1178
|
-
.kupola-table-pages { display: flex; gap: 4px; align-items: center; }
|
|
1179
|
-
.kupola-table-page-btn { min-width: 32px; height: 32px; border: 1px solid #d9d9d9; border-radius: 4px; background: #fff; cursor: pointer; font-size: 14px; display: flex; align-items: center; justify-content: center; }
|
|
1180
|
-
.kupola-table-page-btn:hover:not(:disabled):not(.active) { border-color: #1890ff; color: #1890ff; }
|
|
1181
|
-
.kupola-table-page-btn.active { background: #1890ff; color: #fff; border-color: #1890ff; }
|
|
1182
|
-
.kupola-table-page-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
1183
|
-
.kupola-table-page-ellipsis { padding: 0 4px; color: #999; }
|
|
1184
|
-
.kupola-table-page-size { padding: 4px 8px; border: 1px solid #d9d9d9; border-radius: 4px; font-size: 13px; }
|
|
1185
|
-
.kupola-table-page-info { color: #999; font-size: 13px; }
|
|
1186
|
-
`;
|
|
1187
|
-
document.head.appendChild(style);
|
|
1188
|
-
stylesInjected = true;
|
|
1164
|
+
// 清理数据引用
|
|
1165
|
+
this._data = [];
|
|
1166
|
+
this._virtualData = null;
|
|
1167
|
+
this._dragState = null;
|
|
1168
|
+
this._editingCell = null;
|
|
1169
|
+
this._editBuffer = {};
|
|
1170
|
+
}
|
|
1189
1171
|
}
|
|
1190
1172
|
|
|
1191
1173
|
function initTable(element, options) {
|
|
1192
|
-
injectTableStyles();
|
|
1193
1174
|
return new KupolaTable(element, options);
|
|
1194
1175
|
}
|
|
1195
1176
|
|
|
@@ -1204,7 +1185,4 @@ function initAllTables() {
|
|
|
1204
1185
|
|
|
1205
1186
|
export { KupolaTable, initTable, initAllTables };
|
|
1206
1187
|
|
|
1207
|
-
|
|
1208
|
-
window.KupolaTable = KupolaTable;
|
|
1209
|
-
window.initTable = initTable;
|
|
1210
|
-
}
|
|
1188
|
+
kupolaInitializer.register('table', initTable);
|
package/js/tag.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { kupolaInitializer } from './initializer.js';
|
|
2
|
+
|
|
1
3
|
class Tag {
|
|
2
4
|
constructor(element, options = {}) {
|
|
3
5
|
this.element = element;
|
|
@@ -325,13 +327,4 @@ function initTags() {
|
|
|
325
327
|
|
|
326
328
|
export { Tag, initTags, initTag, cleanupTag };
|
|
327
329
|
|
|
328
|
-
|
|
329
|
-
window.Tag = Tag;
|
|
330
|
-
window.initTag = initTag;
|
|
331
|
-
window.cleanupTag = cleanupTag;
|
|
332
|
-
window.initTags = initTags;
|
|
333
|
-
|
|
334
|
-
if (window.kupolaInitializer) {
|
|
335
|
-
window.kupolaInitializer.register('tag', initTag, cleanupTag);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
330
|
+
kupolaInitializer.register('tag', initTag, cleanupTag);
|