@oscarpalmer/tabela 0.11.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 (64) hide show
  1. package/dist/components/column.component.js +4 -4
  2. package/dist/components/group.component.js +28 -0
  3. package/dist/helpers/dom.helpers.js +1 -1
  4. package/dist/managers/data.manager.js +76 -15
  5. package/dist/managers/event.manager.js +3 -0
  6. package/dist/managers/filter.manager.js +5 -0
  7. package/dist/managers/group.manager.js +46 -0
  8. package/dist/managers/navigation.manager.js +1 -1
  9. package/dist/managers/render.manager.js +35 -10
  10. package/dist/managers/selection.manager.js +32 -27
  11. package/dist/managers/sort.manager.js +29 -2
  12. package/dist/models/group.model.js +0 -0
  13. package/dist/models/selection.model.js +0 -0
  14. package/dist/tabela.full.js +364 -398
  15. package/dist/tabela.js +4 -1
  16. package/package.json +1 -1
  17. package/src/components/column.component.ts +6 -6
  18. package/src/components/group.component.ts +43 -0
  19. package/src/components/row.component.ts +2 -2
  20. package/src/helpers/dom.helpers.ts +3 -1
  21. package/src/managers/column.manager.ts +4 -4
  22. package/src/managers/data.manager.ts +155 -21
  23. package/src/managers/event.manager.ts +7 -3
  24. package/src/managers/filter.manager.ts +19 -11
  25. package/src/managers/group.manager.ts +79 -0
  26. package/src/managers/navigation.manager.ts +6 -5
  27. package/src/managers/render.manager.ts +55 -17
  28. package/src/managers/row.manager.ts +2 -2
  29. package/src/managers/selection.manager.ts +48 -41
  30. package/src/managers/sort.manager.ts +76 -13
  31. package/src/models/column.model.ts +2 -2
  32. package/src/models/data.model.ts +14 -3
  33. package/src/models/filter.model.ts +11 -3
  34. package/src/models/group.model.ts +4 -0
  35. package/src/models/render.model.ts +2 -2
  36. package/src/models/selection.model.ts +9 -0
  37. package/src/models/sort.model.ts +11 -3
  38. package/src/models/tabela.model.ts +7 -41
  39. package/src/models/tabela.options.ts +3 -2
  40. package/src/tabela.ts +11 -12
  41. package/types/components/column.component.d.ts +3 -3
  42. package/types/components/group.component.d.ts +14 -0
  43. package/types/components/row.component.d.ts +2 -2
  44. package/types/helpers/style.helper.d.ts +1 -1
  45. package/types/managers/column.manager.d.ts +5 -5
  46. package/types/managers/data.manager.d.ts +5 -3
  47. package/types/managers/event.manager.d.ts +3 -3
  48. package/types/managers/filter.manager.d.ts +11 -11
  49. package/types/managers/group.manager.d.ts +17 -0
  50. package/types/managers/navigation.manager.d.ts +3 -3
  51. package/types/managers/render.manager.d.ts +4 -3
  52. package/types/managers/row.manager.d.ts +3 -3
  53. package/types/managers/selection.manager.d.ts +12 -6
  54. package/types/managers/sort.manager.d.ts +10 -8
  55. package/types/models/column.model.d.ts +2 -2
  56. package/types/models/data.model.d.ts +13 -3
  57. package/types/models/filter.model.d.ts +10 -3
  58. package/types/models/group.model.d.ts +4 -0
  59. package/types/models/render.model.d.ts +2 -2
  60. package/types/models/selection.model.d.ts +8 -0
  61. package/types/models/sort.model.d.ts +10 -3
  62. package/types/models/tabela.model.d.ts +7 -37
  63. package/types/models/tabela.options.d.ts +3 -2
  64. package/types/tabela.d.ts +4 -1
package/dist/tabela.js CHANGED
@@ -2,14 +2,15 @@ import { BodyComponent } from "./components/body.component.js";
2
2
  import { FooterComponent } from "./components/footer.component.js";
3
3
  import { HeaderComponent } from "./components/header.component.js";
