@oscarpalmer/tabela 0.12.0 → 0.14.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/tabela.full.js +146 -18
- package/package.json +45 -37
- package/src/components/body.component.ts +11 -7
- package/src/components/column.component.ts +23 -24
- package/src/components/footer.component.ts +7 -5
- package/src/components/group.component.ts +73 -9
- package/src/components/header.component.ts +6 -4
- package/src/components/row.component.ts +28 -18
- package/src/helpers/dom.helpers.ts +27 -29
- package/src/helpers/misc.helpers.ts +5 -0
- package/src/helpers/style.helper.ts +1 -1
- package/src/managers/column.manager.ts +4 -0
- package/src/managers/data.manager.ts +197 -124
- package/src/managers/event.manager.ts +27 -17
- package/src/managers/filter.manager.ts +49 -25
- package/src/managers/group.manager.ts +73 -12
- package/src/managers/navigation.manager.ts +48 -50
- package/src/managers/render.manager.ts +56 -29
- package/src/managers/row.manager.ts +22 -10
- package/src/managers/selection.manager.ts +40 -31
- package/src/managers/sort.manager.ts +58 -43
- package/src/managers/style.manager.ts +171 -0
- package/src/models/column.model.ts +2 -6
- package/src/models/data.model.ts +12 -10
- package/src/models/dom.model.ts +33 -0
- package/src/models/event.model.ts +7 -0
- package/src/models/filter.model.ts +20 -0
- package/src/models/group.model.ts +10 -2
- package/src/models/sort.model.ts +4 -0
- package/src/models/style.model.ts +51 -0
- package/src/models/tabela.model.ts +11 -8
- package/src/tabela.ts +67 -37
- package/types/components/body.component.d.ts +0 -6
- package/types/components/column.component.d.ts +0 -13
- package/types/components/footer.component.d.ts +0 -8
- package/types/components/group.component.d.ts +0 -14
- package/types/components/header.component.d.ts +0 -8
- package/types/components/row.component.d.ts +0 -11
- package/types/helpers/dom.helpers.d.ts +0 -10
- package/types/helpers/misc.helpers.d.ts +0 -2
- package/types/helpers/style.helper.d.ts +0 -1
- package/types/index.d.ts +0 -4
- package/types/managers/column.manager.d.ts +0 -12
- package/types/managers/data.manager.d.ts +0 -29
- package/types/managers/event.manager.d.ts +0 -7
- package/types/managers/filter.manager.d.ts +0 -19
- package/types/managers/group.manager.d.ts +0 -17
- package/types/managers/navigation.manager.d.ts +0 -10
- package/types/managers/render.manager.d.ts +0 -17
- package/types/managers/row.manager.d.ts +0 -13
- package/types/managers/selection.manager.d.ts +0 -24
- package/types/managers/sort.manager.d.ts +0 -28
- package/types/models/body.model.d.ts +0 -4
- package/types/models/column.model.d.ts +0 -13
- package/types/models/data.model.d.ts +0 -24
- package/types/models/filter.model.d.ts +0 -13
- package/types/models/footer.model.d.ts +0 -5
- package/types/models/group.model.d.ts +0 -4
- package/types/models/header.model.d.ts +0 -4
- package/types/models/render.model.d.ts +0 -13
- package/types/models/selection.model.d.ts +0 -8
- package/types/models/sort.model.d.ts +0 -12
- package/types/models/tabela.model.d.ts +0 -39
- package/types/models/tabela.options.d.ts +0 -10
- package/types/tabela.d.ts +0 -15
|
@@ -3,17 +3,30 @@ 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
|
|
6
|
+
import {isGroupKey} from '../helpers/misc.helpers';
|
|
7
|
+
import {
|
|
8
|
+
FILTER_CONTAINS,
|
|
9
|
+
FILTER_ENDS_WITH,
|
|
10
|
+
FILTER_EQUALS,
|
|
11
|
+
FILTER_GREATER_THAN,
|
|
12
|
+
FILTER_GREATER_THAN_OR_EQUAL,
|
|
13
|
+
FILTER_LESS_THAN,
|
|
14
|
+
FILTER_LESS_THAN_OR_EQUAL,
|
|
15
|
+
FILTER_NOT_CONTAINS,
|
|
16
|
+
FILTER_NOT_EQUALS,
|
|
17
|
+
FILTER_STARTS_WITH,
|
|
18
|
+
type TabelaFilter,
|
|
19
|
+
type TabelaFilterItem,
|
|
20
|
+
} from '../models/filter.model';
|
|
7
21
|
import type {State} from '../models/tabela.model';
|
|
8
|
-
import {GroupComponent} from '../components/group.component';
|
|
9
22
|
|
|
10
23
|
export class FilterManager {
|
|
11
|
-
handlers =
|
|
24
|
+
handlers: TabelaFilter = {
|
|
12
25
|
add: item => this.add(item),
|
|
13
26
|
clear: () => this.clear(),
|
|
14
27
|
remove: value => this.remove(value),
|
|
15
28
|
set: items => this.set(items),
|
|
16
|
-
}
|
|
29
|
+
};
|
|
17
30
|
|
|
18
31
|
items: Record<string, TabelaFilterItem[]> = {};
|
|
19
32
|
|
|
@@ -25,7 +38,7 @@ export class FilterManager {
|
|
|
25
38
|
} else {
|
|
26
39
|
const index = this.items[item.field].findIndex(existing => equal(existing, item));
|
|
27
40
|
|
|
28
|
-
if (index
|
|
41
|
+
if (index > -1) {
|
|
29
42
|
return;
|
|
30
43
|
}
|
|
31
44
|
}
|
|
@@ -52,21 +65,21 @@ export class FilterManager {
|
|
|
52
65
|
filter(): void {
|
|
53
66
|
const {state} = this;
|
|
54
67
|
|
|
55
|
-
const filtered:
|
|
68
|
+
const filtered: Key[] = [];
|
|
56
69
|
const filters = Object.entries(this.items);
|
|
57
70
|
|
|
58
|
-
const
|
|
71
|
+
const itemsLength = state.managers.data.state.keys.original.length;
|
|
59
72
|
|
|
60
|
-
rowLoop: for (let
|
|
61
|
-
const
|
|
73
|
+
rowLoop: for (let itemIndex = 0; itemIndex < itemsLength; itemIndex += 1) {
|
|
74
|
+
const item = state.managers.data.state.keys.original[itemIndex];
|
|
62
75
|
|
|
63
|
-
if (
|
|
64
|
-
filtered.push(
|
|
76
|
+
if (isGroupKey(item)) {
|
|
77
|
+
filtered.push(item);
|
|
65
78
|
|
|
66
79
|
continue;
|
|
67
80
|
}
|
|
68
81
|
|
|
69
|
-
const row = state.managers.data.values.
|
|
82
|
+
const row = state.managers.data.state.values.mapped.get(item);
|
|
70
83
|
|
|
71
84
|
if (row == null) {
|
|
72
85
|
continue;
|
|
@@ -88,10 +101,10 @@ export class FilterManager {
|
|
|
88
101
|
continue rowLoop;
|
|
89
102
|
}
|
|
90
103
|
|
|
91
|
-
filtered.push(
|
|
104
|
+
filtered.push(item);
|
|
92
105
|
}
|
|
93
106
|
|
|
94
|
-
state.managers.data.
|
|
107
|
+
state.managers.data.state.keys.active = filtered;
|
|
95
108
|
|
|
96
109
|
if (state.managers.sort.items.length > 0) {
|
|
97
110
|
state.managers.sort.sort();
|
|
@@ -108,6 +121,17 @@ export class FilterManager {
|
|
|
108
121
|
|
|
109
122
|
const keyed: Record<string, TabelaFilterItem[]> = {};
|
|
110
123
|
|
|
124
|
+
const filters = Object.keys(this.items);
|
|
125
|
+
const {length} = filters;
|
|
126
|
+
|
|
127
|
+
for (let index = 0; index < length; index += 1) {
|
|
128
|
+
const field = filters[index];
|
|
129
|
+
|
|
130
|
+
if (field !== value) {
|
|
131
|
+
keyed[field] = this.items[field];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
111
135
|
this.items = keyed;
|
|
112
136
|
} else {
|
|
113
137
|
const {field} = value;
|
|
@@ -145,17 +169,17 @@ export class FilterManager {
|
|
|
145
169
|
}
|
|
146
170
|
}
|
|
147
171
|
|
|
148
|
-
const comparators: Record<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
172
|
+
const comparators: Record<string, (row: unknown, filter: unknown) => boolean> = {
|
|
173
|
+
[FILTER_CONTAINS]: (row, filter) => includes(getString(row), getString(filter), true),
|
|
174
|
+
[FILTER_ENDS_WITH]: (row, filter) => endsWith(getString(row), getString(filter), true),
|
|
175
|
+
[FILTER_EQUALS]: (row, filter) => equalizer(row, filter),
|
|
176
|
+
[FILTER_GREATER_THAN]: (row, filter) => getNumber(row) > getNumber(filter),
|
|
177
|
+
[FILTER_GREATER_THAN_OR_EQUAL]: (row, filter) => getNumber(row) >= getNumber(filter),
|
|
178
|
+
[FILTER_LESS_THAN]: (row, filter) => getNumber(row) < getNumber(filter),
|
|
179
|
+
[FILTER_LESS_THAN_OR_EQUAL]: (row, filter) => getNumber(row) <= getNumber(filter),
|
|
180
|
+
[FILTER_NOT_CONTAINS]: (row, filter) => !includes(getString(row), getString(filter), true),
|
|
181
|
+
[FILTER_NOT_EQUALS]: (row, filter) => !equalizer(row, filter),
|
|
182
|
+
[FILTER_STARTS_WITH]: (row, filter) => startsWith(getString(row), getString(filter), true),
|
|
159
183
|
};
|
|
160
184
|
|
|
161
185
|
const equalizer = equal.initialize({
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import {sort} from '@oscarpalmer/atoms/array';
|
|
2
|
+
import {toMap} from '@oscarpalmer/atoms/array/to-map';
|
|
2
3
|
import {toRecord} from '@oscarpalmer/atoms/array/to-record';
|
|
3
4
|
import {isNullableOrWhitespace} from '@oscarpalmer/atoms/is';
|
|
4
5
|
import type {Key, Simplify} from '@oscarpalmer/atoms/models';
|
|
5
|
-
import
|
|
6
|
+
import {getString} from '@oscarpalmer/atoms/string';
|
|
7
|
+
import {removeGroup, type GroupComponent} from '../components/group.component';
|
|
8
|
+
import type {TabelaGroup} from '../models/group.model';
|
|
6
9
|
import type {State} from '../models/tabela.model';
|
|
7
10
|
|
|
8
11
|
export class GroupManager {
|
|
@@ -12,11 +15,26 @@ export class GroupManager {
|
|
|
12
15
|
|
|
13
16
|
field!: string;
|
|
14
17
|
|
|
18
|
+
handlers: TabelaGroup = {
|
|
19
|
+
set: (group?: string) => {
|
|
20
|
+
if (group === this.field) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
this.enabled = !isNullableOrWhitespace(group);
|
|
25
|
+
this.field = group ?? '';
|
|
26
|
+
|
|
27
|
+
this.state.managers.data.set(this.state.managers.data.get());
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
15
31
|
items: GroupComponent[] = [];
|
|
16
32
|
|
|
33
|
+
mapped = new Map<string, GroupComponent>();
|
|
34
|
+
|
|
17
35
|
order: Record<never, number> = {};
|
|
18
36
|
|
|
19
|
-
constructor(
|
|
37
|
+
constructor(public state: State) {
|
|
20
38
|
if (isNullableOrWhitespace(state.options.grouping)) {
|
|
21
39
|
return;
|
|
22
40
|
}
|
|
@@ -29,13 +47,42 @@ export class GroupManager {
|
|
|
29
47
|
this.set([...this.items, group]);
|
|
30
48
|
}
|
|
31
49
|
|
|
32
|
-
|
|
33
|
-
|
|
50
|
+
clear(): void {
|
|
51
|
+
const groups = this.items.splice(0);
|
|
52
|
+
const {length} = groups;
|
|
53
|
+
|
|
54
|
+
for (let index = 0; index < length; index += 1) {
|
|
55
|
+
this.remove(groups[index]);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
destroy(): void {
|
|
60
|
+
const groups = this.items.splice(0);
|
|
61
|
+
const {length} = groups;
|
|
62
|
+
|
|
63
|
+
for (let index = 0; index < length; index += 1) {
|
|
64
|
+
removeGroup(groups[index]);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
this.collapsed.clear();
|
|
68
|
+
|
|
69
|
+
this.handlers = undefined as never;
|
|
70
|
+
this.state = undefined as never;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
getForKey(key: string): GroupComponent | undefined {
|
|
74
|
+
return this.mapped.get(key);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
getForValue(value: unknown): GroupComponent | undefined {
|
|
78
|
+
const asString = getString(value);
|
|
79
|
+
|
|
80
|
+
return this.items.find(item => item.value.stringified === asString);
|
|
34
81
|
}
|
|
35
82
|
|
|
36
83
|
handle(button: HTMLElement): void {
|
|
37
|
-
const key = button.dataset.key;
|
|
38
|
-
const group = this.
|
|
84
|
+
const key = button.dataset.key?.replace(`${this.state.prefix}_`, '');
|
|
85
|
+
const group = this.getForKey(key ?? '');
|
|
39
86
|
|
|
40
87
|
if (group == null) {
|
|
41
88
|
return;
|
|
@@ -47,15 +94,15 @@ export class GroupManager {
|
|
|
47
94
|
|
|
48
95
|
const index = items.indexOf(group);
|
|
49
96
|
|
|
50
|
-
let first = state.managers.data.
|
|
97
|
+
let first = state.managers.data.state.keys.original.indexOf(group.key) + 1;
|
|
51
98
|
|
|
52
99
|
const last =
|
|
53
100
|
items[index + 1] == null
|
|
54
|
-
? state.managers.data.keys.length - 1
|
|
55
|
-
: state.managers.data.
|
|
101
|
+
? state.managers.data.state.keys.original.length - 1
|
|
102
|
+
: state.managers.data.state.keys.original.indexOf(items[index + 1].key) - 1;
|
|
56
103
|
|
|
57
104
|
for (; first <= last; first += 1) {
|
|
58
|
-
const key = state.managers.data.
|
|
105
|
+
const key = state.managers.data.state.keys.original[first] as Key;
|
|
59
106
|
|
|
60
107
|
if (group.expanded) {
|
|
61
108
|
collapsed.delete(key);
|
|
@@ -64,16 +111,30 @@ export class GroupManager {
|
|
|
64
111
|
}
|
|
65
112
|
}
|
|
66
113
|
|
|
67
|
-
state.managers.
|
|
114
|
+
if (Object.keys(state.managers.filter.items).length > 0) {
|
|
115
|
+
state.managers.filter.filter();
|
|
116
|
+
} else if (state.managers.sort.items.length > 0) {
|
|
117
|
+
state.managers.sort.sort();
|
|
118
|
+
} else {
|
|
119
|
+
state.managers.render.update(true, true);
|
|
120
|
+
}
|
|
68
121
|
}
|
|
69
122
|
|
|
70
123
|
remove(group: GroupComponent): void {
|
|
124
|
+
removeGroup(group);
|
|
125
|
+
|
|
71
126
|
this.set(this.items.filter(item => item !== group));
|
|
72
127
|
}
|
|
73
128
|
|
|
74
129
|
set(items: GroupComponent[]) {
|
|
75
130
|
this.items = sort(items, item => item.label);
|
|
76
131
|
|
|
77
|
-
this.
|
|
132
|
+
this.mapped = toMap(items, group => group.key);
|
|
133
|
+
|
|
134
|
+
this.order = toRecord(
|
|
135
|
+
items as Simplify<GroupComponent>[],
|
|
136
|
+
group => group.value.stringified,
|
|
137
|
+
(_, index) => index,
|
|
138
|
+
);
|
|
78
139
|
}
|
|
79
140
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {isNullableOrWhitespace} from '@oscarpalmer/atoms/is';
|
|
2
2
|
import type {Key} from '@oscarpalmer/atoms/models';
|
|
3
3
|
import {clamp} from '@oscarpalmer/atoms/number';
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
4
|
+
import {getKey, isGroupKey} from '../helpers/misc.helpers';
|
|
5
|
+
import {ARIA_ACTIVEDESCENDANT, ATTRIBUTE_DATA_ACTIVE} from '../models/dom.model';
|
|
6
6
|
import type {State} from '../models/tabela.model';
|
|
7
7
|
|
|
8
8
|
export class NavigationManager {
|
|
@@ -21,9 +21,9 @@ export class NavigationManager {
|
|
|
21
21
|
|
|
22
22
|
event.preventDefault();
|
|
23
23
|
|
|
24
|
-
const {components,
|
|
24
|
+
const {components, managers} = this.state;
|
|
25
25
|
|
|
26
|
-
const activeDescendant = components.body.elements.group.getAttribute(
|
|
26
|
+
const activeDescendant = components.body.elements.group.getAttribute(ARIA_ACTIVEDESCENDANT);
|
|
27
27
|
|
|
28
28
|
const {keys} = managers.data;
|
|
29
29
|
const {length} = keys;
|
|
@@ -33,38 +33,40 @@ export class NavigationManager {
|
|
|
33
33
|
if (isNullableOrWhitespace(activeDescendant)) {
|
|
34
34
|
next = getDefaultIndex(event.key, length);
|
|
35
35
|
} else {
|
|
36
|
-
next = getIndex(event, activeDescendant
|
|
36
|
+
next = getIndex(this.state, event, activeDescendant)!;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
if (next != null) {
|
|
40
|
-
this.setActive(keys.at(next)
|
|
40
|
+
this.setActive(keys.at(next));
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
setActive(
|
|
45
|
-
const {components, managers, options} = this.state;
|
|
44
|
+
setActive(item: Key | undefined, scroll?: boolean): void {
|
|
45
|
+
const {components, managers, options, prefix} = this.state;
|
|
46
46
|
|
|
47
|
-
this.active =
|
|
47
|
+
this.active = item;
|
|
48
48
|
|
|
49
|
-
const active = components.body.elements.group.querySelectorAll(
|
|
49
|
+
const active = components.body.elements.group.querySelectorAll(attributeDataActiveTrue);
|
|
50
50
|
|
|
51
51
|
for (const item of active) {
|
|
52
|
-
item.setAttribute(
|
|
52
|
+
item.setAttribute(ATTRIBUTE_DATA_ACTIVE, 'false');
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
const
|
|
55
|
+
const component = isGroupKey(item)
|
|
56
|
+
? managers.group.getForKey(item as string)
|
|
57
|
+
: managers.row.get(item!, false);
|
|
56
58
|
|
|
57
|
-
if (
|
|
58
|
-
|
|
59
|
+
if (component != null) {
|
|
60
|
+
component.element?.setAttribute(ATTRIBUTE_DATA_ACTIVE, 'true');
|
|
59
61
|
|
|
60
62
|
if (scroll ?? true) {
|
|
61
|
-
if (
|
|
63
|
+
if (component.element == null) {
|
|
62
64
|
components.body.elements.group.scrollTo({
|
|
63
|
-
top: managers.data.getIndex(
|
|
65
|
+
top: managers.data.getIndex(item!) * options.rowHeight,
|
|
64
66
|
behavior: 'smooth',
|
|
65
67
|
});
|
|
66
68
|
} else {
|
|
67
|
-
|
|
69
|
+
component.element.scrollIntoView({
|
|
68
70
|
block: 'nearest',
|
|
69
71
|
});
|
|
70
72
|
}
|
|
@@ -72,8 +74,8 @@ export class NavigationManager {
|
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
components.body.elements.group.setAttribute(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
ARIA_ACTIVEDESCENDANT,
|
|
78
|
+
component == null ? '' : `${prefix}${component.key}`,
|
|
77
79
|
);
|
|
78
80
|
}
|
|
79
81
|
}
|
|
@@ -83,10 +85,10 @@ function getDefaultIndex(key: string, max: number): number {
|
|
|
83
85
|
case negativeDefaultKeys.has(key):
|
|
84
86
|
return -1;
|
|
85
87
|
|
|
86
|
-
case key ===
|
|
88
|
+
case key === KEY_PAGE_DOWN:
|
|
87
89
|
return Math.min(9, max - 1);
|
|
88
90
|
|
|
89
|
-
case key ===
|
|
91
|
+
case key === KEY_PAGE_UP:
|
|
90
92
|
return max < 10 ? 0 : max - 10;
|
|
91
93
|
|
|
92
94
|
default:
|
|
@@ -94,53 +96,49 @@ function getDefaultIndex(key: string, max: number): number {
|
|
|
94
96
|
}
|
|
95
97
|
}
|
|
96
98
|
|
|
97
|
-
function getIndex(
|
|
98
|
-
|
|
99
|
-
active: string,
|
|
100
|
-
id: number,
|
|
101
|
-
keys: Array<GroupComponent | Key>,
|
|
102
|
-
): number | undefined {
|
|
103
|
-
const key = getKey(active.replace(`tabela_${id}_row_`, ''));
|
|
99
|
+
function getIndex(state: State, event: KeyboardEvent, active: string): number | undefined {
|
|
100
|
+
const key = getKey(active.replace(state.prefix, ''));
|
|
104
101
|
|
|
105
102
|
if (key == null) {
|
|
106
103
|
return;
|
|
107
104
|
}
|
|
108
105
|
|
|
109
106
|
if (absoluteKeys.has(event.key)) {
|
|
110
|
-
return event.key ===
|
|
107
|
+
return event.key === KEY_HOME ? 0 : state.managers.data.size - 1;
|
|
111
108
|
}
|
|
112
109
|
|
|
113
|
-
const index =
|
|
114
|
-
const offset = getOffset(event.key);
|
|
110
|
+
const index = state.managers.data.getIndex(key);
|
|
115
111
|
|
|
116
|
-
return clamp(index + offset, 0,
|
|
112
|
+
return clamp(index + (offset[event.key] ?? 0), 0, state.managers.data.size - 1, true);
|
|
117
113
|
}
|
|
118
114
|
|
|
119
|
-
|
|
120
|
-
switch (key) {
|
|
121
|
-
case 'ArrowDown':
|
|
122
|
-
return 1;
|
|
115
|
+
const KEY_ARROW_DOWN = 'ArrowDown';
|
|
123
116
|
|
|
124
|
-
|
|
125
|
-
return -1;
|
|
117
|
+
const KEY_ARROW_UP = 'ArrowUp';
|
|
126
118
|
|
|
127
|
-
|
|
128
|
-
return 10;
|
|
119
|
+
const KEY_END = 'End';
|
|
129
120
|
|
|
130
|
-
|
|
131
|
-
return -10;
|
|
121
|
+
const KEY_HOME = 'Home';
|
|
132
122
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
123
|
+
const KEY_PAGE_DOWN = 'PageDown';
|
|
124
|
+
|
|
125
|
+
const KEY_PAGE_UP = 'PageUp';
|
|
126
|
+
|
|
127
|
+
const absoluteKeys = new Set([KEY_END, KEY_HOME]);
|
|
128
|
+
|
|
129
|
+
const arrowKeys = new Set([KEY_ARROW_DOWN, KEY_ARROW_UP]);
|
|
137
130
|
|
|
138
|
-
const
|
|
131
|
+
export const attributeDataActiveTrue = `[${ATTRIBUTE_DATA_ACTIVE}="true"]`;
|
|
139
132
|
|
|
140
|
-
const
|
|
133
|
+
const negativeDefaultKeys = new Set([KEY_ARROW_UP, KEY_END]);
|
|
141
134
|
|
|
142
|
-
const
|
|
135
|
+
const offset: Record<string, number> = {
|
|
136
|
+
[KEY_ARROW_DOWN]: 1,
|
|
137
|
+
[KEY_ARROW_UP]: -1,
|
|
138
|
+
[KEY_PAGE_DOWN]: 10,
|
|
139
|
+
[KEY_PAGE_UP]: -10,
|
|
140
|
+
};
|
|
143
141
|
|
|
144
|
-
const pageKeys = new Set([
|
|
142
|
+
const pageKeys = new Set([KEY_PAGE_DOWN, KEY_PAGE_UP]);
|
|
145
143
|
|
|
146
144
|
const allKeys = new Set([...absoluteKeys, ...arrowKeys, ...pageKeys]);
|
|
@@ -1,29 +1,30 @@
|
|
|
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 {
|
|
4
|
+
import {renderGroup} from '../components/group.component';
|
|
5
5
|
import {removeRow, renderRow} from '../components/row.component';
|
|
6
|
+
import {isGroupKey} from '../helpers/misc.helpers';
|
|
6
7
|
import type {RenderElementPool, RenderRange, RenderState} from '../models/render.model';
|
|
7
8
|
import type {State} from '../models/tabela.model';
|
|
8
9
|
|
|
9
10
|
function getRange(state: State, down: boolean): RenderRange {
|
|
10
|
-
const {
|
|
11
|
-
const {clientHeight, scrollTop} =
|
|
11
|
+
const {element, managers, options} = state;
|
|
12
|
+
const {clientHeight, scrollTop} = element;
|
|
12
13
|
|
|
13
14
|
const {keys} = managers.data;
|
|
14
15
|
|
|
15
|
-
const
|
|
16
|
+
const firstIndex = Math.floor(scrollTop / options.rowHeight);
|
|
17
|
+
const lastIndex = keys.length - managers.group.collapsed.size - 1;
|
|
16
18
|
|
|
17
|
-
const last = Math.min(
|
|
18
|
-
keys.length - managers.group.collapsed.size - 1,
|
|
19
|
-
Math.ceil((scrollTop + clientHeight) / options.rowHeight) - 1,
|
|
20
|
-
);
|
|
19
|
+
const last = Math.min(lastIndex, Math.ceil((scrollTop + clientHeight) / options.rowHeight) - 1);
|
|
21
20
|
|
|
22
|
-
const
|
|
23
|
-
const after = Math.ceil(clientHeight / options.rowHeight) * (down ? 2 : 1);
|
|
21
|
+
const visible = clientHeight / options.rowHeight;
|
|
24
22
|
|
|
25
|
-
const
|
|
26
|
-
const
|
|
23
|
+
const before = Math.ceil(visible) * (down ? 1 : 2);
|
|
24
|
+
const after = Math.ceil(visible) * (down ? 2 : 1);
|
|
25
|
+
|
|
26
|
+
const start = Math.max(0, firstIndex - before);
|
|
27
|
+
const end = Math.min(lastIndex, last + after);
|
|
27
28
|
|
|
28
29
|
return {end, start};
|
|
29
30
|
}
|
|
@@ -33,7 +34,7 @@ function onScroll(this: RenderManager): void {
|
|
|
33
34
|
|
|
34
35
|
if (!state.active) {
|
|
35
36
|
requestAnimationFrame(() => {
|
|
36
|
-
const top = state.
|
|
37
|
+
const top = state.element.scrollTop;
|
|
37
38
|
|
|
38
39
|
this.update(top > state.top);
|
|
39
40
|
|
|
@@ -57,10 +58,10 @@ export class RenderManager {
|
|
|
57
58
|
|
|
58
59
|
state: RenderState;
|
|
59
60
|
|
|
60
|
-
visible = new Map<number,
|
|
61
|
+
visible = new Map<number, Key>();
|
|
61
62
|
|
|
62
63
|
constructor(state: State) {
|
|
63
|
-
this.listener = on(state.
|
|
64
|
+
this.listener = on(state.element, 'scroll', onScroll.bind(this));
|
|
64
65
|
|
|
65
66
|
this.state = {
|
|
66
67
|
...state,
|
|
@@ -75,6 +76,20 @@ export class RenderManager {
|
|
|
75
76
|
listener();
|
|
76
77
|
visible.clear();
|
|
77
78
|
|
|
79
|
+
const cells = Object.values(pool.cells).flat();
|
|
80
|
+
|
|
81
|
+
let {length} = cells;
|
|
82
|
+
|
|
83
|
+
for (let index = 0; index < length; index += 1) {
|
|
84
|
+
cells[index].remove();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
length = pool.rows.length;
|
|
88
|
+
|
|
89
|
+
for (let index = 0; index < length; index += 1) {
|
|
90
|
+
pool.rows[index].remove();
|
|
91
|
+
}
|
|
92
|
+
|
|
78
93
|
pool.cells = {};
|
|
79
94
|
pool.rows = [];
|
|
80
95
|
|
|
@@ -94,11 +109,11 @@ export class RenderManager {
|
|
|
94
109
|
}
|
|
95
110
|
|
|
96
111
|
for (const [, key] of visible) {
|
|
97
|
-
if (key
|
|
112
|
+
if (isGroupKey(key)) {
|
|
98
113
|
continue;
|
|
99
114
|
}
|
|
100
115
|
|
|
101
|
-
const row = state.managers.row.get(key);
|
|
116
|
+
const row = state.managers.row.get(key, false);
|
|
102
117
|
|
|
103
118
|
if (row == null || row.element == null) {
|
|
104
119
|
continue;
|
|
@@ -139,17 +154,17 @@ export class RenderManager {
|
|
|
139
154
|
let remove = rerender ?? false;
|
|
140
155
|
|
|
141
156
|
for (const [index, key] of visible) {
|
|
142
|
-
if (key
|
|
157
|
+
if (isGroupKey(key)) {
|
|
143
158
|
if (remove || !indices.has(index)) {
|
|
144
159
|
visible.delete(index);
|
|
145
160
|
|
|
146
|
-
key
|
|
161
|
+
state.managers.group.getForKey(key as string)?.element?.remove();
|
|
147
162
|
}
|
|
148
163
|
|
|
149
164
|
continue;
|
|
150
165
|
}
|
|
151
166
|
|
|
152
|
-
const row = managers.row.get(key);
|
|
167
|
+
const row = managers.row.get(key, false);
|
|
153
168
|
|
|
154
169
|
if (remove || row == null || !indices.has(index) || managers.group.collapsed.has(key)) {
|
|
155
170
|
visible.delete(index);
|
|
@@ -174,23 +189,29 @@ export class RenderManager {
|
|
|
174
189
|
|
|
175
190
|
const key = keys[index];
|
|
176
191
|
|
|
177
|
-
if (key
|
|
192
|
+
if (isGroupKey(key)) {
|
|
193
|
+
const group = managers.group.getForKey(key as string);
|
|
194
|
+
|
|
195
|
+
if (group == null) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
|
|
178
199
|
count += 1;
|
|
179
200
|
|
|
180
|
-
renderGroup(state,
|
|
201
|
+
renderGroup(state, group);
|
|
181
202
|
|
|
182
|
-
visible.set(index, key);
|
|
203
|
+
visible.set(index, group.key);
|
|
183
204
|
|
|
184
|
-
if (
|
|
185
|
-
|
|
205
|
+
if (group.element != null) {
|
|
206
|
+
group.element.style.transform = `translateY(${(index - offset) * options.rowHeight}px)`;
|
|
186
207
|
|
|
187
|
-
fragment.append(
|
|
208
|
+
fragment.append(group.element);
|
|
188
209
|
}
|
|
189
210
|
|
|
190
211
|
continue;
|
|
191
212
|
}
|
|
192
213
|
|
|
193
|
-
const row = managers.row.get(key);
|
|
214
|
+
const row = managers.row.get(key, true);
|
|
194
215
|
|
|
195
216
|
if (row == null) {
|
|
196
217
|
continue;
|
|
@@ -215,8 +236,14 @@ export class RenderManager {
|
|
|
215
236
|
}
|
|
216
237
|
}
|
|
217
238
|
|
|
218
|
-
if (count
|
|
219
|
-
|
|
239
|
+
if (count === 0) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (down) {
|
|
244
|
+
components.body.elements.group.append(fragment);
|
|
245
|
+
} else {
|
|
246
|
+
components.body.elements.group.prepend(fragment);
|
|
220
247
|
}
|
|
221
248
|
}
|
|
222
249
|
}
|
|
@@ -7,24 +7,30 @@ export class RowManager {
|
|
|
7
7
|
|
|
8
8
|
constructor(public state: State) {}
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
const components =
|
|
12
|
-
|
|
10
|
+
clear(): void {
|
|
11
|
+
const {components} = this;
|
|
12
|
+
|
|
13
|
+
const rows = [...components.values()];
|
|
14
|
+
const {length} = rows;
|
|
13
15
|
|
|
14
16
|
for (let index = 0; index < length; index += 1) {
|
|
15
|
-
removeRow(
|
|
17
|
+
this.removeRow(rows[index]);
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
components.clear();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
destroy(): void {
|
|
24
|
+
this.clear();
|
|
19
25
|
|
|
20
26
|
this.components = undefined as never;
|
|
21
27
|
this.state = undefined as never;
|
|
22
28
|
}
|
|
23
29
|
|
|
24
|
-
get(key: Key): RowComponent | undefined {
|
|
30
|
+
get(key: Key, create: boolean): RowComponent | undefined {
|
|
25
31
|
let row = this.components.get(key);
|
|
26
32
|
|
|
27
|
-
if (row == null) {
|
|
33
|
+
if (row == null && create) {
|
|
28
34
|
row = new RowComponent(key);
|
|
29
35
|
|
|
30
36
|
this.components.set(key, row);
|
|
@@ -41,16 +47,22 @@ export class RowManager {
|
|
|
41
47
|
const row = this.components.get(key);
|
|
42
48
|
|
|
43
49
|
if (row != null) {
|
|
44
|
-
removeRow(
|
|
50
|
+
this.removeRow(row);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
45
53
|
|
|
46
|
-
|
|
54
|
+
removeRow(row: RowComponent): void {
|
|
55
|
+
if (row.element != null) {
|
|
56
|
+
removeRow(this.state.managers.render.pool, row);
|
|
47
57
|
}
|
|
58
|
+
|
|
59
|
+
this.components.delete(row.key);
|
|
48
60
|
}
|
|
49
61
|
|
|
50
62
|
update(key: Key): void {
|
|
51
63
|
const row = this.components.get(key);
|
|
52
64
|
|
|
53
|
-
if (row != null) {
|
|
65
|
+
if (row?.element != null) {
|
|
54
66
|
renderRow(this.state, row);
|
|
55
67
|
}
|
|
56
68
|
}
|