@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.
- package/dist/components/body.component.js +1 -0
- package/dist/components/column.component.js +4 -4
- package/dist/components/group.component.js +28 -0
- package/dist/components/row.component.js +11 -8
- package/dist/helpers/dom.helpers.js +1 -1
- package/dist/managers/column.manager.js +9 -8
- package/dist/managers/data.manager.js +95 -30
- package/dist/managers/event.manager.js +42 -23
- package/dist/managers/filter.manager.js +16 -10
- package/dist/managers/group.manager.js +46 -0
- package/dist/managers/navigation.manager.js +73 -0
- package/dist/managers/render.manager.js +61 -31
- package/dist/managers/row.manager.js +7 -7
- package/dist/managers/selection.manager.js +49 -46
- package/dist/managers/sort.manager.js +37 -9
- package/dist/models/group.model.js +0 -0
- package/dist/models/selection.model.js +0 -0
- package/dist/tabela.full.js +682 -591
- package/dist/tabela.js +31 -10
- package/package.json +1 -1
- package/src/components/body.component.ts +2 -0
- package/src/components/column.component.ts +6 -6
- package/src/components/group.component.ts +43 -0
- package/src/components/row.component.ts +14 -9
- package/src/helpers/dom.helpers.ts +3 -1
- package/src/managers/column.manager.ts +13 -15
- package/src/managers/data.manager.ts +176 -38
- package/src/managers/event.manager.ts +68 -41
- package/src/managers/filter.manager.ts +29 -20
- package/src/managers/group.manager.ts +79 -0
- package/src/managers/navigation.manager.ts +146 -0
- package/src/managers/render.manager.ts +84 -40
- package/src/managers/row.manager.ts +9 -14
- package/src/managers/selection.manager.ts +68 -67
- package/src/managers/sort.manager.ts +85 -22
- package/src/models/column.model.ts +2 -2
- package/src/models/data.model.ts +14 -3
- package/src/models/filter.model.ts +11 -3
- package/src/models/group.model.ts +4 -0
- package/src/models/render.model.ts +3 -1
- package/src/models/selection.model.ts +9 -0
- package/src/models/sort.model.ts +11 -3
- package/src/models/tabela.model.ts +14 -36
- package/src/models/tabela.options.ts +3 -2
- package/src/tabela.ts +43 -19
- package/types/components/column.component.d.ts +3 -3
- package/types/components/group.component.d.ts +14 -0
- package/types/components/row.component.d.ts +2 -2
- package/types/helpers/style.helper.d.ts +1 -1
- package/types/managers/column.manager.d.ts +6 -7
- package/types/managers/data.manager.d.ts +7 -6
- package/types/managers/event.manager.d.ts +3 -6
- package/types/managers/filter.manager.d.ts +11 -11
- package/types/managers/group.manager.d.ts +17 -0
- package/types/managers/navigation.manager.d.ts +10 -0
- package/types/managers/render.manager.d.ts +6 -7
- package/types/managers/row.manager.d.ts +4 -5
- package/types/managers/selection.manager.d.ts +12 -7
- package/types/managers/sort.manager.d.ts +11 -9
- package/types/models/column.model.d.ts +2 -2
- package/types/models/data.model.d.ts +13 -3
- package/types/models/filter.model.d.ts +10 -3
- package/types/models/group.model.d.ts +4 -0
- package/types/models/render.model.d.ts +2 -1
- package/types/models/selection.model.d.ts +8 -0
- package/types/models/sort.model.d.ts +10 -3
- package/types/models/tabela.model.d.ts +14 -33
- package/types/models/tabela.options.d.ts +3 -2
- package/types/tabela.d.ts +4 -1
|
@@ -1,62 +1,89 @@
|
|
|
1
1
|
import {on} from '@oscarpalmer/toretto/event';
|
|
2
|
-
import
|
|
3
|
-
import type {
|
|
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
|
-
|
|
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.
|
|
11
|
+
mapped.delete(this.state.element);
|
|
12
|
+
|
|
13
|
+
this.state = undefined as never;
|
|
27
14
|
}
|
|
28
15
|
|
|
29
|
-
|
|
30
|
-
const
|
|
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 (
|
|
33
|
-
|
|
20
|
+
if (field != null) {
|
|
21
|
+
this.state.managers.sort.toggle(event, field, direction);
|
|
34
22
|
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
35
25
|
|
|
36
|
-
|
|
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
|
-
|
|
41
|
-
|
|
48
|
+
manager.onSort(event, target);
|
|
49
|
+
break;
|
|
42
50
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
51
|
+
case 'row':
|
|
52
|
+
manager.state.managers.selection.handle(event, target);
|
|
53
|
+
break;
|
|
46
54
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
55
|
+
default:
|
|
56
|
+
break;
|
|
50
57
|
}
|
|
58
|
+
}
|
|
51
59
|
|
|
52
|
-
|
|
53
|
-
|
|
60
|
+
function onKeydown(event: KeyboardEvent): void {
|
|
61
|
+
const target = findAncestor(event, '[data-event]');
|
|
62
|
+
const table = findAncestor(event, '.tabela');
|
|
54
63
|
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
if (!(target instanceof HTMLElement) || !(table instanceof HTMLElement)) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
57
67
|
|
|
58
|
-
|
|
59
|
-
|
|
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 {
|
|
7
|
-
import type {
|
|
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,
|
|
18
|
+
items: Record<string, TabelaFilterItem[]> = {};
|
|
18
19
|
|
|
19
|
-
constructor(
|
|
20
|
+
constructor(public state: State) {}
|
|
20
21
|
|
|
21
|
-
add(item:
|
|
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 {
|
|
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
|
-
|
|
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 |
|
|
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,
|
|
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:
|
|
121
|
-
const keyed: Record<string,
|
|
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<
|
|
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 {
|
|
7
|
+
import type {State} from '../models/tabela.model';
|
|
7
8
|
|
|
8
|
-
function getRange(
|
|
9
|
-
const {components, managers} =
|
|
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
|
|
13
|
+
const {keys} = managers.data;
|
|
14
|
+
|
|
15
|
+
const first = Math.floor(scrollTop / options.rowHeight);
|
|
13
16
|
|
|
14
17
|
const last = Math.min(
|
|
15
|
-
|
|
16
|
-
Math.ceil((scrollTop + clientHeight) /
|
|
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 /
|
|
20
|
-
const after = Math.ceil(clientHeight /
|
|
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
|
-
|
|
32
|
+
const {state} = this;
|
|
33
|
+
|
|
34
|
+
if (!state.active) {
|
|
34
35
|
requestAnimationFrame(() => {
|
|
35
|
-
const top =
|
|
36
|
+
const top = state.components.body.elements.group.scrollTop;
|
|
36
37
|
|
|
37
|
-
this.update(top >
|
|
38
|
+
this.update(top > state.top);
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
state.active = false;
|
|
41
|
+
state.top = top;
|
|
41
42
|
});
|
|
42
43
|
|
|
43
|
-
|
|
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
|
-
|
|
53
|
+
pool: RenderElementPool = {
|
|
53
54
|
cells: {},
|
|
54
55
|
rows: [],
|
|
55
56
|
};
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
};
|
|
58
|
+
state: RenderState;
|
|
59
|
+
|
|
60
|
+
visible = new Map<number, GroupComponent | Key>();
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
constructor(state: State) {
|
|
63
|
+
this.listener = on(state.components.body.elements.group, 'scroll', onScroll.bind(this));
|
|
63
64
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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 {
|
|
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
|
-
|
|
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 {
|
|
126
|
+
const {state, pool, visible} = this;
|
|
127
|
+
const {components, managers, options} = state;
|
|
119
128
|
|
|
120
|
-
components.body.elements.faker.style.height = `${managers.data.size
|
|
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
|
-
|
|
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
|
|
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(
|
|
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 *
|
|
212
|
+
row.element.style.transform = `translateY(${(index - offset) * options.rowHeight}px)`;
|
|
169
213
|
|
|
170
214
|
fragment.append(row.element);
|
|
171
215
|
}
|