@oscarpalmer/tabela 0.10.0 → 0.12.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 (69) hide show
  1. package/dist/components/body.component.js +1 -0
  2. package/dist/components/column.component.js +4 -4
  3. package/dist/components/group.component.js +28 -0
  4. package/dist/components/row.component.js +11 -8
  5. package/dist/helpers/dom.helpers.js +1 -1
  6. package/dist/managers/column.manager.js +9 -8
  7. package/dist/managers/data.manager.js +95 -30
  8. package/dist/managers/event.manager.js +42 -23
  9. package/dist/managers/filter.manager.js +16 -10
  10. package/dist/managers/group.manager.js +46 -0
  11. package/dist/managers/navigation.manager.js +73 -0
  12. package/dist/managers/render.manager.js +61 -31
  13. package/dist/managers/row.manager.js +7 -7
  14. package/dist/managers/selection.manager.js +49 -46
  15. package/dist/managers/sort.manager.js +37 -9
  16. package/dist/models/group.model.js +0 -0
  17. package/dist/models/selection.model.js +0 -0
  18. package/dist/tabela.full.js +682 -591
  19. package/dist/tabela.js +31 -10
  20. package/package.json +1 -1
  21. package/src/components/body.component.ts +2 -0
  22. package/src/components/column.component.ts +6 -6
  23. package/src/components/group.component.ts +43 -0
  24. package/src/components/row.component.ts +14 -9
  25. package/src/helpers/dom.helpers.ts +3 -1
  26. package/src/managers/column.manager.ts +13 -15
  27. package/src/managers/data.manager.ts +176 -38
  28. package/src/managers/event.manager.ts +68 -41
  29. package/src/managers/filter.manager.ts +29 -20
  30. package/src/managers/group.manager.ts +79 -0
  31. package/src/managers/navigation.manager.ts +146 -0
  32. package/src/managers/render.manager.ts +84 -40
  33. package/src/managers/row.manager.ts +9 -14
  34. package/src/managers/selection.manager.ts +68 -67
  35. package/src/managers/sort.manager.ts +85 -22
  36. package/src/models/column.model.ts +2 -2
  37. package/src/models/data.model.ts +14 -3
  38. package/src/models/filter.model.ts +11 -3
  39. package/src/models/group.model.ts +4 -0
  40. package/src/models/render.model.ts +3 -1
  41. package/src/models/selection.model.ts +9 -0
  42. package/src/models/sort.model.ts +11 -3
  43. package/src/models/tabela.model.ts +14 -36
  44. package/src/models/tabela.options.ts +3 -2
  45. package/src/tabela.ts +43 -19
  46. package/types/components/column.component.d.ts +3 -3
  47. package/types/components/group.component.d.ts +14 -0
  48. package/types/components/row.component.d.ts +2 -2
  49. package/types/helpers/style.helper.d.ts +1 -1
  50. package/types/managers/column.manager.d.ts +6 -7
  51. package/types/managers/data.manager.d.ts +7 -6
  52. package/types/managers/event.manager.d.ts +3 -6
  53. package/types/managers/filter.manager.d.ts +11 -11
  54. package/types/managers/group.manager.d.ts +17 -0
  55. package/types/managers/navigation.manager.d.ts +10 -0
  56. package/types/managers/render.manager.d.ts +6 -7
  57. package/types/managers/row.manager.d.ts +4 -5
  58. package/types/managers/selection.manager.d.ts +12 -7
  59. package/types/managers/sort.manager.d.ts +11 -9
  60. package/types/models/column.model.d.ts +2 -2
  61. package/types/models/data.model.d.ts +13 -3
  62. package/types/models/filter.model.d.ts +10 -3
  63. package/types/models/group.model.d.ts +4 -0
  64. package/types/models/render.model.d.ts +2 -1
  65. package/types/models/selection.model.d.ts +8 -0
  66. package/types/models/sort.model.d.ts +10 -3
  67. package/types/models/tabela.model.d.ts +14 -33
  68. package/types/models/tabela.options.d.ts +3 -2
  69. package/types/tabela.d.ts +4 -1
