@yuneta/gobj-ui 2.6.0 → 3.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/src/ytable.css DELETED
@@ -1,63 +0,0 @@
1
- /*********************************************************
2
- * YTable
3
- *********************************************************/
4
- .ytable {
5
- /*width: 100%;*/
6
- table-layout: fixed;
7
- }
8
- .ytable thead, .ytable tbody, .ytable tfoot {
9
- display: block;
10
- }
11
- .ytable tbody {
12
- -webkit-overflow-scrolling: touch;
13
- /*overflow-y: auto;*/
14
- /*width: 100%;*/
15
- /*overscroll-behavior: contain; !* Contain scroll behavior within the table *!*/
16
- }
17
- .ytable th, .ytable td {
18
- padding: 8px; /* ??? */
19
- text-align: left; /* ??? */
20
- box-sizing: border-box;
21
- overflow: hidden;
22
- }
23
-
24
- .ytable th {
25
- white-space: nowrap;
26
- }
27
- .ytable td {
28
- max-height: 3.6em; /* Adjust this value as needed */
29
- white-space: pre-wrap; /* Wrap text */
30
- overflow: hidden;
31
- position: relative;
32
- }
33
- .ytable td::after {
34
- content: '...';
35
- position: absolute;
36
- bottom: 0;
37
- right: 0;
38
- /*background: white;*/
39
- padding: 0 4px;
40
- display: none;
41
- }
42
- .ytable td.overflow::after {
43
- display: block;
44
- }
45
- .ytable tr {
46
- display: table;
47
- width: 100%;
48
- table-layout: fixed;
49
- }
50
-
51
- :root {
52
- /*--bulma-table-cell-background-color: var(--bulma-background);*/
53
- }
54
-
55
-
56
- /*
57
- TODO change a tabulator-bulma css
58
- Ensure Bulma styles override Tabulator
59
- */
60
- .tabulator-col-content.is-success {
61
- background-color: #48c774 !important; /* Bulma's green color */
62
- color: white !important; /* Optional: adjust text color for contrast */
63
- }
package/src/ytable.js DELETED
@@ -1,505 +0,0 @@
1
- /*
2
- Options Handling:
3
-
4
- id: If provided in the options, it sets the table's id.
5
- className: If provided, it adds the specified classes to the table.
6
- If table is not included in the className, it is added.
7
- If className is not provided, the default classes table
8
- is-bordered is-striped is-hoverable are used.
9
- on_header_select: A callback that is called when a header is selected or deselected.
10
- on_cell_select: A callback that is called when a cell is selected or deselected.
11
-
12
- Callbacks:
13
-
14
- The toggleHeader method calls the on_header_select callback with the header's field name and
15
- selection state if the callback is defined.
16
- The toggleCell method calls the on_cell_select callback with the cell's row index, column ID,
17
- value, and selection state if the callback is defined.
18
-
19
- // Example usage
20
-
21
- <div id="mytable"></div>
22
-
23
- // Example usage
24
- const $container = document.getElementById('mytable');
25
- const columns = [
26
- { id: 'name', header: 'Name', fillspace: 2 },
27
- { id: 'age', header: 'Age' },
28
- { id: 'email', header: 'Email', fillspace: 3 }
29
- ];
30
-
31
- const options = {
32
- id: 'example-table',
33
- className: 'is-narrow',
34
- height: '400px',
35
- width: '100%',
36
- on_header_select: (columnId, isSelected, $header) => {
37
- console.log(`Header ${columnId} is ${isSelected ? 'selected' : 'deselected'}`);
38
- },
39
- on_cell_select: (rowIndex, columnId, isSelected, value, $cell) => {
40
- console.log(`Cell at row ${rowIndex}, column ${columnId} with value "${value}" is ${isSelected ? 'selected' : 'deselected'}`);
41
- },
42
- };
43
-
44
- const table = createYTable($container, columns, options);
45
-
46
- const data = [
47
- { name: 'John Doe', age: 30, email: 'john@example.com' },
48
- { name: 'Jane Smith', age: 25, email: 'jane@example.com' },
49
- { name: 'Sam Green', age: 35, email: 'sam@example.com' },
50
- { name: 'Alice Brown', age: 28, email: 'alice@example.com' },
51
- { name: 'Bob White', age: 32, email: 'bob@example.com' },
52
- { name: 'Charlie Black', age: 45, email: 'charlie@example.com' },
53
- { name: 'Diane Blue', age: 38, email: 'diane@example.com' },
54
- { name: 'Eve Red', age: 22, email: 'eve@example.com' },
55
- { name: 'Frank Gray', age: 27, email: 'frank@example.com' },
56
- { name: 'Grace Green', age: 29, email: 'grace@example.com' },
57
- { name: 'Hank Purple', age: 41, email: 'hank@example.com' },
58
- { name: 'Ivy Pink', age: 26, email: 'ivy@example.com' },
59
- { name: 'Jack Yellow', age: 34, email: 'jack@example.com' },
60
- { name: 'Kate Orange', age: 33, email: 'kate@example.com' },
61
- { name: 'Leo Cyan', age: 36, email: 'leo@example.com' },
62
- { name: 'Mia Magenta', age: 39, email: 'mia@example.com' }
63
- ];
64
-
65
- table.loadData(data);
66
-
67
- */
68
-
69
- import {
70
- duplicate_objects,
71
- is_pure_number,
72
- escapeHtml,
73
- } from "@yuneta/gobj-js";
74
-
75
- import "./ytable.css"; // Must be in index.js ?
76
-
77
- let default_options = {
78
- id: undefined,
79
- width: "100%",
80
- height: 400,
81
- minWidth: undefined,
82
- minHeight: undefined,
83
-
84
- wrapper_width: undefined,
85
- wrapper_height: undefined,
86
-
87
- table_class: undefined,
88
-
89
- with_footer: false,
90
- show_rowIndex: false,
91
- fillspace_rowIndex: 1.2,
92
-
93
- selectable_header_cell: false,
94
- selectable_row_cell: false,
95
-
96
- on_cell_select: null, // => (rowIndex, columnId, isSelected, value, $cell)
97
- on_header_select: null, // => (columnId, isSelected, $header);
98
- };
99
-
100
- class YTable {
101
- constructor($container, columns, options_ = {}) {
102
- this.$container = $container;
103
- this.columns = columns;
104
- this.options = duplicate_objects(default_options, options_);
105
-
106
- /*
107
- * See and check the available options
108
- */
109
- this.wrapper_width = parse_size(this.options.wrapper_width);
110
- this.wrapper_height = parse_size(this.options.wrapper_height);
111
-
112
- this.table_width = parse_size(this.options.width || 600);
113
- this.table_height = parse_size(this.options.height || 400);
114
- this.table_minWidth = parse_size(this.options.minWidth);
115
- this.table_minHeight = parse_size(this.options.minHeight);
116
-
117
-
118
- this.with_footer = this.options.with_footer;
119
- this.table_id = this.options.id;
120
- this.table_class = this.options.table_class;
121
-
122
- // Ensure fillspace has a default value of 1 if not provided
123
- this.columns.forEach(col => {
124
- if (typeof col.fillspace === 'undefined') {
125
- col.fillspace = 1;
126
- }
127
- });
128
-
129
- this.totalFillspace = this.columns.reduce((sum, col) => sum + col.fillspace, 0);
130
- // Add extra internal columns
131
- if(this.options.show_rowIndex) {
132
- this.totalFillspace += this.options.fillspace_rowIndex;
133
- }
134
- this.createTable();
135
- }
136
-
137
- createTable() {
138
- this.wrapper = document.createElement('div');
139
- this.table = document.createElement('table');
140
- this.thead = document.createElement('thead');
141
- this.tbody = document.createElement('tbody');
142
- if(this.with_footer) {
143
- this.tfoot = document.createElement('tfoot');
144
- } else {
145
- this.tfoot = null;
146
- }
147
-
148
- /*--------------------------*
149
- * <div> wrapper
150
- *--------------------------*/
151
- this.wrapper.className = 'ytable-wrapper';
152
-
153
- /*--------------------------*
154
- * <table>
155
- *--------------------------*/
156
- if (this.table_id) {
157
- this.table.id = this.table_id;
158
- }
159
-
160
- // Set table classes
161
- if (this.table_class) {
162
- let table_class = this.table_class;
163
- if(!table_class.includes('table')) {
164
- table_class = `table ${table_class}`;
165
- }
166
- if(!table_class.includes('ytable')) {
167
- table_class = `ytable ${table_class}`;
168
- }
169
- this.table.className = table_class;
170
- } else {
171
- this.table.className = 'ytable table is-bordered is-striped is-hoverable is-fullwidth';
172
- }
173
-
174
- /*--------------------------*
175
- * <thead>
176
- *--------------------------*/
177
- const theadRow = document.createElement('tr');
178
- if(this.options.show_rowIndex) {
179
- const th = document.createElement('th');
180
- th.textContent = "rowIndex";
181
- th.dataset.id = "rowIndex";
182
- th.style.width = `${(this.options.fillspace_rowIndex / this.totalFillspace) * 100}%`;
183
- theadRow.appendChild(th);
184
- }
185
-
186
- this.columns.forEach(col => {
187
- const th = document.createElement('th');
188
- th.textContent = col.header;
189
- th.dataset.id = col.id;
190
- th.dataset.formatter = col.formatter;
191
- th.style.width = `${(col.fillspace / this.totalFillspace) * 100}%`;
192
- th.addEventListener('click', (evt) => this.on_click_header(col.id, evt));
193
- theadRow.appendChild(th);
194
- });
195
- this.thead.appendChild(theadRow);
196
-
197
- /*--------------------------*
198
- * <tfoot>
199
- *--------------------------*/
200
- if(this.with_footer) {
201
- const tfootRow = document.createElement('tr');
202
- if(this.options.show_rowIndex) {
203
- const td = document.createElement('td');
204
- td.textContent = "rowIndex";
205
- td.dataset.id = "rowIndex";
206
- td.style.width = `${(this.options.fillspace_rowIndex / this.totalFillspace) * 100}%`;
207
- tfootRow.appendChild(td);
208
- }
209
- this.columns.forEach(col => {
210
- const td = document.createElement('td');
211
- td.textContent = `${col.header}`; // TODO use foot col definition
212
- td.dataset.id = col.id;
213
- td.dataset.formatter = col.formatter;
214
- td.style.width = `${(col.fillspace / this.totalFillspace) * 100}%`;
215
- td.addEventListener('click', (evt) => this.on_click_footer(col.id, evt));
216
- tfootRow.appendChild(td);
217
- });
218
- this.tfoot.appendChild(tfootRow);
219
- }
220
-
221
- /*--------------------------*
222
- * Make tree
223
- *--------------------------*/
224
- this.table.appendChild(this.thead);
225
- this.table.appendChild(this.tbody);
226
- if(this.with_footer) {
227
- this.table.appendChild(this.tfoot);
228
- }
229
- this.wrapper.appendChild(this.table);
230
- this.$container.appendChild(this.wrapper);
231
-
232
- /*--------------------------*
233
- * Set styles
234
- *--------------------------*/
235
- this.wrapper_width = parse_size(this.options.wrapper_width);
236
- this.wrapper_height = parse_size(this.options.wrapper_height);
237
-
238
- if(this.wrapper_width) {
239
- this.wrapper.style.width = this.wrapper_width;
240
- this.wrapper.style.overflowX = 'auto';
241
- }
242
- if(this.wrapper_height) {
243
- this.wrapper.style.height = this.wrapper_height;
244
- this.wrapper.style.overflowY = 'auto';
245
- }
246
-
247
- this.table.style.width = this.table_width;
248
- if(this.table_minWidth) {
249
- this.table.style.minWidth = this.table_minWidth;
250
- }
251
- if(this.table_minHeight) {
252
- this.table.style.minHeight = this.table_minHeight;
253
- }
254
-
255
-
256
- this.tbody.style.display = 'block';
257
- this.tbody.style.height = this.table_height;
258
- this.tbody.style.width = this.table_width;
259
- this.tbody.style.overflowY = 'auto';
260
-
261
- this.thead.style.display = 'table';
262
- this.thead.style.width = this.table_width;
263
- if(this.with_footer) {
264
- this.tfoot.style.display = 'table';
265
- this.tfoot.style.width = this.table_width;
266
- }
267
- }
268
-
269
- /*----------------------------------*
270
- * API
271
- *----------------------------------*/
272
- destroy() {
273
- /*
274
- * WARNING things to do:
275
- * - remove all DOM event handlers
276
- * - remove elements from the DOM
277
- * - delete from this.
278
- */
279
- this.clearData();
280
- this.wrapper.remove();
281
-
282
- delete this.table;
283
- delete this.thead;
284
- delete this.tbody;
285
- delete this.tfoot;
286
- delete this.wrapper;
287
- }
288
-
289
- loadData(data) {
290
- // TODO repon this.clearData();
291
-
292
- // Step 1: Create a temporary tbody
293
- let tempTbody = document.createElement('tbody');
294
- tempTbody.style.display = 'block';
295
- tempTbody.style.height = this.table_height;
296
- tempTbody.style.width = this.table_width;
297
- tempTbody.style.overflowY = 'auto';
298
-
299
- data.forEach((record, rowIndex) => {
300
- const tr = document.createElement('tr');
301
- if(this.options.show_rowIndex) {
302
- const td = document.createElement('td');
303
- td.textContent = rowIndex + 1;
304
- td.dataset.id = "rowIndex";
305
- td.style.width = `${(this.options.fillspace_rowIndex / this.totalFillspace) * 100}%`;
306
- td.classList.add('is-size-7');
307
- tr.appendChild(td);
308
- }
309
-
310
- this.columns.forEach(col => {
311
- const td = document.createElement('td');
312
- if(col.formatter) {
313
- // Use createContextualFragment so that any <script> elements
314
- // in the formatter output are inert (never executed).
315
- // Formatter functions are responsible for escaping any
316
- // user-supplied field values they embed in their HTML
317
- // (use the exported escapeHtml() helper).
318
- const frag = document.createRange().createContextualFragment(
319
- col.formatter(record[col.id], rowIndex, col.id).trim()
320
- );
321
- td.appendChild(frag);
322
- } else {
323
- td.textContent = record[col.id];
324
- }
325
- td.dataset.id = col.id;
326
- td.dataset.rowIndex = rowIndex;
327
- td.style.width = `${(col.fillspace / this.totalFillspace) * 100}%`;
328
- td.style.overflow = 'hidden';
329
- td.style.textOverflow = 'ellipsis';
330
- td.style.whiteSpace = 'pre-wrap';
331
- // TODO td.addEventListener('click', (evt) => this.on_click_cell(rowIndex, col.id, evt));
332
- tr.appendChild(td);
333
- });
334
- tempTbody.appendChild(tr);
335
- this._checkOverflow(tr);
336
- });
337
-
338
- let realTbody = this.tbody;
339
- realTbody.parentNode.replaceChild(tempTbody, realTbody);
340
- this.tbody = tempTbody;
341
- }
342
-
343
- _checkOverflow(row) {
344
- row.querySelectorAll('td').forEach(td => {
345
- if (td.scrollHeight > td.clientHeight) {
346
- td.classList.add('overflow');
347
- }
348
- });
349
- }
350
-
351
- clearData() {
352
- this.tbody.innerHTML = '';
353
- // this.tbody.replaceChildren();
354
- }
355
-
356
- toggleHeader(columnId) {
357
- const header = this.thead.querySelector(`th[data-id="${columnId}"]`);
358
- if (header) {
359
- const isSelected = header.classList.toggle('is-success');
360
- if (this.options.on_header_select) {
361
- this.options.on_header_select(columnId, isSelected, header);
362
- }
363
- }
364
- }
365
-
366
- selectHeader(columnId) {
367
- const header = this.thead.querySelector(`th[data-id="${columnId}"]`);
368
- if (header) {
369
- header.classList.add('is-success');
370
- if (this.options.on_header_select) {
371
- this.options.on_header_select(columnId, true, header);
372
- }
373
- }
374
- }
375
-
376
- deselectHeader(columnId) {
377
- const header = this.thead.querySelector(`th[data-id="${columnId}"]`);
378
- if (header) {
379
- header.classList.remove('is-success');
380
- if (this.options.on_header_select) {
381
- this.options.on_header_select(columnId, false, header);
382
- }
383
- }
384
- }
385
-
386
- selectAllHeaders() {
387
- const headers = this.thead.querySelectorAll('th');
388
- headers.forEach(header => {
389
- header.classList.add('is-success');
390
- if (this.options.on_header_select) {
391
- this.options.on_header_select(header.dataset.id, true, header);
392
- }
393
- });
394
- }
395
-
396
- deselectAllHeaders() {
397
- const headers = this.thead.querySelectorAll('th');
398
- headers.forEach(header => {
399
- header.classList.remove('is-success');
400
- if (this.options.on_header_select) {
401
- this.options.on_header_select(header.dataset.id, false, header);
402
- }
403
- });
404
- }
405
-
406
- toggleCell(rowIndex, columnId) {
407
- const row = this.tbody.children[rowIndex];
408
- if (row) {
409
- const cell = row.querySelector(`td[data-id="${columnId}"]`);
410
- if (cell) {
411
- const isSelected = cell.classList.toggle('is-info');
412
- if (this.options.on_cell_select) {
413
- this.options.on_cell_select(rowIndex, columnId, isSelected, cell.textContent, cell);
414
- }
415
- }
416
- }
417
- }
418
-
419
- selectCell(rowIndex, columnId) {
420
- const row = this.tbody.children[rowIndex];
421
- if (row) {
422
- const cell = row.querySelector(`td[data-id="${columnId}"]`);
423
- if (cell) {
424
- cell.classList.add('is-info');
425
- if (this.options.on_cell_select) {
426
- this.options.on_cell_select(rowIndex, columnId, true, cell.textContent, cell);
427
- }
428
- }
429
- }
430
- }
431
-
432
- deselectCell(rowIndex, columnId) {
433
- const row = this.tbody.children[rowIndex];
434
- if (row) {
435
- const cell = row.querySelector(`td[data-id="${columnId}"]`);
436
- if (cell) {
437
- cell.classList.remove('is-info');
438
- if (this.options.on_cell_select) {
439
- this.options.on_cell_select(rowIndex, columnId, false, cell.textContent, cell);
440
- }
441
- }
442
- }
443
- }
444
-
445
- getSelectedHeaders() {
446
- const selectedHeaders = [];
447
- const headers = this.thead.querySelectorAll('th.is-success');
448
- headers.forEach(header => {
449
- selectedHeaders.push(header.dataset.id);
450
- });
451
- return selectedHeaders;
452
- }
453
-
454
- getSelectedCells() {
455
- const selectedCells = [];
456
- for (let rowIndex = 0; rowIndex < this.tbody.children.length; rowIndex++) {
457
- const row = this.tbody.children[rowIndex];
458
- const cells = row.querySelectorAll('td.is-info');
459
- cells.forEach(cell => {
460
- selectedCells.push({
461
- rowIndex: rowIndex,
462
- field: cell.dataset.id,
463
- value: cell.textContent
464
- });
465
- });
466
- }
467
- return selectedCells;
468
- }
469
-
470
- /*----------------------------------*
471
- * Events
472
- *----------------------------------*/
473
- on_click_header(column_id, evt) {
474
- // TODO use a custom function to process click, toggle header must be an option
475
- if(this.options.selectable_header_cell) {
476
- this.toggleHeader(column_id);
477
- }
478
- }
479
-
480
- on_click_cell(rowIndex, column_id, evt) {
481
- // TODO use a custom function to process click, toggle header must be an option
482
- if(this.options.selectable_row_cell) {
483
- this.toggleCell(rowIndex, column_id);
484
- }
485
- }
486
-
487
- on_click_footer(column_id, evt) {
488
- }
489
-
490
- }
491
-
492
- function parse_size(n) {
493
- if(is_pure_number(n)) {
494
- return `${n}px`;
495
- } else {
496
- return n;
497
- }
498
- }
499
-
500
- // Factory function to create YTable instance
501
- function createYTable($container, columns, options) {
502
- return new YTable($container, columns, options);
503
- }
504
-
505
- export {YTable, createYTable};