@oscarpalmer/tabela 0.3.0 → 0.4.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.
Files changed (43) hide show
  1. package/dist/components/body.component.cjs +18 -9
  2. package/dist/components/body.component.js +18 -9
  3. package/dist/components/footer.component.cjs +15 -9
  4. package/dist/components/footer.component.js +15 -9
  5. package/dist/components/header.component.cjs +9 -6
  6. package/dist/components/header.component.js +9 -6
  7. package/dist/components/row.component.cjs +17 -11
  8. package/dist/components/row.component.js +18 -12
  9. package/dist/helpers/dom.helpers.cjs +10 -3
  10. package/dist/helpers/dom.helpers.js +10 -3
  11. package/dist/managers/virtualization.manager.cjs +25 -12
  12. package/dist/managers/virtualization.manager.js +25 -12
  13. package/dist/tabela.cjs +19 -1
  14. package/dist/tabela.js +19 -1
  15. package/package.json +7 -7
  16. package/src/components/body.component.ts +27 -9
  17. package/src/components/footer.component.ts +22 -9
  18. package/src/components/header.component.ts +15 -6
  19. package/src/components/row.component.ts +36 -23
  20. package/src/helpers/dom.helpers.ts +12 -3
  21. package/src/managers/virtualization.manager.ts +38 -13
  22. package/src/tabela.ts +39 -15
  23. package/types/components/body.component.d.cts +47 -35
  24. package/types/components/body.component.d.ts +7 -2
  25. package/types/components/column.component.d.cts +47 -35
  26. package/types/components/footer.component.d.cts +47 -35
  27. package/types/components/footer.component.d.ts +8 -3
  28. package/types/components/header.component.d.cts +47 -35
  29. package/types/components/header.component.d.ts +7 -2
  30. package/types/components/row.component.d.cts +47 -35
  31. package/types/components/row.component.d.ts +4 -4
  32. package/types/helpers/dom.helpers.d.cts +2 -2
  33. package/types/helpers/dom.helpers.d.ts +2 -2
  34. package/types/index.d.cts +47 -35
  35. package/types/managers/virtualization.manager.d.cts +47 -35
  36. package/types/managers/virtualization.manager.d.ts +6 -0
  37. package/types/tabela.d.cts +47 -35
  38. package/types/tabela.d.ts +2 -1
  39. package/dist/components/cell.component.cjs +0 -17
  40. package/dist/components/cell.component.js +0 -17
  41. package/src/components/cell.component.ts +0 -18
  42. package/types/components/cell.component.d.cts +0 -86
  43. package/types/components/cell.component.d.ts +0 -10
@@ -4,6 +4,11 @@ import {VirtualizationManager} from '../managers/virtualization.manager';
4
4
  import type {Tabela} from '../tabela';
5
5
  import {RowComponent} from './row.component';
6
6
 
