tablevu 0.3.0-beta.2 → 0.3.0-beta.3

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.
@@ -85,6 +85,7 @@
85
85
  Ready: 0,
86
86
  Loading: 1
87
87
  };
88
+
88
89
  const tablevu = {
89
90
  props: {
90
91
  settings: { type: Object, default: null },
@@ -92,14 +93,13 @@
92
93
  group: { type: [String, Object] },
93
94
  search: { type: String },
94
95
  filter: { type: [Array, Object] },
95
- command: { type: Array, default: () => [] },
96
96
  rowid: { type: String },
97
97
  onRowClick: { type: Function },
98
98
  onRowDblClick: { type: Function },
99
99
  class: { type: String, default: 'table' },
100
100
  style: { type: String, default: null }
101
101
  },
102
- emits: ['onRowClick', 'onRowDblClick'],
102
+ emits: ['onRowClick', 'onRowDblClick', 'selectionChange', 'columnVisibilityChange'],
103
103
  setup(props, { attrs, slots, emit, expose }) {
104
104
  let rows = [];
105
105
  let rowg = [];
@@ -109,16 +109,16 @@
109
109
  const spec = vue.reactive({
110
110
  caption: null,
111
111
  group: {
112
- value: (props.settings && props.settings.group) ? props.settings.group.value : props.group ? props.group : null,
113
- content: (props.settings && props.settings.group) ? props.settings.group.content : null,
112
+ value: (props.settings && props.settings.group) ? props.settings.group.value : props.group ? props.group : undefined,
113
+ content: (props.settings && props.settings.group) ? props.settings.group.content : undefined,
114
114
  expandAll: true,
115
115
  class: 'table-light'
116
116
  },
117
117
  page: {
118
118
  count: 0,
119
- size: (props.settings && props.settings.page) ? props.settings.page.size : 0,
119
+ size: (props.settings && props.settings.page) ? (props.settings.page.size || 0) : 0,
120
120
  current: 0,
121
- type: null
121
+ type: undefined
122
122
  },
123
123
  sort: (props.settings && props.settings.sort) ? props.settings.sort : [],
124
124
  columns: (props.settings && props.settings.columns) ? props.settings.columns : [],
@@ -131,7 +131,11 @@
131
131
  time: null,
132
132
  search: null,
133
133
  filter: null,
134
- onData: (props.settings && props.settings.onData) ? props.settings.onData : undefined
134
+ onData: (props.settings && props.settings.onData) ? props.settings.onData : undefined,
135
+ selectedRows: [],
136
+ columnWidths: {},
137
+ columnOrder: [],
138
+ columnVisibility: {}
135
139
  });
136
140
  if (props.data) {
137
141
  if (Array.isArray(props.data)) {
@@ -167,32 +171,6 @@
167
171
  spec.filter = null;
168
172
  refresh();
169
173
  }, { deep: true });
170
- vue.watch(() => props.command, (n, o) => {
171
- if (n && n.length > 0) {
172
- const c = props.command[n.length - 1];
173
- if (c && c.name) {
174
- switch (c.name.toLowerCase()) {
175
- case 'refresh':
176
- refresh();
177
- break;
178
- case 'first':
179
- first();
180
- break;
181
- case 'prior':
182
- prior();
183
- break;
184
- case 'next':
185
- next();
186
- break;
187
- case 'last':
188
- last();
189
- break;
190
- default: console.log('Unknown command');
191
- }
192
- }
193
- props.command.length = 0;
194
- }
195
- }, { deep: true });
196
174
  function prepareGroup(g, r) {
197
175
  grps = [];
198
176
  rowg = [];
@@ -231,28 +209,26 @@
231
209
  rowg.push(n);
232
210
  }
233
211
  }
234
- function first() { page(1); }
212
+ function first() { loadPage(1); }
235
213
  function prior() {
236
214
  if (spec.page.current > 1) {
237
- page(spec.page.current - 1);
215
+ loadPage(spec.page.current - 1);
238
216
  }
239
217
  else {
240
- page(spec.page.current);
218
+ loadPage(spec.page.current);
241
219
  }
242
220
  }
243
- function next() { page(spec.page.current + 1); }
244
- function last() { page(spec.page.count); }
245
- function go() {
246
- let p;
247
- let g = spec.page.current;
248
- do {
249
- p = prompt(undefined, String(spec.page.current));
250
- g = parseInt(p || '0');
251
- } while (p && isNaN(g));
252
- if (g && g > 0 && g != spec.page.current)
253
- page(g);
221
+ function next() { loadPage(spec.page.current + 1); }
222
+ function last() { loadPage(spec.page.count); }
223
+ // Navigate to specific page number
224
+ function page(currentPage) {
225
+ if (currentPage < 1)
226
+ currentPage = 1;
227
+ if (currentPage > spec.page.count && spec.page.count > 0)
228
+ currentPage = spec.page.count;
229
+ loadPage(currentPage);
254
230
  }
255
- function refresh() { page(spec.page.current); }
231
+ function refresh() { loadPage(spec.page.current); }
256
232
  function orderby(ind, dir) {
257
233
  if (spec.sort && spec.sort.length > 0) {
258
234
  let l = spec.sort.findIndex(f => f.index == ind);
@@ -276,9 +252,10 @@
276
252
  else {
277
253
  spec.sort.push({ index: ind, dir: dir ? dir : null });
278
254
  }
279
- page(spec.page.current);
255
+ loadPage(spec.page.current);
280
256
  }
281
- function page(currentPage) {
257
+ // Internal load function (alias for adapter call)
258
+ function loadPage(currentPage) {
282
259
  let dat = null;
283
260
  if (props.data) {
284
261
  if (Array.isArray(props.data))
@@ -288,8 +265,9 @@
288
265
  return;
289
266
  spec.status = TablevuStatus.Loading;
290
267
  if (adapter) {
268
+ const pageSize = spec.page.size || 20;
291
269
  adapter({
292
- pageSize: spec.page.size,
270
+ pageSize,
293
271
  currPage: currentPage,
294
272
  sort: spec.sort,
295
273
  cols: spec.columns,
@@ -303,7 +281,7 @@
303
281
  const groupValue = spec.group.value;
304
282
  if (groupValue && (typeof groupValue == 'string' || typeof groupValue == 'number' || typeof groupValue == 'bigint' || (typeof groupValue == 'object' && groupValue.value)))
305
283
  prepareGroup(spec.group, rows);
306
- spec.page.count = (ada.count > 0 && spec.page.size > 0) ? Math.ceil(ada.count / spec.page.size) : 0;
284
+ spec.page.count = (ada.count > 0 && pageSize > 0) ? Math.ceil(ada.count / pageSize) : 0;
307
285
  spec.page.current = currentPage;
308
286
  spec.status = TablevuStatus.Ready;
309
287
  spec.time = Date.now();
@@ -365,8 +343,8 @@
365
343
  o.style['text-align'] = col.align;
366
344
  if (col.width)
367
345
  o.style['max-width'] = col.width;
368
- const f = col.render(row);
369
- return vue.h('td', o, f);
346
+ const f = col.render?.(row) ?? '';
347
+ return vue.h('td', { ...o, innerHTML: f });
370
348
  }
371
349
  function renderGroup(grp) {
372
350
  const b = vue.h('button', {
@@ -425,6 +403,10 @@
425
403
  }
426
404
  };
427
405
  }
406
+ // Selection
407
+ if (spec.selectedRows.some(s => s[props.rowid || 'id'] === row[props.rowid || 'id'])) {
408
+ o.class = (o.class || '') + ' selected';
409
+ }
428
410
  return vue.h('tr', o, r);
429
411
  }
430
412
  function renderHeader() {
@@ -475,7 +457,10 @@
475
457
  else
476
458
  a = vue.h('a', { href: '#', onClick: (event) => { orderby(i); event.preventDefault(); }, style: cy }, cl);
477
459
  if (spec.columns[i].filter) ;
478
- ar.push(vue.h('th', { colspan: null, style: ch }, [a, f]));
460
+ ar.push(vue.h('th', {
461
+ colspan: null,
462
+ style: ch
463
+ }, [a, f]));
479
464
  }
480
465
  const tr = vue.h('tr', {}, ar);
481
466
  return [tr];
@@ -503,14 +488,34 @@
503
488
  }
504
489
  }
505
490
  }
491
+ else if (spec.settings?.emptyMessage) {
492
+ dr.push(vue.h('tr', {}, vue.h('td', { colspan: spec.columns.length, style: { textAlign: 'center', padding: '2rem', color: '#666' } }, spec.settings.emptyMessage)));
493
+ }
506
494
  return dr;
507
495
  }
496
+ // Prompt user for page number and navigate
497
+ function goToPage() {
498
+ const input = prompt('Page number:', String(spec.page.current));
499
+ if (input) {
500
+ const pageNum = parseInt(input, 10);
501
+ if (!isNaN(pageNum) && pageNum > 0 && pageNum !== spec.page.current) {
502
+ page(pageNum);
503
+ }
504
+ }
505
+ }
506
+ function makeHandler(fn) {
507
+ return (e) => {
508
+ e.preventDefault();
509
+ e.stopPropagation();
510
+ fn();
511
+ };
512
+ }
508
513
  function renderFooter() {
509
- const fr = vue.h('button', { class: spec.footer.button.class, style: 'font: initial; margin-right: .5rem', onClick: first, disabled: spec.page.current == 1 }, '◀◀');
510
- const ls = vue.h('button', { class: spec.footer.button.class, style: 'font: initial; margin-right: .5rem', onClick: last, disabled: spec.page.count == 0 || spec.page.current == spec.page.count }, '▶▶');
511
- const pg = vue.h('button', { class: spec.footer.button.class, style: 'font: initial; margin-right: .5rem', onClick: go }, spec.page.current + (spec.page.count > 0 ? (' / ' + spec.page.count) : ''));
512
- const pr = vue.h('button', { class: spec.footer.button.class, style: 'font: initial; margin-right: .5rem', onClick: prior, disabled: spec.page.current == 1 }, '◀');
513
- const nx = vue.h('button', { class: spec.footer.button.class, style: 'font: initial; margin-right: .5rem', onClick: next, disabled: spec.page.count > 0 && spec.page.current == spec.page.count }, '▶');
514
+ const fr = vue.h('button', { class: spec.footer.button.class, style: 'font: initial; margin-right: .5rem', onClick: makeHandler(first), disabled: spec.page.current == 1 }, '◀◀');
515
+ const ls = vue.h('button', { class: spec.footer.button.class, style: 'font: initial; margin-right: .5rem', onClick: makeHandler(last), disabled: spec.page.count == 0 || spec.page.current == spec.page.count }, '▶▶');
516
+ const pg = vue.h('button', { class: spec.footer.button.class, style: 'font: initial; margin-right: .5rem', onClick: makeHandler(goToPage) }, spec.page.current + (spec.page.count > 0 ? (' / ' + spec.page.count) : ''));
517
+ const pr = vue.h('button', { class: spec.footer.button.class, style: 'font: initial; margin-right: .5rem', onClick: makeHandler(prior), disabled: spec.page.current == 1 }, '◀');
518
+ const nx = vue.h('button', { class: spec.footer.button.class, style: 'font: initial; margin-right: .5rem', onClick: makeHandler(next), disabled: spec.page.count > 0 && spec.page.current == spec.page.count }, '▶');
514
519
  const td = vue.h('td', { colspan: spec.columns.length, align: 'center' }, [fr, pr, pg, nx, ls]);
515
520
  const tr = vue.h('tr', {}, [td]);
516
521
  return [tr];
@@ -518,19 +523,41 @@
518
523
  function renderCaption() {
519
524
  return null;
520
525
  }
526
+ // Initialize column metadata
521
527
  if (spec.columns.length == 0) {
522
528
  console.error('Columns not defined !');
523
529
  }
524
- for (let i = 0; i < spec.columns.length; i++)
530
+ for (let i = 0; i < spec.columns.length; i++) {
525
531
  if (spec.columns[i].name && spec.columns[i].name.indexOf('.') > -1) {
526
532
  spec.columns[i].depth = spec.columns[i].name.split('.');
527
533
  }
528
- page(1);
534
+ // Initialize visibility
535
+ if (spec.columns[i].visible === undefined) {
536
+ spec.columns[i].visible = true;
537
+ spec.columnVisibility[spec.columns[i].name] = true;
538
+ }
539
+ }
540
+ loadPage(1);
541
+ // Expose methods for external access via ref
542
+ expose({
543
+ first,
544
+ prior,
545
+ next,
546
+ last,
547
+ page,
548
+ refresh,
549
+ orderby,
550
+ getSelectedRows: () => spec.selectedRows,
551
+ setSelectedRows: (rows) => { spec.selectedRows = rows; }
552
+ });
529
553
  vue.onMounted(() => {
530
554
  // mounted hook
531
555
  });
532
556
  return {
533
- spec, renderHeader, renderBody, renderFooter, renderCaption
557
+ spec, renderHeader, renderBody, renderFooter, renderCaption,
558
+ first, prior, next, last, page, refresh, orderby,
559
+ getSelectedRows: () => spec.selectedRows,
560
+ setSelectedRows: (rows) => { spec.selectedRows = rows; }
534
561
  };
535
562
  },
536
563
  render(props) {
@@ -552,7 +579,6 @@
552
579
  }
553
580
  };
554
581
 
555
- exports.TablevuStatus = TablevuStatus;
556
582
  exports.default = tablevu;
557
583
 
558
584
  Object.defineProperty(exports, '__esModule', { value: true });
@@ -0,0 +1,15 @@
1
+ import type { TablevuSort } from './tablevu-sort';
2
+ import type { TablevuColumn } from './tablevu-column';
3
+ export interface AdapterParams {
4
+ pageSize?: number;
5
+ currPage?: number;
6
+ sort?: TablevuSort[];
7
+ cols?: TablevuColumn[];
8
+ filter?: any;
9
+ search?: string | null;
10
+ }
11
+ export interface AdapterResult<T = any> {
12
+ rows: T[];
13
+ count: number;
14
+ }
15
+ export type AdapterFunction = (params: AdapterParams, data: any[]) => Promise<AdapterResult>;
@@ -0,0 +1,11 @@
1
+ export type { TablevuStatus } from './tablevu-status';
2
+ export type { TablevuColumn } from './tablevu-column';
3
+ export type { TablevuSort } from './tablevu-sort';
4
+ export type { TablevuGroup } from './tablevu-group';
5
+ export type { TablevuPageSettings } from './tablevu-page-settings';
6
+ export type { TablevuFooterSettings } from './tablevu-footer-settings';
7
+ export type { TablevuSettings } from './tablevu-settings';
8
+ export type { TablevuProps } from './tablevu-props';
9
+ export type { TablevuEmits } from './tablevu-emits';
10
+ export type { TablevuSpec } from './tablevu-spec';
11
+ export type { AdapterFunction, AdapterResult } from './adapter';
@@ -0,0 +1,22 @@
1
+ export interface TablevuColumn {
2
+ name: string;
3
+ label?: string;
4
+ type?: 'string' | 'number' | 'numeric' | 'integer' | 'date' | 'time' | 'datetime' | 'boolean';
5
+ align?: 'left' | 'center' | 'right';
6
+ width?: string;
7
+ style?: Record<string, string>;
8
+ render?: (row: any) => any;
9
+ colspan?: number;
10
+ depth?: string[];
11
+ header?: {
12
+ colspan?: number;
13
+ style?: Record<string, string>;
14
+ align?: string;
15
+ };
16
+ popup?: boolean;
17
+ filter?: any;
18
+ visible?: boolean;
19
+ resizable?: boolean;
20
+ draggable?: boolean;
21
+ searchable?: boolean;
22
+ }
@@ -0,0 +1,8 @@
1
+ export interface TablevuEmits {
2
+ (e: 'onRowClick', event: MouseEvent, row: any): void;
3
+ (e: 'onRowDblClick', event: MouseEvent, row: any): void;
4
+ (e: 'selectionChange', selectedRows: any[]): void;
5
+ (e: 'columnResize', column: string, width: string): void;
6
+ (e: 'columnReorder', columns: string[]): void;
7
+ (e: 'columnVisibilityChange', column: string, visible: boolean): void;
8
+ }
@@ -0,0 +1,5 @@
1
+ export interface TablevuFooterSettings {
2
+ button?: {
3
+ class?: string;
4
+ };
5
+ }
@@ -0,0 +1,6 @@
1
+ export interface TablevuGroup {
2
+ value?: string | number | bigint | ((row: any) => any);
3
+ content?: string | number | ((row: any) => any);
4
+ expandAll?: boolean;
5
+ class?: string;
6
+ }
@@ -0,0 +1,4 @@
1
+ export interface TablevuPageSettings {
2
+ size?: number;
3
+ type?: 'incremental' | 'dynamic';
4
+ }
@@ -0,0 +1,13 @@
1
+ import type { TablevuSettings } from './tablevu-settings';
2
+ export interface TablevuProps {
3
+ settings?: TablevuSettings;
4
+ data?: any[] | ((params: any, data: any[]) => Promise<any>);
5
+ group?: string | object;
6
+ search?: string;
7
+ filter?: any;
8
+ rowid?: string;
9
+ onRowClick?: (event: MouseEvent, row: any) => void;
10
+ onRowDblClick?: (event: MouseEvent, row: any) => void;
11
+ class?: string;
12
+ style?: string;
13
+ }
@@ -0,0 +1,26 @@
1
+ import type { TablevuGroup } from './tablevu-group';
2
+ import type { TablevuPageSettings } from './tablevu-page-settings';
3
+ import type { TablevuSort } from './tablevu-sort';
4
+ import type { TablevuColumn } from './tablevu-column';
5
+ import type { TablevuFooterSettings } from './tablevu-footer-settings';
6
+ export interface TablevuSettings {
7
+ group?: TablevuGroup;
8
+ page?: TablevuPageSettings;
9
+ sort?: TablevuSort[];
10
+ columns?: TablevuColumn[];
11
+ searchMinChar?: number;
12
+ footer?: TablevuFooterSettings;
13
+ onData?: (rows: any[]) => any[];
14
+ selection?: TablevuSelectionSettings;
15
+ resizableColumns?: boolean;
16
+ reorderableColumns?: boolean;
17
+ stickyHeader?: boolean;
18
+ emptyMessage?: string;
19
+ loadingMode?: 'skeleton' | 'spinner';
20
+ }
21
+ export interface TablevuSelectionSettings {
22
+ enabled: boolean;
23
+ multiple?: boolean;
24
+ rowId?: string;
25
+ onChange?: (selectedRows: any[]) => void;
26
+ }
@@ -0,0 +1,5 @@
1
+ export interface TablevuSort {
2
+ index?: number;
3
+ name?: string;
4
+ dir?: 'asc' | 'desc' | null | string;
5
+ }
@@ -0,0 +1,31 @@
1
+ import type { TablevuGroup } from './tablevu-group';
2
+ import type { TablevuPageSettings } from './tablevu-page-settings';
3
+ import type { TablevuSort } from './tablevu-sort';
4
+ import type { TablevuColumn } from './tablevu-column';
5
+ import type { TablevuFooterSettings } from './tablevu-footer-settings';
6
+ import type { TablevuStatus } from './tablevu-status';
7
+ export interface TablevuSpec {
8
+ caption: any;
9
+ group: TablevuGroup & {
10
+ value: any;
11
+ content: any;
12
+ expandAll: boolean;
13
+ class: string;
14
+ };
15
+ page: TablevuPageSettings & {
16
+ count: number;
17
+ current: number;
18
+ };
19
+ sort: TablevuSort[];
20
+ columns: TablevuColumn[];
21
+ footer: TablevuFooterSettings;
22
+ status: TablevuStatus;
23
+ time: number | null;
24
+ search: string | null;
25
+ filter: any;
26
+ onData?: (rows: any[]) => any[];
27
+ selectedRows: any[];
28
+ columnWidths: Record<string, string>;
29
+ columnOrder: string[];
30
+ columnVisibility: Record<string, boolean>;
31
+ }
@@ -0,0 +1,6 @@
1
+ export type TablevuStatus = -1 | 0 | 1;
2
+ export declare const TablevuStatus: {
3
+ Error: TablevuStatus;
4
+ Ready: TablevuStatus;
5
+ Loading: TablevuStatus;
6
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tablevu",
3
- "version": "0.3.0-beta.2",
3
+ "version": "0.3.0-beta.3",
4
4
  "type": "module",
5
5
  "description": "Vue 3 data table component",
6
6
  "main": "dist/tablevu-index.umd.cjs.js",