@oscarpalmer/tabela 0.8.0 → 0.10.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 (55) hide show
  1. package/dist/components/body.component.js +2 -15
  2. package/dist/components/footer.component.js +3 -3
  3. package/dist/components/header.component.js +2 -2
  4. package/dist/components/row.component.js +13 -4
  5. package/dist/helpers/dom.helpers.js +5 -10
  6. package/dist/helpers/misc.helpers.js +7 -0
  7. package/dist/helpers/style.helper.js +6 -0
  8. package/dist/managers/column.manager.js +7 -2
  9. package/dist/managers/data.manager.js +6 -4
  10. package/dist/managers/event.manager.js +6 -2
  11. package/dist/managers/filter.manager.js +92 -0
  12. package/dist/managers/{virtualization.manager.js → render.manager.js} +28 -22
  13. package/dist/managers/row.manager.js +9 -2
  14. package/dist/managers/selection.manager.js +191 -0
  15. package/dist/managers/sort.manager.js +17 -6
  16. package/dist/models/render.model.js +0 -0
  17. package/dist/tabela.full.js +1253 -105
  18. package/dist/tabela.js +19 -6
  19. package/package.json +1 -1
  20. package/src/components/body.component.ts +4 -21
  21. package/src/components/footer.component.ts +3 -3
  22. package/src/components/header.component.ts +2 -2
  23. package/src/components/row.component.ts +22 -7
  24. package/src/helpers/dom.helpers.ts +3 -10
  25. package/src/helpers/misc.helpers.ts +15 -0
  26. package/src/helpers/style.helper.ts +6 -0
  27. package/src/managers/column.manager.ts +9 -1
  28. package/src/managers/data.manager.ts +9 -5
  29. package/src/managers/event.manager.ts +10 -3
  30. package/src/managers/filter.manager.ts +154 -0
  31. package/src/managers/{virtualization.manager.ts → render.manager.ts} +36 -31
  32. package/src/managers/row.manager.ts +16 -2
  33. package/src/managers/selection.manager.ts +338 -0
  34. package/src/managers/sort.manager.ts +35 -16
  35. package/src/models/filter.model.ts +17 -0
  36. package/src/models/{virtualization.model.ts → render.model.ts} +3 -3
  37. package/src/models/sort.model.ts +1 -1
  38. package/src/models/tabela.model.ts +22 -2
  39. package/src/tabela.ts +28 -6
  40. package/types/components/row.component.d.ts +2 -2
  41. package/types/helpers/dom.helpers.d.ts +1 -1
  42. package/types/helpers/misc.helpers.d.ts +2 -0
  43. package/types/helpers/style.helper.d.ts +1 -0
  44. package/types/managers/data.manager.d.ts +1 -1
  45. package/types/managers/event.manager.d.ts +1 -1
  46. package/types/managers/filter.manager.d.ts +19 -0
  47. package/types/managers/{virtualization.manager.d.ts → render.manager.d.ts} +6 -6
  48. package/types/managers/selection.manager.d.ts +19 -0
  49. package/types/managers/sort.manager.d.ts +5 -2
  50. package/types/models/filter.model.d.ts +6 -0
  51. package/types/models/{virtualization.model.d.ts → render.model.d.ts} +3 -3
  52. package/types/models/sort.model.d.ts +1 -1
  53. package/types/models/tabela.model.d.ts +20 -2
  54. package/types/tabela.d.ts +3 -1
  55. /package/dist/models/{virtualization.model.js → filter.model.js} +0 -0
package/dist/tabela.js CHANGED
@@ -4,9 +4,11 @@ import { HeaderComponent } from "./components/header.component.js";
4
4
  import { ColumnManager } from "./managers/column.manager.js";
5
5
  import { DataManager } from "./managers/data.manager.js";
6
6
  import { EventManager } from "./managers/event.manager.js";
7
+ import { FilterManager } from "./managers/filter.manager.js";
7
8
  import { RowManager } from "./managers/row.manager.js";
9
+ import { SelectionManager } from "./managers/selection.manager.js";
8
10
  import { SortManager } from "./managers/sort.manager.js";