4
4
  import { ColumnManager } from "./managers/column.manager.js";
5
+ import { SortManager } from "./managers/sort.manager.js";
5
6
  import { DataManager } from "./managers/data.manager.js";
6
7
  import { EventManager } from "./managers/event.manager.js";
7
8
  import { FilterManager } from "./managers/filter.manager.js";
9
+ import { GroupManager } from "./managers/group.manager.js";
8
10
  import { NavigationManager } from "./managers/navigation.manager.js";
9
11
  import { RenderManager } from "./managers/render.manager.js";
10
12
  import { RowManager } from "./managers/row.manager.js";
11
13
  import { SelectionManager } from "./managers/selection.manager.js";
12
- import { SortManager } from "./managers/sort.manager.js";
13
14
  var Tabela = class {
14
15
  #components = {
15
16
  header: void 0,
@@ -24,6 +25,7 @@ var Tabela = class {
24
25
  data: void 0,
25
26
  event: void 0,
26
27
  filter: void 0,
28
+ group: void 0,
27
29
  navigation: void 0,
28
30
  render: void 0,
29
31
  row: void 0,
@@ -59,6 +61,7 @@ var Tabela = class {
59
61
  this.#managers.data = new DataManager(state);
60
62
  this.#managers.event = new EventManager(state);
61
63
  this.#managers.filter = new FilterManager(state);
64
+ this.#managers.group = new GroupManager(state);
62
65
  this.#managers.navigation = new NavigationManager(state);
63
66
  this.#managers.render = new RenderManager(state);
64
67
  this.#managers.row = new RowManager(state);
package/package.json CHANGED
@@ -46,5 +46,5 @@
46
46
  },
47
47
  "type": "module",
48
48
  "types": "./types/index.d.ts",
49
- "version": "0.11.0"
49
+ "version": "0.12.0"
50
50
  }
@@ -1,21 +1,21 @@
1
1
  import {createElement} from '../helpers/dom.helpers';
2
- import type {TabelaColumn, TabelaColumnOptions} from '../models/column.model';
2
+ import type {Column, TabelaColumn} from '../models/column.model';
3
3
 
4
4
  export class ColumnComponent {
5
5
  elements: ColumnElements;
6
- options: TabelaColumn;
6
+ options: Column;
7
7
 
8
- constructor(options: TabelaColumnOptions) {
8
+ constructor(column: TabelaColumn) {
9
9
  const width =
10
10
  Number.parseInt(getComputedStyle(document.body).fontSize, 10) *
11
- (options.width ?? options.title.length * 1.5);
11
+ (column.width ?? column.title.length * 1.5);
12
12
 
13
13
  this.options = {
14
- ...options,
14
+ ...column,
15
15
  width,
16
16
  };
17
17
 
18
- this.elements = createHeading(options.field, options.title, width);
18
+ this.elements = createHeading(this.options.field, this.options.title, width);
19
19
  }
20
20
 
21
21
  destroy(): void {
@@ -0,0 +1,43 @@
1
+ import {createElement} from '../helpers/dom.helpers';
2
+ import type {State} from '../models/tabela.model';
3
+
4
+ export class GroupComponent {
5
+ element: HTMLElement | undefined;
6
+
7
+ expanded = true;
8
+
9
+ filtered = 0;
10
+
11
+ selected = 0;
12
+
13
+ total = 0;
14
+
15
+ constructor(
16
+ readonly key: string,
17
+ readonly label: string,
18
+ readonly value: unknown,
19
+ ) {}
20
+ }
21
+
22
+ export function renderGroup(state: State, component: GroupComponent): void {
23
+ component.element ??= createElement(
24
+ 'div',
25
+ {
26
+ className: 'tabela__row tabela__row--group',
27
+ innerHTML: `<div class="tabela__cell tabela__cell--group" role="cell">
28
+ <button class="tabela__button tabela__button--group" data-event="group" data-key="${component.key}" type="button">
29
+ <span aria-hidden="true"></span>
30
+ <span>Open/close</span>
31
+ </button>
32
+ <p>${component.label}</p>
33
+ </div>`,
34
+ role: 'row',
35
+ },
36
+ {},
37
+ {
38
+ height: `${state.options.rowHeight}px`,
39
+ },
40
+ );
41
+ }
42
+
43
+ export function updateGroup(state: State, component: GroupComponent): void {}
@@ -2,7 +2,7 @@ import type {Key} from '@oscarpalmer/atoms/models';
2
2
  import {setAttributes} from '@oscarpalmer/toretto/attribute';
3
3
  import {createCell, createRow} from '../helpers/dom.helpers';
4
4
  import type {RenderElementPool} from '../models/render.model';
5
- import type {TabelaState} from '../models/tabela.model';
5
+ import type {State} from '../models/tabela.model';
6
6
 
7
7
  export function removeRow(pool: RenderElementPool, row: RowComponent): void {
8
8
  if (row.element != null) {
@@ -17,7 +17,7 @@ export function removeRow(pool: RenderElementPool, row: RowComponent): void {
17
17
  row.cells = {};
18
18
  }
19
19
 
20
- export function renderRow(state: TabelaState, row: RowComponent): void {
20
+ export function renderRow(state: State, row: RowComponent): void {
21
21
  const element = row.element ?? state.managers.render.pool.rows.shift() ?? createRow();
22
22
 
23
23
  row.element = element;
@@ -80,7 +80,9 @@ export function createRow(): HTMLDivElement {
80
80
  role: 'row',
81
81
  },
82
82
  {},
83
- {},
83
+ {
84
+ height: '32px',
85
+ },
84
86
  );
85
87
 
86
88
  return row;
@@ -1,11 +1,11 @@
1
1
  import {ColumnComponent} from '../components/column.component';
2
- import type {TabelaColumnOptions} from '../models/column.model';
3
- import type {TabelaState} from '../models/tabela.model';
2
+ import type {TabelaColumn} from '../models/column.model';
3
+ import type {State} from '../models/tabela.model';
4
4
 
5
5
  export class ColumnManager {
6
6
  items: ColumnComponent[] = [];
7
7
 
8
- constructor(public state: TabelaState) {
8
+ constructor(public state: State) {
9
9
  this.set(state.options.columns);
10
10
  }
11
11
 
@@ -56,7 +56,7 @@ export class ColumnManager {
56
56
  managers.render.removeCells(fields);
57
57
  }
58
58
 
59
- set(columns: TabelaColumnOptions[]): void {
59
+ set(columns: TabelaColumn[]): void {
60
60
  const {items, state} = this;
61
61
  const {footer, header} = state.components;
62
62
 
@@ -1,9 +1,12 @@
1
- import {push, sort} from '@oscarpalmer/atoms/array';
1
+ import {select, sort} from '@oscarpalmer/atoms/array';
2
2
  import {toMap} from '@oscarpalmer/atoms/array/to-map';
3
- import {isPlainObject} from '@oscarpalmer/atoms/is';
3
+ import {toRecord} from '@oscarpalmer/atoms/array/to-record';
4
4
  import type {Key, PlainObject} from '@oscarpalmer/atoms/models';
5
- import type {DataValues} from '../models/data.model';
6
- import type {TabelaData, TabelaState} from '../models/tabela.model';
5
+ import type {DataValues, TabelaData} from '../models/data.model';
6
+ import type {State} from '../models/tabela.model';
7
+ import {GroupComponent} from '../components/group.component';
8
+ import {sortWithGroups} from './sort.manager';
9
+ import {isPlainObject} from '@oscarpalmer/atoms/is';
7
10
 
8
11
  export class DataManager {
9
12
  handlers = Object.freeze({
@@ -25,20 +28,61 @@ export class DataManager {
25
28
  },
26
29
  };
27
30
 
31
+ get keys(): Array<GroupComponent | Key> {
32
+ return this.values.keys.active ?? this.values.keys.original;
33
+ }
34
+
28
35
  get size(): number {
29
- return this.values.keys.active?.length ?? this.values.keys.original.length;
36
+ return this.keys.length;
30
37
  }
31
38
 
32
- constructor(public state: TabelaState) {}
39
+ constructor(public state: State) {}
33
40
 
34
41
  async add(data: PlainObject[], render: boolean): Promise<void> {
35
42
  const {state, values} = this;
43
+ const {length} = data;
36
44
 
37
- push(values.objects.array, data);
45
+ const updates: PlainObject[] = [];
38
46
 
39
- values.objects.mapped = toMap(values.objects.array, state.key) as Map<Key, PlainObject>;
47
+ for (let index = 0; index < length; index += 1) {
48
+ const item = data[index];
49
+ const key = item[state.key] as Key;
40
50
 
41
- if (render) {
51
+ if (values.objects.mapped.has(key)) {
52
+ updates.push(item);
53
+
54
+ continue;
55
+ }
56
+
57
+ values.objects.array.push(item);
58
+ values.objects.mapped.set(key, item);
59
+
60
+ if (!state.managers.group.enabled) {
61
+ continue;
62
+ }
63
+
64
+ const groupKey = item[state.managers.group.field] as unknown;
65
+
66
+ let group = state.managers.group.get(groupKey);
67
+
68
+ if (group == null) {
69
+ group = new GroupComponent(String(groupKey), String(groupKey), groupKey);
70
+
71
+ values.objects.array.push(group);
72
+
73
+ state.managers.group.add(group);
74
+ }
75
+
76
+ if (!group.expanded) {
77
+ state.managers.group.collapsed.add(key);
78
+ }
79
+
80
+ group.total += 1;
81
+ }
82
+
83
+ if (updates.length > 0) {
84
+ void this.update(updates);
85
+ } else if (render) {
42
86
  this.render();
43
87
  }
44
88
  }
@@ -67,14 +111,16 @@ export class DataManager {
67
111
  const {values} = this;
68
112
 
69
113
  return (active ?? false)
70
- ? (values.keys.active?.map(key => values.objects.mapped.get(key) as PlainObject) ?? [])
71
- : values.objects.array;
114
+ ? select(
115
+ values.keys.active ?? [],
116
+ key => !(key instanceof GroupComponent),
117
+ key => values.objects.mapped.get(key as Key)!,
118
+ )
119
+ : (values.objects.array.filter(item => !(item instanceof GroupComponent)) as PlainObject[]);
72
120
  }
73
121
 
74
122
  getIndex(key: Key): number {
75
- const {values} = this;
76
-
77
- return (values.keys.active ?? values.keys.original).indexOf(key);
123
+ return this.keys.indexOf(key);
78
124
  }
79
125
 
80
126
  async remove(items: Array<Key | PlainObject>, render: boolean): Promise<void> {
@@ -95,15 +141,49 @@ export class DataManager {
95
141
 
96
142
  values.objects.mapped.delete(key);
97
143
 
98
- const arrayIndex = values.objects.array.findIndex(object => object[state.key] === key);
144
+ const arrayIndex = values.objects.array.findIndex(
145
+ item => !(item instanceof GroupComponent) && item[state.key] === key,
146
+ );
147
+
148
+ let item: PlainObject | undefined;
99
149
 
100
150
  if (arrayIndex > -1) {
101
- values.objects.array.splice(arrayIndex, 1);
151
+ [item] = values.objects.array.splice(arrayIndex, 1) as PlainObject[];
102
152
  }
103
153
 
104
154
  values.keys.original.splice(values.keys.original.indexOf(key), 1);
105
155
 
106
156
  state.managers.row.remove(key);
157
+
158
+ if (!state.managers.group.enabled || item == null) {
159
+ continue;
160
+ }
161
+
162
+ state.managers.group.collapsed.delete(key);
163
+
164
+ const groupKey = item[state.managers.group.field] as unknown;
165
+
166
+ const group = state.managers.group.get(groupKey);
167
+
168
+ if (group == null) {
169
+ continue;
170
+ }
171
+
172
+ group.total -= 1;
173
+
174
+ if (group.total > 0) {
175
+ continue;
176
+ }
177
+
178
+ const groupIndex = values.objects.array.findIndex(
179
+ item => item instanceof GroupComponent && item.value === groupKey,
180
+ );
181
+
182
+ if (groupIndex > -1) {
183
+ values.objects.array.splice(groupIndex, 1);
184
+ }
185
+
186
+ state.managers.group.remove(group);
107
187
  }
108
188
 
109
189
  if (render) {
@@ -114,22 +194,74 @@ export class DataManager {
114
194
  render(): void {
115
195
  const {state, values} = this;
116
196
 
117
- values.keys.original = sort(values.objects.array.map(item => item[state.key] as Key));
197
+ if (state.managers.group.enabled) {
198
+ sortWithGroups(state, values.objects.array, [
199
+ {
200
+ direction: 'ascending',
201
+ key: state.key,
202
+ },
203
+ ]);
204
+ } else {
205
+ sort(values.objects.array as PlainObject[], [
206
+ {
207
+ direction: 'ascending',
208
+ key: state.key,
209
+ },
210
+ ]);
211
+ }
212
+
213
+ values.keys.original = values.objects.array.map(item =>
214
+ item instanceof GroupComponent ? item : (item[state.key] as Key),
215
+ );
216
+
217
+ values.objects.mapped = toMap(
218
+ values.objects.array.filter(item => !(item instanceof GroupComponent)) as PlainObject[],
219
+ item => item[state.key] as Key,
220
+ );
118
221
 
119
222
  if (Object.keys(state.managers.filter.items).length > 0) {
120
223
  state.managers.filter.filter();
121
224
  } else if (state.managers.sort.items.length > 0) {
122
225
  state.managers.sort.sort();
123
226
  } else {
124
- state.managers.render.update(true);
227
+ state.managers.render.update(true, true);
125
228
  }
126
229
  }
127
230
 
128
231
  set(data: PlainObject[]): void {
129
232
  const {state, values} = this;
130
233
 
131
- values.objects.mapped = toMap(data, state.key) as Map<Key, PlainObject>;
132
- values.objects.array = data;
234
+ const array: Array<GroupComponent | PlainObject> = data.slice();
235
+
236
+ if (state.managers.group.enabled) {
237
+ const grouped = toRecord.arrays(data, state.managers.group.field) as Record<
238
+ string,
239
+ PlainObject[]
240
+ >;
241
+
242
+ const entries = Object.entries(grouped);
243
+ const {length} = entries;
244
+
245
+ const groups: GroupComponent[] = [];
246
+
247
+ for (let index = 0; index < length; index += 1) {
248
+ const [value, items] = entries[index];
249
+
250
+ const key = String(value);
251
+
252
+ const group = new GroupComponent(key, key, value);
253
+
254
+ group.total = items.length;
255
+
256
+ groups.push(group);
257
+
258
+ array.push(group);
259
+ }
260
+
261
+ state.managers.group.set(groups);
262
+ }
263
+
264
+ values.objects.array = array;
133
265
 
134
266
  this.render();
135
267
  }
@@ -162,7 +294,9 @@ export class DataManager {
162
294
  }
163
295
 
164
296
  if (remove ?? false) {
165
- const toRemove = values.keys.original.filter(key => !keys.has(key));
297
+ const toRemove = values.keys.original.filter(
298
+ key => !(key instanceof GroupComponent) && !keys.has(key),
299
+ ) as Key[];
166
300
 
167
301
  if (toRemove.length > 0) {
168
302
  await this.remove(toRemove, false);
@@ -1,9 +1,9 @@
1
1
  import {on} from '@oscarpalmer/toretto/event';
2
2
  import {findAncestor} from '@oscarpalmer/toretto/find';
3
- import type {TabelaState} from '../models/tabela.model';
3
+ import type {State} from '../models/tabela.model';
4
4
 
5
5
  export class EventManager {
6
- constructor(public state: TabelaState) {
6
+ constructor(public state: State) {
7
7
  mapped.set(state.element, this);
8
8
  }
9
9
 
@@ -40,7 +40,11 @@ function onClick(event: MouseEvent): void {
40
40
  const type = target?.getAttribute('data-event');
41
41
 
42
42
  switch (type) {
43
- case 'heading':
43
+ case 'group':
44
+ manager.state.managers.group.handle(target);
45
+ break;
46
+
47
+ case 'heading':
44
48
  manager.onSort(event, target);
45
49
  break;
46
50
 
@@ -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, TabelaState} 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(public state: TabelaState) {}
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 {
@@ -51,13 +52,20 @@ export class FilterManager {
51
52
  filter(): void {
52
53
  const {state} = this;
53
54
 
54
- const filtered: Key[] = [];
55
+ const filtered: Array<GroupComponent | Key> = [];
55
56
  const filters = Object.entries(this.items);
56
57
 
57
58
  const keysLength = state.managers.data.values.keys.original.length;
58
59
 
59
60
  rowLoop: for (let keyIndex = 0; keyIndex < keysLength; keyIndex += 1) {
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
+
61
69
  const row = state.managers.data.values.objects.mapped.get(key);
62
70
 
63
71
  if (row == null) {
@@ -92,13 +100,13 @@ export class FilterManager {
92
100
  }
93
101
  }
94
102
 
95
- remove(value: string | FilterItem): void {
103
+ remove(value: string | TabelaFilterItem): void {
96
104
  if (typeof value === 'string') {
97
105
  if (this.items[value] == null) {
98
106
  return;
99
107
  }
100
108
 
101
- const keyed: Record<string, FilterItem[]> = {};
109
+ const keyed: Record<string, TabelaFilterItem[]> = {};
102
110
 
103
111
  this.items = keyed;
104
112
  } else {
@@ -118,8 +126,8 @@ export class FilterManager {
118
126
  this.filter();
119
127
  }
120
128
 
121
- set(items: FilterItem[]): void {
122
- const keyed: Record<string, FilterItem[]> = {};
129
+ set(items: TabelaFilterItem[]): void {
130
+ const keyed: Record<string, TabelaFilterItem[]> = {};
123
131
 
124
132
  const {length} = items;
125
133
 
@@ -137,7 +145,7 @@ export class FilterManager {
137
145
  }
138
146
  }
139
147
 
140
- const comparators: Record<FilterComparison, (row: unknown, filter: unknown) => boolean> = {
148
+ const comparators: Record<TabelaFilterComparison, (row: unknown, filter: unknown) => boolean> = {
141
149
  contains: (row, filter) => includes(getString(row), getString(filter), true),
142
150
  'ends-with': (row, filter) => endsWith(getString(row), getString(filter), true),
143
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
+ }
@@ -1,13 +1,14 @@
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 type {GroupComponent} from '../components/group.component';
4
5
  import {getKey} from '../helpers/misc.helpers';
5
- import type {TabelaState} from '../models/tabela.model';
6
+ import type {State} from '../models/tabela.model';
6
7
 
7
8
  export class NavigationManager {
8
9
  active: Key | undefined;
9
10
 
10
- constructor(public state: TabelaState) {}
11
+ constructor(public state: State) {}
11
12
 
12
13
  destroy(): void {
13
14
  this.state = undefined as never;
@@ -24,7 +25,7 @@ export class NavigationManager {
24
25
 
25
26
  const activeDescendant = components.body.elements.group.getAttribute('aria-activedescendant');
26
27
 
27
- const keys = managers.data.values.keys.active ?? managers.data.values.keys.original;
28
+ const {keys} = managers.data;
28
29
  const {length} = keys;
29
30
 
30
31
  let next: number;
@@ -36,7 +37,7 @@ export class NavigationManager {
36
37
  }
37
38
 
38
39
  if (next != null) {
39
- this.setActive(keys.at(next)!);
40
+ this.setActive(keys.at(next) as Key);
40
41
  }
41
42
  }
42
43
 
@@ -97,7 +98,7 @@ function getIndex(
97
98
  event: KeyboardEvent,
98
99
  active: string,
99
100
  id: number,
100
- keys: Key[],
101
+ keys: Array<GroupComponent | Key>,
101
102
  ): number | undefined {
102
103
  const key = getKey(active.replace(`tabela_${id}_row_`, ''));
103
104