@@ -1,62 +1,89 @@
1
1
  import {on} from '@oscarpalmer/toretto/event';
2
- import type {RemovableEventListener} from '@oscarpalmer/toretto/models';
3
- import type {TabelaManagers} from '../models/tabela.model';
4
- import {findAncestor} from '@oscarpalmer/toretto';
2
+ import {findAncestor} from '@oscarpalmer/toretto/find';
3
+ import type {State} from '../models/tabela.model';
5
4
 
6
5
  export class EventManager {
7
- listener: RemovableEventListener;
8
-
9
- constructor(
10
- element: HTMLElement,
11
- readonly managers: TabelaManagers,
12
- ) {
13
- this.listener = on(
14
- element,
15
- 'click',
16
- event => {
17
- this.onClick(event);
18
- },
19
- {
20
- passive: false,
21
- },
22
- );
6
+ constructor(public state: State) {
7
+ mapped.set(state.element, this);
23
8
  }
24
9
 
25
10
  destroy(): void {
26
- this.listener();
11
+ mapped.delete(this.state.element);
12
+
13
+ this.state = undefined as never;
27
14
  }
28
15
 
29
- onClick(event: MouseEvent): void {
30
- const target = findAncestor(event, '[data-event]');
16
+ onSort(event: MouseEvent, target: HTMLElement): void {
17
+ const direction = target.getAttribute('data-sort-direction');
18
+ const field = target.getAttribute('data-field');
31
19
 
32
- if (!(target instanceof HTMLElement)) {
33
- return;
20
+ if (field != null) {
21
+ this.state.managers.sort.toggle(event, field, direction);
34
22
  }
23
+ }
24
+ }
35
25
 
36
- const type = target?.getAttribute('data-event');
26
+ function onClick(event: MouseEvent): void {
27
+ const target = findAncestor(event, '[data-event]');
28
+ const table = findAncestor(event, '.tabela');
29
+
30
+ if (!(target instanceof HTMLElement) || !(table instanceof HTMLElement)) {
31
+ return;
32
+ }
33
+
34
+ const manager = mapped.get(table);
35
+
36
+ if (manager == null) {
37
+ return;
38
+ }
39
+
40
+ const type = target?.getAttribute('data-event');
41
+
42
+ switch (type) {
43
+ case 'group':
44
+ manager.state.managers.group.handle(target);
45
+ break;
37
46
 
38
- switch (type) {
39
47
  case 'heading':
40
- this.onSort(event, target);
41
- break;
48
+ manager.onSort(event, target);
49
+ break;
42
50
 
43
- case 'row':
44
- this.managers.selection.handle(event, target);
45
- break;
51
+ case 'row':
52
+ manager.state.managers.selection.handle(event, target);
53
+ break;
46
54
 
47
- default:
48
- break;
49
- }
55
+ default:
56
+ break;
50
57
  }
58
+ }
51
59
 
52
- onSort(event: MouseEvent, target: HTMLElement): void {
53
- const {managers} = this;
60
+ function onKeydown(event: KeyboardEvent): void {
61
+ const target = findAncestor(event, '[data-event]');
62
+ const table = findAncestor(event, '.tabela');
54
63
 
55
- const direction = target.getAttribute('data-sort-direction');
56
- const field = target.getAttribute('data-field');
64
+ if (!(target instanceof HTMLElement) || !(table instanceof HTMLElement)) {
65
+ return;
66
+ }
57
67
 
58
- if (field != null) {
59
- managers.sort.toggle(event, field, direction);
60
- }
68
+ const manager = mapped.get(table);
69
+
70
+ if (manager == null) {
71
+ return;
72
+ }
73
+
74
+ const type = target?.getAttribute('data-event');
75
+
76
+ switch (type) {
77
+ case 'body':
78
+ manager.state.managers.navigation.handle(event);
79
+ break;
80
+
81
+ default:
82
+ break;
61
83
  }
62
84
  }
85
+
86
+ const mapped = new WeakMap<HTMLElement, EventManager>();
87
+
88
+ on(document, 'click', onClick);
89
+ on(document, 'keydown', onKeydown, {passive: false});
@@ -3,8 +3,9 @@ import {getNumber} from '@oscarpalmer/atoms/number';
3
3
  import {getString} from '@oscarpalmer/atoms/string';
4
4
  import {endsWith, includes, startsWith} from '@oscarpalmer/atoms/string/match';
5
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';
6
+ import type {TabelaFilter, TabelaFilterComparison, TabelaFilterItem} from '../models/filter.model';
7
+ import type {State} from '../models/tabela.model';
8
+ import {GroupComponent} from '../components/group.component';
8
9
 
9
10
  export class FilterManager {
10
11
  handlers = Object.freeze({
@@ -14,11 +15,11 @@ export class FilterManager {
14
15
  set: items => this.set(items),
15
16
  } satisfies TabelaFilter);
16
17
 
17
- items: Record<string, FilterItem[]> = {};
18
+ items: Record<string, TabelaFilterItem[]> = {};
18
19
 
19
- constructor(readonly managers: TabelaManagers) {}
20
+ constructor(public state: State) {}
20
21
 
21
- add(item: FilterItem): void {
22
+ add(item: TabelaFilterItem): void {
22
23
  if (this.items[item.field] == null) {
23
24
  this.items[item.field] = [];
24
25
  } else {
@@ -44,20 +45,28 @@ export class FilterManager {
44
45
 
45
46
  destroy(): void {
46
47
  this.handlers = undefined as never;
47
- this.items = {};
48
+ this.items = undefined as never;
49
+ this.state = undefined as never;
48
50
  }
49
51
 
50
52
  filter(): void {
51
- const {managers} = this;
53
+ const {state} = this;
52
54
 
53
- const filtered: Key[] = [];
55
+ const filtered: Array<GroupComponent | Key> = [];
54
56
  const filters = Object.entries(this.items);
55
57
 
56
- const keysLength = managers.data.values.keys.original.length;
58
+ const keysLength = state.managers.data.values.keys.original.length;
57
59
 
58
60
  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
+ const key = state.managers.data.values.keys.original[keyIndex];
62
+
63
+ if (key instanceof GroupComponent) {
64
+ filtered.push(key);
65
+
66
+ continue;
67
+ }
68
+
69
+ const row = state.managers.data.values.objects.mapped.get(key);
61
70
 
62
71
  if (row == null) {
63
72
  continue;
@@ -82,22 +91,22 @@ export class FilterManager {
82
91
  filtered.push(key);
83
92
  }
84
93
 
85
- managers.data.values.keys.active = filtered;
94
+ state.managers.data.values.keys.active = filtered;
86
95
 
87
- if (managers.sort.items.length > 0) {
88
- managers.sort.sort();
96
+ if (state.managers.sort.items.length > 0) {
97
+ state.managers.sort.sort();
89
98
  } else {
90
- managers.render.update(true, true);
99
+ state.managers.render.update(true, true);
91
100
  }
92
101
  }
93
102
 
94
- remove(value: string | FilterItem): void {
103
+ remove(value: string | TabelaFilterItem): void {
95
104
  if (typeof value === 'string') {
96
105
  if (this.items[value] == null) {
97
106
  return;
98
107
  }
99
108
 
100
- const keyed: Record<string, FilterItem[]> = {};
109
+ const keyed: Record<string, TabelaFilterItem[]> = {};
101
110
 
102
111
  this.items = keyed;
103
112
  } else {
@@ -117,8 +126,8 @@ export class FilterManager {
117
126
  this.filter();
118
127
  }
119
128
 
120
- set(items: FilterItem[]): void {
121
- const keyed: Record<string, FilterItem[]> = {};
129
+ set(items: TabelaFilterItem[]): void {
130
+ const keyed: Record<string, TabelaFilterItem[]> = {};
122
131
 
123
132
  const {length} = items;
124
133
 
@@ -136,7 +145,7 @@ export class FilterManager {
136
145
  }
137
146
  }
138
147
 
139
- const comparators: Record<FilterComparison, (row: unknown, filter: unknown) => boolean> = {
148
+ const comparators: Record<TabelaFilterComparison, (row: unknown, filter: unknown) => boolean> = {
140
149
  contains: (row, filter) => includes(getString(row), getString(filter), true),
141
150
  'ends-with': (row, filter) => endsWith(getString(row), getString(filter), true),
142
151
  equals: (row, filter) => equalizer(row, filter),
@@ -0,0 +1,79 @@
1
+ import {sort} from '@oscarpalmer/atoms/array';
2
+ import {toRecord} from '@oscarpalmer/atoms/array/to-record';
3
+ import {isNullableOrWhitespace} from '@oscarpalmer/atoms/is';
4
+ import type {Key, Simplify} from '@oscarpalmer/atoms/models';
5
+ import type {GroupComponent} from '../components/group.component';
6
+ import type {State} from '../models/tabela.model';
7
+
8
+ export class GroupManager {
9
+ collapsed = new Set<Key>();
10
+
11
+ enabled = false;
12
+
13
+ field!: string;
14
+
15
+ items: GroupComponent[] = [];
16
+
17
+ order: Record<never, number> = {};
18
+
19
+ constructor(readonly state: State) {
20
+ if (isNullableOrWhitespace(state.options.grouping)) {
21
+ return;
22
+ }
23
+
24
+ this.enabled = true;
25
+ this.field = state.options.grouping;
26
+ }
27
+
28
+ add(group: GroupComponent): void {
29
+ this.set([...this.items, group]);
30
+ }
31
+
32
+ get(value: unknown) {
33
+ return this.items.find(item => item.value === value);
34
+ }
35
+
36
+ handle(button: HTMLElement): void {
37
+ const key = button.dataset.key;
38
+ const group = this.get(key);
39
+
40
+ if (group == null) {
41
+ return;
42
+ }
43
+
44
+ const {collapsed, items, state} = this;
45
+
46
+ group.expanded = !group.expanded;
47
+
48
+ const index = items.indexOf(group);
49
+
50
+ let first = state.managers.data.values.keys.original.indexOf(items[index]) + 1;
51
+
52
+ const last =
53
+ items[index + 1] == null
54
+ ? state.managers.data.keys.length - 1
55
+ : state.managers.data.values.keys.original.indexOf(items[index + 1]) - 1;
56
+
57
+ for (; first <= last; first += 1) {
58
+ const key = state.managers.data.values.keys.original[first] as Key;
59
+
60
+ if (group.expanded) {
61
+ collapsed.delete(key);
62
+ } else {
63
+ collapsed.add(key);
64
+ }
65
+ }
66
+
67
+ state.managers.render.update(true, true);
68
+ }
69
+
70
+ remove(group: GroupComponent): void {
71
+ this.set(this.items.filter(item => item !== group));
72
+ }
73
+
74
+ set(items: GroupComponent[]) {
75
+ this.items = sort(items, item => item.label);
76
+
77
+ this.order = toRecord(items as Simplify<GroupComponent>[], 'value', (_, index) => index);
78
+ }
79
+ }
@@ -0,0 +1,146 @@
1
+ import {isNullableOrWhitespace} from '@oscarpalmer/atoms/is';
2
+ import type {Key} from '@oscarpalmer/atoms/models';
3
+ import {clamp} from '@oscarpalmer/atoms/number';
4
+ import type {GroupComponent} from '../components/group.component';
5
+ import {getKey} from '../helpers/misc.helpers';
6
+ import type {State} from '../models/tabela.model';
7
+
8
+ export class NavigationManager {
9
+ active: Key | undefined;
10
+
11
+ constructor(public state: State) {}
12
+
13
+ destroy(): void {
14
+ this.state = undefined as never;
15
+ }
16
+
17
+ handle(event: KeyboardEvent): void {
18
+ if (!allKeys.has(event.key)) {
19
+ return;
20
+ }
21
+
22
+ event.preventDefault();
23
+
24
+ const {components, id, managers} = this.state;
25
+
26
+ const activeDescendant = components.body.elements.group.getAttribute('aria-activedescendant');
27
+
28
+ const {keys} = managers.data;
29
+ const {length} = keys;
30
+
31
+ let next: number;
32
+
33
+ if (isNullableOrWhitespace(activeDescendant)) {
34
+ next = getDefaultIndex(event.key, length);
35
+ } else {
36
+ next = getIndex(event, activeDescendant, id, keys)!;
37
+ }
38
+
39
+ if (next != null) {
40
+ this.setActive(keys.at(next) as Key);
41
+ }
42
+ }
43
+
44
+ setActive(key: Key | undefined, scroll?: boolean): void {
45
+ const {components, managers, options} = this.state;
46
+
47
+ this.active = key;
48
+
49
+ const active = components.body.elements.group.querySelectorAll('[data-active="true"]');
50
+
51
+ for (const item of active) {
52
+ item.setAttribute('data-active', 'false');
53
+ }
54
+
55
+ const row = managers.row.get(key!);
56
+
57
+ if (row != null) {
58
+ row.element?.setAttribute('data-active', 'true');
59
+
60
+ if (scroll ?? true) {
61
+ if (row.element == null) {
62
+ components.body.elements.group.scrollTo({
63
+ top: managers.data.getIndex(key!) * options.rowHeight,
64
+ behavior: 'smooth',
65
+ });
66
+ } else {
67
+ row.element.scrollIntoView({
68
+ block: 'nearest',
69
+ });
70
+ }
71
+ }
72
+ }
73
+
74
+ components.body.elements.group.setAttribute(
75
+ 'aria-activedescendant',
76
+ row == null ? '' : `tabela_${this.state.id}_row_${key}`,
77
+ );
78
+ }
79
+ }
80
+
81
+ function getDefaultIndex(key: string, max: number): number {
82
+ switch (true) {
83
+ case negativeDefaultKeys.has(key):
84
+ return -1;
85
+
86
+ case key === 'PageDown':
87
+ return Math.min(9, max - 1);
88
+
89
+ case key === 'PageUp':
90
+ return max < 10 ? 0 : max - 10;
91
+
92
+ default:
93
+ return 0;
94
+ }
95
+ }
96
+
97
+ function getIndex(
98
+ event: KeyboardEvent,
99
+ active: string,
100
+ id: number,
101
+ keys: Array<GroupComponent | Key>,
102
+ ): number | undefined {
103
+ const key = getKey(active.replace(`tabela_${id}_row_`, ''));
104
+
105
+ if (key == null) {
106
+ return;
107
+ }
108
+
109
+ if (absoluteKeys.has(event.key)) {
110
+ return event.key === 'Home' ? 0 : keys.length - 1;
111
+ }
112
+
113
+ const index = keys.indexOf(key);
114
+ const offset = getOffset(event.key);
115
+
116
+ return clamp(index + offset, 0, keys.length - 1, true);
117
+ }
118
+
119
+ function getOffset(key: string): number {
120
+ switch (key) {
121
+ case 'ArrowDown':
122
+ return 1;
123
+
124
+ case 'ArrowUp':
125
+ return -1;
126
+
127
+ case 'PageDown':
128
+ return 10;
129
+
130
+ case 'PageUp':
131
+ return -10;
132
+
133
+ default:
134
+ return 0;
135
+ }
136
+ }
137
+
138
+ const absoluteKeys = new Set(['End', 'Home']);
139
+
140
+ const arrowKeys = new Set(['ArrowDown', 'ArrowUp']);
141
+
142
+ const negativeDefaultKeys = new Set(['ArrowUp', 'End']);
143
+
144
+ const pageKeys = new Set(['PageDown', 'PageUp']);
145
+
146
+ const allKeys = new Set([...absoluteKeys, ...arrowKeys, ...pageKeys]);
@@ -1,46 +1,47 @@
1
1
  import type {Key} from '@oscarpalmer/atoms/models';
2
2
  import {on} from '@oscarpalmer/toretto/event';
3
3
  import type {RemovableEventListener} from '@oscarpalmer/toretto/models';
4
+ import {GroupComponent, renderGroup} from '../components/group.component';
4
5
  import {removeRow, renderRow} from '../components/row.component';
5
6
  import type {RenderElementPool, RenderRange, RenderState} from '../models/render.model';
6
- import type {TabelaComponents, TabelaManagers} from '../models/tabela.model';
7
+ import type {State} from '../models/tabela.model';
7
8
 
8
- function getRange(this: RenderManager, down: boolean): RenderRange {
9
- const {components, managers} = this;
9
+ function getRange(state: State, down: boolean): RenderRange {
10
+ const {components, managers, options} = state;
10
11
  const {clientHeight, scrollTop} = components.body.elements.group;
11
12
 
12
- const first = Math.floor(scrollTop / managers.row.height);
13
+ const {keys} = managers.data;
14
+
15
+ const first = Math.floor(scrollTop / options.rowHeight);
13
16
 
14
17
  const last = Math.min(
15
- (managers.data.values.keys.active?.length ?? managers.data.values.keys.original.length) - 1,
16
- Math.ceil((scrollTop + clientHeight) / managers.row.height) - 1,
18
+ keys.length - managers.group.collapsed.size - 1,
19
+ Math.ceil((scrollTop + clientHeight) / options.rowHeight) - 1,
17
20
  );
18
21
 
19
- const before = Math.ceil(clientHeight / managers.row.height) * (down ? 1 : 2);
20
- const after = Math.ceil(clientHeight / managers.row.height) * (down ? 2 : 1);
22
+ const before = Math.ceil(clientHeight / options.rowHeight) * (down ? 1 : 2);
23
+ const after = Math.ceil(clientHeight / options.rowHeight) * (down ? 2 : 1);
21
24
 
22
25
  const start = Math.max(0, first - before);
23
-
24
- const end = Math.min(
25
- (managers.data.values.keys.active?.length ?? managers.data.values.keys.original.length) - 1,
26
- last + after,
27
- );
26
+ const end = Math.min(keys.length - managers.group.collapsed.size - 1, last + after);
28
27
 
29
28
  return {end, start};
30
29
  }
31
30
 
32
31
  function onScroll(this: RenderManager): void {
33
- if (!this.state.active) {
32
+ const {state} = this;
33
+
34
+ if (!state.active) {
34
35
  requestAnimationFrame(() => {
35
- const top = this.components.body.elements.group.scrollTop;
36
+ const top = state.components.body.elements.group.scrollTop;
36
37
 
37
- this.update(top > this.state.top);
38
+ this.update(top > state.top);
38
39
 
39
- this.state.active = false;
40
- this.state.top = top;
40
+ state.active = false;
41
+ state.top = top;
41
42
  });
42
43
 
43
- this.state.active = true;
44
+ state.active = true;
44
45
  }
45
46
  }
46
47
 
@@ -49,23 +50,23 @@ export class RenderManager {
49
50
 
50
51
  listener: RemovableEventListener;
51
52
 
52
- readonly pool: RenderElementPool = {
53
+ pool: RenderElementPool = {
53
54
  cells: {},
54
55
  rows: [],
55
56
  };
56
57
 
57
- readonly state: RenderState = {
58
- active: false,
59
- top: 0,
60
- };
58
+ state: RenderState;
59
+
60
+ visible = new Map<number, GroupComponent | Key>();
61
61
 
62
- visible = new Map<number, Key>();
62
+ constructor(state: State) {
63
+ this.listener = on(state.components.body.elements.group, 'scroll', onScroll.bind(this));
63
64
 
64
- constructor(
65
- public managers: TabelaManagers,
66
- public components: TabelaComponents,
67
- ) {
68
- this.listener = on(components.body.elements.group, 'scroll', onScroll.bind(this));
65
+ this.state = {
66
+ ...state,
67
+ active: false,
68
+ top: 0,
69
+ };
69
70
  }
70
71
 
71
72
  destroy(): void {
@@ -77,12 +78,15 @@ export class RenderManager {
77
78
  pool.cells = {};
78
79
  pool.rows = [];
79
80
 
81
+ this.fragment = undefined as never;
80
82
  this.listener = undefined as never;
83
+ this.pool = undefined as never;
84
+ this.state = undefined as never;
81
85
  this.visible = undefined as never;
82
86
  }
83
87
 
84
88
  removeCells(fields: string[]): void {
85
- const {managers, pool, visible} = this;
89
+ const {pool, state, visible} = this;
86
90
  const {length} = fields;
87
91
 
88
92
  for (let index = 0; index < length; index += 1) {
@@ -90,7 +94,11 @@ export class RenderManager {
90
94
  }
91
95
 
92
96
  for (const [, key] of visible) {
93
- const row = managers.row.get(key);
97
+ if (key instanceof GroupComponent) {
98
+ continue;
99
+ }
100
+
101
+ const row = state.managers.row.get(key);
94
102
 
95
103
  if (row == null || row.element == null) {
96
104
  continue;
@@ -115,12 +123,14 @@ export class RenderManager {
115
123
  }
116
124
 
117
125
  update(down: boolean, rerender?: boolean): void {
118
- const {components, managers, pool, visible} = this;
126
+ const {state, pool, visible} = this;
127
+ const {components, managers, options} = state;
119
128
 
120
- components.body.elements.faker.style.height = `${managers.data.size * managers.row.height}px`;
129
+ components.body.elements.faker.style.height = `${(managers.data.size - managers.group.collapsed.size) * options.rowHeight}px`;
121
130
 
122
131
  const indices = new Set<number>();
123
- const range = getRange.call(this, down);
132
+
133
+ const range = getRange(state, down);
124
134
 
125
135
  for (let index = range.start; index <= range.end; index += 1) {
126
136
  indices.add(index);
@@ -129,9 +139,19 @@ export class RenderManager {
129
139
  let remove = rerender ?? false;
130
140
 
131
141
  for (const [index, key] of visible) {
142
+ if (key instanceof GroupComponent) {
143
+ if (remove || !indices.has(index)) {
144
+ visible.delete(index);
145
+
146
+ key.element?.remove();
147
+ }
148
+
149
+ continue;
150
+ }
151
+
132
152
  const row = managers.row.get(key);
133
153
 
134
- if (remove || row == null || !indices.has(index)) {
154
+ if (remove || row == null || !indices.has(index) || managers.group.collapsed.has(key)) {
135
155
  visible.delete(index);
136
156
 
137
157
  if (row != null) {
@@ -142,30 +162,54 @@ export class RenderManager {
142
162
 
143
163
  const fragment = this.getFragment();
144
164
 
145
- const keys = managers.data.values.keys.active ?? managers.data.values.keys.original;
165
+ const {keys} = managers.data;
146
166
 
147
167
  let count = 0;
168
+ let offset = 0;
148
169
 
149
- for (let index = range.start; index <= range.end; index += 1) {
170
+ for (let index = range.start; index <= range.end + offset; index += 1) {
150
171
  if (visible.has(index)) {
151
172
  continue;
152
173
  }
153
174
 
154
175
  const key = keys[index];
176
+
177
+ if (key instanceof GroupComponent) {
178
+ count += 1;
179
+
180
+ renderGroup(state, key);
181
+
182
+ visible.set(index, key);
183
+
184
+ if (key.element != null) {
185
+ key.element.style.transform = `translateY(${(index - offset) * options.rowHeight}px)`;
186
+
187
+ fragment.append(key.element);
188
+ }
189
+
190
+ continue;
191
+ }
192
+
155
193
  const row = managers.row.get(key);
156
194
 
157
195
  if (row == null) {
158
196
  continue;
159
197
  }
160
198
 
199
+ if (managers.group.collapsed.has(key)) {
200
+ offset += 1;
201
+
202
+ continue;
203
+ }
204
+
161
205
  count += 1;
162
206
 
163
- renderRow(managers, row);
207
+ renderRow(state, row);
164
208
 
165
209
  visible.set(index, key);
166
210
 
167
211
  if (row.element != null) {
168
- row.element.style.transform = `translateY(${index * managers.row.height}px)`;
212
+ row.element.style.transform = `translateY(${(index - offset) * options.rowHeight}px)`;
169
213
 
170
214
  fragment.append(row.element);
171
215
  }