9
- import { VirtualizationManager } from "./managers/virtualization.manager.js";
11
+ import { RenderManager } from "./managers/render.manager.js";
10
12
  var Tabela = class {
11
13
  #components = {
12
14
  header: void 0,
@@ -19,11 +21,15 @@ var Tabela = class {
19
21
  column: void 0,
20
22
  data: void 0,
21
23
  event: void 0,
24
+ filter: void 0,
25
+ render: void 0,
22
26
  row: void 0,
23
- sort: void 0,
24
- virtualization: void 0
27
+ selection: void 0,
28
+ sort: void 0
25
29
  };
26
30
  data;
31
+ filter;
32
+ selection;
27
33
  sort;
28
34
  get key() {
29
35
  return this.#key;
@@ -40,13 +46,17 @@ var Tabela = class {
40
46
  this.#components.footer = new FooterComponent();
41
47
  this.#managers.column = new ColumnManager(this.#managers, this.#components, options.columns);
42
48
  this.#managers.data = new DataManager(this.#managers, this.#components, options.key);
43
- this.#managers.event = new EventManager(this.#managers, this.#element);
49
+ this.#managers.event = new EventManager(this.#element, this.#managers);
50
+ this.#managers.filter = new FilterManager(this.#managers);
51
+ this.#managers.render = new RenderManager(this.#managers, this.#components);
44
52
  this.#managers.row = new RowManager(this.#managers, options.rowHeight);
53
+ this.#managers.selection = new SelectionManager(this.#element, this.#managers);
45
54
  this.#managers.sort = new SortManager(this.#managers);
46
- this.#managers.virtualization = new VirtualizationManager(this.#managers, this.#components);
47
55
  element.append(this.#components.header.elements.group, this.#components.body.elements.group, this.#components.footer.elements.group);
48
56
  this.#managers.data.set(options.data);
49
57
  this.data = this.#managers.data.handlers;
58
+ this.filter = this.#managers.filter.handlers;
59
+ this.selection = this.#managers.selection.handlers;
50
60
  this.sort = this.#managers.sort.handlers;
51
61
  }
52
62
  destroy() {
@@ -58,8 +68,11 @@ var Tabela = class {
58
68
  components.header.destroy();
59
69
  managers.column.destroy();
60
70
  managers.data.destroy();
71
+ managers.event.destroy();
72
+ managers.filter.destroy();
73
+ managers.render.destroy();
61
74
  managers.row.destroy();
62
- managers.virtualization.destroy();
75
+ managers.sort.destroy();
63
76
  element.innerHTML = "";
64
77
  element.role = "";
65
78
  element.classList.remove("tabela");
package/package.json CHANGED
@@ -46,5 +46,5 @@
46
46
  },
47
47
  "type": "module",
48
48
  "types": "./types/index.d.ts",
49
- "version": "0.8.0"
49
+ "version": "0.10.0"
50
50
  }
@@ -1,21 +1,10 @@
1
- import {setStyles} from '@oscarpalmer/toretto/style';
2
1
  import {createElement, createRowGroup} from '../helpers/dom.helpers';
3
2
  import type {BodyElements} from '../models/body.model';
4
3
 
5
4
  function createFaker(): HTMLDivElement {
6
- return createElement(
7
- 'div',
8
- {},
9
- {},
10
- {
11
- height: '0',
12
- inset: '0 auto auto 0',
13
- opacity: '0',
14
- pointerEvents: 'none',
15
- position: 'absolute',
16
- width: '1px',
17
- },
18
- );
5
+ return createElement('div', {
6
+ className: 'tabela__faker',
7
+ }, {}, {});
19
8
  }
20
9
 
21
10
  export class BodyComponent {
@@ -29,16 +18,10 @@ export class BodyComponent {
29
18
 
30
19
  this.elements.group = group;
31
20
 
32
- group.className += ' tabela__rowgroup-body';
21
+ group.className += ' tabela__rowgroup--body';
33
22
 
34
23
  group.tabIndex = 0;
35
24
 
36
- setStyles(group, {
37
- height: '100%',
38
- overflow: 'auto',
39
- position: 'relative',
40
- });
41
-
42
25
  group.append(this.elements.faker);
43
26
  }
44
27
 
@@ -14,8 +14,8 @@ export class FooterComponent {
14
14
  cells: [],
15
15
  };
16
16
 
17
- group.className += ' tabela__rowgroup-footer';
18
- row.className += ' tabela__row-footer';
17
+ group.className += ' tabela__rowgroup--footer';
18
+ row.className += ' tabela__row--footer';
19
19
  }
20
20
 
21
21
  destroy(): void {
@@ -35,7 +35,7 @@ export class FooterComponent {
35
35
  for (let index = 0; index < length; index += 1) {
36
36
  const cell = createCell(columns[index].options.width ?? 4, false);
37
37
 
38
- cell.className += ' tabela__cell-footer';
38
+ cell.className += ' tabela__cell--footer';
39
39
  cell.innerHTML = '&nbsp;';
40
40
 
41
41
  elements.cells.push(cell);
@@ -10,8 +10,8 @@ export class HeaderComponent {
10
10
 
11
11
  this.elements = {group, row};
12
12
 
13
- group.className += ' tabela__rowgroup-header';
14
- row.className += ' tabela__row-header';
13
+ group.className += ' tabela__rowgroup--header';
14
+ row.className += ' tabela__row--header';
15
15
  }
16
16
 
17
17
  destroy(): void {
@@ -1,9 +1,10 @@
1
1
  import type {Key} from '@oscarpalmer/atoms/models';
2
+ import {setAttributes} from '@oscarpalmer/toretto/attribute';
2
3
  import {createCell, createRow} from '../helpers/dom.helpers';
4
+ import type {RenderElementPool} from '../models/render.model';
3
5
  import type {TabelaManagers} from '../models/tabela.model';
4
- import type {VirtualizationPool} from '../models/virtualization.model';
5
6
 
6
- export function removeRow(pool: VirtualizationPool, row: RowComponent): void {
7
+ export function removeRow(pool: RenderElementPool, row: RowComponent): void {
7
8
  if (row.element != null) {
8
9
  row.element.innerHTML = '';
9
10
 
@@ -17,13 +18,28 @@ export function removeRow(pool: VirtualizationPool, row: RowComponent): void {
17
18
  }
18
19
 
19
20
  export function renderRow(managers: TabelaManagers, row: RowComponent): void {
20
- const element = row.element ?? managers.virtualization.pool.rows.shift() ?? createRow();
21
+ const element = row.element ?? managers.render.pool.rows.shift() ?? createRow();
21
22
 
22
23
  row.element = element;
23
24
 
24
- element.dataset.key = String(row.key);
25
25
  element.innerHTML = '';
26
26
 
27
+ const selected = managers.selection.items.has(row.key);
28
+
29
+ setAttributes(element, {
30
+ 'aria-selected': String(selected),
31
+ 'data-event': 'row',
32
+ 'data-key': String(row.key),
33
+ });
34
+
35
+ element.classList.add('tabela__row--body');
36
+
37
+ if (selected) {
38
+ element.classList.add('tabela__row--selected');
39
+ } else {
40
+ element.classList.remove('tabela__row--selected');
41
+ }
42
+
27
43
  const columns = managers.column.items;
28
44
  const {length} = columns;
29
45
 
@@ -36,11 +52,10 @@ export function renderRow(managers: TabelaManagers, row: RowComponent): void {
36
52
  for (let index = 0; index < length; index += 1) {
37
53
  const {options} = columns[index];
38
54
 
39
- managers.virtualization.pool.cells[options.field] ??= [];
55
+ managers.render.pool.cells[options.field] ??= [];
40
56
 
41
57
  const cell =
42
- managers.virtualization.pool.cells[columns[index].options.field].shift() ??
43
- createCell(options.width);
58
+ managers.render.pool.cells[columns[index].options.field].shift() ?? createCell(options.width);
44
59
 
45
60
  cell.textContent = String(data[options.field]);
46
61
 
@@ -20,7 +20,7 @@ export function createCell(width: number, body?: boolean): HTMLDivElement {
20
20
  );
21
21
 
22
22
  if (body ?? true) {
23
- cell.classList.add('tabela__cell-body');
23
+ cell.classList.add('tabela__cell--body');
24
24
  }
25
25
 
26
26
  return cell;
@@ -65,14 +65,14 @@ export function createRowGroup(withRow?: boolean) {
65
65
  return group;
66
66
  }
67
67
 
68
- const row = createRow(false);
68
+ const row = createRow();
69
69
 
70
70
  group.append(row);
71
71
 
72
72
  return {group, row};
73
73
  }
74
74
 
75
- export function createRow(withStyle?: boolean): HTMLDivElement {
75
+ export function createRow(): HTMLDivElement {
76
76
  const row = createElement(
77
77
  'div',
78
78
  {
@@ -83,12 +83,5 @@ export function createRow(withStyle?: boolean): HTMLDivElement {
83
83
  {},
84
84
  );
85
85
 
86
- if (withStyle ?? true) {
87
- setStyles(row, {
88
- inset: '0 auto auto 0',
89
- position: 'absolute',
90
- });
91
- }
92
-
93
86
  return row;
94
87
  }
@@ -0,0 +1,15 @@
1
+ import type {Key} from '@oscarpalmer/atoms/models';
2
+
3
+ export function getKey(value: unknown): Key | undefined {
4
+ if (typeof value === 'number') {
5
+ return value;
6
+ }
7
+
8
+ if (typeof value !== 'string') {
9
+ return;
10
+ }
11
+
12
+ return integerExpression.test(value) ? Number.parseInt(value, 10) : value;
13
+ }
14
+
15
+ const integerExpression = /^\d+$/;
@@ -0,0 +1,6 @@
1
+ import {toggleStyles} from '@oscarpalmer/toretto/style';
2
+
3
+ export const dragStyling = toggleStyles(document.body, {
4
+ userSelect: 'none',
5
+ webkitUserSelect: 'none',
6
+ });
@@ -14,6 +14,12 @@ export class ColumnManager {
14
14
  }
15
15
 
16
16
  destroy(): void {
17
+ const {length} = this.items;
18
+
19
+ for (let index = 0; index < length; index += 1) {
20
+ this.items[index].destroy();
21
+ }
22
+
17
23
  this.items.length = 0;
18
24
  }
19
25
 
@@ -40,6 +46,8 @@ export class ColumnManager {
40
46
  );
41
47
 
42
48
  if (itemIndex > -1) {
49
+ items[itemIndex].destroy();
50
+
43
51
  items.splice(itemIndex, 1);
44
52
  }
45
53
  }
@@ -47,7 +55,7 @@ export class ColumnManager {
47
55
  components.header.update(items);
48
56
  components.footer.update(items);
49
57
 
50
- managers.virtualization.removeCells(fields);
58
+ managers.render.removeCells(fields);
51
59
  }
52
60
 
53
61
  set(columns: TabelaColumnOptions[]): void {
@@ -1,4 +1,4 @@
1
- import {sort} from '@oscarpalmer/atoms/array';
1
+ import {push, sort} from '@oscarpalmer/atoms/array';
2
2
  import {toMap} from '@oscarpalmer/atoms/array/to-map';
3
3
  import {isPlainObject} from '@oscarpalmer/atoms/is';
4
4
  import type {Key, PlainObject} from '@oscarpalmer/atoms/models';
@@ -6,7 +6,7 @@ import type {DataValues} from '../models/data.model';
6
6
  import type {TabelaComponents, TabelaData, TabelaManagers} from '../models/tabela.model';
7
7
 
8
8
  export class DataManager {
9
- readonly handlers = Object.freeze({
9
+ handlers = Object.freeze({
10
10
  add: data => void this.add(data, true),
11
11
  clear: () => void this.clear(),
12
12
  get: active => this.get(active),
@@ -38,7 +38,7 @@ export class DataManager {
38
38
  async add(data: PlainObject[], render: boolean): Promise<void> {
39
39
  const {field, values} = this;
40
40
 
41
- values.objects.array.push(...data);
41
+ push(values.objects.array, data);
42
42
 
43
43
  values.objects.mapped = toMap(values.objects.array, field) as Map<Key, PlainObject>;
44
44
 
@@ -61,6 +61,8 @@ export class DataManager {
61
61
  values.keys.active = undefined;
62
62
  values.keys.original.length = 0;
63
63
  values.objects.array.length = 0;
64
+
65
+ this.handlers = undefined as never;
64
66
  }
65
67
 
66
68
  get(active?: boolean): PlainObject[] {
@@ -110,10 +112,12 @@ export class DataManager {
110
112
 
111
113
  values.keys.original = sort(values.objects.array.map(item => item[field] as Key));
112
114
 
113
- if (managers.sort.items.length > 0) {
115
+ if (Object.keys(managers.filter.items).length > 0) {
116
+ managers.filter.filter();
117
+ } else if (managers.sort.items.length > 0) {
114
118
  managers.sort.sort();
115
119
  } else {
116
- managers.virtualization.update(true);
120
+ managers.render.update(true);
117
121
  }
118
122
  }
119
123
 
@@ -4,13 +4,13 @@ import type {TabelaManagers} from '../models/tabela.model';
4
4
  import {findAncestor} from '@oscarpalmer/toretto';
5
5
 
6
6
  export class EventManager {
7
- listener!: RemovableEventListener;
7
+ listener: RemovableEventListener;
8
8
 
9
9
  constructor(
10
- readonly managers: TabelaManagers,
11
10
  element: HTMLElement,
11
+ readonly managers: TabelaManagers,
12
12
  ) {
13
- on(
13
+ this.listener = on(
14
14
  element,
15
15
  'click',
16
16
  event => {
@@ -39,6 +39,13 @@ export class EventManager {
39
39
  case 'heading':
40
40
  this.onSort(event, target);
41
41
  break;
42
+
43
+ case 'row':
44
+ this.managers.selection.handle(event, target);
45
+ break;
46
+
47
+ default:
48
+ break;
42
49
  }
43
50
  }
44
51
 
@@ -0,0 +1,154 @@
1
+ import type {Key} from '@oscarpalmer/atoms/models';
2
+ import {getNumber} from '@oscarpalmer/atoms/number';
3
+ import {getString} from '@oscarpalmer/atoms/string';
4
+ import {endsWith, includes, startsWith} from '@oscarpalmer/atoms/string/match';
5
+ import {equal} from '@oscarpalmer/atoms/value/equal';
6
+ import type {FilterComparison, FilterItem} from '../models/filter.model';
7
+ import type {TabelaFilter, TabelaManagers} from '../models/tabela.model';
8
+
9
+ export class FilterManager {
10
+ handlers = Object.freeze({
11
+ add: item => this.add(item),
12
+ clear: () => this.clear(),
13
+ remove: value => this.remove(value),
14
+ set: items => this.set(items),
15
+ } satisfies TabelaFilter);
16
+
17
+ items: Record<string, FilterItem[]> = {};
18
+
19
+ constructor(readonly managers: TabelaManagers) {}
20
+
21
+ add(item: FilterItem): void {
22
+ if (this.items[item.field] == null) {
23
+ this.items[item.field] = [];
24
+ } else {
25
+ const index = this.items[item.field].findIndex(existing => equal(existing, item));
26
+
27
+ if (index !== -1) {
28
+ return;
29
+ }
30
+ }
31
+
32
+ this.items[item.field].push(item);
33
+
34
+ this.filter();
35
+ }
36
+
37
+ clear(): void {
38
+ if (Object.keys(this.items).length > 0) {
39
+ this.items = {};
40
+
41
+ this.filter();
42
+ }
43
+ }
44
+
45
+ destroy(): void {
46
+ this.handlers = undefined as never;
47
+ this.items = {};
48
+ }
49
+
50
+ filter(): void {
51
+ const {managers} = this;
52
+
53
+ const filtered: Key[] = [];
54
+ const filters = Object.entries(this.items);
55
+
56
+ const keysLength = managers.data.values.keys.original.length;
57
+
58
+ rowLoop: for (let keyIndex = 0; keyIndex < keysLength; keyIndex += 1) {
59
+ const key = managers.data.values.keys.original[keyIndex];
60
+ const row = managers.data.values.objects.mapped.get(key);
61
+
62
+ if (row == null) {
63
+ continue;
64
+ }
65
+
66
+ filterLoop: for (let filterIndex = 0; filterIndex < filters.length; filterIndex += 1) {
67
+ const [field, items] = filters[filterIndex];
68
+
69
+ const value = row[field];
70
+
71
+ for (let itemIndex = 0; itemIndex < items.length; itemIndex += 1) {
72
+ const filter = items[itemIndex];
73
+
74
+ if (comparators[filter.comparison](value, filter.value)) {
75
+ continue filterLoop;
76
+ }
77
+ }
78
+
79
+ continue rowLoop;
80
+ }
81
+
82
+ filtered.push(key);
83
+ }
84
+
85
+ managers.data.values.keys.active = filtered;
86
+
87
+ if (managers.sort.items.length > 0) {
88
+ managers.sort.sort();
89
+ } else {
90
+ managers.render.update(true, true);
91
+ }
92
+ }
93
+
94
+ remove(value: string | FilterItem): void {
95
+ if (typeof value === 'string') {
96
+ if (this.items[value] == null) {
97
+ return;
98
+ }
99
+
100
+ const keyed: Record<string, FilterItem[]> = {};
101
+
102
+ this.items = keyed;
103
+ } else {
104
+ const {field} = value;
105
+
106
+ if (this.items[field] == null) {
107
+ return;
108
+ }
109
+
110
+ const index = this.items[field].findIndex(item => equal(item, value));
111
+
112
+ if (index === -1) {
113
+ return;
114
+ }
115
+ }
116
+
117
+ this.filter();
118
+ }
119
+
120
+ set(items: FilterItem[]): void {
121
+ const keyed: Record<string, FilterItem[]> = {};
122
+
123
+ const {length} = items;
124
+
125
+ for (let index = 0; index < length; index += 1) {
126
+ const item = items[index];
127
+
128
+ keyed[item.field] ??= [];
129
+
130
+ keyed[item.field].push(item);
131
+ }
132
+
133
+ this.items = keyed;
134
+
135
+ this.filter();
136
+ }
137
+ }
138
+
139
+ const comparators: Record<FilterComparison, (row: unknown, filter: unknown) => boolean> = {
140
+ contains: (row, filter) => includes(getString(row), getString(filter), true),
141
+ 'ends-with': (row, filter) => endsWith(getString(row), getString(filter), true),
142
+ equals: (row, filter) => equalizer(row, filter),
143
+ 'greater-than': (row, filter) => getNumber(row) > getNumber(filter),
144
+ 'greater-than-or-equal': (row, filter) => getNumber(row) >= getNumber(filter),
145
+ 'less-than': (row, filter) => getNumber(row) < getNumber(filter),
146
+ 'less-than-or-equal': (row, filter) => getNumber(row) <= getNumber(filter),
147
+ 'not-contains': (row, filter) => !includes(getString(row), getString(filter), true),
148
+ 'not-equals': (row, filter) => !equalizer(row, filter),
149
+ 'starts-with': (row, filter) => startsWith(getString(row), getString(filter), true),
150
+ };
151
+
152
+ const equalizer = equal.initialize({
153
+ ignoreCase: true,
154
+ });
@@ -1,21 +1,18 @@
1
+ import type {Key} from '@oscarpalmer/atoms/models';
1
2
  import {on} from '@oscarpalmer/toretto/event';
2
3
  import type {RemovableEventListener} from '@oscarpalmer/toretto/models';
3
- import {removeRow, renderRow, RowComponent} from '../components/row.component';
4
+ import {removeRow, renderRow} from '../components/row.component';
5
+ import type {RenderElementPool, RenderRange, RenderState} from '../models/render.model';
4
6
  import type {TabelaComponents, TabelaManagers} from '../models/tabela.model';
5
- import type {
6
- VirtualizationPool,
7
- VirtualizationRange,
8
- VirtualizationState,
9
- } from '../models/virtualization.model';
10
7
 
11
- function getRange(this: VirtualizationManager, down: boolean): VirtualizationRange {
8
+ function getRange(this: RenderManager, down: boolean): RenderRange {
12
9
  const {components, managers} = this;
13
10
  const {clientHeight, scrollTop} = components.body.elements.group;
14
11
 
15
12
  const first = Math.floor(scrollTop / managers.row.height);
16
13
 
17
14
  const last = Math.min(
18
- managers.data.values.keys.active?.length ?? managers.data.values.keys.original.length - 1,
15
+ (managers.data.values.keys.active?.length ?? managers.data.values.keys.original.length) - 1,
19
16
  Math.ceil((scrollTop + clientHeight) / managers.row.height) - 1,
20
17
  );
21
18
 
@@ -25,14 +22,14 @@ function getRange(this: VirtualizationManager, down: boolean): VirtualizationRan
25
22
  const start = Math.max(0, first - before);
26
23
 
27
24
  const end = Math.min(
28
- managers.data.values.keys.active?.length ?? managers.data.values.keys.original.length - 1,
25
+ (managers.data.values.keys.active?.length ?? managers.data.values.keys.original.length) - 1,
29
26
  last + after,
30
27
  );
31
28
 
32
29
  return {end, start};
33
30
  }
34
31
 
35
- function onScroll(this: VirtualizationManager): void {
32
+ function onScroll(this: RenderManager): void {
36
33
  if (!this.state.active) {
37
34
  requestAnimationFrame(() => {
38
35
  const top = this.components.body.elements.group.scrollTop;
@@ -47,22 +44,22 @@ function onScroll(this: VirtualizationManager): void {
47
44
  }
48
45
  }
49
46
 
50
- export class VirtualizationManager {
47
+ export class RenderManager {
51
48
  fragment!: DocumentFragment;
52
49
 
53
50
  listener: RemovableEventListener;
54
51
 
55
- readonly pool: VirtualizationPool = {
52
+ readonly pool: RenderElementPool = {
56
53
  cells: {},
57
54
  rows: [],
58
55
  };
59
56
 
60
- readonly state: VirtualizationState = {
57
+ readonly state: RenderState = {
61
58
  active: false,
62
59
  top: 0,
63
60
  };
64
61
 
65
- readonly visible = new Map<number, RowComponent>();
62
+ visible = new Map<number, Key>();
66
63
 
67
64
  constructor(
68
65
  public managers: TabelaManagers,
@@ -72,26 +69,33 @@ export class VirtualizationManager {
72
69
  }
73
70
 
74
71
  destroy(): void {
75
- this.listener();
72
+ const {listener, pool, visible} = this;
76
73
 
77
- for (const [index, row] of this.visible) {
78
- removeRow(this.pool, row);
74
+ listener();
75
+ visible.clear();
79
76
 
80
- this.visible.delete(index);
81
- }
77
+ pool.cells = {};
78
+ pool.rows = [];
82
79
 
83
- this.pool.cells = {};
84
- this.pool.rows = [];
80
+ this.listener = undefined as never;
81
+ this.visible = undefined as never;
85
82
  }
86
83
 
87
84
  removeCells(fields: string[]): void {
85
+ const {managers, pool, visible} = this;
88
86
  const {length} = fields;
89
87
 
90
88
  for (let index = 0; index < length; index += 1) {
91
- delete this.pool.cells[fields[index]];
89
+ delete pool.cells[fields[index]];
92
90
  }
93
91
 
94
- for (const [, row] of this.visible) {
92
+ for (const [, key] of visible) {
93
+ const row = managers.row.get(key);
94
+
95
+ if (row == null || row.element == null) {
96
+ continue;
97
+ }
98
+
95
99
  for (let index = 0; index < length; index += 1) {
96
100
  row.cells[fields[index]].innerHTML = '';
97
101
 
@@ -124,15 +128,15 @@ export class VirtualizationManager {
124
128
 
125
129
  let remove = rerender ?? false;
126
130
 
127
- for (const [index, row] of visible) {
128
- if (!managers.row.has(row.key) || !indices.has(index)) {
129
- remove = true;
130
- }
131
+ for (const [index, key] of visible) {
132
+ const row = managers.row.get(key);
131
133
 
132
- if (remove) {
134
+ if (remove || row == null || !indices.has(index)) {
133
135
  visible.delete(index);
134
136
 
135
- removeRow(pool, row);
137
+ if (row != null) {
138
+ removeRow(pool, row);
139
+ }
136
140
  }
137
141
  }
138
142
 
@@ -147,7 +151,8 @@ export class VirtualizationManager {
147
151
  continue;
148
152
  }
149
153
 
150
- const row = managers.row.get(keys[index]);
154
+ const key = keys[index];
155
+ const row = managers.row.get(key);
151
156
 
152
157
  if (row == null) {
153
158
  continue;
@@ -157,7 +162,7 @@ export class VirtualizationManager {
157
162
 
158
163
  renderRow(managers, row);
159
164
 
160
- visible.set(index, row);
165
+ visible.set(index, key);
161
166
 
162
167
  if (row.element != null) {
163
168
  row.element.style.transform = `translateY(${index * managers.row.height}px)`;