@livenetworks/ashlar 2.1.9 → 2.1.11
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/components/COMPONENTS.md +1 -1
- package/components/ln-data-store/README.md +19 -0
- package/components/ln-data-store/ln-data-store.js +1 -1
- package/components/ln-data-store/ln-data-store.schema.json +92 -84
- package/components/ln-data-store/src/data-store-model.js +17 -0
- package/components/ln-data-store/src/ln-data-store.js +77 -1
- package/components/ln-debug/ln-debug.js +1 -1
- package/components/ln-debug/src/generated-attributes.js +2 -3
- package/components/ln-table/README.md +90 -41
- package/components/ln-table/ln-table.js +1 -1
- package/components/ln-table/ln-table.schema.json +19 -35
- package/components/ln-table/src/ln-table.js +370 -411
- package/components/ln-table/src/table-model.js +31 -0
- package/ln-ashlar.html-data.json +10 -15
- package/package.json +1 -1
- package/theme/components/_ln-table.scss +9 -0
- package/theme/config/mixins/_ln-table.scss +18 -10
- package/theme/config/mixins/_table.scss +15 -2
- package/web-types.json +19 -26
|
@@ -1,60 +1,20 @@
|
|
|
1
|
-
import { cloneTemplateScoped,
|
|
2
|
-
import { calculateSelectionState, calculateVirtualWindow, toggleRowSelection, toggleSelectAll } from './table-model.js';
|
|
1
|
+
import { cloneTemplateScoped, dispatch, fill, fillTemplate, readValue, registerComponent, requestData, attrBool, attrStr, attrSpec, defineAttrs } from '../../ln-core';
|
|
2
|
+
import { calculateSelectionState, calculateVirtualWindow, calculatePoolSize, toggleRowSelection, toggleSelectAll } from './table-model.js';
|
|
3
3
|
|
|
4
4
|
(function () {
|
|
5
5
|
const DOM_SELECTOR = 'data-ln-table';
|
|
6
6
|
const DOM_ATTRIBUTE = 'lnTable';
|
|
7
|
-
const EMPTY_TEMPLATE = 'data-ln-table-empty';
|
|
8
7
|
// Tuning constant — duplicated in ln-data-table for component independence
|
|
9
8
|
const VIRTUAL_THRESHOLD = 200;
|
|
10
9
|
const BUFFER_ROWS = 15;
|
|
11
|
-
const WINDOW_DEFAULT = 1000;
|
|
12
|
-
const WINDOW_PAGE = 200;
|
|
13
|
-
const FETCH_DEBOUNCE = 120;
|
|
14
10
|
|
|
15
11
|
if (window[DOM_ATTRIBUTE] !== undefined) return;
|
|
16
12
|
|
|
17
13
|
// ─── Attribute Contract (SSOT) ──────────────────────────
|
|
18
|
-
function _applyWindow(inst, val) {
|
|
19
|
-
if (!inst || !inst.isDataDriven) return;
|
|
20
|
-
const present = inst.dom.hasAttribute('data-ln-table-window');
|
|
21
|
-
if (present && !inst._windowed) {
|
|
22
|
-
inst._enterWindowedMode();
|
|
23
|
-
inst._kickWindowInitial();
|
|
24
|
-
} else if (!present && inst._windowed) {
|
|
25
|
-
inst._exitWindowedMode();
|
|
26
|
-
} else if (present && inst._windowed) {
|
|
27
|
-
const v = parseInt(val, 10);
|
|
28
|
-
if (v > 0) inst._cache.configure({ windowSize: v });
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function _applyWindowPage(inst, val) {
|
|
33
|
-
if (!inst || !inst.isDataDriven || !inst._windowed || !inst._cache) return;
|
|
34
|
-
const v = parseInt(val, 10);
|
|
35
|
-
if (v > 0) inst._cache.configure({ pageSize: v });
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function _applyWindowThreshold(inst, val) {
|
|
39
|
-
if (!inst || !inst.isDataDriven || !inst._windowed || !inst._cache) return;
|
|
40
|
-
const v = parseInt(val, 10);
|
|
41
|
-
if (v >= 0) inst._cache.configure({ threshold: v });
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function _applyCount(inst, val) {
|
|
45
|
-
if (!inst || !inst.isDataDriven || !inst._windowed || !inst._cache) return;
|
|
46
|
-
const v = parseInt(val, 10);
|
|
47
|
-
if (v >= 0) inst._cache.setGrandTotal(v);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
14
|
const ATTRIBUTES = {
|
|
51
15
|
'data-ln-table': { prop: 'name', type: 'string', read: attrStr, fallback: '', description: 'Table instance name or identifier' },
|
|
52
16
|
'data-ln-table-source': { prop: 'source', type: 'string', read: attrStr, fallback: '', description: 'Source store or coordinator addressing' },
|
|
53
17
|
'data-ln-table-selectable': { prop: '_selectable', type: 'boolean', read: attrBool, description: 'Enables row selection check controls' },
|
|
54
|
-
'data-ln-table-window': { type: 'integer', fallback: WINDOW_DEFAULT, min: 10, effect: _applyWindow, description: 'Virtual scrolling window size in rows' },
|
|
55
|
-
'data-ln-table-window-page': { type: 'integer', fallback: WINDOW_PAGE, min: 5, effect: _applyWindowPage, description: 'Virtual scrolling slice page size' },
|
|
56
|
-
'data-ln-table-window-threshold': { type: 'integer', fallback: 50, min: 0, effect: _applyWindowThreshold, description: 'Scroll threshold margin in pixels to trigger page fetch' },
|
|
57
|
-
'data-ln-table-count': { type: 'integer', min: 0, effect: _applyCount, description: 'Total record count override for virtual scrollbar calculation' },
|
|
58
18
|
'data-ln-table-row': { type: 'marker', description: 'Table row template element' },
|
|
59
19
|
'data-ln-table-row-id': { type: 'string', description: 'Record identifier on row element' },
|
|
60
20
|
'data-ln-table-row-action': { type: 'trigger', description: 'Action trigger inside a table row' },
|
|
@@ -62,7 +22,6 @@ import { calculateSelectionState, calculateVirtualWindow, toggleRowSelection, to
|
|
|
62
22
|
'data-ln-table-col': { type: 'string', description: 'Column header identifier or field mapping' },
|
|
63
23
|
'data-ln-table-col-select': { type: 'trigger', description: 'Header select-all checkbox trigger' },
|
|
64
24
|
'data-ln-table-cell-attr': { type: 'string', description: 'Cell attribute template mapping' },
|
|
65
|
-
'data-ln-table-empty': { type: 'marker', description: 'Container for table empty state' },
|
|
66
25
|
'data-ln-table-select-all-label': { type: 'string', description: 'Accessibility label for select-all header trigger' }
|
|
67
26
|
};
|
|
68
27
|
|
|
@@ -73,17 +32,30 @@ import { calculateSelectionState, calculateVirtualWindow, toggleRowSelection, to
|
|
|
73
32
|
? new Intl.Collator(document.documentElement.lang || undefined, { sensitivity: 'base' })
|
|
74
33
|
: null;
|
|
75
34
|
|
|
76
|
-
function
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
35
|
+
function _setNumberValue(el, val) {
|
|
36
|
+
if (!el) return;
|
|
37
|
+
if (val == null || isNaN(val)) {
|
|
38
|
+
el.removeAttribute('data-ln-value');
|
|
39
|
+
if ('value' in el && el.tagName === 'DATA') el.value = '';
|
|
40
|
+
if (el.lnNumber) {
|
|
41
|
+
el.lnNumber.value = NaN;
|
|
42
|
+
} else {
|
|
43
|
+
el.textContent = '';
|
|
44
|
+
}
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const s = String(val);
|
|
48
|
+
el.setAttribute('data-ln-value', s);
|
|
49
|
+
if ('value' in el && el.tagName === 'DATA') el.value = s;
|
|
50
|
+
if (el.lnNumber) {
|
|
51
|
+
el.lnNumber.value = val;
|
|
52
|
+
} else {
|
|
53
|
+
el.textContent = s;
|
|
82
54
|
}
|
|
83
55
|
}
|
|
84
56
|
|
|
85
|
-
function _findScrollContainer(el) {
|
|
86
|
-
let p =
|
|
57
|
+
function _findScrollContainer(el, table) {
|
|
58
|
+
let p = table ? table.parentElement : el;
|
|
87
59
|
while (p && p !== document.body && p !== document.documentElement) {
|
|
88
60
|
const cs = getComputedStyle(p);
|
|
89
61
|
const oy = cs.overflowY;
|
|
@@ -119,6 +91,10 @@ import { calculateSelectionState, calculateVirtualWindow, toggleRowSelection, to
|
|
|
119
91
|
? this._selectedSpan.parentElement
|
|
120
92
|
: null;
|
|
121
93
|
}
|
|
94
|
+
this._loadingIndicator = dom.querySelector('[data-ln-table-loading]');
|
|
95
|
+
if (this._loadingIndicator) {
|
|
96
|
+
this._loadingIndicator.classList.add('hidden');
|
|
97
|
+
}
|
|
122
98
|
|
|
123
99
|
this.isDataDriven = dom.hasAttribute('data-ln-table-source');
|
|
124
100
|
|
|
@@ -139,6 +115,11 @@ import { calculateSelectionState, calculateVirtualWindow, toggleRowSelection, to
|
|
|
139
115
|
this._scrollHandler = null;
|
|
140
116
|
this._scrollContainer = null;
|
|
141
117
|
this._colgroup = null;
|
|
118
|
+
this._rowPool = null;
|
|
119
|
+
this._topSpacer = null;
|
|
120
|
+
this._topTd = null;
|
|
121
|
+
this._bottomSpacer = null;
|
|
122
|
+
this._bottomTd = null;
|
|
142
123
|
|
|
143
124
|
const self = this;
|
|
144
125
|
|
|
@@ -206,6 +187,7 @@ import { calculateSelectionState, calculateVirtualWindow, toggleRowSelection, to
|
|
|
206
187
|
|
|
207
188
|
if (this.isDataDriven) {
|
|
208
189
|
this.isLoaded = false;
|
|
190
|
+
this._initialLoaded = false;
|
|
209
191
|
this.totalCount = 0;
|
|
210
192
|
this.visibleCount = 0;
|
|
211
193
|
this.currentSort = null;
|
|
@@ -216,11 +198,6 @@ import { calculateSelectionState, calculateVirtualWindow, toggleRowSelection, to
|
|
|
216
198
|
this._lastFiltered = 0;
|
|
217
199
|
this._hasInitialSeed = false;
|
|
218
200
|
|
|
219
|
-
// Windowed (sliding-window) server-side virtualization — opt-in
|
|
220
|
-
this._windowed = false;
|
|
221
|
-
this._cache = null;
|
|
222
|
-
if (this.isDataDriven && dom.hasAttribute('data-ln-table-window')) this._enterWindowedMode();
|
|
223
|
-
|
|
224
201
|
// --- Event listeners ---
|
|
225
202
|
this._onSetData = function (e) {
|
|
226
203
|
const detail = e.detail || {};
|
|
@@ -232,15 +209,6 @@ import { calculateSelectionState, calculateVirtualWindow, toggleRowSelection, to
|
|
|
232
209
|
return;
|
|
233
210
|
}
|
|
234
211
|
|
|
235
|
-
if (self._windowed) {
|
|
236
|
-
// Only an accepted, authoritative page ends the refresh. A declined
|
|
237
|
-
// one means the source has not resolved the new query yet; a
|
|
238
|
-
// provisional one is the store's own answer, rendered while the
|
|
239
|
-
// server query is still out.
|
|
240
|
-
if (self._cache.ingest(detail) && !detail.provisional) dom.classList.remove('ln-table--loading');
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
212
|
self._data = newData;
|
|
245
213
|
self._lastTotal = newTotal;
|
|
246
214
|
self._lastFiltered = detail.filtered != null ? detail.filtered : self._data.length;
|
|
@@ -248,9 +216,14 @@ import { calculateSelectionState, calculateVirtualWindow, toggleRowSelection, to
|
|
|
248
216
|
self.totalCount = self._lastTotal;
|
|
249
217
|
self.visibleCount = self._lastFiltered;
|
|
250
218
|
self.isLoaded = true;
|
|
219
|
+
self._initialLoaded = true;
|
|
251
220
|
self._hasInitialSeed = false;
|
|
252
221
|
|
|
253
222
|
dom.classList.remove('ln-table--loading');
|
|
223
|
+
dom.setAttribute('aria-busy', 'false');
|
|
224
|
+
if (self._loadingIndicator) {
|
|
225
|
+
self._loadingIndicator.classList.add('hidden');
|
|
226
|
+
}
|
|
254
227
|
|
|
255
228
|
self._vStart = -1;
|
|
256
229
|
self._vEnd = -1;
|
|
@@ -268,40 +241,18 @@ import { calculateSelectionState, calculateVirtualWindow, toggleRowSelection, to
|
|
|
268
241
|
dom.addEventListener('ln-table:set-data', this._onSetData);
|
|
269
242
|
|
|
270
243
|
this._onSetLoading = function (e) {
|
|
271
|
-
const loading = e.detail && e.detail.loading;
|
|
272
|
-
dom.classList.toggle('ln-table--loading',
|
|
273
|
-
|
|
244
|
+
const loading = !!(e.detail && e.detail.loading);
|
|
245
|
+
dom.classList.toggle('ln-table--loading', loading);
|
|
246
|
+
dom.setAttribute('aria-busy', loading ? 'true' : 'false');
|
|
247
|
+
if (loading && !self._initialLoaded) {
|
|
274
248
|
self.isLoaded = false;
|
|
249
|
+
self._showLoadingState();
|
|
275
250
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
this._onPageFailed = function (e) {
|
|
280
|
-
if (!self._windowed || !self._cache) return;
|
|
281
|
-
self._cache.release(e.detail && e.detail.offset);
|
|
282
|
-
};
|
|
283
|
-
dom.addEventListener('ln-table:page-failed', this._onPageFailed);
|
|
284
|
-
|
|
285
|
-
this._onRequestRevalidate = function () {
|
|
286
|
-
if (!self._windowed || !self._cache) return;
|
|
287
|
-
self._cache.revalidate();
|
|
288
|
-
};
|
|
289
|
-
dom.addEventListener('ln-table:request-revalidate', this._onRequestRevalidate);
|
|
290
|
-
|
|
291
|
-
// The source owns the query, so a query change reaches the view as a
|
|
292
|
-
// restart order, not as query state — the window drops to page 0 and
|
|
293
|
-
// the composed query is resolved at serve time.
|
|
294
|
-
this._onRequestInvalidate = function (e) {
|
|
295
|
-
if (!self._windowed || !self._cache) return;
|
|
296
|
-
if (e && e.detail && e.detail.query) {
|
|
297
|
-
const q = e.detail.query;
|
|
298
|
-
if (q.search !== undefined) self.currentSearch = q.search;
|
|
299
|
-
if (q.filters !== undefined) self.currentFilters = q.filters;
|
|
300
|
-
if (q.sort !== undefined) self.currentSort = q.sort;
|
|
251
|
+
if (self._loadingIndicator) {
|
|
252
|
+
self._loadingIndicator.classList.toggle('hidden', !(loading && self._initialLoaded));
|
|
301
253
|
}
|
|
302
|
-
self._requestData();
|
|
303
254
|
};
|
|
304
|
-
dom.addEventListener('ln-table:
|
|
255
|
+
dom.addEventListener('ln-table:set-loading', this._onSetLoading);
|
|
305
256
|
|
|
306
257
|
// --- Sort ---
|
|
307
258
|
this._onSort = function (e) {
|
|
@@ -312,11 +263,6 @@ import { calculateSelectionState, calculateVirtualWindow, toggleRowSelection, to
|
|
|
312
263
|
};
|
|
313
264
|
dom.addEventListener('ln-sort:change', this._onSort);
|
|
314
265
|
|
|
315
|
-
// D4 — windowed tables cannot select rows they have never fetched
|
|
316
|
-
if (this._windowed && this._selectable && this._selectAllCheckbox) {
|
|
317
|
-
this._selectAllCheckbox.classList.add('hidden');
|
|
318
|
-
}
|
|
319
|
-
|
|
320
266
|
// --- Row Click & Actions ---
|
|
321
267
|
this._onRowClick = function (e) {
|
|
322
268
|
if (e.target.closest('[data-ln-table-row-select]')) return;
|
|
@@ -358,26 +304,23 @@ import { calculateSelectionState, calculateVirtualWindow, toggleRowSelection, to
|
|
|
358
304
|
};
|
|
359
305
|
if (this.tbody) this.tbody.addEventListener('click', this._onRowAction);
|
|
360
306
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
// Local hydration of initial SSR rows
|
|
307
|
+
// Local hydration of initial SSR rows or initial loading state
|
|
364
308
|
if (this.tbody && this.tbody.rows.length > 0) {
|
|
365
309
|
this._parseRows();
|
|
310
|
+
} else {
|
|
311
|
+
this._showLoadingState();
|
|
312
|
+
dom.setAttribute('aria-busy', 'true');
|
|
366
313
|
}
|
|
367
314
|
|
|
368
315
|
// Initial request-data deferred so coordinators have attached listeners
|
|
369
316
|
setTimeout(function () {
|
|
370
317
|
if (!self.dom || !document.contains(self.dom)) return;
|
|
371
|
-
|
|
372
|
-
self.
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
filters: self.currentFilters,
|
|
378
|
-
search: self.currentSearch
|
|
379
|
-
});
|
|
380
|
-
}
|
|
318
|
+
dispatch(dom, 'ln-table:request-data', {
|
|
319
|
+
table: self.name,
|
|
320
|
+
sort: self.currentSort,
|
|
321
|
+
filters: self.currentFilters,
|
|
322
|
+
search: self.currentSearch
|
|
323
|
+
});
|
|
381
324
|
}, 0);
|
|
382
325
|
|
|
383
326
|
} else {
|
|
@@ -555,18 +498,25 @@ import { calculateSelectionState, calculateVirtualWindow, toggleRowSelection, to
|
|
|
555
498
|
if (!this.tbody) return;
|
|
556
499
|
|
|
557
500
|
if (this.isDataDriven) {
|
|
558
|
-
if (this._windowed) { this._renderWindowed(); return; }
|
|
559
501
|
const total = this._lastTotal;
|
|
560
502
|
const filtered = this.visibleCount;
|
|
561
503
|
|
|
562
504
|
if (total === 0) {
|
|
563
505
|
this._disableVirtualScroll();
|
|
506
|
+
if (!this.isLoaded) {
|
|
507
|
+
this._showLoadingState();
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
564
510
|
this._showEmptyState();
|
|
565
511
|
return;
|
|
566
512
|
}
|
|
567
513
|
|
|
568
514
|
if (this._filteredData.length === 0 || filtered === 0) {
|
|
569
515
|
this._disableVirtualScroll();
|
|
516
|
+
if (!this.isLoaded) {
|
|
517
|
+
this._showLoadingState();
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
570
520
|
this._showEmptyState();
|
|
571
521
|
return;
|
|
572
522
|
}
|
|
@@ -595,8 +545,15 @@ import { calculateSelectionState, calculateVirtualWindow, toggleRowSelection, to
|
|
|
595
545
|
};
|
|
596
546
|
|
|
597
547
|
_component.prototype._renderAll = function () {
|
|
598
|
-
|
|
599
|
-
|
|
548
|
+
this._rowPool = null;
|
|
549
|
+
this._topSpacer = null;
|
|
550
|
+
this._topTd = null;
|
|
551
|
+
this._bottomSpacer = null;
|
|
552
|
+
this._bottomTd = null;
|
|
553
|
+
|
|
554
|
+
const data = this._filteredData;
|
|
555
|
+
|
|
556
|
+
if (this.isDataDriven || (data.length > 0 && !data[0].html)) {
|
|
600
557
|
const frag = document.createDocumentFragment();
|
|
601
558
|
|
|
602
559
|
for (let i = 0; i < data.length; i++) {
|
|
@@ -610,7 +567,6 @@ import { calculateSelectionState, calculateVirtualWindow, toggleRowSelection, to
|
|
|
610
567
|
if (this._selectable) this._updateSelectAll();
|
|
611
568
|
} else {
|
|
612
569
|
const html = [];
|
|
613
|
-
const data = this._filteredData;
|
|
614
570
|
for (let i = 0; i < data.length; i++) html.push(data[i].html);
|
|
615
571
|
|
|
616
572
|
this.tbody.innerHTML = html.join('');
|
|
@@ -633,10 +589,7 @@ import { calculateSelectionState, calculateVirtualWindow, toggleRowSelection, to
|
|
|
633
589
|
this._rowHeight = this.tbody.rows[0].offsetHeight || 40;
|
|
634
590
|
} else {
|
|
635
591
|
let tempRow = null;
|
|
636
|
-
if (this.
|
|
637
|
-
const sample = this._cache ? this._cache.peek() : null;
|
|
638
|
-
tempRow = sample ? this._buildRow(sample) : this._buildPlaceholderRow();
|
|
639
|
-
} else if (this.isDataDriven && this._data.length > 0) {
|
|
592
|
+
if (this.isDataDriven && this._data.length > 0) {
|
|
640
593
|
tempRow = this._buildRow(this._data[0]);
|
|
641
594
|
}
|
|
642
595
|
if (tempRow && this.tbody) {
|
|
@@ -647,18 +600,21 @@ import { calculateSelectionState, calculateVirtualWindow, toggleRowSelection, to
|
|
|
647
600
|
}
|
|
648
601
|
}
|
|
649
602
|
|
|
650
|
-
|
|
651
|
-
this._scrollContainer = _findScrollContainer(this.dom);
|
|
652
|
-
} else {
|
|
653
|
-
this._scrollContainer = null;
|
|
654
|
-
}
|
|
603
|
+
this._scrollContainer = _findScrollContainer(this.dom, this.table);
|
|
655
604
|
const scrollTarget = this._scrollContainer || window;
|
|
656
605
|
|
|
606
|
+
// Disable browser scroll anchoring on table, tbody, and scroll container
|
|
607
|
+
// to prevent runaway scroll feedback loops when spacer heights dynamically resize.
|
|
608
|
+
if (this.dom) this.dom.style.overflowAnchor = 'none';
|
|
609
|
+
if (this.table) this.table.style.overflowAnchor = 'none';
|
|
610
|
+
if (this.tbody) this.tbody.style.overflowAnchor = 'none';
|
|
611
|
+
if (this._scrollContainer) this._scrollContainer.style.overflowAnchor = 'none';
|
|
612
|
+
|
|
657
613
|
this._scrollHandler = function () {
|
|
658
614
|
if (self._rafId) return;
|
|
659
615
|
self._rafId = requestAnimationFrame(function () {
|
|
660
616
|
self._rafId = null;
|
|
661
|
-
self.
|
|
617
|
+
self._renderVirtual();
|
|
662
618
|
});
|
|
663
619
|
};
|
|
664
620
|
|
|
@@ -683,123 +639,205 @@ import { calculateSelectionState, calculateVirtualWindow, toggleRowSelection, to
|
|
|
683
639
|
}
|
|
684
640
|
this._vStart = -1;
|
|
685
641
|
this._vEnd = -1;
|
|
642
|
+
this._rowPool = null;
|
|
643
|
+
this._topSpacer = null;
|
|
644
|
+
this._topTd = null;
|
|
645
|
+
this._bottomSpacer = null;
|
|
646
|
+
this._bottomTd = null;
|
|
686
647
|
};
|
|
687
648
|
|
|
688
|
-
|
|
689
|
-
const data = this._filteredData;
|
|
690
|
-
const total = data.length;
|
|
691
|
-
const rowH = this._rowHeight;
|
|
692
|
-
if (!rowH || !total) return;
|
|
649
|
+
// ─── Row Pool (Cheetah / Glide style DOM Recycling) ────────
|
|
693
650
|
|
|
694
|
-
|
|
695
|
-
const
|
|
696
|
-
|
|
697
|
-
|
|
651
|
+
_component.prototype._compileRowBindings = function (tr) {
|
|
652
|
+
const bindings = {
|
|
653
|
+
textNodes: [],
|
|
654
|
+
attrs: [],
|
|
655
|
+
cellAttrs: []
|
|
656
|
+
};
|
|
698
657
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
const
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
658
|
+
const walker = document.createTreeWalker(tr, NodeFilter.SHOW_TEXT);
|
|
659
|
+
while (walker.nextNode()) {
|
|
660
|
+
const node = walker.currentNode;
|
|
661
|
+
if (node.textContent.indexOf('{{') !== -1) {
|
|
662
|
+
const parent = node.parentElement;
|
|
663
|
+
bindings.textNodes.push({
|
|
664
|
+
node: node,
|
|
665
|
+
raw: node.textContent,
|
|
666
|
+
el: parent,
|
|
667
|
+
isLeaf: !!(parent && parent.children.length === 0)
|
|
668
|
+
});
|
|
669
|
+
}
|
|
711
670
|
}
|
|
712
671
|
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
672
|
+
const elements = Array.from(tr.querySelectorAll('*'));
|
|
673
|
+
if (tr.nodeType === 1) elements.push(tr);
|
|
674
|
+
|
|
675
|
+
for (let i = 0; i < elements.length; i++) {
|
|
676
|
+
const el = elements[i];
|
|
677
|
+
if (el.hasAttribute('data-ln-table-cell-attr')) {
|
|
678
|
+
const pairs = el.getAttribute('data-ln-table-cell-attr').split(',');
|
|
679
|
+
for (let j = 0; j < pairs.length; j++) {
|
|
680
|
+
const parts = pairs[j].trim().split(':');
|
|
681
|
+
if (parts.length === 2) {
|
|
682
|
+
bindings.cellAttrs.push({
|
|
683
|
+
el: el,
|
|
684
|
+
field: parts[0].trim(),
|
|
685
|
+
attr: parts[1].trim()
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
720
690
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
691
|
+
const attrs = el.attributes;
|
|
692
|
+
for (let j = 0; j < attrs.length; j++) {
|
|
693
|
+
const a = attrs[j];
|
|
694
|
+
if (a.value.indexOf('{{') !== -1) {
|
|
695
|
+
bindings.attrs.push({
|
|
696
|
+
el: el,
|
|
697
|
+
name: a.name,
|
|
698
|
+
raw: a.value
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
724
703
|
|
|
725
|
-
|
|
726
|
-
|
|
704
|
+
tr._lnBindings = bindings;
|
|
705
|
+
return bindings;
|
|
706
|
+
};
|
|
727
707
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
topTd.style.height = topH + 'px';
|
|
735
|
-
topSpacer.appendChild(topTd);
|
|
736
|
-
frag.appendChild(topSpacer);
|
|
708
|
+
_component.prototype._createTemplateRow = function () {
|
|
709
|
+
let clone = cloneTemplateScoped(this.dom, this.name + '-row', 'ln-table');
|
|
710
|
+
if (!clone) {
|
|
711
|
+
const genericTpl = this.dom.querySelector('template[data-ln-table-row]');
|
|
712
|
+
if (genericTpl) {
|
|
713
|
+
clone = document.importNode(genericTpl.content, true);
|
|
737
714
|
}
|
|
715
|
+
}
|
|
738
716
|
|
|
739
|
-
|
|
740
|
-
const tr = this._buildRow(data[i]);
|
|
741
|
-
if (tr) frag.appendChild(tr);
|
|
742
|
-
}
|
|
717
|
+
let tr = clone ? (clone.querySelector('[data-ln-table-row]') || clone.firstElementChild) : null;
|
|
743
718
|
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
bottomSpacer.setAttribute('aria-hidden', 'true');
|
|
748
|
-
const bottomTd = document.createElement('td');
|
|
749
|
-
bottomTd.setAttribute('colspan', colSpan);
|
|
750
|
-
bottomTd.style.height = bottomH + 'px';
|
|
751
|
-
bottomSpacer.appendChild(bottomTd);
|
|
752
|
-
frag.appendChild(bottomSpacer);
|
|
719
|
+
if (!tr) {
|
|
720
|
+
if (window.lnDebug || (typeof document !== 'undefined' && document.body && document.body.hasAttribute('data-ln-debug'))) {
|
|
721
|
+
console.warn(`[ln-table] Missing row template: <template data-ln-template="${this.name}-row">`);
|
|
753
722
|
}
|
|
723
|
+
return null;
|
|
724
|
+
}
|
|
754
725
|
|
|
755
|
-
|
|
726
|
+
this._compileRowBindings(tr);
|
|
727
|
+
return tr;
|
|
728
|
+
};
|
|
756
729
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
730
|
+
_component.prototype._updateRow = function (tr, record) {
|
|
731
|
+
if (!record) return;
|
|
732
|
+
tr._lnRecord = record;
|
|
733
|
+
if (record.id != null) {
|
|
734
|
+
tr.setAttribute('data-ln-table-row-id', record.id);
|
|
735
|
+
}
|
|
760
736
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
737
|
+
const bindings = tr._lnBindings || this._compileRowBindings(tr);
|
|
738
|
+
|
|
739
|
+
const textNodes = bindings.textNodes;
|
|
740
|
+
for (let i = 0; i < textNodes.length; i++) {
|
|
741
|
+
const item = textNodes[i];
|
|
742
|
+
const val = item.raw.replace(
|
|
743
|
+
/\{\{\s*(\w+)\s*\}\}/g,
|
|
744
|
+
function (_, key) { return record[key] !== undefined && record[key] !== null ? record[key] : ''; }
|
|
745
|
+
);
|
|
746
|
+
if (item.isLeaf && item.el) {
|
|
747
|
+
item.el.textContent = val;
|
|
748
|
+
} else if (item.node && item.node.parentNode) {
|
|
749
|
+
item.node.textContent = val;
|
|
750
|
+
} else if (item.el) {
|
|
751
|
+
item.el.textContent = val;
|
|
769
752
|
}
|
|
753
|
+
}
|
|
770
754
|
|
|
771
|
-
|
|
755
|
+
const attrs = bindings.attrs;
|
|
756
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
757
|
+
const item = attrs[i];
|
|
758
|
+
item.el.setAttribute(
|
|
759
|
+
item.name,
|
|
760
|
+
item.raw.replace(
|
|
761
|
+
/\{\{\s*(\w+)\s*\}\}/g,
|
|
762
|
+
function (_, key) { return record[key] !== undefined && record[key] !== null ? record[key] : ''; }
|
|
763
|
+
)
|
|
764
|
+
);
|
|
765
|
+
}
|
|
772
766
|
|
|
773
|
-
|
|
767
|
+
const cellAttrs = bindings.cellAttrs;
|
|
768
|
+
for (let i = 0; i < cellAttrs.length; i++) {
|
|
769
|
+
const item = cellAttrs[i];
|
|
770
|
+
const val = record[item.field];
|
|
771
|
+
if (val != null) {
|
|
772
|
+
item.el.setAttribute(item.attr, val);
|
|
773
|
+
}
|
|
774
774
|
}
|
|
775
|
-
};
|
|
776
775
|
|
|
777
|
-
|
|
776
|
+
if (bindings.textNodes.length === 0 && bindings.attrs.length === 0 && this.ths.length > 0) {
|
|
777
|
+
for (let j = 0; j < this.ths.length; j++) {
|
|
778
|
+
const colName = this.ths[j].getAttribute('data-ln-table-col');
|
|
779
|
+
if (colName && j < tr.cells.length && record[colName] != null) {
|
|
780
|
+
tr.cells[j].textContent = String(record[colName]);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
778
784
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
tr.appendChild(td);
|
|
787
|
-
return tr;
|
|
785
|
+
if (this._selectable) {
|
|
786
|
+
const id = record.id != null ? String(record.id) : null;
|
|
787
|
+
const isSelected = id != null && this.selectedIds.has(id);
|
|
788
|
+
tr.classList.toggle('ln-row-selected', isSelected);
|
|
789
|
+
const rowCb = tr.querySelector('[data-ln-table-row-select]');
|
|
790
|
+
if (rowCb) rowCb.checked = isSelected;
|
|
791
|
+
}
|
|
788
792
|
};
|
|
789
793
|
|
|
790
|
-
_component.prototype.
|
|
791
|
-
if (this.
|
|
792
|
-
this.
|
|
793
|
-
this.
|
|
794
|
-
|
|
794
|
+
_component.prototype._ensureRowPool = function (requiredCount, colSpan) {
|
|
795
|
+
if (!this._rowPool) {
|
|
796
|
+
this._rowPool = [];
|
|
797
|
+
this._topSpacer = document.createElement('tr');
|
|
798
|
+
this._topSpacer.className = 'ln-table__spacer';
|
|
799
|
+
this._topSpacer.setAttribute('aria-hidden', 'true');
|
|
800
|
+
this._topSpacer.style.overflowAnchor = 'none';
|
|
801
|
+
this._topTd = document.createElement('td');
|
|
802
|
+
this._topTd.setAttribute('colspan', colSpan);
|
|
803
|
+
this._topTd.style.padding = '0';
|
|
804
|
+
this._topTd.style.border = 'none';
|
|
805
|
+
this._topTd.style.overflowAnchor = 'none';
|
|
806
|
+
this._topSpacer.appendChild(this._topTd);
|
|
807
|
+
|
|
808
|
+
this._bottomSpacer = document.createElement('tr');
|
|
809
|
+
this._bottomSpacer.className = 'ln-table__spacer';
|
|
810
|
+
this._bottomSpacer.setAttribute('aria-hidden', 'true');
|
|
811
|
+
this._bottomSpacer.style.overflowAnchor = 'none';
|
|
812
|
+
this._bottomTd = document.createElement('td');
|
|
813
|
+
this._bottomTd.setAttribute('colspan', colSpan);
|
|
814
|
+
this._bottomTd.style.padding = '0';
|
|
815
|
+
this._bottomTd.style.border = 'none';
|
|
816
|
+
this._bottomTd.style.overflowAnchor = 'none';
|
|
817
|
+
this._bottomSpacer.appendChild(this._bottomTd);
|
|
818
|
+
|
|
819
|
+
this.tbody.replaceChildren(this._topSpacer, this._bottomSpacer);
|
|
820
|
+
} else {
|
|
821
|
+
if (this._topTd && this._topTd.getAttribute('colspan') !== String(colSpan)) {
|
|
822
|
+
this._topTd.setAttribute('colspan', colSpan);
|
|
823
|
+
this._bottomTd.setAttribute('colspan', colSpan);
|
|
824
|
+
}
|
|
795
825
|
}
|
|
796
826
|
|
|
797
|
-
|
|
827
|
+
while (this._rowPool.length < requiredCount) {
|
|
828
|
+
const tr = this._createTemplateRow();
|
|
829
|
+
if (!tr) break;
|
|
830
|
+
this._rowPool.push(tr);
|
|
831
|
+
this.tbody.insertBefore(tr, this._bottomSpacer);
|
|
832
|
+
}
|
|
833
|
+
};
|
|
798
834
|
|
|
835
|
+
_component.prototype._renderVirtual = function () {
|
|
836
|
+
const data = this._filteredData;
|
|
837
|
+
const total = data.length;
|
|
799
838
|
const rowH = this._rowHeight;
|
|
800
|
-
if (!rowH) return;
|
|
839
|
+
if (!rowH || !total) return;
|
|
801
840
|
|
|
802
|
-
const total = this._cache.logicalTotal;
|
|
803
841
|
const theadH = this.thead ? this.thead.offsetHeight : 0;
|
|
804
842
|
const sc = this._scrollContainer;
|
|
805
843
|
let scrollIntoData;
|
|
@@ -824,59 +862,111 @@ import { calculateSelectionState, calculateVirtualWindow, toggleRowSelection, to
|
|
|
824
862
|
const endRow = vw.end;
|
|
825
863
|
|
|
826
864
|
if (startRow === this._vStart && endRow === this._vEnd) return;
|
|
865
|
+
this._vStart = startRow;
|
|
866
|
+
this._vEnd = endRow;
|
|
827
867
|
|
|
828
868
|
const colSpan = this.ths.length || 1;
|
|
829
|
-
const
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
topTd.setAttribute('colspan', colSpan);
|
|
840
|
-
topTd.style.height = topH + 'px';
|
|
841
|
-
topSpacer.appendChild(topTd);
|
|
842
|
-
frag.appendChild(topSpacer);
|
|
869
|
+
const neededRows = endRow - startRow;
|
|
870
|
+
|
|
871
|
+
this._ensureRowPool(neededRows, colSpan);
|
|
872
|
+
|
|
873
|
+
if (vw.topPadding > 0) {
|
|
874
|
+
this._topTd.style.height = vw.topPadding + 'px';
|
|
875
|
+
this._topSpacer.style.display = '';
|
|
876
|
+
} else {
|
|
877
|
+
this._topTd.style.height = '0px';
|
|
878
|
+
this._topSpacer.style.display = 'none';
|
|
843
879
|
}
|
|
844
880
|
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
881
|
+
if (vw.bottomPadding > 0) {
|
|
882
|
+
this._bottomTd.style.height = vw.bottomPadding + 'px';
|
|
883
|
+
this._bottomSpacer.style.display = '';
|
|
884
|
+
} else {
|
|
885
|
+
this._bottomTd.style.height = '0px';
|
|
886
|
+
this._bottomSpacer.style.display = 'none';
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
for (let k = 0; k < this._rowPool.length; k++) {
|
|
890
|
+
const rowEl = this._rowPool[k];
|
|
891
|
+
const dataIndex = startRow + k;
|
|
892
|
+
if (k < neededRows && dataIndex < total) {
|
|
893
|
+
rowEl.style.display = '';
|
|
894
|
+
const rec = data[dataIndex];
|
|
895
|
+
if (rec && rec.html) {
|
|
896
|
+
const temp = document.createElement('tbody');
|
|
897
|
+
temp.innerHTML = rec.html;
|
|
898
|
+
const parsedTr = temp.firstElementChild;
|
|
899
|
+
if (parsedTr) {
|
|
900
|
+
rowEl.innerHTML = parsedTr.innerHTML;
|
|
901
|
+
rowEl.className = parsedTr.className;
|
|
902
|
+
rowEl._lnRecord = rec;
|
|
903
|
+
if (rec.id != null) rowEl.setAttribute('data-ln-table-row-id', rec.id);
|
|
904
|
+
}
|
|
905
|
+
} else {
|
|
906
|
+
this._updateRow(rowEl, rec);
|
|
907
|
+
}
|
|
849
908
|
} else {
|
|
850
|
-
|
|
909
|
+
rowEl.style.display = 'none';
|
|
851
910
|
}
|
|
852
911
|
}
|
|
853
912
|
|
|
854
|
-
if (
|
|
855
|
-
|
|
856
|
-
bottomSpacer.className = 'ln-table__spacer';
|
|
857
|
-
bottomSpacer.setAttribute('aria-hidden', 'true');
|
|
858
|
-
const bottomTd = document.createElement('td');
|
|
859
|
-
bottomTd.setAttribute('colspan', colSpan);
|
|
860
|
-
bottomTd.style.height = bottomH + 'px';
|
|
861
|
-
bottomSpacer.appendChild(bottomTd);
|
|
862
|
-
frag.appendChild(bottomSpacer);
|
|
863
|
-
}
|
|
913
|
+
if (this._selectable) this._updateSelectAll();
|
|
914
|
+
};
|
|
864
915
|
|
|
865
|
-
this.tbody.replaceChildren(frag);
|
|
866
916
|
|
|
867
|
-
// Select-all is disabled in windowed mode (D4) — no _updateSelectAll() call.
|
|
868
|
-
this._vStart = startRow;
|
|
869
|
-
this._vEnd = endRow;
|
|
870
917
|
|
|
871
|
-
|
|
918
|
+
// ─── Loading state ──────────────────────────────────────────
|
|
919
|
+
|
|
920
|
+
_component.prototype._showLoadingState = function () {
|
|
921
|
+
this._rowPool = null;
|
|
922
|
+
this._topSpacer = null;
|
|
923
|
+
this._topTd = null;
|
|
924
|
+
this._bottomSpacer = null;
|
|
925
|
+
this._bottomTd = null;
|
|
926
|
+
const colSpan = this.ths.length || 1;
|
|
927
|
+
let loadingEl = null;
|
|
928
|
+
|
|
929
|
+
let clone = cloneTemplateScoped(this.dom, this.name + '-loading', 'ln-table')
|
|
930
|
+
|| cloneTemplateScoped(this.dom, 'table-loading', 'ln-table');
|
|
931
|
+
|
|
932
|
+
if (!clone) {
|
|
933
|
+
const genericTpl = this.dom.querySelector('template[data-ln-table-loading]');
|
|
934
|
+
if (genericTpl) {
|
|
935
|
+
clone = document.importNode(genericTpl.content, true);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
if (clone) {
|
|
940
|
+
if (clone.tagName === 'TR') {
|
|
941
|
+
loadingEl = clone;
|
|
942
|
+
} else {
|
|
943
|
+
const td = document.createElement('td');
|
|
944
|
+
td.setAttribute('colspan', String(colSpan));
|
|
945
|
+
td.appendChild(clone);
|
|
946
|
+
const tr = document.createElement('tr');
|
|
947
|
+
tr.className = 'table-empty ln-table__empty';
|
|
948
|
+
tr.appendChild(td);
|
|
949
|
+
loadingEl = tr;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
if (loadingEl) {
|
|
954
|
+
this.tbody.replaceChildren(loadingEl);
|
|
955
|
+
} else {
|
|
956
|
+
this.tbody.replaceChildren();
|
|
957
|
+
}
|
|
872
958
|
};
|
|
873
959
|
|
|
874
960
|
// ─── Empty state ───────────────────────────────────────────
|
|
875
961
|
|
|
876
962
|
_component.prototype._showEmptyState = function () {
|
|
963
|
+
this._rowPool = null;
|
|
964
|
+
this._topSpacer = null;
|
|
965
|
+
this._topTd = null;
|
|
966
|
+
this._bottomSpacer = null;
|
|
967
|
+
this._bottomTd = null;
|
|
877
968
|
const colSpan = this.ths.length || 1;
|
|
878
969
|
let emptyEl = null;
|
|
879
|
-
|
|
880
970
|
let clone = null;
|
|
881
971
|
|
|
882
972
|
if (this.isDataDriven) {
|
|
@@ -888,7 +978,8 @@ import { calculateSelectionState, calculateVirtualWindow, toggleRowSelection, to
|
|
|
888
978
|
const isFiltered = filtered === 0 && total > 0;
|
|
889
979
|
|
|
890
980
|
const templateName = isFiltered ? (this.name + '-empty-filtered') : (this.name + '-empty');
|
|
891
|
-
clone = cloneTemplateScoped(this.dom, templateName, 'ln-table')
|
|
981
|
+
clone = cloneTemplateScoped(this.dom, templateName, 'ln-table')
|
|
982
|
+
|| cloneTemplateScoped(this.dom, isFiltered ? 'table-empty-filtered' : 'table-empty', 'ln-table');
|
|
892
983
|
|
|
893
984
|
if (!clone) {
|
|
894
985
|
const genericTpl = this.dom.querySelector('template[data-ln-table-empty]');
|
|
@@ -901,30 +992,29 @@ import { calculateSelectionState, calculateVirtualWindow, toggleRowSelection, to
|
|
|
901
992
|
}
|
|
902
993
|
}
|
|
903
994
|
}
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
td.appendChild(clone);
|
|
912
|
-
const tr = document.createElement('tr');
|
|
913
|
-
tr.className = 'ln-table__empty';
|
|
914
|
-
tr.appendChild(td);
|
|
915
|
-
emptyEl = tr;
|
|
995
|
+
} else {
|
|
996
|
+
clone = cloneTemplateScoped(this.dom, this.name + '-empty', 'ln-table')
|
|
997
|
+
|| cloneTemplateScoped(this.dom, 'table-empty', 'ln-table');
|
|
998
|
+
if (!clone) {
|
|
999
|
+
const genericTpl = this.dom.querySelector('template[data-ln-table-empty]');
|
|
1000
|
+
if (genericTpl) {
|
|
1001
|
+
clone = document.importNode(genericTpl.content, true);
|
|
916
1002
|
}
|
|
917
1003
|
}
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
if (clone) {
|
|
1007
|
+
if (clone.tagName === 'TR') {
|
|
1008
|
+
emptyEl = clone;
|
|
1009
|
+
} else {
|
|
1010
|
+
const td = document.createElement('td');
|
|
1011
|
+
td.setAttribute('colspan', String(colSpan));
|
|
1012
|
+
td.appendChild(clone);
|
|
1013
|
+
const tr = document.createElement('tr');
|
|
1014
|
+
tr.className = 'ln-table__empty';
|
|
1015
|
+
tr.appendChild(td);
|
|
1016
|
+
emptyEl = tr;
|
|
1017
|
+
}
|
|
928
1018
|
}
|
|
929
1019
|
|
|
930
1020
|
if (emptyEl) {
|
|
@@ -977,28 +1067,10 @@ import { calculateSelectionState, calculateVirtualWindow, toggleRowSelection, to
|
|
|
977
1067
|
temp.innerHTML = record.html;
|
|
978
1068
|
tr = temp.firstElementChild;
|
|
979
1069
|
} else {
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
const ths = this.ths;
|
|
983
|
-
|
|
984
|
-
for (let j = 0; j < ths.length; j++) {
|
|
985
|
-
const isSelectCol = ths[j].hasAttribute('data-ln-table-col-select');
|
|
986
|
-
const td = document.createElement('td');
|
|
987
|
-
|
|
988
|
-
if (isSelectCol) {
|
|
989
|
-
const cb = document.createElement('input');
|
|
990
|
-
cb.type = 'checkbox';
|
|
991
|
-
cb.setAttribute('data-ln-table-row-select', '');
|
|
992
|
-
cb.setAttribute('aria-label', 'Select row');
|
|
993
|
-
td.appendChild(cb);
|
|
994
|
-
} else {
|
|
995
|
-
const colName = ths[j].getAttribute('data-ln-table-col');
|
|
996
|
-
if (colName && record[colName] != null) {
|
|
997
|
-
td.textContent = String(record[colName]);
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
tr.appendChild(td);
|
|
1070
|
+
if (window.lnDebug || (typeof document !== 'undefined' && document.body && document.body.hasAttribute('data-ln-debug'))) {
|
|
1071
|
+
console.warn(`[ln-table] Missing row template: <template data-ln-template="${this.name}-row">`);
|
|
1001
1072
|
}
|
|
1073
|
+
return null;
|
|
1002
1074
|
}
|
|
1003
1075
|
} else {
|
|
1004
1076
|
this._fillRow(tr, record);
|
|
@@ -1019,119 +1091,9 @@ import { calculateSelectionState, calculateVirtualWindow, toggleRowSelection, to
|
|
|
1019
1091
|
};
|
|
1020
1092
|
|
|
1021
1093
|
_component.prototype._requestData = function () {
|
|
1022
|
-
if (this._windowed) {
|
|
1023
|
-
this.dom.classList.add('ln-table--loading');
|
|
1024
|
-
this._cache.invalidate({
|
|
1025
|
-
sort: this.currentSort,
|
|
1026
|
-
filters: this.currentFilters,
|
|
1027
|
-
search: this.currentSearch
|
|
1028
|
-
});
|
|
1029
|
-
return;
|
|
1030
|
-
}
|
|
1031
1094
|
requestData(this, 'ln-table:request-data', 'table');
|
|
1032
1095
|
};
|
|
1033
1096
|
|
|
1034
|
-
// ─── Windowed Mode — enter/exit/seed (live toggle) ──────────
|
|
1035
|
-
|
|
1036
|
-
_component.prototype._enterWindowedMode = function () {
|
|
1037
|
-
const self = this;
|
|
1038
|
-
const dom = this.dom;
|
|
1039
|
-
const winAttr = parseInt(dom.getAttribute('data-ln-table-window'), 10);
|
|
1040
|
-
const pageAttr = parseInt(dom.getAttribute('data-ln-table-window-page'), 10);
|
|
1041
|
-
const threshAttr = parseInt(dom.getAttribute('data-ln-table-window-threshold'), 10);
|
|
1042
|
-
|
|
1043
|
-
// Cache change → sync totals off the cache, re-render (pull), notify.
|
|
1044
|
-
this._onCacheChange = function () {
|
|
1045
|
-
if (!self._windowed || !self._cache) return;
|
|
1046
|
-
self.totalCount = self._cache.grandTotal;
|
|
1047
|
-
self.visibleCount = self._cache.logicalTotal;
|
|
1048
|
-
self._lastTotal = self._cache.grandTotal;
|
|
1049
|
-
self.isLoaded = true;
|
|
1050
|
-
self._vStart = -1;
|
|
1051
|
-
self._vEnd = -1;
|
|
1052
|
-
self._render();
|
|
1053
|
-
self._updateFooter();
|
|
1054
|
-
dispatch(dom, 'ln-table:rendered', {
|
|
1055
|
-
table: self.name,
|
|
1056
|
-
total: self.totalCount,
|
|
1057
|
-
visible: self.visibleCount
|
|
1058
|
-
});
|
|
1059
|
-
};
|
|
1060
|
-
this._renderBatch = createBatcher(this._onCacheChange);
|
|
1061
|
-
|
|
1062
|
-
this._cache = createWindowCache({
|
|
1063
|
-
windowSize: winAttr > 0 ? winAttr : WINDOW_DEFAULT,
|
|
1064
|
-
pageSize: pageAttr > 0 ? pageAttr : WINDOW_PAGE,
|
|
1065
|
-
threshold: threshAttr >= 0 ? threshAttr : 25,
|
|
1066
|
-
fetchDebounce: FETCH_DEBOUNCE,
|
|
1067
|
-
requestPage: function (query, offset, limit) {
|
|
1068
|
-
dispatch(dom, 'ln-table:request-data', {
|
|
1069
|
-
table: self.name,
|
|
1070
|
-
sort: query.sort,
|
|
1071
|
-
filters: query.filters,
|
|
1072
|
-
search: query.search,
|
|
1073
|
-
offset: offset,
|
|
1074
|
-
limit: limit,
|
|
1075
|
-
queryGen: self._cache.queryGen
|
|
1076
|
-
});
|
|
1077
|
-
},
|
|
1078
|
-
onChange: this._renderBatch
|
|
1079
|
-
});
|
|
1080
|
-
|
|
1081
|
-
this._windowed = true;
|
|
1082
|
-
|
|
1083
|
-
// D4 — windowed tables cannot select rows they have never fetched
|
|
1084
|
-
if (this._selectable && this._selectAllCheckbox) {
|
|
1085
|
-
this._selectAllCheckbox.classList.add('hidden');
|
|
1086
|
-
}
|
|
1087
|
-
};
|
|
1088
|
-
|
|
1089
|
-
_component.prototype._kickWindowInitial = function () {
|
|
1090
|
-
if (this._data.length > 0) {
|
|
1091
|
-
// SSR-seeded / warm-seeded: page 0 is already resident, the grand
|
|
1092
|
-
// total is declared in markup — no initial fetch needed. No
|
|
1093
|
-
// queryGen on the seed — it must never be dropped as stale.
|
|
1094
|
-
let declaredTotal = parseInt(this.dom.getAttribute('data-ln-table-count'), 10);
|
|
1095
|
-
if (isNaN(declaredTotal) && this._totalSpan) {
|
|
1096
|
-
const spanText = this._totalSpan.textContent.replace(/[^\d]/g, '');
|
|
1097
|
-
if (spanText) declaredTotal = parseInt(spanText, 10);
|
|
1098
|
-
}
|
|
1099
|
-
const seedTotal = (declaredTotal > 0) ? declaredTotal : this._data.length;
|
|
1100
|
-
this._cache.ingest({
|
|
1101
|
-
data: this._data,
|
|
1102
|
-
offset: 0,
|
|
1103
|
-
total: seedTotal,
|
|
1104
|
-
filtered: seedTotal
|
|
1105
|
-
});
|
|
1106
|
-
} else {
|
|
1107
|
-
this.dom.classList.add('ln-table--loading');
|
|
1108
|
-
this._cache.requestInitial({
|
|
1109
|
-
sort: this.currentSort,
|
|
1110
|
-
filters: this.currentFilters,
|
|
1111
|
-
search: this.currentSearch
|
|
1112
|
-
});
|
|
1113
|
-
}
|
|
1114
|
-
};
|
|
1115
|
-
|
|
1116
|
-
_component.prototype._exitWindowedMode = function () {
|
|
1117
|
-
this._disableVirtualScroll();
|
|
1118
|
-
if (this._cache) this._cache.destroy();
|
|
1119
|
-
this._cache = null;
|
|
1120
|
-
this._windowed = false;
|
|
1121
|
-
this._renderBatch = null;
|
|
1122
|
-
this._onCacheChange = null;
|
|
1123
|
-
if (this._selectAllCheckbox) {
|
|
1124
|
-
this._selectAllCheckbox.classList.remove('hidden');
|
|
1125
|
-
}
|
|
1126
|
-
this._rowHeight = 0;
|
|
1127
|
-
this._vStart = -1;
|
|
1128
|
-
this._vEnd = -1;
|
|
1129
|
-
this._data = [];
|
|
1130
|
-
this._filteredData = [];
|
|
1131
|
-
this.dom.classList.add('ln-table--loading');
|
|
1132
|
-
this._requestData();
|
|
1133
|
-
};
|
|
1134
|
-
|
|
1135
1097
|
// ─── Selection Helpers ─────────────────────────────────────
|
|
1136
1098
|
|
|
1137
1099
|
_component.prototype._updateSelectAll = function () {
|
|
@@ -1304,11 +1266,11 @@ import { calculateSelectionState, calculateVirtualWindow, toggleRowSelection, to
|
|
|
1304
1266
|
const isFiltered = filtered < total;
|
|
1305
1267
|
|
|
1306
1268
|
if (this._totalSpan) {
|
|
1307
|
-
this._totalSpan
|
|
1269
|
+
_setNumberValue(this._totalSpan, total);
|
|
1308
1270
|
}
|
|
1309
1271
|
|
|
1310
1272
|
if (this._filteredSpan) {
|
|
1311
|
-
this._filteredSpan
|
|
1273
|
+
_setNumberValue(this._filteredSpan, isFiltered ? filtered : null);
|
|
1312
1274
|
}
|
|
1313
1275
|
|
|
1314
1276
|
if (this._filteredWrap) {
|
|
@@ -1317,7 +1279,7 @@ import { calculateSelectionState, calculateVirtualWindow, toggleRowSelection, to
|
|
|
1317
1279
|
|
|
1318
1280
|
if (this._selectedSpan) {
|
|
1319
1281
|
const count = this.selectedIds ? this.selectedIds.size : 0;
|
|
1320
|
-
this._selectedSpan
|
|
1282
|
+
_setNumberValue(this._selectedSpan, count > 0 ? count : null);
|
|
1321
1283
|
if (this._selectedWrap) {
|
|
1322
1284
|
this._selectedWrap.classList.toggle('hidden', count === 0);
|
|
1323
1285
|
}
|
|
@@ -1338,15 +1300,11 @@ import { calculateSelectionState, calculateVirtualWindow, toggleRowSelection, to
|
|
|
1338
1300
|
if (this.isDataDriven) {
|
|
1339
1301
|
this.dom.removeEventListener('ln-table:set-data', this._onSetData);
|
|
1340
1302
|
this.dom.removeEventListener('ln-table:set-loading', this._onSetLoading);
|
|
1341
|
-
this.dom.removeEventListener('ln-table:page-failed', this._onPageFailed);
|
|
1342
|
-
this.dom.removeEventListener('ln-table:request-revalidate', this._onRequestRevalidate);
|
|
1343
|
-
this.dom.removeEventListener('ln-table:request-invalidate', this._onRequestInvalidate);
|
|
1344
1303
|
this.dom.removeEventListener('ln-sort:change', this._onSort);
|
|
1345
1304
|
if (this.tbody) {
|
|
1346
1305
|
this.tbody.removeEventListener('click', this._onRowClick);
|
|
1347
1306
|
this.tbody.removeEventListener('click', this._onRowAction);
|
|
1348
1307
|
}
|
|
1349
|
-
if (this._cache) this._cache.destroy();
|
|
1350
1308
|
} else {
|
|
1351
1309
|
if (this._emptyTbodyObserver) {
|
|
1352
1310
|
this._emptyTbodyObserver.disconnect();
|
|
@@ -1365,6 +1323,7 @@ import { calculateSelectionState, calculateVirtualWindow, toggleRowSelection, to
|
|
|
1365
1323
|
this._colgroup = null;
|
|
1366
1324
|
}
|
|
1367
1325
|
if (this.table) this.table.style.tableLayout = '';
|
|
1326
|
+
this._loadingIndicator = null;
|
|
1368
1327
|
this._data = [];
|
|
1369
1328
|
this._filteredData = [];
|
|
1370
1329
|
delete this.dom[DOM_ATTRIBUTE];
|