7
+ type Elements = {
8
+ faker: HTMLDivElement;
9
+ group: HTMLDivElement;
10
+ };
11
+
7
12
  function createFaker(): HTMLDivElement {
8
13
  const element = document.createElement('div');
9
14
 
@@ -18,22 +23,27 @@ function createFaker(): HTMLDivElement {
18
23
  }
19
24
 
20
25
  export class BodyComponent {
21
- readonly faker = createFaker();
22
- readonly group: HTMLDivElement;
26
+ readonly elements: Elements = {
27
+ faker: createFaker(),
28
+ group: undefined as never,
29
+ };
30
+
23
31
  readonly rows: RowComponent[] = [];
24
32
  readonly virtualization: VirtualizationManager;
25
33
 
26
34
  constructor(readonly tabela: Tabela) {
27
- this.group = createRowGroup(false);
35
+ const group = createRowGroup(false);
36
+
37
+ this.elements.group = group;
28
38
  this.virtualization = new VirtualizationManager(this);
29
39
 
30
- this.group.className += ' tabela__rowgroup-body';
40
+ group.className += ' tabela__rowgroup-body';
31
41
 
32
- this.group.style.height = '100%';
33
- this.group.style.overflow = 'auto';
34
- this.group.style.position = 'relative';
42
+ group.style.height = '100%';
43
+ group.style.overflow = 'auto';
44
+ group.style.position = 'relative';
35
45
 
36
- this.group.append(this.faker);
46
+ group.append(this.elements.faker);
37
47
 
38
48
  void this.addData(tabela.options.data).then(() => {
39
49
  this.virtualization.update(true);
@@ -50,8 +60,16 @@ export class BodyComponent {
50
60
  this.updateVirtualization();
51
61
  }
52
62
 
63
+ destroy(): void {
64
+ this.virtualization.destroy();
65
+
66
+ this.elements.faker = undefined as never;
67
+ this.elements.group = undefined as never;
68
+ this.rows.length = 0;
69
+ }
70
+
53
71
  private updateVirtualization(): void {
54
- this.faker.style.height = `${this.rows.length * 32}px`;
72
+ this.elements.faker.style.height = `${this.rows.length * 32}px`;
55
73
 
56
74
  this.virtualization.update(true);
57
75
  }
@@ -1,31 +1,44 @@
1
1
  import {createCell, createRowGroup} from '../helpers/dom.helpers';
2
2
  import type {Tabela} from '../tabela';
3
3
 
4
+ type Elements = {
5
+ cells: HTMLDivElement[];
6
+ group: HTMLDivElement;
7
+ row: HTMLDivElement;
8
+ };
9
+
4
10
  export class FooterComponent {
5
- readonly cells: HTMLDivElement[] = [];
6
- readonly group: HTMLDivElement;
7
- readonly row: HTMLDivElement;
11
+ readonly elements: Elements;
8
12
 
9
13
  constructor(readonly tabela: Tabela) {
10
14
  const {group, row} = createRowGroup();
11
15
 
12
- this.group = group;
13
- this.row = row;
16
+ this.elements = {
17
+ group,
18
+ row,
19
+ cells: [],
20
+ };
14
21
 
15
- this.group.className += ' tabela__rowgroup-footer';
16
- this.row.className += ' tabela__row-footer';
22
+ group.className += ' tabela__rowgroup-footer';
23
+ row.className += ' tabela__row-footer';
17
24
 
18
25
  const {columns} = tabela.header;
19
26
  const {length} = columns;
20
27
 
21
28
  for (let index = 0; index < length; index += 1) {
22
- const cell = createCell(columns[index].options.width ?? 4);
29
+ const cell = createCell(columns[index].options.width ?? 4, false);
23
30
 
24
31
  cell.className += ' tabela__cell-footer';
25
32
  cell.innerHTML = '&nbsp;';
26
33
 
27
- this.cells.push(cell);
34
+ this.elements.cells.push(cell);
28
35
  row.append(cell);
29
36
  }
30
37
  }
38
+
39
+ destroy(): void {
40
+ this.elements.cells = [];
41
+ this.elements.group = undefined as never;
42
+ this.elements.row = undefined as never;
43
+ }
31
44
  }
@@ -2,10 +2,14 @@ import {createRowGroup} from '../helpers/dom.helpers';
2
2
  import type {Tabela} from '../tabela';
3
3
  import {ColumnComponent} from './column.component';
4
4
 
5
+ type Elements = {
6
+ group: HTMLDivElement;
7
+ row: HTMLDivElement;
8
+ };
9
+
5
10
  export class HeaderComponent {
6
11
  readonly columns: ColumnComponent[];
7
- readonly group: HTMLDivElement;
8
- readonly row: HTMLDivElement;
12
+ readonly elements: Elements;
9
13
 
10
14
  constructor(readonly tabela: Tabela) {
11
15
  this.columns = tabela.options.columns.map(
@@ -14,12 +18,17 @@ export class HeaderComponent {
14
18
 
15
19
  const {group, row} = createRowGroup();
16
20
 
17
- this.group = group;
18
- this.row = row;
21
+ this.elements = {group, row};
19
22
 
20
- this.group.className += ' tabela__rowgroup-header';
21
- this.row.className += ' tabela__row-header';
23
+ group.className += ' tabela__rowgroup-header';
24
+ row.className += ' tabela__row-header';
22
25
 
23
26
  row.append(...this.columns.map(column => column.element));
24
27
  }
28
+
29
+ destroy(): void {
30
+ this.columns.length = 0;
31
+ this.elements.group = undefined as never;
32
+ this.elements.row = undefined as never;
33
+ }
25
34
  }
@@ -1,37 +1,50 @@
1
1
  import type {PlainObject} from '@oscarpalmer/atoms/models';
2
- import {createRow} from '../helpers/dom.helpers';
2
+ import {createCell, createRow} from '../helpers/dom.helpers';
3
+ import type {ElementPool} from '../managers/virtualization.manager';
3
4
  import type {Tabela} from '../tabela';
4
- import {CellComponent} from './cell.component';
5
5
 
6
6
  export class RowComponent {
7
- readonly cells: CellComponent[] = [];
8
- readonly element: HTMLDivElement;
7
+ element: HTMLDivElement | undefined;
9
8
 
10
- constructor(
11
- readonly tabela: Tabela,
12
- readonly data: PlainObject,
13
- ) {
14
- this.element = createRow();
9
+ constructor(
10
+ readonly tabela: Tabela,
11
+ readonly data: PlainObject,
12
+ ) {}
15
13
 
16
- this.element.className += ' tabela__row-body';
14
+ remove(pool: ElementPool): void {
15
+ if (this.element != null) {
16
+ this.element.innerHTML = '';
17
+
18
+ pool.rows.push(this.element);
19
+ this.element.remove();
20
+
21
+ this.element = undefined;
17
22
  }
23
+ }
24
+
25
+ render(pool: ElementPool): void {
26
+ const element = this.element ?? pool.rows.shift() ?? createRow();
27
+
28
+ this.element = element;
29
+
30
+ element.innerHTML = '';
31
+
32
+ const {tabela} = this;
33
+ const {columns} = tabela.header;
34
+ const {length} = columns;
18
35
 
19
- render(): void {
20
- if (this.element.innerHTML !== '') {
21
- return;
22
- }
36
+ for (let index = 0; index < length; index += 1) {
37
+ const {options} = columns[index];
23
38
 
24
- const {tabela} = this;
25
- const {columns} = tabela.header;
26
- const {length} = columns;
39
+ pool.cells[options.field] ??= [];
27
40
 
28
- for (let index = 0; index < length; index += 1) {
29
- const cell = new CellComponent(tabela, columns[index], this);
41
+ const cell =
42
+ pool.cells[columns[index].options.field].shift() ??
43
+ createCell(options.width);
30
44
 
31
- cell.element.className += ' tabela__cell-body';
45
+ cell.textContent = String(this.data[options.field]);
32
46
 
33
- this.cells.push(cell);
34
- this.element.append(cell.element);
35
- }
47
+ element.append(cell);
36
48
  }
37
49
  }
50
+ }
@@ -3,13 +3,17 @@ type RowGroupWithRow = {
3
3
  row: HTMLDivElement;
4
4
  };
5
5
 
6
- export function createCell(width: number): HTMLDivElement {
6
+ export function createCell(width: number, body?: boolean): HTMLDivElement {
7
7
  const cell = document.createElement('div');
8
8
 
9
9
  cell.className = 'tabela__cell';
10
10
  cell.role = 'cell';
11
11
  cell.style.width = `${width}px`;
12
12
 
13
+ if (body ?? true) {
14
+ cell.classList.add('tabela__cell-body');
15
+ }
16
+
13
17
  return cell;
14
18
  }
15
19
 
@@ -27,18 +31,23 @@ export function createRowGroup(withRow?: boolean) {
27
31
  return group;
28
32
  }
29
33
 
30
- const row = createRow();
34
+ const row = createRow(false);
31
35
 
32
36
  group.append(row);
33
37
 
34
38
  return {group, row};
35
39
  }
36
40
 
37
- export function createRow() {
41
+ export function createRow(withStyle?: boolean): HTMLDivElement {
38
42
  const row = document.createElement('div');
39
43
 
40
44
  row.className = 'tabela__row';
41
45
  row.role = 'row';
42
46
 
47
+ if (withStyle ?? true) {
48
+ row.style.inset = '0 auto auto 0';
49
+ row.style.position = 'absolute';
50
+ }
51
+
43
52
  return row;
44
53
  }
@@ -1,14 +1,19 @@
1
1
  import type {BodyComponent} from '../components/body.component';
2
2
  import type {RowComponent} from '../components/row.component';
3
3
 
4
+ export type ElementPool = {
5
+ cells: Record<string, HTMLDivElement[]>;
6
+ rows: HTMLDivElement[];
7
+ };
8
+
4
9
  type Range = {
5
10
  end: number;
6
11
  start: number;
7
12
  };
8
13
 
9
14
  function getRange(body: BodyComponent, down: boolean): Range {
10
- const {group, rows} = body;
11
- const {clientHeight, scrollTop} = group;
15
+ const {elements, rows} = body;
16
+ const {clientHeight, scrollTop} = elements.group;
12
17
 
13
18
  const first = Math.floor(scrollTop / 32);
14
19
 
@@ -28,11 +33,18 @@ function getRange(body: BodyComponent, down: boolean): Range {
28
33
 
29
34
  export class VirtualizationManager {
30
35
  private active = false;
36
+
37
+ private readonly pool: ElementPool = {
38
+ cells: {},
39
+ rows: [],
40
+ };
41
+
31
42
  private top = 0;
43
+
32
44
  private readonly visible = new Map<number, RowComponent>();
33
45
 
34
46
  constructor(private readonly body: BodyComponent) {
35
- this.body.group.addEventListener(
47
+ this.body.elements.group.addEventListener(
36
48
  'scroll',
37
49
  () => {
38
50
  this.onScroll();
@@ -43,8 +55,20 @@ export class VirtualizationManager {
43
55
  );
44
56
  }
45
57
 
58
+ destroy(): void {
59
+ const {visible, pool} = this;
60
+
61
+ for (const [index, row] of visible) {
62
+ row.remove(pool);
63
+ visible.delete(index);
64
+ }
65
+
66
+ pool.cells = {};
67
+ pool.rows = [];
68
+ }
69
+
46
70
  update(down: boolean): void {
47
- const {body, visible} = this;
71
+ const {body, pool, visible} = this;
48
72
 
49
73
  const indices = new Set<number>();
50
74
  const range = getRange(body, down);
@@ -56,7 +80,8 @@ export class VirtualizationManager {
56
80
  for (const [index, row] of visible) {
57
81
  if (!indices.has(index)) {
58
82
  visible.delete(index);
59
- row.element.remove();
83
+
84
+ row.remove(pool);
60
85
  }
61
86
  }
62
87
 
@@ -66,25 +91,25 @@ export class VirtualizationManager {
66
91
  if (!visible.has(index)) {
67
92
  const row = body.rows[index];
68
93
 
69
- row.element.style.inset = '0 auto auto 0';
70
- row.element.style.position = 'absolute';
71
- row.element.style.transform = `translateY(${index * 32}px)`;
72
-
73
- row.render();
94
+ row.render(pool);
74
95
 
75
96
  visible.set(index, row);
76
97
 
77
- fragment.append(row.element);
98
+ if (row.element != null) {
99
+ row.element.style.transform = `translateY(${index * 32}px)`;
100
+
101
+ fragment.append(row.element);
102
+ }
78
103
  }
79
104
 
80
- body.group.append(fragment);
105
+ body.elements.group.append(fragment);
81
106
  }
82
107
  }
83
108
 
84
109
  private onScroll(): void {
85
110
  if (!this.active) {
86
111
  requestAnimationFrame(() => {
87
- const top = this.body.group.scrollTop;
112
+ const top = this.body.elements.group.scrollTop;
88
113
 
89
114
  this.update(top > this.top);
90
115
 
package/src/tabela.ts CHANGED
@@ -4,26 +4,50 @@ import {HeaderComponent} from './components/header.component';
4
4
  import type {TabelaOptions} from './models/tabela.options';
5
5
 
6
6
  export class Tabela {
7
- readonly body: BodyComponent;
8
- readonly footer: FooterComponent;
9
- readonly header: HeaderComponent;
7
+ readonly body: BodyComponent;
8
+ readonly footer: FooterComponent;
9
+ readonly header: HeaderComponent;
10
10
 
11
- constructor(
12
- readonly element: HTMLElement,
13
- readonly options: TabelaOptions,
14
- ) {
15
- element.role = 'table';
11
+ constructor(
12
+ public element: HTMLElement,
13
+ readonly options: TabelaOptions,
14
+ ) {
15
+ element.innerHTML = '';
16
+ element.role = 'table';
16
17
 
17
- element.classList.add('tabela');
18
- element.setAttribute('aria-label', options.label);
18
+ element.classList.add('tabela');
19
19
 
20
- this.header = new HeaderComponent(this);
21
- this.body = new BodyComponent(this);
22
- this.footer = new FooterComponent(this);
20
+ element.setAttribute('aria-label', options.label);
23
21
 
24
- element.append(this.header.group, this.body.group, this.footer.group);
22
+ this.header = new HeaderComponent(this);
23
+ this.body = new BodyComponent(this);
24
+ this.footer = new FooterComponent(this);
25
+
26
+ element.append(
27
+ this.header.elements.group,
28
+ this.body.elements.group,
29
+ this.footer.elements.group,
30
+ );
31
+ }
32
+
33
+ destroy(): void {
34
+ this.body.destroy();
35
+ this.footer.destroy();
36
+ this.header.destroy();
37
+
38
+ this.options.columns = [];
39
+ this.options.data = [];
40
+
41
+ this.element.innerHTML = '';
42
+ this.element.role = '';
43
+
44
+ this.element.classList.remove('tabela');
45
+ this.element.removeAttribute('aria-label');
46
+ this.element.removeAttribute('role');
47
+
48
+ this.element = undefined as never;
49
+ }
25
50
  }
26
- }
27
51
 
28
52
  export function tabela(element: HTMLElement, options: TabelaOptions): Tabela {
29
53
  return new Tabela(element, options);
@@ -2,15 +2,54 @@
2
2
 
3
3
  import { PlainObject } from '@oscarpalmer/atoms/models';
4
4
 
5
+ export type ElementPool = {
6
+ cells: Record<string, HTMLDivElement[]>;
7
+ rows: HTMLDivElement[];
8
+ };
5
9
  declare class VirtualizationManager {
6
10
  private readonly body;
7
11
  private active;
12
+ private readonly pool;
8
13
  private top;
9
14
  private readonly visible;
10
15
  constructor(body: BodyComponent);
16
+ destroy(): void;
11
17
  update(down: boolean): void;
12
18
  private onScroll;
13
19
  }
20
+ declare class RowComponent {
21
+ readonly tabela: Tabela;
22
+ readonly data: PlainObject;
23
+ element: HTMLDivElement | undefined;
24
+ constructor(tabela: Tabela, data: PlainObject);
25
+ remove(pool: ElementPool): void;
26
+ render(pool: ElementPool): void;
27
+ }
28
+ export type Elements = {
29
+ faker: HTMLDivElement;
30
+ group: HTMLDivElement;
31
+ };
32
+ export declare class BodyComponent {
33
+ readonly tabela: Tabela;
34
+ readonly elements: Elements;
35
+ readonly rows: RowComponent[];
36
+ readonly virtualization: VirtualizationManager;
37
+ constructor(tabela: Tabela);
38
+ addData(data: PlainObject[]): Promise<void>;
39
+ destroy(): void;
40
+ private updateVirtualization;
41
+ }
42
+ type Elements$1 = {
43
+ cells: HTMLDivElement[];
44
+ group: HTMLDivElement;
45
+ row: HTMLDivElement;
46
+ };
47
+ declare class FooterComponent {
48
+ readonly tabela: Tabela;
49
+ readonly elements: Elements$1;
50
+ constructor(tabela: Tabela);
51
+ destroy(): void;
52
+ }
14
53
  export type TabelaColumn = {
15
54
  field: string;
16
55
  title: string;
@@ -30,44 +69,16 @@ declare class ColumnComponent {
30
69
  readonly options: TabelaColumn;
31
70
  constructor(tabela: Tabela, options: TabelaColumnOptions);
32
71
  }
33
- declare class CellComponent {
34
- readonly tabela: Tabela;
35
- readonly column: ColumnComponent;
36
- readonly row: RowComponent;
37
- readonly element: HTMLDivElement;
38
- constructor(tabela: Tabela, column: ColumnComponent, row: RowComponent);
39
- }
40
- declare class RowComponent {
41
- readonly tabela: Tabela;
42
- readonly data: PlainObject;
43
- readonly cells: CellComponent[];
44
- readonly element: HTMLDivElement;
45
- constructor(tabela: Tabela, data: PlainObject);
46
- render(): void;
47
- }
48
- export declare class BodyComponent {
49
- readonly tabela: Tabela;
50
- readonly faker: HTMLDivElement;
51
- readonly group: HTMLDivElement;
52
- readonly rows: RowComponent[];
53
- readonly virtualization: VirtualizationManager;
54
- constructor(tabela: Tabela);
55
- addData(data: PlainObject[]): Promise<void>;
56
- private updateVirtualization;
57
- }
58
- declare class FooterComponent {
59
- readonly tabela: Tabela;
60
- readonly cells: HTMLDivElement[];
61
- readonly group: HTMLDivElement;
62
- readonly row: HTMLDivElement;
63
- constructor(tabela: Tabela);
64
- }
72
+ type Elements$2 = {
73
+ group: HTMLDivElement;
74
+ row: HTMLDivElement;
75
+ };
65
76
  declare class HeaderComponent {
66
77
  readonly tabela: Tabela;
67
78
  readonly columns: ColumnComponent[];
68
- readonly group: HTMLDivElement;
69
- readonly row: HTMLDivElement;
79
+ readonly elements: Elements$2;
70
80
  constructor(tabela: Tabela);
81
+ destroy(): void;
71
82
  }
72
83
  export type TabelaOptions = {
73
84
  columns: TabelaColumnOptions[];
@@ -75,12 +86,13 @@ export type TabelaOptions = {
75
86
  label: string;
76
87
  };
77
88
  declare class Tabela {
78
- readonly element: HTMLElement;
89
+ element: HTMLElement;
79
90
  readonly options: TabelaOptions;
80
91
  readonly body: BodyComponent;
81
92
  readonly footer: FooterComponent;
82
93
  readonly header: HeaderComponent;
83
94
  constructor(element: HTMLElement, options: TabelaOptions);
95
+ destroy(): void;
84
96
  }
85
97
 
86
98
  export {};
@@ -2,13 +2,18 @@ import type { PlainObject } from '@oscarpalmer/atoms/models';
2
2
  import { VirtualizationManager } from '../managers/virtualization.manager';
3
3
  import type { Tabela } from '../tabela';
4
4
  import { RowComponent } from './row.component';
5
+ type Elements = {
6
+ faker: HTMLDivElement;
7
+ group: HTMLDivElement;
8
+ };
5
9
  export declare class BodyComponent {
6
10
  readonly tabela: Tabela;
7
- readonly faker: HTMLDivElement;
8
- readonly group: HTMLDivElement;
11
+ readonly elements: Elements;
9
12
  readonly rows: RowComponent[];
10
13
  readonly virtualization: VirtualizationManager;
11
14
  constructor(tabela: Tabela);
12
15
  addData(data: PlainObject[]): Promise<void>;
16
+ destroy(): void;
13
17
  private updateVirtualization;
14
18
  }
19
+ export {};