neiki-table 1.0.0
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/LICENSE +21 -0
- package/README.md +445 -0
- package/dist/neiki-table.css +636 -0
- package/dist/neiki-table.js +1609 -0
- package/dist/neiki-table.min.css +2 -0
- package/dist/neiki-table.min.js +8 -0
- package/package.json +40 -0
|
@@ -0,0 +1,1609 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Neiki's Table 1.0.0
|
|
3
|
+
* A lightweight, dependency-free data table Web Component.
|
|
4
|
+
* https://github.com/neikiri/neiki-table
|
|
5
|
+
* MIT License
|
|
6
|
+
*/
|
|
7
|
+
(function () {
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
if (customElements.get('neiki-table')) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------
|
|
15
|
+
// i18n
|
|
16
|
+
// ---------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
var I18N = {
|
|
19
|
+
en: {
|
|
20
|
+
searchPlaceholder: 'Search…',
|
|
21
|
+
filterPlaceholder: 'Filter…',
|
|
22
|
+
noResults: 'No matching rows found.',
|
|
23
|
+
noData: 'No data available.',
|
|
24
|
+
selectAll: 'Select all rows',
|
|
25
|
+
selectRow: 'Select row',
|
|
26
|
+
actions: 'Actions',
|
|
27
|
+
edit: 'Edit',
|
|
28
|
+
save: 'Save',
|
|
29
|
+
cancel: 'Cancel',
|
|
30
|
+
clearFilters: 'Clear filters',
|
|
31
|
+
exportCsv: 'Export CSV',
|
|
32
|
+
exportJson: 'Export JSON',
|
|
33
|
+
rowsPerPage: 'Rows per page',
|
|
34
|
+
previous: 'Previous',
|
|
35
|
+
next: 'Next',
|
|
36
|
+
pageOf: 'Page {page} of {pages}',
|
|
37
|
+
showingRange: 'Showing {start}–{end} of {total}',
|
|
38
|
+
showingNone: 'No rows to show',
|
|
39
|
+
all: 'All',
|
|
40
|
+
yes: 'Yes',
|
|
41
|
+
no: 'No',
|
|
42
|
+
selectedCount: '{count} selected',
|
|
43
|
+
copy: 'Copy',
|
|
44
|
+
copied: 'Copied',
|
|
45
|
+
loading: 'Loading…',
|
|
46
|
+
firstPage: 'First page',
|
|
47
|
+
lastPage: 'Last page',
|
|
48
|
+
gotoPage: 'Go to page {page}',
|
|
49
|
+
resultsAnnounce: '{total} rows'
|
|
50
|
+
},
|
|
51
|
+
cs: {
|
|
52
|
+
searchPlaceholder: 'Hledat…',
|
|
53
|
+
filterPlaceholder: 'Filtrovat…',
|
|
54
|
+
noResults: 'Nebyly nalezeny žádné odpovídající řádky.',
|
|
55
|
+
noData: 'Žádná data k dispozici.',
|
|
56
|
+
selectAll: 'Vybrat všechny řádky',
|
|
57
|
+
selectRow: 'Vybrat řádek',
|
|
58
|
+
actions: 'Akce',
|
|
59
|
+
edit: 'Upravit',
|
|
60
|
+
save: 'Uložit',
|
|
61
|
+
cancel: 'Zrušit',
|
|
62
|
+
clearFilters: 'Vymazat filtry',
|
|
63
|
+
exportCsv: 'Export CSV',
|
|
64
|
+
exportJson: 'Export JSON',
|
|
65
|
+
rowsPerPage: 'Řádků na stránku',
|
|
66
|
+
previous: 'Předchozí',
|
|
67
|
+
next: 'Další',
|
|
68
|
+
pageOf: 'Stránka {page} z {pages}',
|
|
69
|
+
showingRange: 'Zobrazeno {start}–{end} z {total}',
|
|
70
|
+
showingNone: 'Žádné řádky k zobrazení',
|
|
71
|
+
all: 'Vše',
|
|
72
|
+
yes: 'Ano',
|
|
73
|
+
no: 'Ne',
|
|
74
|
+
selectedCount: 'Vybráno: {count}',
|
|
75
|
+
copy: 'Kopírovat',
|
|
76
|
+
copied: 'Zkopírováno',
|
|
77
|
+
loading: 'Načítání…',
|
|
78
|
+
firstPage: 'První stránka',
|
|
79
|
+
lastPage: 'Poslední stránka',
|
|
80
|
+
gotoPage: 'Přejít na stránku {page}',
|
|
81
|
+
resultsAnnounce: 'Řádků: {total}'
|
|
82
|
+
},
|
|
83
|
+
de: {
|
|
84
|
+
searchPlaceholder: 'Suchen…',
|
|
85
|
+
filterPlaceholder: 'Filtern…',
|
|
86
|
+
noResults: 'Keine passenden Zeilen gefunden.',
|
|
87
|
+
noData: 'Keine Daten verfügbar.',
|
|
88
|
+
selectAll: 'Alle Zeilen auswählen',
|
|
89
|
+
selectRow: 'Zeile auswählen',
|
|
90
|
+
actions: 'Aktionen',
|
|
91
|
+
edit: 'Bearbeiten',
|
|
92
|
+
save: 'Speichern',
|
|
93
|
+
cancel: 'Abbrechen',
|
|
94
|
+
clearFilters: 'Filter zurücksetzen',
|
|
95
|
+
exportCsv: 'CSV exportieren',
|
|
96
|
+
exportJson: 'JSON exportieren',
|
|
97
|
+
rowsPerPage: 'Zeilen pro Seite',
|
|
98
|
+
previous: 'Zurück',
|
|
99
|
+
next: 'Weiter',
|
|
100
|
+
pageOf: 'Seite {page} von {pages}',
|
|
101
|
+
showingRange: '{start}–{end} von {total} angezeigt',
|
|
102
|
+
showingNone: 'Keine Zeilen vorhanden',
|
|
103
|
+
all: 'Alle',
|
|
104
|
+
yes: 'Ja',
|
|
105
|
+
no: 'Nein',
|
|
106
|
+
selectedCount: '{count} ausgewählt',
|
|
107
|
+
copy: 'Kopieren',
|
|
108
|
+
copied: 'Kopiert',
|
|
109
|
+
loading: 'Wird geladen…',
|
|
110
|
+
firstPage: 'Erste Seite',
|
|
111
|
+
lastPage: 'Letzte Seite',
|
|
112
|
+
gotoPage: 'Zu Seite {page}',
|
|
113
|
+
resultsAnnounce: '{total} Zeilen'
|
|
114
|
+
},
|
|
115
|
+
es: {
|
|
116
|
+
searchPlaceholder: 'Buscar…',
|
|
117
|
+
filterPlaceholder: 'Filtrar…',
|
|
118
|
+
noResults: 'No se encontraron filas coincidentes.',
|
|
119
|
+
noData: 'No hay datos disponibles.',
|
|
120
|
+
selectAll: 'Seleccionar todas las filas',
|
|
121
|
+
selectRow: 'Seleccionar fila',
|
|
122
|
+
actions: 'Acciones',
|
|
123
|
+
edit: 'Editar',
|
|
124
|
+
save: 'Guardar',
|
|
125
|
+
cancel: 'Cancelar',
|
|
126
|
+
clearFilters: 'Borrar filtros',
|
|
127
|
+
exportCsv: 'Exportar CSV',
|
|
128
|
+
exportJson: 'Exportar JSON',
|
|
129
|
+
rowsPerPage: 'Filas por página',
|
|
130
|
+
previous: 'Anterior',
|
|
131
|
+
next: 'Siguiente',
|
|
132
|
+
pageOf: 'Página {page} de {pages}',
|
|
133
|
+
showingRange: 'Mostrando {start}–{end} de {total}',
|
|
134
|
+
showingNone: 'No hay filas que mostrar',
|
|
135
|
+
all: 'Todos',
|
|
136
|
+
yes: 'Sí',
|
|
137
|
+
no: 'No',
|
|
138
|
+
selectedCount: '{count} seleccionadas',
|
|
139
|
+
copy: 'Copiar',
|
|
140
|
+
copied: 'Copiado',
|
|
141
|
+
loading: 'Cargando…',
|
|
142
|
+
firstPage: 'Primera página',
|
|
143
|
+
lastPage: 'Última página',
|
|
144
|
+
gotoPage: 'Ir a la página {page}',
|
|
145
|
+
resultsAnnounce: '{total} filas'
|
|
146
|
+
},
|
|
147
|
+
fr: {
|
|
148
|
+
searchPlaceholder: 'Rechercher…',
|
|
149
|
+
filterPlaceholder: 'Filtrer…',
|
|
150
|
+
noResults: 'Aucune ligne correspondante trouvée.',
|
|
151
|
+
noData: 'Aucune donnée disponible.',
|
|
152
|
+
selectAll: 'Sélectionner toutes les lignes',
|
|
153
|
+
selectRow: 'Sélectionner la ligne',
|
|
154
|
+
actions: 'Actions',
|
|
155
|
+
edit: 'Modifier',
|
|
156
|
+
save: 'Enregistrer',
|
|
157
|
+
cancel: 'Annuler',
|
|
158
|
+
clearFilters: 'Effacer les filtres',
|
|
159
|
+
exportCsv: 'Exporter en CSV',
|
|
160
|
+
exportJson: 'Exporter en JSON',
|
|
161
|
+
rowsPerPage: 'Lignes par page',
|
|
162
|
+
previous: 'Précédent',
|
|
163
|
+
next: 'Suivant',
|
|
164
|
+
pageOf: 'Page {page} sur {pages}',
|
|
165
|
+
showingRange: 'Affichage de {start}–{end} sur {total}',
|
|
166
|
+
showingNone: 'Aucune ligne à afficher',
|
|
167
|
+
all: 'Tous',
|
|
168
|
+
yes: 'Oui',
|
|
169
|
+
no: 'Non',
|
|
170
|
+
selectedCount: '{count} sélectionnée(s)',
|
|
171
|
+
copy: 'Copier',
|
|
172
|
+
copied: 'Copié',
|
|
173
|
+
loading: 'Chargement…',
|
|
174
|
+
firstPage: 'Première page',
|
|
175
|
+
lastPage: 'Dernière page',
|
|
176
|
+
gotoPage: 'Aller à la page {page}',
|
|
177
|
+
resultsAnnounce: '{total} lignes'
|
|
178
|
+
},
|
|
179
|
+
it: {
|
|
180
|
+
searchPlaceholder: 'Cerca…',
|
|
181
|
+
filterPlaceholder: 'Filtra…',
|
|
182
|
+
noResults: 'Nessuna riga corrispondente trovata.',
|
|
183
|
+
noData: 'Nessun dato disponibile.',
|
|
184
|
+
selectAll: 'Seleziona tutte le righe',
|
|
185
|
+
selectRow: 'Seleziona riga',
|
|
186
|
+
actions: 'Azioni',
|
|
187
|
+
edit: 'Modifica',
|
|
188
|
+
save: 'Salva',
|
|
189
|
+
cancel: 'Annulla',
|
|
190
|
+
clearFilters: 'Cancella filtri',
|
|
191
|
+
exportCsv: 'Esporta CSV',
|
|
192
|
+
exportJson: 'Esporta JSON',
|
|
193
|
+
rowsPerPage: 'Righe per pagina',
|
|
194
|
+
previous: 'Precedente',
|
|
195
|
+
next: 'Successivo',
|
|
196
|
+
pageOf: 'Pagina {page} di {pages}',
|
|
197
|
+
showingRange: 'Visualizzazione di {start}–{end} su {total}',
|
|
198
|
+
showingNone: 'Nessuna riga da mostrare',
|
|
199
|
+
all: 'Tutti',
|
|
200
|
+
yes: 'Sì',
|
|
201
|
+
no: 'No',
|
|
202
|
+
selectedCount: '{count} selezionate',
|
|
203
|
+
copy: 'Copia',
|
|
204
|
+
copied: 'Copiato',
|
|
205
|
+
loading: 'Caricamento…',
|
|
206
|
+
firstPage: 'Prima pagina',
|
|
207
|
+
lastPage: 'Ultima pagina',
|
|
208
|
+
gotoPage: 'Vai alla pagina {page}',
|
|
209
|
+
resultsAnnounce: '{total} righe'
|
|
210
|
+
},
|
|
211
|
+
pl: {
|
|
212
|
+
searchPlaceholder: 'Szukaj…',
|
|
213
|
+
filterPlaceholder: 'Filtruj…',
|
|
214
|
+
noResults: 'Nie znaleziono pasujących wierszy.',
|
|
215
|
+
noData: 'Brak dostępnych danych.',
|
|
216
|
+
selectAll: 'Zaznacz wszystkie wiersze',
|
|
217
|
+
selectRow: 'Zaznacz wiersz',
|
|
218
|
+
actions: 'Akcje',
|
|
219
|
+
edit: 'Edytuj',
|
|
220
|
+
save: 'Zapisz',
|
|
221
|
+
cancel: 'Anuluj',
|
|
222
|
+
clearFilters: 'Wyczyść filtry',
|
|
223
|
+
exportCsv: 'Eksportuj CSV',
|
|
224
|
+
exportJson: 'Eksportuj JSON',
|
|
225
|
+
rowsPerPage: 'Wierszy na stronę',
|
|
226
|
+
previous: 'Poprzednia',
|
|
227
|
+
next: 'Następna',
|
|
228
|
+
pageOf: 'Strona {page} z {pages}',
|
|
229
|
+
showingRange: 'Wyświetlanie {start}–{end} z {total}',
|
|
230
|
+
showingNone: 'Brak wierszy do wyświetlenia',
|
|
231
|
+
all: 'Wszystkie',
|
|
232
|
+
yes: 'Tak',
|
|
233
|
+
no: 'Nie',
|
|
234
|
+
selectedCount: 'Zaznaczono: {count}',
|
|
235
|
+
copy: 'Kopiuj',
|
|
236
|
+
copied: 'Skopiowano',
|
|
237
|
+
loading: 'Ładowanie…',
|
|
238
|
+
firstPage: 'Pierwsza strona',
|
|
239
|
+
lastPage: 'Ostatnia strona',
|
|
240
|
+
gotoPage: 'Przejdź do strony {page}',
|
|
241
|
+
resultsAnnounce: 'Wierszy: {total}'
|
|
242
|
+
},
|
|
243
|
+
sk: {
|
|
244
|
+
searchPlaceholder: 'Hľadať…',
|
|
245
|
+
filterPlaceholder: 'Filtrovať…',
|
|
246
|
+
noResults: 'Neboli nájdené žiadne zodpovedajúce riadky.',
|
|
247
|
+
noData: 'Nie sú k dispozícii žiadne dáta.',
|
|
248
|
+
selectAll: 'Vybrať všetky riadky',
|
|
249
|
+
selectRow: 'Vybrať riadok',
|
|
250
|
+
actions: 'Akcie',
|
|
251
|
+
edit: 'Upraviť',
|
|
252
|
+
save: 'Uložiť',
|
|
253
|
+
cancel: 'Zrušiť',
|
|
254
|
+
clearFilters: 'Vymazať filtre',
|
|
255
|
+
exportCsv: 'Exportovať CSV',
|
|
256
|
+
exportJson: 'Exportovať JSON',
|
|
257
|
+
rowsPerPage: 'Riadkov na stránku',
|
|
258
|
+
previous: 'Predchádzajúca',
|
|
259
|
+
next: 'Ďalšia',
|
|
260
|
+
pageOf: 'Stránka {page} z {pages}',
|
|
261
|
+
showingRange: 'Zobrazené {start}–{end} z {total}',
|
|
262
|
+
showingNone: 'Žiadne riadky na zobrazenie',
|
|
263
|
+
all: 'Všetky',
|
|
264
|
+
yes: 'Áno',
|
|
265
|
+
no: 'Nie',
|
|
266
|
+
selectedCount: 'Vybraté: {count}',
|
|
267
|
+
copy: 'Kopírovať',
|
|
268
|
+
copied: 'Skopírované',
|
|
269
|
+
loading: 'Načítava sa…',
|
|
270
|
+
firstPage: 'Prvá stránka',
|
|
271
|
+
lastPage: 'Posledná stránka',
|
|
272
|
+
gotoPage: 'Prejsť na stránku {page}',
|
|
273
|
+
resultsAnnounce: 'Riadkov: {total}'
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
var FALLBACK_LOCALE = 'en';
|
|
278
|
+
|
|
279
|
+
function translate(locale, dictionaries, key, vars) {
|
|
280
|
+
var dict = dictionaries[locale] || dictionaries[FALLBACK_LOCALE] || {};
|
|
281
|
+
var fallback = dictionaries[FALLBACK_LOCALE] || {};
|
|
282
|
+
var text = dict[key] !== undefined ? dict[key] : fallback[key] !== undefined ? fallback[key] : key;
|
|
283
|
+
if (vars) {
|
|
284
|
+
Object.keys(vars).forEach(function (name) {
|
|
285
|
+
text = text.replace('{' + name + '}', String(vars[name]));
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
return text;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ---------------------------------------------------------------------
|
|
292
|
+
// Constants
|
|
293
|
+
// ---------------------------------------------------------------------
|
|
294
|
+
|
|
295
|
+
var VALID_THEMES = ['light', 'dark', 'auto'];
|
|
296
|
+
var VALID_TYPES = ['text', 'number', 'boolean', 'date', 'select'];
|
|
297
|
+
var VALID_DENSITIES = ['compact', 'normal', 'spacious'];
|
|
298
|
+
var VALID_ALIGN = ['left', 'center', 'right'];
|
|
299
|
+
var PAGE_SIZE_OPTIONS = [10, 25, 50, 100];
|
|
300
|
+
var MIN_COLUMN_WIDTH = 60;
|
|
301
|
+
|
|
302
|
+
var DEFAULT_CONFIG = {
|
|
303
|
+
locale: 'en',
|
|
304
|
+
theme: 'auto',
|
|
305
|
+
density: 'normal',
|
|
306
|
+
rowKey: 'id',
|
|
307
|
+
searchable: true,
|
|
308
|
+
filterable: true,
|
|
309
|
+
selectable: true,
|
|
310
|
+
editable: true,
|
|
311
|
+
paginated: true,
|
|
312
|
+
exportable: true,
|
|
313
|
+
resizable: true,
|
|
314
|
+
pageSize: 10,
|
|
315
|
+
searchDebounce: 180
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
function oneOf(value, list, fallback) {
|
|
319
|
+
return list.indexOf(value) !== -1 ? value : fallback;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function toBool(value, fallback) {
|
|
323
|
+
if (value === true || value === false) return value;
|
|
324
|
+
if (value === 'true') return true;
|
|
325
|
+
if (value === 'false') return false;
|
|
326
|
+
return fallback;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function getValue(row, key) {
|
|
330
|
+
return row ? row[key] : undefined;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function formatDisplay(value, column, locale) {
|
|
334
|
+
if (value === null || value === undefined || value === '') return '';
|
|
335
|
+
switch (column.type) {
|
|
336
|
+
case 'number': {
|
|
337
|
+
var num = Number(value);
|
|
338
|
+
return isNaN(num) ? String(value) : num.toLocaleString(locale);
|
|
339
|
+
}
|
|
340
|
+
case 'boolean':
|
|
341
|
+
return value ? 'yes' : 'no';
|
|
342
|
+
case 'date': {
|
|
343
|
+
var date = new Date(value);
|
|
344
|
+
return isNaN(date.getTime()) ? String(value) : date.toLocaleDateString(locale);
|
|
345
|
+
}
|
|
346
|
+
case 'select': {
|
|
347
|
+
var opt = findOption(column, value);
|
|
348
|
+
return opt ? opt.label : String(value);
|
|
349
|
+
}
|
|
350
|
+
default:
|
|
351
|
+
return String(value);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function findOption(column, value) {
|
|
356
|
+
var options = normalizeOptions(column);
|
|
357
|
+
for (var i = 0; i < options.length; i++) {
|
|
358
|
+
if (String(options[i].value) === String(value)) return options[i];
|
|
359
|
+
}
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function normalizeOptions(column) {
|
|
364
|
+
var raw = column.options || [];
|
|
365
|
+
return raw.map(function (opt) {
|
|
366
|
+
if (opt && typeof opt === 'object') {
|
|
367
|
+
return { value: opt.value, label: opt.label !== undefined ? opt.label : String(opt.value) };
|
|
368
|
+
}
|
|
369
|
+
return { value: opt, label: String(opt) };
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function compareValues(a, b, type) {
|
|
374
|
+
var av = a, bv = b;
|
|
375
|
+
if (av === null || av === undefined) av = '';
|
|
376
|
+
if (bv === null || bv === undefined) bv = '';
|
|
377
|
+
if (type === 'number') {
|
|
378
|
+
return (Number(av) || 0) - (Number(bv) || 0);
|
|
379
|
+
}
|
|
380
|
+
if (type === 'boolean') {
|
|
381
|
+
return (av ? 1 : 0) - (bv ? 1 : 0);
|
|
382
|
+
}
|
|
383
|
+
if (type === 'date') {
|
|
384
|
+
return new Date(av).getTime() - new Date(bv).getTime();
|
|
385
|
+
}
|
|
386
|
+
return String(av).localeCompare(String(bv));
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function csvEscape(value) {
|
|
390
|
+
var text = value === null || value === undefined ? '' : String(value);
|
|
391
|
+
if (/^[=+\-@\t\r]/.test(text)) {
|
|
392
|
+
text = "'" + text;
|
|
393
|
+
}
|
|
394
|
+
if (/[",\n\r]/.test(text)) {
|
|
395
|
+
text = '"' + text.replace(/"/g, '""') + '"';
|
|
396
|
+
}
|
|
397
|
+
return text;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function downloadBlob(content, mime, filename) {
|
|
401
|
+
var blob = new Blob([content], { type: mime });
|
|
402
|
+
var url = URL.createObjectURL(blob);
|
|
403
|
+
var link = document.createElement('a');
|
|
404
|
+
link.href = url;
|
|
405
|
+
link.download = filename;
|
|
406
|
+
link.style.display = 'none';
|
|
407
|
+
document.body.appendChild(link);
|
|
408
|
+
link.click();
|
|
409
|
+
document.body.removeChild(link);
|
|
410
|
+
setTimeout(function () { URL.revokeObjectURL(url); }, 1000);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// ---------------------------------------------------------------------
|
|
414
|
+
// Styling (shared adopted stylesheet, mirrors neiki-social-bar)
|
|
415
|
+
// ---------------------------------------------------------------------
|
|
416
|
+
|
|
417
|
+
// Replaced by minify.py at build time with the actual (minified) CSS text.
|
|
418
|
+
// Stays empty in src/ so development can edit neiki-table.css without
|
|
419
|
+
// rebuilding — the component falls back to a sibling <link> in that case.
|
|
420
|
+
var EMBEDDED_CSS = "/*!\n * Neiki's Table 1.0.0 \u2014 styles\n * MIT License\n */\n\n:host {\n --ntbl-radius: 14px;\n --ntbl-radius-inner: 10px;\n --ntbl-radius-control: 9px;\n --ntbl-font-size: 14px;\n --ntbl-transition: 140ms ease;\n --ntbl-shadow: 0 1px 2px rgba(16, 24, 40, 0.04), 0 6px 20px rgba(16, 24, 40, 0.08);\n --ntbl-row-height: 44px;\n\n /* Density (overridden by [density] below) */\n --ntbl-cell-py: 9px;\n --ntbl-cell-px: 13px;\n --ntbl-head-py: 10px;\n\n --ntbl-bg: #ffffff;\n --ntbl-color: #1f2328;\n --ntbl-muted: #6b7280;\n --ntbl-border: rgba(16, 24, 40, 0.09);\n --ntbl-border-strong: rgba(16, 24, 40, 0.14);\n --ntbl-header-bg: #f7f8fa;\n --ntbl-row-hover: #f3f6fc;\n --ntbl-row-selected: #e9f0ff;\n --ntbl-stripe: rgba(16, 24, 40, 0.018);\n --ntbl-accent: #2563eb;\n --ntbl-accent-hover: #1d4ed8;\n --ntbl-accent-color: #ffffff;\n --ntbl-accent-soft: rgba(37, 99, 235, 0.10);\n --ntbl-focus-ring: rgba(37, 99, 235, 0.45);\n --ntbl-input-bg: #ffffff;\n --ntbl-input-border: rgba(16, 24, 40, 0.16);\n --ntbl-badge-true-bg: #dcfce7;\n --ntbl-badge-true-color: #166534;\n --ntbl-badge-false-bg: #f3f4f6;\n --ntbl-badge-false-color: #6b7280;\n --ntbl-skeleton: linear-gradient(90deg, rgba(16,24,40,0.05) 25%, rgba(16,24,40,0.11) 37%, rgba(16,24,40,0.05) 63%);\n\n display: block;\n font-family: system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif;\n font-size: var(--ntbl-font-size);\n color: var(--ntbl-color);\n line-height: 1.45;\n -webkit-text-size-adjust: 100%;\n}\n\n:host([hidden]) {\n display: none !important;\n}\n\n:host([resolved-theme=\"dark\"]) {\n --ntbl-shadow: 0 1px 2px rgba(0, 0, 0, 0.30), 0 8px 26px rgba(0, 0, 0, 0.40);\n --ntbl-bg: #1a1d23;\n --ntbl-color: #eef0f3;\n --ntbl-muted: #9aa3af;\n --ntbl-border: rgba(255, 255, 255, 0.09);\n --ntbl-border-strong: rgba(255, 255, 255, 0.16);\n --ntbl-header-bg: #21252c;\n --ntbl-row-hover: #262b33;\n --ntbl-row-selected: #22314f;\n --ntbl-stripe: rgba(255, 255, 255, 0.02);\n --ntbl-accent: #3b82f6;\n --ntbl-accent-hover: #60a5fa;\n --ntbl-accent-soft: rgba(59, 130, 246, 0.16);\n --ntbl-focus-ring: rgba(96, 165, 250, 0.55);\n --ntbl-input-bg: #21252c;\n --ntbl-input-border: rgba(255, 255, 255, 0.16);\n --ntbl-badge-true-bg: rgba(34, 197, 94, 0.18);\n --ntbl-badge-true-color: #4ade80;\n --ntbl-badge-false-bg: rgba(255, 255, 255, 0.08);\n --ntbl-badge-false-color: #9aa3af;\n --ntbl-skeleton: linear-gradient(90deg, rgba(255,255,255,0.04) 25%, rgba(255,255,255,0.09) 37%, rgba(255,255,255,0.04) 63%);\n}\n\n/* Density */\n:host([density=\"compact\"]) {\n --ntbl-cell-py: 5px;\n --ntbl-cell-px: 10px;\n --ntbl-head-py: 7px;\n --ntbl-font-size: 13px;\n}\n:host([density=\"spacious\"]) {\n --ntbl-cell-py: 14px;\n --ntbl-cell-px: 16px;\n --ntbl-head-py: 14px;\n}\n\n* {\n box-sizing: border-box;\n}\n\n.ntbl-root {\n position: relative;\n display: flex;\n flex-direction: column;\n gap: 12px;\n background: var(--ntbl-bg);\n border: 1px solid var(--ntbl-border);\n border-radius: var(--ntbl-radius);\n box-shadow: var(--ntbl-shadow);\n padding: 14px;\n}\n\n/* Toolbar */\n.ntbl-toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.ntbl-search-wrap {\n position: relative;\n flex: 1 1 240px;\n min-width: 160px;\n display: flex;\n align-items: center;\n}\n.ntbl-search-wrap[hidden] {\n display: none;\n}\n.ntbl-search-wrap::before {\n content: \"\";\n position: absolute;\n left: 11px;\n width: 16px;\n height: 16px;\n pointer-events: none;\n opacity: 0.5;\n background: currentColor;\n -webkit-mask: no-repeat center / contain url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='m21 21-4.3-4.3'/%3E%3C/svg%3E\");\n mask: no-repeat center / contain url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='m21 21-4.3-4.3'/%3E%3C/svg%3E\");\n}\n\n.ntbl-search {\n width: 100%;\n padding: 9px 12px 9px 34px;\n font: inherit;\n color: var(--ntbl-color);\n background: var(--ntbl-input-bg);\n border: 1px solid var(--ntbl-input-border);\n border-radius: var(--ntbl-radius-control);\n outline: none;\n transition: border-color var(--ntbl-transition), box-shadow var(--ntbl-transition);\n}\n.ntbl-search::placeholder {\n color: var(--ntbl-muted);\n}\n.ntbl-search:hover {\n border-color: var(--ntbl-border-strong);\n}\n.ntbl-search:focus-visible {\n border-color: var(--ntbl-accent);\n box-shadow: 0 0 0 3px var(--ntbl-focus-ring);\n}\n\n.ntbl-toolbar-actions {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.ntbl-selected-count {\n font-size: 0.85em;\n font-weight: 600;\n color: var(--ntbl-accent);\n background: var(--ntbl-accent-soft);\n padding: 4px 10px;\n border-radius: 999px;\n}\n.ntbl-selected-count[hidden] {\n display: none;\n}\n\n.ntbl-btn,\n.ntbl-toolbar-actions button,\n.ntbl-pagination button {\n font: inherit;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 8px 13px;\n border: 1px solid var(--ntbl-input-border);\n border-radius: var(--ntbl-radius-control);\n background: var(--ntbl-input-bg);\n color: var(--ntbl-color);\n cursor: pointer;\n white-space: nowrap;\n transition: background var(--ntbl-transition), border-color var(--ntbl-transition), color var(--ntbl-transition), opacity var(--ntbl-transition), transform var(--ntbl-transition);\n}\n.ntbl-toolbar-actions button[hidden] {\n display: none;\n}\n.ntbl-toolbar-actions button:hover,\n.ntbl-pagination button:not(:disabled):hover {\n background: var(--ntbl-row-hover);\n border-color: var(--ntbl-border-strong);\n}\n.ntbl-toolbar-actions button:active,\n.ntbl-pagination button:not(:disabled):active {\n transform: translateY(1px);\n}\n.ntbl-export-csv {\n color: var(--ntbl-accent);\n border-color: color-mix(in srgb, var(--ntbl-accent) 40%, transparent);\n}\n.ntbl-export-csv:hover {\n background: var(--ntbl-accent-soft) !important;\n}\n.ntbl-toolbar-actions button:focus-visible,\n.ntbl-pagination button:focus-visible,\n.ntbl-page-size:focus-visible {\n outline: none;\n border-color: var(--ntbl-accent);\n box-shadow: 0 0 0 3px var(--ntbl-focus-ring);\n}\n.ntbl-pagination button:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n\n/* Icons inside buttons */\n.ntbl-icon {\n width: 15px;\n height: 15px;\n flex: none;\n}\n\n/* Scroll area / table */\n.ntbl-scroll {\n position: relative;\n overflow-x: auto;\n border: 1px solid var(--ntbl-border);\n border-radius: var(--ntbl-radius-inner);\n}\n\n.ntbl-table {\n width: 100%;\n border-collapse: collapse;\n min-width: 480px;\n}\n\n.ntbl-thead {\n position: sticky;\n top: 0;\n z-index: 2;\n}\n\n.ntbl-th {\n position: relative;\n background: var(--ntbl-header-bg);\n color: var(--ntbl-color);\n text-align: left;\n padding: var(--ntbl-head-py) var(--ntbl-cell-px);\n font-weight: 600;\n font-size: 0.9em;\n letter-spacing: 0.01em;\n border-bottom: 1px solid var(--ntbl-border-strong);\n white-space: nowrap;\n user-select: none;\n}\n\n.ntbl-th-label {\n display: inline-flex;\n align-items: center;\n vertical-align: middle;\n}\n\n.ntbl-th-align-center { text-align: center; }\n.ntbl-th-align-right { text-align: right; }\n\n.ntbl-th-sortable {\n cursor: pointer;\n}\n.ntbl-th-sortable:hover {\n background: var(--ntbl-row-hover);\n color: var(--ntbl-accent);\n}\n.ntbl-th-sortable:focus-visible {\n outline: none;\n box-shadow: inset 0 0 0 2px var(--ntbl-accent);\n}\n\n.ntbl-sort-icon {\n display: inline-block;\n width: 0.85em;\n height: 0.85em;\n margin-left: 5px;\n vertical-align: middle;\n opacity: 0.3;\n background: currentColor;\n -webkit-mask: no-repeat center / contain var(--ntbl-sort-mask, url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M8 9l4-4 4 4M8 15l4 4 4-4' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\"));\n mask: no-repeat center / contain var(--ntbl-sort-mask, url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M8 9l4-4 4 4M8 15l4 4 4-4' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\"));\n}\n.ntbl-th-sorted-asc,\n.ntbl-th-sorted-desc {\n color: var(--ntbl-accent);\n}\n.ntbl-th-sorted-asc .ntbl-sort-icon,\n.ntbl-th-sorted-desc .ntbl-sort-icon {\n opacity: 1;\n}\n.ntbl-th-sorted-asc .ntbl-sort-icon {\n --ntbl-sort-mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6 14l6-6 6 6' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\");\n}\n.ntbl-th-sorted-desc .ntbl-sort-icon {\n --ntbl-sort-mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6 10l6 6 6-6' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\");\n}\n\n/* Column resize handle */\n.ntbl-resize-handle {\n position: absolute;\n top: 0;\n right: 0;\n width: 9px;\n height: 100%;\n cursor: col-resize;\n z-index: 3;\n touch-action: none;\n}\n.ntbl-resize-handle::after {\n content: \"\";\n position: absolute;\n top: 20%;\n right: 3px;\n width: 2px;\n height: 60%;\n background: var(--ntbl-border-strong);\n opacity: 0;\n border-radius: 2px;\n transition: opacity var(--ntbl-transition);\n}\n.ntbl-resize-handle:hover::after,\n.ntbl-th-resizing .ntbl-resize-handle::after {\n opacity: 1;\n background: var(--ntbl-accent);\n}\n\n.ntbl-th-select {\n width: 44px;\n padding-left: 12px;\n padding-right: 8px;\n}\n\n.ntbl-filter-row {\n background: var(--ntbl-bg);\n}\n.ntbl-filter-row[hidden] {\n display: none;\n}\n.ntbl-filter-cell {\n padding: 6px 10px;\n font-weight: normal;\n background: var(--ntbl-bg);\n border-bottom: 1px solid var(--ntbl-border);\n}\n\n.ntbl-filter-input {\n width: 100%;\n min-width: 80px;\n font: inherit;\n font-size: 0.9em;\n padding: 6px 9px;\n color: var(--ntbl-color);\n background: var(--ntbl-input-bg);\n border: 1px solid var(--ntbl-input-border);\n border-radius: var(--ntbl-radius-control);\n transition: border-color var(--ntbl-transition), box-shadow var(--ntbl-transition);\n}\n.ntbl-filter-input:hover {\n border-color: var(--ntbl-border-strong);\n}\n.ntbl-filter-input:focus-visible {\n outline: none;\n border-color: var(--ntbl-accent);\n box-shadow: 0 0 0 3px var(--ntbl-focus-ring);\n}\n\n.ntbl-tbody .ntbl-tr {\n transition: background var(--ntbl-transition);\n}\n.ntbl-tbody .ntbl-tr:nth-child(even) {\n background: var(--ntbl-stripe);\n}\n.ntbl-tbody .ntbl-tr:hover {\n background: var(--ntbl-row-hover);\n}\n.ntbl-tbody .ntbl-tr-selected,\n.ntbl-tbody .ntbl-tr-selected:hover {\n background: var(--ntbl-row-selected);\n}\n.ntbl-tbody .ntbl-tr-selected .ntbl-td-select {\n box-shadow: inset 3px 0 0 var(--ntbl-accent);\n}\n\n.ntbl-td {\n padding: var(--ntbl-cell-py) var(--ntbl-cell-px);\n border-bottom: 1px solid var(--ntbl-border);\n vertical-align: middle;\n}\n.ntbl-td-align-center { text-align: center; }\n.ntbl-td-align-right { text-align: right; font-variant-numeric: tabular-nums; }\n.ntbl-td-num { font-variant-numeric: tabular-nums; }\n.ntbl-tbody .ntbl-tr:last-child .ntbl-td {\n border-bottom: none;\n}\n\n.ntbl-td-select {\n width: 44px;\n padding-left: 12px;\n padding-right: 8px;\n}\n\n/* Checkboxes */\n.ntbl-td-select input,\n.ntbl-th-select input {\n width: 16px;\n height: 16px;\n accent-color: var(--ntbl-accent);\n cursor: pointer;\n}\n\n.ntbl-td-editable {\n cursor: text;\n border-radius: 6px;\n transition: box-shadow var(--ntbl-transition);\n}\n.ntbl-td-editable:hover {\n box-shadow: inset 0 0 0 1px var(--ntbl-border-strong);\n}\n.ntbl-td-editable:focus-visible {\n outline: none;\n box-shadow: inset 0 0 0 2px var(--ntbl-accent);\n}\n\n.ntbl-td-editing {\n padding: 4px 6px;\n}\n\n.ntbl-edit-input {\n width: 100%;\n font: inherit;\n padding: 6px 9px;\n color: var(--ntbl-color);\n background: var(--ntbl-input-bg);\n border: 1px solid var(--ntbl-accent);\n border-radius: var(--ntbl-radius-control);\n box-shadow: 0 0 0 3px var(--ntbl-focus-ring);\n outline: none;\n}\n\n.ntbl-badge {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 2px 10px;\n border-radius: 999px;\n font-size: 0.82em;\n font-weight: 600;\n line-height: 1.6;\n}\n.ntbl-badge::before {\n content: \"\";\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: currentColor;\n}\n.ntbl-badge-true {\n background: var(--ntbl-badge-true-bg);\n color: var(--ntbl-badge-true-color);\n}\n.ntbl-badge-false {\n background: var(--ntbl-badge-false-bg);\n color: var(--ntbl-badge-false-color);\n}\n.ntbl-td-editable .ntbl-badge {\n cursor: pointer;\n}\n\n.ntbl-empty {\n padding: 44px 16px;\n text-align: center;\n color: var(--ntbl-muted);\n}\n.ntbl-empty[hidden] {\n display: none;\n}\n\n/* Loading overlay */\n.ntbl-loading-overlay {\n position: absolute;\n inset: 0;\n display: none;\n align-items: center;\n justify-content: center;\n background: color-mix(in srgb, var(--ntbl-bg) 62%, transparent);\n backdrop-filter: blur(1px);\n border-radius: var(--ntbl-radius-inner);\n z-index: 4;\n}\n:host([loading]) .ntbl-loading-overlay {\n display: flex;\n}\n:host([loading]) .ntbl-scroll {\n min-height: 160px;\n}\n.ntbl-spinner {\n width: 26px;\n height: 26px;\n border-radius: 50%;\n border: 3px solid var(--ntbl-border-strong);\n border-top-color: var(--ntbl-accent);\n animation: ntbl-spin 0.7s linear infinite;\n}\n@keyframes ntbl-spin {\n to { transform: rotate(360deg); }\n}\n\n/* Footer / pagination */\n.ntbl-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-wrap: wrap;\n font-size: 0.88em;\n color: var(--ntbl-muted);\n}\n\n.ntbl-info {\n font-variant-numeric: tabular-nums;\n}\n\n.ntbl-pagination {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n.ntbl-pagination[hidden] {\n display: none;\n}\n\n.ntbl-pagination button {\n padding: 7px 10px;\n min-width: 36px;\n justify-content: center;\n}\n.ntbl-page-numbers {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n.ntbl-page-btn {\n font-variant-numeric: tabular-nums;\n}\n.ntbl-page-btn.ntbl-page-current {\n background: var(--ntbl-accent);\n border-color: var(--ntbl-accent);\n color: var(--ntbl-accent-color);\n cursor: default;\n}\n.ntbl-page-btn.ntbl-page-current:hover {\n background: var(--ntbl-accent) !important;\n}\n.ntbl-page-ellipsis {\n padding: 0 2px;\n color: var(--ntbl-muted);\n user-select: none;\n}\n\n.ntbl-page-size {\n font: inherit;\n font-size: 0.9em;\n padding: 7px 8px;\n color: var(--ntbl-color);\n background: var(--ntbl-input-bg);\n border: 1px solid var(--ntbl-input-border);\n border-radius: var(--ntbl-radius-control);\n cursor: pointer;\n}\n\n.ntbl-page-indicator {\n min-width: 84px;\n text-align: center;\n font-variant-numeric: tabular-nums;\n}\n\n/* Visually-hidden live region for screen readers */\n.ntbl-sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0 0 0 0);\n white-space: nowrap;\n border: 0;\n}\n\n/* Reduced motion */\n@media (prefers-reduced-motion: reduce) {\n .ntbl-search,\n .ntbl-tbody .ntbl-tr,\n .ntbl-toolbar-actions button,\n .ntbl-pagination button,\n .ntbl-filter-input,\n .ntbl-td-editable,\n .ntbl-resize-handle::after {\n transition: none !important;\n }\n .ntbl-spinner {\n animation-duration: 1.4s;\n }\n}\n\n/* Mobile */\n@media (max-width: 640px) {\n .ntbl-root {\n padding: 10px;\n }\n .ntbl-footer {\n flex-direction: column;\n align-items: stretch;\n }\n .ntbl-pagination {\n justify-content: center;\n flex-wrap: wrap;\n }\n}\n";
|
|
421
|
+
|
|
422
|
+
var sharedSheet = null;
|
|
423
|
+
var sharedSheetFailed = false;
|
|
424
|
+
|
|
425
|
+
function getSharedSheet(cssText) {
|
|
426
|
+
if (sharedSheet || sharedSheetFailed) return sharedSheet;
|
|
427
|
+
if (typeof CSSStyleSheet === 'undefined' || !('adoptedStyleSheets' in Document.prototype)) {
|
|
428
|
+
sharedSheetFailed = true;
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
try {
|
|
432
|
+
sharedSheet = new CSSStyleSheet();
|
|
433
|
+
sharedSheet.replaceSync(cssText);
|
|
434
|
+
} catch (err) {
|
|
435
|
+
sharedSheet = null;
|
|
436
|
+
sharedSheetFailed = true;
|
|
437
|
+
}
|
|
438
|
+
return sharedSheet;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
var TEMPLATE = document.createElement('template');
|
|
442
|
+
TEMPLATE.innerHTML =
|
|
443
|
+
'<div class="ntbl-root" part="root">' +
|
|
444
|
+
'<div class="ntbl-toolbar" part="toolbar">' +
|
|
445
|
+
'<div class="ntbl-search-wrap">' +
|
|
446
|
+
'<input type="search" class="ntbl-search" part="search">' +
|
|
447
|
+
'</div>' +
|
|
448
|
+
'<div class="ntbl-toolbar-actions">' +
|
|
449
|
+
'<span class="ntbl-selected-count" part="selected-count" hidden></span>' +
|
|
450
|
+
'<button type="button" class="ntbl-clear-filters" part="button"></button>' +
|
|
451
|
+
'<button type="button" class="ntbl-copy" part="button"></button>' +
|
|
452
|
+
'<button type="button" class="ntbl-export-csv" part="button"></button>' +
|
|
453
|
+
'<button type="button" class="ntbl-export-json" part="button"></button>' +
|
|
454
|
+
'</div>' +
|
|
455
|
+
'</div>' +
|
|
456
|
+
'<div class="ntbl-scroll" part="scroll">' +
|
|
457
|
+
'<table class="ntbl-table" part="table">' +
|
|
458
|
+
'<colgroup class="ntbl-colgroup"></colgroup>' +
|
|
459
|
+
'<thead class="ntbl-thead" part="thead">' +
|
|
460
|
+
'<tr class="ntbl-header-row"></tr>' +
|
|
461
|
+
'<tr class="ntbl-filter-row"></tr>' +
|
|
462
|
+
'</thead>' +
|
|
463
|
+
'<tbody class="ntbl-tbody" part="tbody"></tbody>' +
|
|
464
|
+
'</table>' +
|
|
465
|
+
'<div class="ntbl-empty" part="empty" hidden></div>' +
|
|
466
|
+
'<div class="ntbl-loading-overlay" part="loading" aria-hidden="true">' +
|
|
467
|
+
'<span class="ntbl-spinner"></span>' +
|
|
468
|
+
'</div>' +
|
|
469
|
+
'</div>' +
|
|
470
|
+
'<div class="ntbl-footer" part="footer">' +
|
|
471
|
+
'<div class="ntbl-info" part="info"></div>' +
|
|
472
|
+
'<div class="ntbl-pagination" part="pagination">' +
|
|
473
|
+
'<label class="ntbl-page-size-label">' +
|
|
474
|
+
'<select class="ntbl-page-size" part="page-size"></select>' +
|
|
475
|
+
'</label>' +
|
|
476
|
+
'<button type="button" class="ntbl-first" part="button">«</button>' +
|
|
477
|
+
'<button type="button" class="ntbl-prev" part="button">‹</button>' +
|
|
478
|
+
'<span class="ntbl-page-numbers" part="page-numbers"></span>' +
|
|
479
|
+
'<span class="ntbl-page-indicator" part="page-indicator"></span>' +
|
|
480
|
+
'<button type="button" class="ntbl-next" part="button">›</button>' +
|
|
481
|
+
'<button type="button" class="ntbl-last" part="button">»</button>' +
|
|
482
|
+
'</div>' +
|
|
483
|
+
'</div>' +
|
|
484
|
+
'<div class="ntbl-sr-only" role="status" aria-live="polite"></div>' +
|
|
485
|
+
'</div>';
|
|
486
|
+
|
|
487
|
+
class NeikiTable extends HTMLElement {
|
|
488
|
+
constructor() {
|
|
489
|
+
super();
|
|
490
|
+
this._init();
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
NeikiTable.observedAttributes = [
|
|
495
|
+
'locale', 'theme', 'density', 'row-key', 'searchable', 'filterable', 'selectable',
|
|
496
|
+
'editable', 'paginated', 'exportable', 'resizable', 'loading', 'search-debounce',
|
|
497
|
+
'page-size', 'columns', 'data'
|
|
498
|
+
];
|
|
499
|
+
|
|
500
|
+
NeikiTable.prototype._init = function () {
|
|
501
|
+
this._ready = false;
|
|
502
|
+
this._reflecting = false;
|
|
503
|
+
this._mediaQuery = null;
|
|
504
|
+
this._i18n = Object.assign({}, I18N);
|
|
505
|
+
this._config = Object.assign({}, DEFAULT_CONFIG);
|
|
506
|
+
this._columns = [];
|
|
507
|
+
this._rows = [];
|
|
508
|
+
this._state = {
|
|
509
|
+
search: '',
|
|
510
|
+
filters: {},
|
|
511
|
+
sort: { key: null, dir: null },
|
|
512
|
+
page: 1,
|
|
513
|
+
selected: {},
|
|
514
|
+
editing: null,
|
|
515
|
+
columnWidths: {}
|
|
516
|
+
};
|
|
517
|
+
this._searchTimer = null;
|
|
518
|
+
this._copyResetTimer = null;
|
|
519
|
+
this._resize = null;
|
|
520
|
+
|
|
521
|
+
this.attachShadow({ mode: 'open' });
|
|
522
|
+
this.shadowRoot.appendChild(TEMPLATE.content.cloneNode(true));
|
|
523
|
+
this._injectStyles();
|
|
524
|
+
|
|
525
|
+
this._root = this.shadowRoot.querySelector('.ntbl-root');
|
|
526
|
+
this._searchInput = this.shadowRoot.querySelector('.ntbl-search');
|
|
527
|
+
this._selectedCountEl = this.shadowRoot.querySelector('.ntbl-selected-count');
|
|
528
|
+
this._clearFiltersBtn = this.shadowRoot.querySelector('.ntbl-clear-filters');
|
|
529
|
+
this._copyBtn = this.shadowRoot.querySelector('.ntbl-copy');
|
|
530
|
+
this._exportCsvBtn = this.shadowRoot.querySelector('.ntbl-export-csv');
|
|
531
|
+
this._exportJsonBtn = this.shadowRoot.querySelector('.ntbl-export-json');
|
|
532
|
+
this._colgroup = this.shadowRoot.querySelector('.ntbl-colgroup');
|
|
533
|
+
this._headerRow = this.shadowRoot.querySelector('.ntbl-header-row');
|
|
534
|
+
this._filterRow = this.shadowRoot.querySelector('.ntbl-filter-row');
|
|
535
|
+
this._tbody = this.shadowRoot.querySelector('.ntbl-tbody');
|
|
536
|
+
this._emptyEl = this.shadowRoot.querySelector('.ntbl-empty');
|
|
537
|
+
this._infoEl = this.shadowRoot.querySelector('.ntbl-info');
|
|
538
|
+
this._liveRegion = this.shadowRoot.querySelector('.ntbl-sr-only');
|
|
539
|
+
this._pageSizeSelect = this.shadowRoot.querySelector('.ntbl-page-size');
|
|
540
|
+
this._firstBtn = this.shadowRoot.querySelector('.ntbl-first');
|
|
541
|
+
this._prevBtn = this.shadowRoot.querySelector('.ntbl-prev');
|
|
542
|
+
this._nextBtn = this.shadowRoot.querySelector('.ntbl-next');
|
|
543
|
+
this._lastBtn = this.shadowRoot.querySelector('.ntbl-last');
|
|
544
|
+
this._pageNumbers = this.shadowRoot.querySelector('.ntbl-page-numbers');
|
|
545
|
+
this._pageIndicator = this.shadowRoot.querySelector('.ntbl-page-indicator');
|
|
546
|
+
|
|
547
|
+
this._onMediaChange = this._onMediaChange.bind(this);
|
|
548
|
+
this._onResizeMove = this._onResizeMove.bind(this);
|
|
549
|
+
this._onResizeEnd = this._onResizeEnd.bind(this);
|
|
550
|
+
|
|
551
|
+
var self = this;
|
|
552
|
+
this._searchInput.addEventListener('input', function () {
|
|
553
|
+
var value = self._searchInput.value;
|
|
554
|
+
var apply = function () {
|
|
555
|
+
self._searchTimer = null;
|
|
556
|
+
self._state.search = value;
|
|
557
|
+
self._state.page = 1;
|
|
558
|
+
self._render();
|
|
559
|
+
self._emit('search', { query: self._state.search });
|
|
560
|
+
};
|
|
561
|
+
if (self._searchTimer) clearTimeout(self._searchTimer);
|
|
562
|
+
var delay = self._config.searchDebounce;
|
|
563
|
+
if (delay > 0) self._searchTimer = setTimeout(apply, delay);
|
|
564
|
+
else apply();
|
|
565
|
+
});
|
|
566
|
+
this._clearFiltersBtn.addEventListener('click', function () {
|
|
567
|
+
if (self._searchTimer) { clearTimeout(self._searchTimer); self._searchTimer = null; }
|
|
568
|
+
self._state.filters = {};
|
|
569
|
+
self._state.search = '';
|
|
570
|
+
self._state.page = 1;
|
|
571
|
+
self._searchInput.value = '';
|
|
572
|
+
self._render();
|
|
573
|
+
self._emit('filter', { filters: {} });
|
|
574
|
+
});
|
|
575
|
+
this._copyBtn.addEventListener('click', function () { self.copyCSV(); });
|
|
576
|
+
this._exportCsvBtn.addEventListener('click', function () { self.exportCSV(); });
|
|
577
|
+
this._exportJsonBtn.addEventListener('click', function () { self.exportJSON(); });
|
|
578
|
+
this._pageSizeSelect.addEventListener('change', function () {
|
|
579
|
+
self.setPageSize(parseInt(self._pageSizeSelect.value, 10));
|
|
580
|
+
});
|
|
581
|
+
this._firstBtn.addEventListener('click', function () { self.goToPage(1); });
|
|
582
|
+
this._prevBtn.addEventListener('click', function () { self.goToPage(self._state.page - 1); });
|
|
583
|
+
this._nextBtn.addEventListener('click', function () { self.goToPage(self._state.page + 1); });
|
|
584
|
+
this._lastBtn.addEventListener('click', function () { self.goToPage(self._lastPageCount || 1); });
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
NeikiTable.prototype._injectStyles = function () {
|
|
588
|
+
if (EMBEDDED_CSS) {
|
|
589
|
+
var sheet = getSharedSheet(EMBEDDED_CSS);
|
|
590
|
+
if (sheet) {
|
|
591
|
+
this.shadowRoot.adoptedStyleSheets = [sheet];
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
var style = document.createElement('style');
|
|
595
|
+
style.textContent = EMBEDDED_CSS;
|
|
596
|
+
this.shadowRoot.insertBefore(style, this.shadowRoot.firstChild);
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
var link = document.createElement('link');
|
|
600
|
+
link.rel = 'stylesheet';
|
|
601
|
+
link.href = this._resolveStylesheetUrl();
|
|
602
|
+
this.shadowRoot.insertBefore(link, this.shadowRoot.firstChild);
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
NeikiTable.prototype._resolveStylesheetUrl = function () {
|
|
606
|
+
var scriptEl = document.currentScript;
|
|
607
|
+
if (!scriptEl) {
|
|
608
|
+
var scripts = document.querySelectorAll('script[src]');
|
|
609
|
+
for (var i = scripts.length - 1; i >= 0; i--) {
|
|
610
|
+
if (/neiki-table(\.min)?\.js/.test(scripts[i].src)) {
|
|
611
|
+
scriptEl = scripts[i];
|
|
612
|
+
break;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
var src = scriptEl ? scriptEl.src : '';
|
|
617
|
+
if (/\.min\.js(\?.*)?$/.test(src)) {
|
|
618
|
+
return src.replace(/\.min\.js(\?.*)?$/, '.min.css$1');
|
|
619
|
+
}
|
|
620
|
+
if (/\.js(\?.*)?$/.test(src)) {
|
|
621
|
+
return src.replace(/\.js(\?.*)?$/, '.css$1');
|
|
622
|
+
}
|
|
623
|
+
return 'neiki-table.css';
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
NeikiTable.prototype.connectedCallback = function () {
|
|
627
|
+
this._readAttributesIntoConfig();
|
|
628
|
+
this._render();
|
|
629
|
+
if (!this._ready) {
|
|
630
|
+
this._ready = true;
|
|
631
|
+
this._emit('ready', { config: this.getConfig() });
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
NeikiTable.prototype.disconnectedCallback = function () {
|
|
636
|
+
if (this._mediaQuery) {
|
|
637
|
+
this._mediaQuery.removeEventListener('change', this._onMediaChange);
|
|
638
|
+
this._mediaQuery = null;
|
|
639
|
+
}
|
|
640
|
+
if (this._searchTimer) { clearTimeout(this._searchTimer); this._searchTimer = null; }
|
|
641
|
+
if (this._copyResetTimer) { clearTimeout(this._copyResetTimer); this._copyResetTimer = null; }
|
|
642
|
+
this._endResize();
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
NeikiTable.prototype.attributeChangedCallback = function (name, oldValue, newValue) {
|
|
646
|
+
if (this._reflecting || oldValue === newValue) return;
|
|
647
|
+
if (name === 'columns') {
|
|
648
|
+
if (newValue) {
|
|
649
|
+
try { this.setColumns(JSON.parse(newValue)); } catch (err) { this._emit('error', { reason: 'invalid-columns-json' }); }
|
|
650
|
+
}
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
if (name === 'data') {
|
|
654
|
+
if (newValue) {
|
|
655
|
+
try { this.setData(JSON.parse(newValue)); } catch (err) { this._emit('error', { reason: 'invalid-data-json' }); }
|
|
656
|
+
}
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
this._readAttributesIntoConfig();
|
|
660
|
+
if (this.isConnected) this._render();
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
NeikiTable.prototype._readAttributesIntoConfig = function () {
|
|
664
|
+
var cfg = this._config;
|
|
665
|
+
cfg.locale = this.getAttribute('locale') || cfg.locale || DEFAULT_CONFIG.locale;
|
|
666
|
+
cfg.theme = oneOf(this.getAttribute('theme'), VALID_THEMES, cfg.theme || DEFAULT_CONFIG.theme);
|
|
667
|
+
cfg.density = oneOf(this.getAttribute('density'), VALID_DENSITIES, cfg.density || DEFAULT_CONFIG.density);
|
|
668
|
+
cfg.rowKey = this.getAttribute('row-key') || cfg.rowKey || DEFAULT_CONFIG.rowKey;
|
|
669
|
+
cfg.searchable = toBool(this.getAttribute('searchable'), cfg.searchable);
|
|
670
|
+
cfg.filterable = toBool(this.getAttribute('filterable'), cfg.filterable);
|
|
671
|
+
cfg.selectable = toBool(this.getAttribute('selectable'), cfg.selectable);
|
|
672
|
+
cfg.editable = toBool(this.getAttribute('editable'), cfg.editable);
|
|
673
|
+
cfg.paginated = toBool(this.getAttribute('paginated'), cfg.paginated);
|
|
674
|
+
cfg.exportable = toBool(this.getAttribute('exportable'), cfg.exportable);
|
|
675
|
+
cfg.resizable = toBool(this.getAttribute('resizable'), cfg.resizable);
|
|
676
|
+
cfg.loading = this.hasAttribute('loading') && this.getAttribute('loading') !== 'false';
|
|
677
|
+
var debounce = parseInt(this.getAttribute('search-debounce'), 10);
|
|
678
|
+
if (!isNaN(debounce) && debounce >= 0) cfg.searchDebounce = debounce;
|
|
679
|
+
var pageSize = parseInt(this.getAttribute('page-size'), 10);
|
|
680
|
+
if (!isNaN(pageSize) && pageSize > 0) cfg.pageSize = pageSize;
|
|
681
|
+
};
|
|
682
|
+
|
|
683
|
+
NeikiTable.prototype._reflectAttributes = function () {
|
|
684
|
+
this._reflecting = true;
|
|
685
|
+
var cfg = this._config;
|
|
686
|
+
this.setAttribute('locale', cfg.locale);
|
|
687
|
+
this.setAttribute('theme', cfg.theme);
|
|
688
|
+
this.setAttribute('density', cfg.density);
|
|
689
|
+
if (cfg.loading) this.setAttribute('loading', '');
|
|
690
|
+
else this.removeAttribute('loading');
|
|
691
|
+
this._reflecting = false;
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
NeikiTable.prototype._resolveTheme = function () {
|
|
695
|
+
if (this._config.theme !== 'auto') return this._config.theme;
|
|
696
|
+
if (!this._mediaQuery) {
|
|
697
|
+
this._mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
698
|
+
this._mediaQuery.addEventListener('change', this._onMediaChange);
|
|
699
|
+
}
|
|
700
|
+
return this._mediaQuery.matches ? 'dark' : 'light';
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
NeikiTable.prototype._onMediaChange = function () {
|
|
704
|
+
if (this._config.theme === 'auto') this._render();
|
|
705
|
+
};
|
|
706
|
+
|
|
707
|
+
NeikiTable.prototype._t = function (key, vars) {
|
|
708
|
+
return translate(this._config.locale, this._i18n, key, vars);
|
|
709
|
+
};
|
|
710
|
+
|
|
711
|
+
// ---------------------------------------------------------------------
|
|
712
|
+
// Data pipeline
|
|
713
|
+
// ---------------------------------------------------------------------
|
|
714
|
+
|
|
715
|
+
NeikiTable.prototype._filterableColumns = function () {
|
|
716
|
+
return this._columns.filter(function (col) { return col.filterable !== false; });
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
NeikiTable.prototype._cellText = function (row, col) {
|
|
720
|
+
var value = getValue(row, col.key);
|
|
721
|
+
if (typeof col.format === 'function') {
|
|
722
|
+
try {
|
|
723
|
+
var out = col.format(value, row);
|
|
724
|
+
return out === null || out === undefined ? '' : String(out);
|
|
725
|
+
} catch (err) { /* fall through to default formatting */ }
|
|
726
|
+
}
|
|
727
|
+
return formatDisplay(value, col, this._config.locale);
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
NeikiTable.prototype._computeView = function () {
|
|
731
|
+
var self = this;
|
|
732
|
+
var cfg = this._config;
|
|
733
|
+
var state = this._state;
|
|
734
|
+
var rows = this._rows.slice();
|
|
735
|
+
|
|
736
|
+
if (cfg.searchable && state.search) {
|
|
737
|
+
var query = state.search.toLowerCase();
|
|
738
|
+
rows = rows.filter(function (row) {
|
|
739
|
+
return self._columns.some(function (col) {
|
|
740
|
+
return self._cellText(row, col).toLowerCase().indexOf(query) !== -1;
|
|
741
|
+
});
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
if (cfg.filterable) {
|
|
746
|
+
Object.keys(state.filters).forEach(function (key) {
|
|
747
|
+
var filterValue = state.filters[key];
|
|
748
|
+
if (filterValue === undefined || filterValue === null || filterValue === '') return;
|
|
749
|
+
var col = self._columns.filter(function (c) { return c.key === key; })[0];
|
|
750
|
+
if (!col) return;
|
|
751
|
+
rows = rows.filter(function (row) {
|
|
752
|
+
var raw = getValue(row, key);
|
|
753
|
+
if (col.type === 'boolean') {
|
|
754
|
+
return String(!!raw) === filterValue;
|
|
755
|
+
}
|
|
756
|
+
if (col.type === 'select') {
|
|
757
|
+
return String(raw) === filterValue;
|
|
758
|
+
}
|
|
759
|
+
var display = self._cellText(row, col).toLowerCase();
|
|
760
|
+
return display.indexOf(String(filterValue).toLowerCase()) !== -1;
|
|
761
|
+
});
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
if (state.sort.key) {
|
|
766
|
+
var sortCol = this._columns.filter(function (c) { return c.key === state.sort.key; })[0];
|
|
767
|
+
var type = sortCol ? sortCol.type : 'text';
|
|
768
|
+
var dir = state.sort.dir === 'desc' ? -1 : 1;
|
|
769
|
+
rows.sort(function (a, b) {
|
|
770
|
+
return dir * compareValues(getValue(a, state.sort.key), getValue(b, state.sort.key), type);
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
return rows;
|
|
775
|
+
};
|
|
776
|
+
|
|
777
|
+
NeikiTable.prototype._rowKeyValue = function (row, index) {
|
|
778
|
+
var key = this._config.rowKey;
|
|
779
|
+
return row && row[key] !== undefined ? row[key] : '__index_' + index;
|
|
780
|
+
};
|
|
781
|
+
|
|
782
|
+
// ---------------------------------------------------------------------
|
|
783
|
+
// Rendering
|
|
784
|
+
// ---------------------------------------------------------------------
|
|
785
|
+
|
|
786
|
+
NeikiTable.prototype._captureFocus = function () {
|
|
787
|
+
var active = this.shadowRoot.activeElement;
|
|
788
|
+
if (!active) return null;
|
|
789
|
+
if (active === this._searchInput) {
|
|
790
|
+
return { type: 'search', start: active.selectionStart, end: active.selectionEnd };
|
|
791
|
+
}
|
|
792
|
+
if (active.classList && active.classList.contains('ntbl-filter-input')) {
|
|
793
|
+
return { type: 'filter', key: active.dataset.key, start: active.selectionStart, end: active.selectionEnd };
|
|
794
|
+
}
|
|
795
|
+
return null;
|
|
796
|
+
};
|
|
797
|
+
|
|
798
|
+
NeikiTable.prototype._restoreFocus = function (info) {
|
|
799
|
+
if (!info) return;
|
|
800
|
+
var el = null;
|
|
801
|
+
if (info.type === 'search') {
|
|
802
|
+
el = this._searchInput;
|
|
803
|
+
} else if (info.type === 'filter') {
|
|
804
|
+
el = this._filterRow.querySelector('.ntbl-filter-input[data-key="' + info.key + '"]');
|
|
805
|
+
}
|
|
806
|
+
if (!el) return;
|
|
807
|
+
el.focus();
|
|
808
|
+
if (typeof info.start === 'number' && typeof el.setSelectionRange === 'function') {
|
|
809
|
+
try { el.setSelectionRange(info.start, info.end); } catch (err) { /* not a text selection input */ }
|
|
810
|
+
}
|
|
811
|
+
};
|
|
812
|
+
|
|
813
|
+
NeikiTable.prototype._render = function () {
|
|
814
|
+
var focusInfo = this._captureFocus();
|
|
815
|
+
this._reflectAttributes();
|
|
816
|
+
this.setAttribute('resolved-theme', this._resolveTheme());
|
|
817
|
+
|
|
818
|
+
this._searchInput.placeholder = this._t('searchPlaceholder');
|
|
819
|
+
if (document.activeElement !== this && this.shadowRoot.activeElement !== this._searchInput) {
|
|
820
|
+
this._searchInput.value = this._state.search;
|
|
821
|
+
}
|
|
822
|
+
this._searchInput.parentElement.hidden = !this._config.searchable;
|
|
823
|
+
this._clearFiltersBtn.textContent = this._t('clearFilters');
|
|
824
|
+
if (!this._copyResetTimer) this._copyBtn.textContent = this._t('copy');
|
|
825
|
+
this._exportCsvBtn.textContent = this._t('exportCsv');
|
|
826
|
+
this._exportJsonBtn.textContent = this._t('exportJson');
|
|
827
|
+
this._clearFiltersBtn.hidden = !(this._config.filterable || this._config.searchable);
|
|
828
|
+
this._copyBtn.hidden = !this._config.exportable;
|
|
829
|
+
this._exportCsvBtn.hidden = !this._config.exportable;
|
|
830
|
+
this._exportJsonBtn.hidden = !this._config.exportable;
|
|
831
|
+
|
|
832
|
+
var selectedKeys = Object.keys(this._state.selected).filter(function (k) { return this[k]; }, this._state.selected);
|
|
833
|
+
if (this._config.selectable && selectedKeys.length > 0) {
|
|
834
|
+
this._selectedCountEl.hidden = false;
|
|
835
|
+
this._selectedCountEl.textContent = this._t('selectedCount', { count: selectedKeys.length });
|
|
836
|
+
} else {
|
|
837
|
+
this._selectedCountEl.hidden = true;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
var view = this._computeView();
|
|
841
|
+
var total = view.length;
|
|
842
|
+
|
|
843
|
+
var pageSize = this._config.paginated ? this._config.pageSize : total || 1;
|
|
844
|
+
var pageCount = Math.max(1, Math.ceil(total / pageSize));
|
|
845
|
+
if (this._state.page > pageCount) this._state.page = pageCount;
|
|
846
|
+
if (this._state.page < 1) this._state.page = 1;
|
|
847
|
+
|
|
848
|
+
this._lastPageCount = pageCount;
|
|
849
|
+
|
|
850
|
+
var start = this._config.paginated ? (this._state.page - 1) * pageSize : 0;
|
|
851
|
+
var pageRows = this._config.paginated ? view.slice(start, start + pageSize) : view;
|
|
852
|
+
|
|
853
|
+
this._renderColgroup();
|
|
854
|
+
this._renderHeader();
|
|
855
|
+
this._renderFilterRow();
|
|
856
|
+
this._renderBody(pageRows, view, start);
|
|
857
|
+
this._renderFooter(total, pageCount, start, pageRows.length);
|
|
858
|
+
this._announce(total);
|
|
859
|
+
this._restoreFocus(focusInfo);
|
|
860
|
+
};
|
|
861
|
+
|
|
862
|
+
NeikiTable.prototype._announce = function (total) {
|
|
863
|
+
if (!this._liveRegion) return;
|
|
864
|
+
var msg = this._t('resultsAnnounce', { total: total });
|
|
865
|
+
if (this._liveRegion.textContent !== msg) this._liveRegion.textContent = msg;
|
|
866
|
+
};
|
|
867
|
+
|
|
868
|
+
NeikiTable.prototype._renderColgroup = function () {
|
|
869
|
+
var self = this;
|
|
870
|
+
this._colgroup.textContent = '';
|
|
871
|
+
if (this._config.selectable) {
|
|
872
|
+
var selCol = document.createElement('col');
|
|
873
|
+
selCol.style.width = '44px';
|
|
874
|
+
this._colgroup.appendChild(selCol);
|
|
875
|
+
}
|
|
876
|
+
this._columns.forEach(function (col) {
|
|
877
|
+
var c = document.createElement('col');
|
|
878
|
+
var width = self._state.columnWidths[col.key] || col.width;
|
|
879
|
+
if (width) c.style.width = typeof width === 'number' ? width + 'px' : width;
|
|
880
|
+
self._colgroup.appendChild(c);
|
|
881
|
+
});
|
|
882
|
+
};
|
|
883
|
+
|
|
884
|
+
NeikiTable.prototype._renderHeader = function () {
|
|
885
|
+
var self = this;
|
|
886
|
+
var cfg = this._config;
|
|
887
|
+
this._headerRow.textContent = '';
|
|
888
|
+
|
|
889
|
+
if (cfg.selectable) {
|
|
890
|
+
var th = document.createElement('th');
|
|
891
|
+
th.className = 'ntbl-th ntbl-th-select';
|
|
892
|
+
th.setAttribute('part', 'th');
|
|
893
|
+
var checkbox = document.createElement('input');
|
|
894
|
+
checkbox.type = 'checkbox';
|
|
895
|
+
checkbox.className = 'ntbl-select-all';
|
|
896
|
+
checkbox.setAttribute('aria-label', this._t('selectAll'));
|
|
897
|
+
var view = this._computeView();
|
|
898
|
+
var allSelected = view.length > 0 && view.every(function (row, i) {
|
|
899
|
+
return self._state.selected[self._rowKeyValue(row, i)];
|
|
900
|
+
});
|
|
901
|
+
checkbox.checked = allSelected;
|
|
902
|
+
checkbox.addEventListener('change', function () {
|
|
903
|
+
self.selectAll(checkbox.checked);
|
|
904
|
+
});
|
|
905
|
+
th.appendChild(checkbox);
|
|
906
|
+
this._headerRow.appendChild(th);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
this._columns.forEach(function (col, colIndex) {
|
|
910
|
+
var cell = document.createElement('th');
|
|
911
|
+
cell.className = 'ntbl-th';
|
|
912
|
+
cell.setAttribute('part', 'th');
|
|
913
|
+
var align = oneOf(col.align, VALID_ALIGN, 'left');
|
|
914
|
+
if (align !== 'left') cell.classList.add('ntbl-th-align-' + align);
|
|
915
|
+
|
|
916
|
+
var label = document.createElement('span');
|
|
917
|
+
label.className = 'ntbl-th-label';
|
|
918
|
+
label.textContent = col.label !== undefined ? col.label : col.key;
|
|
919
|
+
cell.appendChild(label);
|
|
920
|
+
|
|
921
|
+
if (col.sortable !== false) {
|
|
922
|
+
cell.classList.add('ntbl-th-sortable');
|
|
923
|
+
var isSorted = self._state.sort.key === col.key;
|
|
924
|
+
if (isSorted) {
|
|
925
|
+
cell.classList.add('ntbl-th-sorted-' + self._state.sort.dir);
|
|
926
|
+
cell.setAttribute('aria-sort', self._state.sort.dir === 'asc' ? 'ascending' : 'descending');
|
|
927
|
+
}
|
|
928
|
+
var icon = document.createElement('span');
|
|
929
|
+
icon.className = 'ntbl-sort-icon';
|
|
930
|
+
icon.setAttribute('aria-hidden', 'true');
|
|
931
|
+
cell.appendChild(icon);
|
|
932
|
+
cell.setAttribute('tabindex', '0');
|
|
933
|
+
cell.setAttribute('role', 'button');
|
|
934
|
+
cell.addEventListener('click', function () { self._toggleSort(col.key); });
|
|
935
|
+
cell.addEventListener('keydown', function (event) {
|
|
936
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
937
|
+
event.preventDefault();
|
|
938
|
+
self._toggleSort(col.key);
|
|
939
|
+
}
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
if (self._config.resizable && col.resizable !== false) {
|
|
944
|
+
var handle = document.createElement('span');
|
|
945
|
+
handle.className = 'ntbl-resize-handle';
|
|
946
|
+
handle.setAttribute('aria-hidden', 'true');
|
|
947
|
+
var colOffset = self._config.selectable ? 1 : 0;
|
|
948
|
+
handle.addEventListener('pointerdown', function (event) {
|
|
949
|
+
self._startResize(event, col.key, cell, colOffset + colIndex);
|
|
950
|
+
});
|
|
951
|
+
handle.addEventListener('click', function (event) { event.stopPropagation(); });
|
|
952
|
+
cell.appendChild(handle);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
self._headerRow.appendChild(cell);
|
|
956
|
+
});
|
|
957
|
+
};
|
|
958
|
+
|
|
959
|
+
// ---------------------------------------------------------------------
|
|
960
|
+
// Column resizing
|
|
961
|
+
// ---------------------------------------------------------------------
|
|
962
|
+
|
|
963
|
+
NeikiTable.prototype._startResize = function (event, key, cell, colElIndex) {
|
|
964
|
+
event.preventDefault();
|
|
965
|
+
event.stopPropagation();
|
|
966
|
+
var colEl = this._colgroup.children[colElIndex];
|
|
967
|
+
this._resize = {
|
|
968
|
+
key: key,
|
|
969
|
+
cell: cell,
|
|
970
|
+
colEl: colEl,
|
|
971
|
+
startX: event.clientX,
|
|
972
|
+
startWidth: cell.getBoundingClientRect().width
|
|
973
|
+
};
|
|
974
|
+
cell.classList.add('ntbl-th-resizing');
|
|
975
|
+
document.addEventListener('pointermove', this._onResizeMove);
|
|
976
|
+
document.addEventListener('pointerup', this._onResizeEnd);
|
|
977
|
+
document.addEventListener('pointercancel', this._onResizeEnd);
|
|
978
|
+
};
|
|
979
|
+
|
|
980
|
+
NeikiTable.prototype._onResizeMove = function (event) {
|
|
981
|
+
if (!this._resize) return;
|
|
982
|
+
var delta = event.clientX - this._resize.startX;
|
|
983
|
+
var width = Math.max(MIN_COLUMN_WIDTH, Math.round(this._resize.startWidth + delta));
|
|
984
|
+
if (this._resize.colEl) this._resize.colEl.style.width = width + 'px';
|
|
985
|
+
this._resize.currentWidth = width;
|
|
986
|
+
};
|
|
987
|
+
|
|
988
|
+
NeikiTable.prototype._onResizeEnd = function () {
|
|
989
|
+
if (!this._resize) return;
|
|
990
|
+
if (this._resize.currentWidth) {
|
|
991
|
+
this._state.columnWidths[this._resize.key] = this._resize.currentWidth;
|
|
992
|
+
this._emit('column-resize', { key: this._resize.key, width: this._resize.currentWidth });
|
|
993
|
+
}
|
|
994
|
+
this._endResize();
|
|
995
|
+
};
|
|
996
|
+
|
|
997
|
+
NeikiTable.prototype._endResize = function () {
|
|
998
|
+
if (this._resize && this._resize.cell) this._resize.cell.classList.remove('ntbl-th-resizing');
|
|
999
|
+
document.removeEventListener('pointermove', this._onResizeMove);
|
|
1000
|
+
document.removeEventListener('pointerup', this._onResizeEnd);
|
|
1001
|
+
document.removeEventListener('pointercancel', this._onResizeEnd);
|
|
1002
|
+
this._resize = null;
|
|
1003
|
+
};
|
|
1004
|
+
|
|
1005
|
+
NeikiTable.prototype._toggleSort = function (key) {
|
|
1006
|
+
var state = this._state;
|
|
1007
|
+
if (state.sort.key !== key) {
|
|
1008
|
+
state.sort.key = key;
|
|
1009
|
+
state.sort.dir = 'asc';
|
|
1010
|
+
} else if (state.sort.dir === 'asc') {
|
|
1011
|
+
state.sort.dir = 'desc';
|
|
1012
|
+
} else {
|
|
1013
|
+
state.sort.key = null;
|
|
1014
|
+
state.sort.dir = null;
|
|
1015
|
+
}
|
|
1016
|
+
this._render();
|
|
1017
|
+
this._emit('sort', { key: state.sort.key, dir: state.sort.dir });
|
|
1018
|
+
};
|
|
1019
|
+
|
|
1020
|
+
NeikiTable.prototype._renderFilterRow = function () {
|
|
1021
|
+
var self = this;
|
|
1022
|
+
var cfg = this._config;
|
|
1023
|
+
this._filterRow.textContent = '';
|
|
1024
|
+
this._filterRow.hidden = !cfg.filterable;
|
|
1025
|
+
if (!cfg.filterable) return;
|
|
1026
|
+
|
|
1027
|
+
if (cfg.selectable) {
|
|
1028
|
+
var spacer = document.createElement('th');
|
|
1029
|
+
spacer.className = 'ntbl-th ntbl-th-select';
|
|
1030
|
+
this._filterRow.appendChild(spacer);
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
this._columns.forEach(function (col) {
|
|
1034
|
+
var th = document.createElement('th');
|
|
1035
|
+
th.className = 'ntbl-th ntbl-filter-cell';
|
|
1036
|
+
if (col.filterable === false) {
|
|
1037
|
+
self._filterRow.appendChild(th);
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
var currentValue = self._state.filters[col.key] || '';
|
|
1042
|
+
|
|
1043
|
+
if (col.type === 'boolean') {
|
|
1044
|
+
var boolSelect = document.createElement('select');
|
|
1045
|
+
boolSelect.className = 'ntbl-filter-input';
|
|
1046
|
+
boolSelect.dataset.key = col.key;
|
|
1047
|
+
boolSelect.appendChild(new Option(self._t('all'), ''));
|
|
1048
|
+
boolSelect.appendChild(new Option(self._t('yes'), 'true'));
|
|
1049
|
+
boolSelect.appendChild(new Option(self._t('no'), 'false'));
|
|
1050
|
+
boolSelect.value = currentValue;
|
|
1051
|
+
boolSelect.addEventListener('change', function () {
|
|
1052
|
+
self._setFilterInternal(col.key, boolSelect.value);
|
|
1053
|
+
});
|
|
1054
|
+
th.appendChild(boolSelect);
|
|
1055
|
+
} else if (col.type === 'select') {
|
|
1056
|
+
var select = document.createElement('select');
|
|
1057
|
+
select.className = 'ntbl-filter-input';
|
|
1058
|
+
select.dataset.key = col.key;
|
|
1059
|
+
select.appendChild(new Option(self._t('all'), ''));
|
|
1060
|
+
normalizeOptions(col).forEach(function (opt) {
|
|
1061
|
+
select.appendChild(new Option(opt.label, String(opt.value)));
|
|
1062
|
+
});
|
|
1063
|
+
select.value = currentValue;
|
|
1064
|
+
select.addEventListener('change', function () {
|
|
1065
|
+
self._setFilterInternal(col.key, select.value);
|
|
1066
|
+
});
|
|
1067
|
+
th.appendChild(select);
|
|
1068
|
+
} else {
|
|
1069
|
+
var input = document.createElement('input');
|
|
1070
|
+
input.type = 'text';
|
|
1071
|
+
input.className = 'ntbl-filter-input';
|
|
1072
|
+
input.dataset.key = col.key;
|
|
1073
|
+
input.placeholder = self._t('filterPlaceholder');
|
|
1074
|
+
input.value = currentValue;
|
|
1075
|
+
input.addEventListener('input', function () {
|
|
1076
|
+
self._setFilterInternal(col.key, input.value);
|
|
1077
|
+
});
|
|
1078
|
+
th.appendChild(input);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
self._filterRow.appendChild(th);
|
|
1082
|
+
});
|
|
1083
|
+
};
|
|
1084
|
+
|
|
1085
|
+
NeikiTable.prototype._setFilterInternal = function (key, value) {
|
|
1086
|
+
if (value === '') delete this._state.filters[key];
|
|
1087
|
+
else this._state.filters[key] = value;
|
|
1088
|
+
this._state.page = 1;
|
|
1089
|
+
this._render();
|
|
1090
|
+
this._emit('filter', { filters: Object.assign({}, this._state.filters) });
|
|
1091
|
+
};
|
|
1092
|
+
|
|
1093
|
+
NeikiTable.prototype._renderBody = function (pageRows, fullView, offset) {
|
|
1094
|
+
var self = this;
|
|
1095
|
+
var cfg = this._config;
|
|
1096
|
+
this._tbody.textContent = '';
|
|
1097
|
+
|
|
1098
|
+
var hasData = this._rows.length > 0;
|
|
1099
|
+
var hasResults = pageRows.length > 0;
|
|
1100
|
+
this._emptyEl.hidden = hasResults;
|
|
1101
|
+
if (!hasResults) {
|
|
1102
|
+
this._emptyEl.textContent = hasData ? this._t('noResults') : this._t('noData');
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
pageRows.forEach(function (row, i) {
|
|
1106
|
+
var index = offset + i;
|
|
1107
|
+
var rowKey = self._rowKeyValue(row, index);
|
|
1108
|
+
var tr = document.createElement('tr');
|
|
1109
|
+
tr.className = 'ntbl-tr';
|
|
1110
|
+
tr.setAttribute('part', 'tr');
|
|
1111
|
+
if (self._state.selected[rowKey]) tr.classList.add('ntbl-tr-selected');
|
|
1112
|
+
tr.addEventListener('click', function (event) {
|
|
1113
|
+
var tag = event.target && event.target.tagName;
|
|
1114
|
+
if (tag === 'INPUT' || tag === 'SELECT' || tag === 'OPTION') return;
|
|
1115
|
+
self._emit('row-click', { rowKey: rowKey, row: Object.assign({}, row) });
|
|
1116
|
+
});
|
|
1117
|
+
|
|
1118
|
+
if (cfg.selectable) {
|
|
1119
|
+
var td = document.createElement('td');
|
|
1120
|
+
td.className = 'ntbl-td ntbl-td-select';
|
|
1121
|
+
var checkbox = document.createElement('input');
|
|
1122
|
+
checkbox.type = 'checkbox';
|
|
1123
|
+
checkbox.setAttribute('aria-label', self._t('selectRow'));
|
|
1124
|
+
checkbox.checked = !!self._state.selected[rowKey];
|
|
1125
|
+
checkbox.addEventListener('change', function () {
|
|
1126
|
+
self.selectRow(rowKey, checkbox.checked);
|
|
1127
|
+
});
|
|
1128
|
+
td.appendChild(checkbox);
|
|
1129
|
+
tr.appendChild(td);
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
self._columns.forEach(function (col) {
|
|
1133
|
+
var td = document.createElement('td');
|
|
1134
|
+
td.className = 'ntbl-td';
|
|
1135
|
+
td.setAttribute('part', 'td');
|
|
1136
|
+
var align = oneOf(col.align, VALID_ALIGN, col.type === 'number' ? 'right' : 'left');
|
|
1137
|
+
if (align !== 'left') td.classList.add('ntbl-td-align-' + align);
|
|
1138
|
+
else if (col.type === 'number') td.classList.add('ntbl-td-num');
|
|
1139
|
+
self._renderCell(td, row, col, rowKey);
|
|
1140
|
+
tr.appendChild(td);
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1143
|
+
self._tbody.appendChild(tr);
|
|
1144
|
+
});
|
|
1145
|
+
};
|
|
1146
|
+
|
|
1147
|
+
NeikiTable.prototype._renderCell = function (td, row, col, rowKey) {
|
|
1148
|
+
var self = this;
|
|
1149
|
+
var value = getValue(row, col.key);
|
|
1150
|
+
var editable = this._config.editable && col.editable !== false;
|
|
1151
|
+
var isEditing = this._state.editing && this._state.editing.rowKey === rowKey && this._state.editing.key === col.key;
|
|
1152
|
+
|
|
1153
|
+
if (isEditing) {
|
|
1154
|
+
td.classList.add('ntbl-td-editing');
|
|
1155
|
+
this._renderEditor(td, row, col, rowKey, value);
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
if (col.type === 'boolean') {
|
|
1160
|
+
var badge = document.createElement('span');
|
|
1161
|
+
badge.className = 'ntbl-badge ' + (value ? 'ntbl-badge-true' : 'ntbl-badge-false');
|
|
1162
|
+
badge.textContent = value ? this._t('yes') : this._t('no');
|
|
1163
|
+
td.appendChild(badge);
|
|
1164
|
+
if (editable) {
|
|
1165
|
+
td.classList.add('ntbl-td-editable');
|
|
1166
|
+
td.addEventListener('click', function () {
|
|
1167
|
+
self._commitEdit(rowKey, col.key, !value);
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
td.textContent = this._cellText(row, col);
|
|
1174
|
+
|
|
1175
|
+
if (editable) {
|
|
1176
|
+
td.classList.add('ntbl-td-editable');
|
|
1177
|
+
td.setAttribute('tabindex', '0');
|
|
1178
|
+
td.addEventListener('dblclick', function () { self._startEdit(rowKey, col.key); });
|
|
1179
|
+
td.addEventListener('keydown', function (event) {
|
|
1180
|
+
if (event.key === 'Enter') self._startEdit(rowKey, col.key);
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
};
|
|
1184
|
+
|
|
1185
|
+
NeikiTable.prototype._startEdit = function (rowKey, key) {
|
|
1186
|
+
this._state.editing = { rowKey: rowKey, key: key };
|
|
1187
|
+
this._render();
|
|
1188
|
+
};
|
|
1189
|
+
|
|
1190
|
+
NeikiTable.prototype._renderEditor = function (td, row, col, rowKey, value) {
|
|
1191
|
+
var self = this;
|
|
1192
|
+
td.textContent = '';
|
|
1193
|
+
var input;
|
|
1194
|
+
|
|
1195
|
+
if (col.type === 'select') {
|
|
1196
|
+
input = document.createElement('select');
|
|
1197
|
+
normalizeOptions(col).forEach(function (opt) {
|
|
1198
|
+
input.appendChild(new Option(opt.label, String(opt.value)));
|
|
1199
|
+
});
|
|
1200
|
+
input.value = String(value);
|
|
1201
|
+
} else if (col.type === 'number') {
|
|
1202
|
+
input = document.createElement('input');
|
|
1203
|
+
input.type = 'number';
|
|
1204
|
+
input.value = value === null || value === undefined ? '' : value;
|
|
1205
|
+
} else if (col.type === 'date') {
|
|
1206
|
+
input = document.createElement('input');
|
|
1207
|
+
input.type = 'date';
|
|
1208
|
+
input.value = value ? String(value).slice(0, 10) : '';
|
|
1209
|
+
} else {
|
|
1210
|
+
input = document.createElement('input');
|
|
1211
|
+
input.type = 'text';
|
|
1212
|
+
input.value = value === null || value === undefined ? '' : value;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
input.className = 'ntbl-edit-input';
|
|
1216
|
+
|
|
1217
|
+
function commit() {
|
|
1218
|
+
var newValue = input.value;
|
|
1219
|
+
if (col.type === 'number') newValue = newValue === '' ? null : Number(newValue);
|
|
1220
|
+
self._commitEdit(rowKey, col.key, newValue);
|
|
1221
|
+
}
|
|
1222
|
+
function cancel() {
|
|
1223
|
+
self._state.editing = null;
|
|
1224
|
+
self._render();
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
input.addEventListener('keydown', function (event) {
|
|
1228
|
+
if (event.key === 'Enter') { event.preventDefault(); commit(); }
|
|
1229
|
+
else if (event.key === 'Escape') { event.preventDefault(); cancel(); }
|
|
1230
|
+
});
|
|
1231
|
+
input.addEventListener('blur', function () { commit(); });
|
|
1232
|
+
|
|
1233
|
+
td.appendChild(input);
|
|
1234
|
+
requestAnimationFrame(function () { input.focus(); input.select && input.select(); });
|
|
1235
|
+
};
|
|
1236
|
+
|
|
1237
|
+
NeikiTable.prototype._commitEdit = function (rowKey, key, newValue) {
|
|
1238
|
+
var self = this;
|
|
1239
|
+
var row = this._rows.filter(function (r, i) { return self._rowKeyValue(r, i) === rowKey; })[0];
|
|
1240
|
+
this._state.editing = null;
|
|
1241
|
+
if (!row) { this._render(); return; }
|
|
1242
|
+
var oldValue = row[key];
|
|
1243
|
+
if (oldValue === newValue) { this._render(); return; }
|
|
1244
|
+
row[key] = newValue;
|
|
1245
|
+
this._render();
|
|
1246
|
+
this._emit('cell-edit', { rowKey: rowKey, key: key, oldValue: oldValue, newValue: newValue, row: Object.assign({}, row) });
|
|
1247
|
+
};
|
|
1248
|
+
|
|
1249
|
+
NeikiTable.prototype._renderFooter = function (total, pageCount, start, pageRowsLength) {
|
|
1250
|
+
var cfg = this._config;
|
|
1251
|
+
|
|
1252
|
+
this._infoEl.textContent = total === 0
|
|
1253
|
+
? this._t('showingNone')
|
|
1254
|
+
: this._t('showingRange', {
|
|
1255
|
+
start: start + 1,
|
|
1256
|
+
end: start + pageRowsLength,
|
|
1257
|
+
total: total
|
|
1258
|
+
});
|
|
1259
|
+
|
|
1260
|
+
var paginationEl = this.shadowRoot.querySelector('.ntbl-pagination');
|
|
1261
|
+
paginationEl.hidden = !cfg.paginated;
|
|
1262
|
+
if (!cfg.paginated) return;
|
|
1263
|
+
|
|
1264
|
+
this._pageSizeSelect.textContent = '';
|
|
1265
|
+
var self = this;
|
|
1266
|
+
PAGE_SIZE_OPTIONS.forEach(function (size) {
|
|
1267
|
+
var opt = new Option(String(size) + ' / ' + self._t('rowsPerPage'), String(size));
|
|
1268
|
+
opt.selected = size === cfg.pageSize;
|
|
1269
|
+
self._pageSizeSelect.appendChild(opt);
|
|
1270
|
+
});
|
|
1271
|
+
|
|
1272
|
+
var page = this._state.page;
|
|
1273
|
+
this._firstBtn.setAttribute('aria-label', this._t('firstPage'));
|
|
1274
|
+
this._prevBtn.setAttribute('aria-label', this._t('previous'));
|
|
1275
|
+
this._nextBtn.setAttribute('aria-label', this._t('next'));
|
|
1276
|
+
this._lastBtn.setAttribute('aria-label', this._t('lastPage'));
|
|
1277
|
+
this._firstBtn.disabled = page <= 1;
|
|
1278
|
+
this._prevBtn.disabled = page <= 1;
|
|
1279
|
+
this._nextBtn.disabled = page >= pageCount;
|
|
1280
|
+
this._lastBtn.disabled = page >= pageCount;
|
|
1281
|
+
|
|
1282
|
+
var showNumbers = pageCount > 1 && pageCount <= 200;
|
|
1283
|
+
this._firstBtn.hidden = pageCount <= 2;
|
|
1284
|
+
this._lastBtn.hidden = pageCount <= 2;
|
|
1285
|
+
|
|
1286
|
+
this._pageNumbers.textContent = '';
|
|
1287
|
+
if (showNumbers) {
|
|
1288
|
+
this._pageIndicator.hidden = true;
|
|
1289
|
+
this._renderPageNumbers(page, pageCount);
|
|
1290
|
+
} else {
|
|
1291
|
+
this._pageIndicator.hidden = false;
|
|
1292
|
+
this._pageIndicator.textContent = this._t('pageOf', { page: page, pages: pageCount });
|
|
1293
|
+
}
|
|
1294
|
+
};
|
|
1295
|
+
|
|
1296
|
+
NeikiTable.prototype._buildPageList = function (current, total) {
|
|
1297
|
+
var delta = 1;
|
|
1298
|
+
var pages = [];
|
|
1299
|
+
var last = 0;
|
|
1300
|
+
for (var i = 1; i <= total; i++) {
|
|
1301
|
+
if (i === 1 || i === total || (i >= current - delta && i <= current + delta)) {
|
|
1302
|
+
if (last && i - last > 1) pages.push('…');
|
|
1303
|
+
pages.push(i);
|
|
1304
|
+
last = i;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
return pages;
|
|
1308
|
+
};
|
|
1309
|
+
|
|
1310
|
+
NeikiTable.prototype._renderPageNumbers = function (current, total) {
|
|
1311
|
+
var self = this;
|
|
1312
|
+
this._buildPageList(current, total).forEach(function (entry) {
|
|
1313
|
+
if (entry === '…') {
|
|
1314
|
+
var gap = document.createElement('span');
|
|
1315
|
+
gap.className = 'ntbl-page-ellipsis';
|
|
1316
|
+
gap.textContent = '…';
|
|
1317
|
+
self._pageNumbers.appendChild(gap);
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
var btn = document.createElement('button');
|
|
1321
|
+
btn.type = 'button';
|
|
1322
|
+
btn.className = 'ntbl-page-btn';
|
|
1323
|
+
btn.setAttribute('part', 'button');
|
|
1324
|
+
btn.textContent = String(entry);
|
|
1325
|
+
btn.setAttribute('aria-label', self._t('gotoPage', { page: entry }));
|
|
1326
|
+
if (entry === current) {
|
|
1327
|
+
btn.classList.add('ntbl-page-current');
|
|
1328
|
+
btn.setAttribute('aria-current', 'page');
|
|
1329
|
+
} else {
|
|
1330
|
+
btn.addEventListener('click', function () { self.goToPage(entry); });
|
|
1331
|
+
}
|
|
1332
|
+
self._pageNumbers.appendChild(btn);
|
|
1333
|
+
});
|
|
1334
|
+
};
|
|
1335
|
+
|
|
1336
|
+
NeikiTable.prototype._emit = function (name, detail) {
|
|
1337
|
+
this.dispatchEvent(new CustomEvent('neiki-table:' + name, {
|
|
1338
|
+
detail: detail,
|
|
1339
|
+
bubbles: true,
|
|
1340
|
+
composed: true
|
|
1341
|
+
}));
|
|
1342
|
+
};
|
|
1343
|
+
|
|
1344
|
+
// ---------------------------------------------------------------------
|
|
1345
|
+
// Public API
|
|
1346
|
+
// ---------------------------------------------------------------------
|
|
1347
|
+
|
|
1348
|
+
NeikiTable.prototype.setColumns = function (columns) {
|
|
1349
|
+
this._columns = (columns || []).map(function (col) {
|
|
1350
|
+
return Object.assign({
|
|
1351
|
+
type: 'text',
|
|
1352
|
+
sortable: true,
|
|
1353
|
+
filterable: true,
|
|
1354
|
+
editable: true
|
|
1355
|
+
}, col, {
|
|
1356
|
+
type: oneOf(col.type, VALID_TYPES, 'text')
|
|
1357
|
+
});
|
|
1358
|
+
});
|
|
1359
|
+
this._state.columnWidths = {};
|
|
1360
|
+
if (this.isConnected) this._render();
|
|
1361
|
+
return this;
|
|
1362
|
+
};
|
|
1363
|
+
|
|
1364
|
+
NeikiTable.prototype.getColumns = function () {
|
|
1365
|
+
return this._columns.map(function (col) { return Object.assign({}, col); });
|
|
1366
|
+
};
|
|
1367
|
+
|
|
1368
|
+
NeikiTable.prototype.setData = function (rows) {
|
|
1369
|
+
this._rows = Array.isArray(rows) ? rows.slice() : [];
|
|
1370
|
+
this._state.selected = {};
|
|
1371
|
+
this._state.page = 1;
|
|
1372
|
+
if (this.isConnected) this._render();
|
|
1373
|
+
return this;
|
|
1374
|
+
};
|
|
1375
|
+
|
|
1376
|
+
NeikiTable.prototype.getData = function () {
|
|
1377
|
+
return this._rows.map(function (row) { return Object.assign({}, row); });
|
|
1378
|
+
};
|
|
1379
|
+
|
|
1380
|
+
NeikiTable.prototype.setLocale = function (locale) {
|
|
1381
|
+
this._config.locale = locale;
|
|
1382
|
+
if (this.isConnected) this._render();
|
|
1383
|
+
return this;
|
|
1384
|
+
};
|
|
1385
|
+
|
|
1386
|
+
NeikiTable.prototype.getLocale = function () {
|
|
1387
|
+
return this._config.locale;
|
|
1388
|
+
};
|
|
1389
|
+
|
|
1390
|
+
NeikiTable.prototype.addTranslations = function (locale, dictionary) {
|
|
1391
|
+
this._i18n[locale] = Object.assign({}, this._i18n[locale] || {}, dictionary || {});
|
|
1392
|
+
if (this.isConnected) this._render();
|
|
1393
|
+
return this;
|
|
1394
|
+
};
|
|
1395
|
+
|
|
1396
|
+
NeikiTable.prototype.setConfig = function (config) {
|
|
1397
|
+
config = config || {};
|
|
1398
|
+
var cfg = this._config;
|
|
1399
|
+
if (config.locale !== undefined) cfg.locale = config.locale;
|
|
1400
|
+
if (config.theme !== undefined) cfg.theme = oneOf(config.theme, VALID_THEMES, cfg.theme);
|
|
1401
|
+
if (config.density !== undefined) cfg.density = oneOf(config.density, VALID_DENSITIES, cfg.density);
|
|
1402
|
+
if (config.rowKey !== undefined) cfg.rowKey = config.rowKey;
|
|
1403
|
+
if (config.searchable !== undefined) cfg.searchable = !!config.searchable;
|
|
1404
|
+
if (config.filterable !== undefined) cfg.filterable = !!config.filterable;
|
|
1405
|
+
if (config.selectable !== undefined) cfg.selectable = !!config.selectable;
|
|
1406
|
+
if (config.editable !== undefined) cfg.editable = !!config.editable;
|
|
1407
|
+
if (config.paginated !== undefined) cfg.paginated = !!config.paginated;
|
|
1408
|
+
if (config.exportable !== undefined) cfg.exportable = !!config.exportable;
|
|
1409
|
+
if (config.resizable !== undefined) cfg.resizable = !!config.resizable;
|
|
1410
|
+
if (config.loading !== undefined) cfg.loading = !!config.loading;
|
|
1411
|
+
if (config.searchDebounce !== undefined && config.searchDebounce >= 0) cfg.searchDebounce = config.searchDebounce;
|
|
1412
|
+
if (config.pageSize !== undefined) cfg.pageSize = config.pageSize;
|
|
1413
|
+
if (this.isConnected) this._render();
|
|
1414
|
+
return this;
|
|
1415
|
+
};
|
|
1416
|
+
|
|
1417
|
+
NeikiTable.prototype.getConfig = function () {
|
|
1418
|
+
return Object.assign({}, this._config);
|
|
1419
|
+
};
|
|
1420
|
+
|
|
1421
|
+
NeikiTable.prototype.sortBy = function (key, dir) {
|
|
1422
|
+
this._state.sort.key = key || null;
|
|
1423
|
+
this._state.sort.dir = key ? oneOf(dir, ['asc', 'desc'], 'asc') : null;
|
|
1424
|
+
if (this.isConnected) this._render();
|
|
1425
|
+
this._emit('sort', { key: this._state.sort.key, dir: this._state.sort.dir });
|
|
1426
|
+
return this;
|
|
1427
|
+
};
|
|
1428
|
+
|
|
1429
|
+
NeikiTable.prototype.search = function (query) {
|
|
1430
|
+
this._state.search = query || '';
|
|
1431
|
+
this._state.page = 1;
|
|
1432
|
+
if (this.isConnected) this._render();
|
|
1433
|
+
this._emit('search', { query: this._state.search });
|
|
1434
|
+
return this;
|
|
1435
|
+
};
|
|
1436
|
+
|
|
1437
|
+
NeikiTable.prototype.setFilter = function (key, value) {
|
|
1438
|
+
if (value === undefined || value === null || value === '') delete this._state.filters[key];
|
|
1439
|
+
else this._state.filters[key] = value;
|
|
1440
|
+
this._state.page = 1;
|
|
1441
|
+
if (this.isConnected) this._render();
|
|
1442
|
+
this._emit('filter', { filters: Object.assign({}, this._state.filters) });
|
|
1443
|
+
return this;
|
|
1444
|
+
};
|
|
1445
|
+
|
|
1446
|
+
NeikiTable.prototype.clearFilters = function () {
|
|
1447
|
+
this._state.filters = {};
|
|
1448
|
+
this._state.search = '';
|
|
1449
|
+
this._state.page = 1;
|
|
1450
|
+
if (this.isConnected) this._render();
|
|
1451
|
+
this._emit('filter', { filters: {} });
|
|
1452
|
+
return this;
|
|
1453
|
+
};
|
|
1454
|
+
|
|
1455
|
+
NeikiTable.prototype.goToPage = function (page) {
|
|
1456
|
+
this._state.page = Math.max(1, page);
|
|
1457
|
+
if (this.isConnected) this._render();
|
|
1458
|
+
this._emit('page-change', { page: this._state.page });
|
|
1459
|
+
return this;
|
|
1460
|
+
};
|
|
1461
|
+
|
|
1462
|
+
NeikiTable.prototype.setPageSize = function (size) {
|
|
1463
|
+
this._config.pageSize = size;
|
|
1464
|
+
this._state.page = 1;
|
|
1465
|
+
if (this.isConnected) this._render();
|
|
1466
|
+
this._emit('page-change', { page: this._state.page, pageSize: size });
|
|
1467
|
+
return this;
|
|
1468
|
+
};
|
|
1469
|
+
|
|
1470
|
+
NeikiTable.prototype.selectRow = function (rowKey, selected) {
|
|
1471
|
+
if (selected) this._state.selected[rowKey] = true;
|
|
1472
|
+
else delete this._state.selected[rowKey];
|
|
1473
|
+
if (this.isConnected) this._render();
|
|
1474
|
+
this._emit('select', { selected: this.getSelectedKeys() });
|
|
1475
|
+
return this;
|
|
1476
|
+
};
|
|
1477
|
+
|
|
1478
|
+
NeikiTable.prototype.selectAll = function (selected) {
|
|
1479
|
+
var self = this;
|
|
1480
|
+
var view = this._computeView();
|
|
1481
|
+
if (selected) {
|
|
1482
|
+
view.forEach(function (row, i) { self._state.selected[self._rowKeyValue(row, i)] = true; });
|
|
1483
|
+
} else {
|
|
1484
|
+
view.forEach(function (row, i) { delete self._state.selected[self._rowKeyValue(row, i)]; });
|
|
1485
|
+
}
|
|
1486
|
+
if (this.isConnected) this._render();
|
|
1487
|
+
this._emit('select', { selected: this.getSelectedKeys() });
|
|
1488
|
+
return this;
|
|
1489
|
+
};
|
|
1490
|
+
|
|
1491
|
+
NeikiTable.prototype.clearSelection = function () {
|
|
1492
|
+
this._state.selected = {};
|
|
1493
|
+
if (this.isConnected) this._render();
|
|
1494
|
+
this._emit('select', { selected: [] });
|
|
1495
|
+
return this;
|
|
1496
|
+
};
|
|
1497
|
+
|
|
1498
|
+
NeikiTable.prototype.getSelectedKeys = function () {
|
|
1499
|
+
var selected = this._state.selected;
|
|
1500
|
+
return Object.keys(selected).filter(function (key) { return selected[key]; });
|
|
1501
|
+
};
|
|
1502
|
+
|
|
1503
|
+
NeikiTable.prototype.getSelectedRows = function () {
|
|
1504
|
+
var self = this;
|
|
1505
|
+
var keys = this.getSelectedKeys();
|
|
1506
|
+
return this._rows.filter(function (row, i) {
|
|
1507
|
+
return keys.indexOf(String(self._rowKeyValue(row, i))) !== -1;
|
|
1508
|
+
}).map(function (row) { return Object.assign({}, row); });
|
|
1509
|
+
};
|
|
1510
|
+
|
|
1511
|
+
NeikiTable.prototype._exportRows = function () {
|
|
1512
|
+
var selected = this.getSelectedKeys();
|
|
1513
|
+
if (selected.length > 0) return this.getSelectedRows();
|
|
1514
|
+
return this._computeView();
|
|
1515
|
+
};
|
|
1516
|
+
|
|
1517
|
+
NeikiTable.prototype._buildCSV = function () {
|
|
1518
|
+
var self = this;
|
|
1519
|
+
var rows = this._exportRows();
|
|
1520
|
+
var headers = this._columns.map(function (col) { return col.label || col.key; });
|
|
1521
|
+
var lines = [headers.map(csvEscape).join(',')];
|
|
1522
|
+
rows.forEach(function (row) {
|
|
1523
|
+
var line = self._columns.map(function (col) {
|
|
1524
|
+
return csvEscape(self._cellText(row, col));
|
|
1525
|
+
}).join(',');
|
|
1526
|
+
lines.push(line);
|
|
1527
|
+
});
|
|
1528
|
+
return { text: lines.join('\r\n'), count: rows.length };
|
|
1529
|
+
};
|
|
1530
|
+
|
|
1531
|
+
NeikiTable.prototype.exportCSV = function (filename) {
|
|
1532
|
+
var csv = this._buildCSV();
|
|
1533
|
+
downloadBlob(csv.text, 'text/csv;charset=utf-8', filename || 'neiki-table-export.csv');
|
|
1534
|
+
this._emit('export', { format: 'csv', count: csv.count });
|
|
1535
|
+
return this;
|
|
1536
|
+
};
|
|
1537
|
+
|
|
1538
|
+
NeikiTable.prototype.copyCSV = function () {
|
|
1539
|
+
var self = this;
|
|
1540
|
+
var csv = this._buildCSV();
|
|
1541
|
+
var done = function (ok) {
|
|
1542
|
+
self._emit('copy', { format: 'csv', count: csv.count, ok: ok });
|
|
1543
|
+
if (!ok || !self._copyBtn) return;
|
|
1544
|
+
if (self._copyResetTimer) clearTimeout(self._copyResetTimer);
|
|
1545
|
+
self._copyBtn.textContent = self._t('copied');
|
|
1546
|
+
self._copyResetTimer = setTimeout(function () {
|
|
1547
|
+
self._copyResetTimer = null;
|
|
1548
|
+
if (self._copyBtn) self._copyBtn.textContent = self._t('copy');
|
|
1549
|
+
}, 1600);
|
|
1550
|
+
};
|
|
1551
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
1552
|
+
navigator.clipboard.writeText(csv.text).then(function () { done(true); }, function () { done(self._fallbackCopy(csv.text)); });
|
|
1553
|
+
} else {
|
|
1554
|
+
done(this._fallbackCopy(csv.text));
|
|
1555
|
+
}
|
|
1556
|
+
return this;
|
|
1557
|
+
};
|
|
1558
|
+
|
|
1559
|
+
NeikiTable.prototype._fallbackCopy = function (text) {
|
|
1560
|
+
try {
|
|
1561
|
+
var area = document.createElement('textarea');
|
|
1562
|
+
area.value = text;
|
|
1563
|
+
area.setAttribute('readonly', '');
|
|
1564
|
+
area.style.position = 'fixed';
|
|
1565
|
+
area.style.left = '-9999px';
|
|
1566
|
+
document.body.appendChild(area);
|
|
1567
|
+
area.select();
|
|
1568
|
+
var ok = document.execCommand('copy');
|
|
1569
|
+
document.body.removeChild(area);
|
|
1570
|
+
return ok;
|
|
1571
|
+
} catch (err) {
|
|
1572
|
+
return false;
|
|
1573
|
+
}
|
|
1574
|
+
};
|
|
1575
|
+
|
|
1576
|
+
NeikiTable.prototype.setLoading = function (loading) {
|
|
1577
|
+
this._config.loading = !!loading;
|
|
1578
|
+
if (loading) this.setAttribute('loading', '');
|
|
1579
|
+
else this.removeAttribute('loading');
|
|
1580
|
+
if (this.isConnected) this._render();
|
|
1581
|
+
return this;
|
|
1582
|
+
};
|
|
1583
|
+
|
|
1584
|
+
NeikiTable.prototype.setDensity = function (density) {
|
|
1585
|
+
this._config.density = oneOf(density, VALID_DENSITIES, this._config.density);
|
|
1586
|
+
if (this.isConnected) this._render();
|
|
1587
|
+
return this;
|
|
1588
|
+
};
|
|
1589
|
+
|
|
1590
|
+
NeikiTable.prototype.exportJSON = function (filename) {
|
|
1591
|
+
var self = this;
|
|
1592
|
+
var rows = this._exportRows();
|
|
1593
|
+
var payload = rows.map(function (row) {
|
|
1594
|
+
var out = {};
|
|
1595
|
+
self._columns.forEach(function (col) { out[col.key] = getValue(row, col.key); });
|
|
1596
|
+
return out;
|
|
1597
|
+
});
|
|
1598
|
+
downloadBlob(JSON.stringify(payload, null, 2), 'application/json;charset=utf-8', filename || 'neiki-table-export.json');
|
|
1599
|
+
this._emit('export', { format: 'json', count: rows.length });
|
|
1600
|
+
return this;
|
|
1601
|
+
};
|
|
1602
|
+
|
|
1603
|
+
NeikiTable.prototype.refresh = function () {
|
|
1604
|
+
if (this.isConnected) this._render();
|
|
1605
|
+
return this;
|
|
1606
|
+
};
|
|
1607
|
+
|
|
1608
|
+
customElements.define('neiki-table', NeikiTable);
|
|
1609
|
+
})();
|