@oscarpalmer/tabela 0.13.0 → 0.15.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 (108) hide show
  1. package/dist/components/body.component.d.mts +10 -1
  2. package/dist/components/body.component.mjs +8 -6
  3. package/dist/components/column.component.d.mts +15 -1
  4. package/dist/components/column.component.mjs +12 -10
  5. package/dist/components/footer.component.d.mts +12 -1
  6. package/dist/components/footer.component.mjs +6 -6
  7. package/dist/components/group.component.d.mts +19 -1
  8. package/dist/components/group.component.mjs +17 -11
  9. package/dist/components/header.component.d.mts +12 -1
  10. package/dist/components/header.component.mjs +5 -5
  11. package/dist/components/row.component.d.mts +14 -1
  12. package/dist/components/row.component.mjs +21 -17
  13. package/dist/helpers/dom.helpers.d.mts +3 -3
  14. package/dist/helpers/dom.helpers.mjs +12 -11
  15. package/dist/helpers/misc.helpers.d.mts +7 -1
  16. package/dist/helpers/misc.helpers.mjs +12 -1
  17. package/dist/index.d.mts +1 -1
  18. package/dist/managers/column.manager.d.mts +16 -1
  19. package/dist/managers/column.manager.mjs +7 -7
  20. package/dist/managers/data.manager.d.mts +26 -1
  21. package/dist/managers/data.manager.mjs +122 -88
  22. package/dist/managers/event.manager.d.mts +17 -1
  23. package/dist/managers/event.manager.mjs +35 -10
  24. package/dist/managers/filter.manager.d.mts +17 -1
  25. package/dist/managers/filter.manager.mjs +43 -35
  26. package/dist/managers/group.manager.d.mts +26 -1
  27. package/dist/managers/group.manager.mjs +36 -16
  28. package/dist/managers/navigation.manager.d.mts +15 -2
  29. package/dist/managers/navigation.manager.mjs +38 -34
  30. package/dist/managers/render.manager.d.mts +18 -1
  31. package/dist/managers/render.manager.mjs +44 -31
  32. package/dist/managers/row.manager.d.mts +18 -1
  33. package/dist/managers/row.manager.mjs +1 -1
  34. package/dist/managers/selection.manager.d.mts +22 -1
  35. package/dist/managers/selection.manager.mjs +26 -23
  36. package/dist/managers/sort.manager.d.mts +22 -1
  37. package/dist/managers/sort.manager.mjs +71 -49
  38. package/dist/managers/style.manager.d.mts +8 -1
  39. package/dist/managers/style.manager.mjs +57 -25
  40. package/dist/models/body.model.d.mts +6 -1
  41. package/dist/models/column.model.d.mts +13 -2
  42. package/dist/models/data.model.d.mts +28 -2
  43. package/dist/models/dom.model.d.mts +19 -0
  44. package/dist/models/dom.model.mjs +19 -0
  45. package/dist/models/event.model.d.mts +99 -0
  46. package/dist/models/event.model.mjs +53 -0
  47. package/dist/models/filter.model.d.mts +26 -2
  48. package/dist/models/filter.model.mjs +13 -1
  49. package/dist/models/footer.model.d.mts +7 -1
  50. package/dist/models/group.model.d.mts +19 -2
  51. package/dist/models/group.model.mjs +5 -1
  52. package/dist/models/header.model.d.mts +6 -1
  53. package/dist/models/render.model.d.mts +22 -2
  54. package/dist/models/selection.model.d.mts +11 -1
  55. package/dist/models/sort.model.d.mts +19 -2
  56. package/dist/models/sort.model.mjs +5 -1
  57. package/dist/models/style.model.d.mts +27 -21
  58. package/dist/models/style.model.mjs +27 -21
  59. package/dist/models/tabela.model.d.mts +45 -1
  60. package/dist/models/tabela.options.d.mts +13 -1
  61. package/dist/tabela.d.mts +9 -8
  62. package/dist/tabela.full.mjs +1083 -574
  63. package/dist/tabela.mjs +19 -19
  64. package/package.json +2 -4
  65. package/src/components/body.component.ts +10 -7
  66. package/src/components/column.component.ts +20 -16
  67. package/src/components/footer.component.ts +7 -10
  68. package/src/components/group.component.ts +39 -13
  69. package/src/components/header.component.ts +6 -5
  70. package/src/components/row.component.ts +27 -19
  71. package/src/helpers/dom.helpers.ts +18 -22
  72. package/src/helpers/misc.helpers.ts +17 -0
  73. package/src/managers/column.manager.ts +9 -9
  74. package/src/managers/data.manager.ts +196 -107
  75. package/src/managers/event.manager.ts +69 -12
  76. package/src/managers/filter.manager.ts +70 -41
  77. package/src/managers/group.manager.ts +60 -18
  78. package/src/managers/navigation.manager.ts +46 -49
  79. package/src/managers/render.manager.ts +60 -34
  80. package/src/managers/row.manager.ts +1 -1
  81. package/src/managers/selection.manager.ts +37 -35
  82. package/src/managers/sort.manager.ts +114 -72
  83. package/src/managers/style.manager.ts +73 -25
  84. package/src/models/column.model.ts +4 -8
  85. package/src/models/data.model.ts +13 -14
  86. package/src/models/dom.model.ts +31 -0
  87. package/src/models/event.model.ts +175 -0
  88. package/src/models/filter.model.ts +22 -2
  89. package/src/models/group.model.ts +13 -0
  90. package/src/models/render.model.ts +6 -0
  91. package/src/models/sort.model.ts +11 -5
  92. package/src/models/style.model.ts +32 -20
  93. package/src/models/tabela.model.ts +1 -0
  94. package/src/tabela.ts +26 -24
  95. package/dist/body.component-_VDOpJhV.d.mts +0 -10
  96. package/dist/body.model-2iwsovAV.d.mts +0 -7
  97. package/dist/column.component-Bx46r3JI.d.mts +0 -16
  98. package/dist/column.model-D-aw4EU4.d.mts +0 -16
  99. package/dist/filter.model-7ukJrtil.d.mts +0 -16
  100. package/dist/footer.component-Curiab8j.d.mts +0 -12
  101. package/dist/footer.model-DhqoS6ds.d.mts +0 -8
  102. package/dist/group.component-Cq1YYbfJ.d.mts +0 -285
  103. package/dist/group.model-BsKFwHbt.d.mts +0 -10
  104. package/dist/header.component-BjjlpZIg.d.mts +0 -12
  105. package/dist/header.model-DN_KzUCV.d.mts +0 -7
  106. package/dist/selection.model-rwQe9fco.d.mts +0 -12
  107. package/dist/sort.model-CauImaLu.d.mts +0 -15
  108. package/dist/tabela.options-RkZvfptB.d.mts +0 -14
@@ -5,36 +5,48 @@ import {isPlainObject} from '@oscarpalmer/atoms/is';
5
5
  import type {Key, PlainObject} from '@oscarpalmer/atoms/models';
6
6
  import {delay} from '@oscarpalmer/atoms/promise/delay';
7
7
  import {getValue} from '@oscarpalmer/atoms/value/handle';
8
+ import type {ColumnComponent} from '../components/column.component';
8
9
  import {GroupComponent, updateGroup} from '../components/group.component';
9
- import type {DataItem, DataState, TabelaData} from '../models/data.model';
10
+ import {getGroup, isGroupKey} from '../helpers/misc.helpers';
11
+ import type {DataState, DataValue, TabelaData} from '../models/data.model';
12
+ import {
13
+ EVENT_DATA_ADD,
14
+ EVENT_DATA_CLEAR,
15
+ EVENT_DATA_REMOVE,
16
+ EVENT_DATA_SYNCHRONIZE,
17
+ EVENT_DATA_UPDATE,
18
+ EVENT_GROUP_ADD,
19
+ EVENT_GROUP_REMOVE,
20
+ EVENT_GROUP_UPDATE,
21
+ } from '../models/event.model';
22
+ import {SORT_ASCENDING} from '../models/sort.model';
10
23
  import type {State} from '../models/tabela.model';
11
24
  import {sortWithGroups} from './sort.manager';
12
- import type {ColumnComponent} from '../components/column.component';
13
25
 
14
26
  export class DataManager {
15
- handlers = Object.freeze({
16
- add: data => void this.add(data, true),
17
- clear: () => void this.clear(),
27
+ handlers: TabelaData = {
28
+ add: data => this.add(data, true),
29
+ clear: () => this.clear(),
18
30
  get: active => this.get(active),
19
- remove: items => void this.remove(items, true),
20
- synchronize: (data, remove) => void this.synchronize(data, remove),
21
- update: data => void this.update(data),
22
- } satisfies TabelaData);
31
+ remove: items => this.remove(items, true),
32
+ synchronize: (data, remove) => this.synchronize(data, remove === true),
33
+ update: data => this.update(data, true),
34
+ };
23
35
 
24
36
  state: DataState;
25
37
 
26
- get items(): DataItem[] {
27
- return this.state.items.active ?? this.state.items.original;
38
+ get keys(): Key[] {
39
+ return this.state.keys.active ?? this.state.keys.original;
28
40
  }
29
41
 
30
42
  get size(): number {
31
- return this.items.length;
43
+ return this.keys.length;
32
44
  }
33
45
 
34
46
  constructor(state: State) {
35
47
  this.state = {
36
48
  ...state,
37
- items: {
49
+ keys: {
38
50
  original: [],
39
51
  },
40
52
  values: {
@@ -47,8 +59,11 @@ export class DataManager {
47
59
  async add(data: PlainObject[], render: boolean): Promise<void> {
48
60
  const {state} = this;
49
61
 
50
- const groups: GroupComponent[] = [];
51
- const updates: PlainObject[] = [];
62
+ const addedData: PlainObject[] = [];
63
+ const updatedData: PlainObject[] = [];
64
+
65
+ const addedGroups: GroupComponent[] = [];
66
+ const updatedGroups: GroupComponent[] = [];
52
67
 
53
68
  let groupColumn: ColumnComponent | undefined;
54
69
  let {length} = data;
@@ -58,11 +73,13 @@ export class DataManager {
58
73
  const key = getValue(item, state.key) as Key;
59
74
 
60
75
  if (state.values.mapped.has(key)) {
61
- updates.push(item);
76
+ updatedData.push(item);
62
77
 
63
78
  continue;
64
79
  }
65
80
 
81
+ addedData.push(item);
82
+
66
83
  state.values.array.push(item);
67
84
  state.values.mapped.set(key, item);
68
85
 
@@ -70,20 +87,24 @@ export class DataManager {
70
87
  continue;
71
88
  }
72
89
 
73
- const groupValue = getValue(item, state.managers.group.field) as unknown;
90
+ const groupValue = getValue(item, state.managers.group.key) as Key;
74
91
 
75
- let group = state.managers.group.get(groupValue);
92
+ let group = state.managers.group.getForValue(groupValue);
76
93
 
77
94
  if (group == null) {
78
- groupColumn ??= state.managers.column.get(state.managers.group.field);
95
+ groupColumn ??= state.managers.column.get(state.managers.group.key);
79
96
 
80
97
  group = new GroupComponent(
81
- `${groupColumn?.options.title ?? state.managers.group.field}: ${groupValue}`,
98
+ `${groupColumn?.options.label ?? state.managers.group.key}: ${groupValue}`,
82
99
  groupValue,
83
100
  );
84
101
 
85
- state.values.array.push(group);
86
- state.managers.group.add(group);
102
+ state.values.array.push(group.key);
103
+ state.managers.group.add(group, false);
104
+
105
+ addedGroups.push(group);
106
+ } else if (!addedGroups.includes(group) && !updatedGroups.includes(group)) {
107
+ updatedGroups.push(group);
87
108
  }
88
109
 
89
110
  if (!group.expanded) {
@@ -91,26 +112,44 @@ export class DataManager {
91
112
  }
92
113
 
93
114
  group.total += 1;
115
+ }
116
+
117
+ length = addedGroups.length;
118
+
119
+ if (length > 0) {
120
+ state.managers.event.emit(EVENT_GROUP_ADD, addedGroups.map(getGroup));
94
121
 
95
- groups.push(group);
122
+ for (let index = 0; index < length; index += 1) {
123
+ updateGroup(state, addedGroups[index], false);
124
+ }
96
125
  }
97
126
 
98
- length = groups.length;
127
+ length = updatedGroups.length;
99
128
 
100
- for (let index = 0; index < length; index += 1) {
101
- updateGroup(state, groups[index]);
129
+ if (length > 0) {
130
+ for (let index = 0; index < length; index += 1) {
131
+ updateGroup(state, updatedGroups[index], false);
132
+ }
133
+
134
+ state.managers.event.emit(EVENT_GROUP_UPDATE, updatedGroups.map(getGroup));
135
+ }
136
+
137
+ await this.update(updatedData, addedData.length === 0);
138
+
139
+ if (addedData.length === 0) {
140
+ return;
102
141
  }
103
142
 
104
- if (updates.length > 0) {
105
- void this.update(updates);
106
- } else if (render) {
143
+ state.managers.event.emit(EVENT_DATA_ADD, addedData);
144
+
145
+ if (render) {
107
146
  this.render();
108
147
  }
109
148
  }
110
149
 
111
- clear(): void {
150
+ async clear(): Promise<void> {
112
151
  if (this.state.values.array.length > 0) {
113
- void this.removeItems([], true, true);
152
+ return this.removeItems([], true, true).then(() => undefined);
114
153
  }
115
154
  }
116
155
 
@@ -119,8 +158,8 @@ export class DataManager {
119
158
 
120
159
  state.values.mapped.clear();
121
160
 
122
- state.items.active = undefined;
123
- state.items.original.length = 0;
161
+ state.keys.active = undefined;
162
+ state.keys.original.length = 0;
124
163
  state.values.array.length = 0;
125
164
 
126
165
  this.handlers = undefined as never;
@@ -130,45 +169,49 @@ export class DataManager {
130
169
  get(active?: boolean): PlainObject[] {
131
170
  const {state} = this;
132
171
 
133
- return (active ?? false)
172
+ return (active ?? false) && state.keys.active != null
134
173
  ? select(
135
- state.items.active ?? [],
136
- key => !(key instanceof GroupComponent),
174
+ state.keys.active,
175
+ key => !isGroupKey(key),
137
176
  key => state.values.mapped.get(key as Key)!,
138
177
  )
139
- : (state.values.array.filter(item => !(item instanceof GroupComponent)) as PlainObject[]);
178
+ : (state.values.array.filter(item => !isGroupKey(item)) as PlainObject[]);
140
179
  }
141
180
 
142
- getIndex(item: DataItem): number {
143
- if (item instanceof GroupComponent) {
144
- return this.items.indexOf(item);
145
- }
146
-
147
- return this.items.findIndex(value =>
148
- value instanceof GroupComponent ? value.key === item : value === item,
149
- );
181
+ getIndex(item: Key): number {
182
+ return this.keys.indexOf(item);
150
183
  }
151
184
 
152
- async remove(items: Array<Key | PlainObject>, render: boolean): Promise<void> {
185
+ async remove(items: Array<Key | PlainObject>, render: false): Promise<PlainObject[]>;
186
+
187
+ async remove(items: Array<Key | PlainObject>, render: true): Promise<void>;
188
+
189
+ async remove(items: Array<Key | PlainObject>, render: boolean): Promise<unknown> {
153
190
  const {state} = this;
154
191
 
155
- const keys = items.map(
156
- value => (isPlainObject(value) ? getValue(value, state.key) : value) as Key,
157
- );
192
+ const keys = items
193
+ .map(value => (isPlainObject(value) ? getValue(value, state.key) : value) as Key)
194
+ .filter(key => !isGroupKey(key));
158
195
 
159
196
  const {length} = keys;
160
197
 
161
- if (length > 0) {
162
- return this.removeItems(keys, false, render === true);
163
- }
198
+ return length === 0
199
+ ? render
200
+ ? undefined
201
+ : []
202
+ : this.removeItems(keys, false, render as never);
164
203
  }
165
204
 
166
- async removeItems(items: DataItem[], clear: boolean, render: boolean): Promise<void> {
205
+ async removeItems(data: Key[], clear: boolean, render: false): Promise<PlainObject[]>;
206
+
207
+ async removeItems(data: Key[], clear: boolean, render: true): Promise<void>;
208
+
209
+ async removeItems(keys: Key[], clear: boolean, render: boolean): Promise<unknown> {
167
210
  const {state} = this;
168
211
 
169
212
  if (clear) {
170
- state.items.active = undefined;
171
- state.items.original = [];
213
+ state.keys.active = undefined;
214
+ state.keys.original = [];
172
215
  state.values.array = [];
173
216
 
174
217
  state.values.mapped.clear();
@@ -179,39 +222,48 @@ export class DataManager {
179
222
  state.managers.group.clear();
180
223
  }
181
224
 
182
- return this.render();
225
+ state.managers.event.emit(EVENT_DATA_CLEAR);
226
+
227
+ this.render();
228
+
229
+ return render ? undefined : [];
183
230
  }
184
231
 
185
- const groups: GroupComponent[] = [];
232
+ const removedGroups: GroupComponent[] = [];
233
+ const updatedGroups: GroupComponent[] = [];
234
+
235
+ const removedData: PlainObject[] = [];
186
236
 
187
- const chunked = chunk(items);
237
+ const chunked = chunk(keys);
188
238
  const chunkedLength = chunked.length;
189
239
 
190
240
  for (let chunkedIndex = 0; chunkedIndex < chunkedLength; chunkedIndex += 1) {
191
241
  const chunk = chunked[chunkedIndex];
192
242
  const chunkLength = chunk.length;
193
243
 
194
- for (let itemIndex = 0; itemIndex < chunkLength; itemIndex += 1) {
195
- const item = chunk[itemIndex];
196
- const dataIndex = state.items.original.indexOf(item);
244
+ for (let keyIndex = 0; keyIndex < chunkLength; keyIndex += 1) {
245
+ const key = chunk[keyIndex];
246
+ const dataIndex = state.keys.original.indexOf(key);
197
247
 
198
248
  let dataValue: PlainObject | undefined;
199
249
 
200
250
  [dataValue] = state.values.array.splice(dataIndex, 1) as PlainObject[];
201
251
 
202
- state.items.original.splice(dataIndex, 1);
203
- state.managers.row.remove(item as never);
204
- state.values.mapped.delete(item as Key);
252
+ removedData.push(dataValue);
253
+
254
+ state.keys.original.splice(dataIndex, 1);
255
+ state.managers.row.remove(key as never);
256
+ state.values.mapped.delete(key as Key);
205
257
 
206
- if (!state.managers.group.enabled || item instanceof GroupComponent) {
258
+ if (!state.managers.group.enabled || isGroupKey(key)) {
207
259
  continue;
208
260
  }
209
261
 
210
- state.managers.group.collapsed.delete(item as never);
262
+ state.managers.group.collapsed.delete(key as never);
211
263
 
212
- const groupKey = getValue(dataValue, state.managers.group.field) as unknown;
264
+ const groupValue = getValue(dataValue, state.managers.group.key) as unknown;
213
265
 
214
- const group = state.managers.group.get(groupKey);
266
+ const group = state.managers.group.getForValue(groupValue);
215
267
 
216
268
  if (group == null) {
217
269
  continue;
@@ -220,41 +272,57 @@ export class DataManager {
220
272
  group.total -= 1;
221
273
 
222
274
  if (group.total > 0) {
223
- groups.push(group);
275
+ updatedGroups.push(group);
224
276
 
225
277
  continue;
226
278
  }
227
279
 
228
- let groupIndex = groups.indexOf(group);
280
+ let groupIndex = updatedGroups.indexOf(group);
229
281
 
230
282
  if (groupIndex > -1) {
231
- groups.splice(groupIndex, 1);
283
+ updatedGroups.splice(groupIndex, 1);
232
284
  }
233
285
 
234
- groupIndex = state.values.array.indexOf(group);
286
+ groupIndex = state.values.array.indexOf(group.key);
235
287
 
236
288
  if (groupIndex > -1) {
237
- state.items.original.splice(groupIndex, 1);
289
+ state.keys.original.splice(groupIndex, 1);
238
290
  state.values.array.splice(groupIndex, 1);
239
291
  }
240
292
 
241
- state.managers.group.remove(group);
293
+ removedGroups.push(group);
242
294
 
243
- if (items.length >= 10_000) {
295
+ state.managers.group.remove(group, false);
296
+
297
+ if (keys.length >= 10_000) {
244
298
  await delay(25);
245
299
  }
246
300
  }
247
301
  }
248
302
 
249
- const {length} = groups;
303
+ let {length} = updatedGroups;
250
304
 
251
- for (let index = 0; index < length; index += 1) {
252
- updateGroup(state, groups[index]);
305
+ if (length > 0) {
306
+ for (let index = 0; index < length; index += 1) {
307
+ updateGroup(state, updatedGroups[index], false);
308
+ }
309
+
310
+ state.managers.event.emit(EVENT_GROUP_UPDATE, updatedGroups.map(getGroup));
253
311
  }
254
312
 
313
+ length = removedGroups.length;
314
+
315
+ if (length > 0) {
316
+ state.managers.event.emit(EVENT_GROUP_REMOVE, removedGroups.map(getGroup));
317
+ }
318
+
319
+ state.managers.event.emit(EVENT_DATA_REMOVE, removedData);
320
+
255
321
  if (render) {
256
- return this.render();
322
+ this.render();
257
323
  }
324
+
325
+ return render ? undefined : removedData;
258
326
  }
259
327
 
260
328
  render(): void {
@@ -263,25 +331,27 @@ export class DataManager {
263
331
  if (state.managers.group.enabled) {
264
332
  sortWithGroups(state, state.values.array, [
265
333
  {
266
- direction: 'ascending',
334
+ direction: SORT_ASCENDING,
267
335
  key: state.key,
268
336
  },
269
337
  ]);
270
338
  } else {
271
339
  sort(state.values.array as PlainObject[], [
272
340
  {
273
- direction: 'ascending',
341
+ direction: SORT_ASCENDING,
274
342
  key: state.key,
275
343
  },
276
344
  ]);
277
345
  }
278
346
 
279
- state.items.original = state.values.array.map(item =>
280
- item instanceof GroupComponent ? item : (getValue(item, state.key) as Key),
347
+ state.keys.active = undefined;
348
+
349
+ state.keys.original = state.values.array.map(item =>
350
+ typeof item === 'string' ? item : (getValue(item, state.key) as Key),
281
351
  );
282
352
 
283
353
  state.values.mapped = toMap(
284
- state.values.array.filter(item => !(item instanceof GroupComponent)) as PlainObject[],
354
+ state.values.array.filter(item => !isGroupKey(item)) as PlainObject[],
285
355
  item => getValue(item, state.key) as Key,
286
356
  );
287
357
 
@@ -297,12 +367,12 @@ export class DataManager {
297
367
  set(data: PlainObject[]): void {
298
368
  const {state} = this;
299
369
 
300
- const array: Array<GroupComponent | PlainObject> = data.slice();
370
+ const array: DataValue[] = data.slice();
301
371
 
302
372
  if (state.managers.group.enabled) {
303
- const column = state.managers.column.get(state.managers.group.field);
373
+ const column = state.managers.column.get(state.managers.group.key);
304
374
 
305
- const grouped = toRecord.arrays(data, state.managers.group.field) as Record<
375
+ const grouped = toRecord.arrays(data, state.managers.group.key) as Record<
306
376
  string,
307
377
  PlainObject[]
308
378
  >;
@@ -316,7 +386,7 @@ export class DataManager {
316
386
  const [value, items] = entries[index];
317
387
 
318
388
  const group = new GroupComponent(
319
- `${column?.options.title ?? state.managers.group.field}: ${value}`,
389
+ `${column?.options.label ?? state.managers.group.key}: ${value}`,
320
390
  value,
321
391
  );
322
392
 
@@ -324,7 +394,7 @@ export class DataManager {
324
394
 
325
395
  groups.push(group);
326
396
 
327
- array.push(group);
397
+ array.push(group.key);
328
398
  }
329
399
 
330
400
  state.managers.group.set(groups);
@@ -335,10 +405,10 @@ export class DataManager {
335
405
  this.render();
336
406
  }
337
407
 
338
- async synchronize(data: PlainObject[], remove?: boolean): Promise<void> {
408
+ async synchronize(data: PlainObject[], remove: boolean): Promise<void> {
339
409
  const {state} = this;
340
410
 
341
- const add: PlainObject[] = [];
411
+ const added: PlainObject[] = [];
342
412
  const updated: PlainObject[] = [];
343
413
 
344
414
  const keys = new Set<Key>([]);
@@ -352,7 +422,7 @@ export class DataManager {
352
422
  if (state.values.mapped.has(key)) {
353
423
  updated.push(object);
354
424
  } else {
355
- add.push(object);
425
+ added.push(object);
356
426
  }
357
427
 
358
428
  keys.add(key);
@@ -362,43 +432,62 @@ export class DataManager {
362
432
  return;
363
433
  }
364
434
 
365
- if (remove ?? false) {
366
- const toRemove = state.items.original.filter(
367
- key => !(key instanceof GroupComponent) && !keys.has(key),
435
+ let removed: PlainObject[] = [];
436
+
437
+ if (remove) {
438
+ const toRemove = state.keys.original.filter(
439
+ key => !isGroupKey(key) && !keys.has(key),
368
440
  ) as Key[];
369
441
 
370
442
  if (toRemove.length > 0) {
371
- await this.remove(toRemove, false);
443
+ removed = await this.remove(toRemove, false);
372
444
  }
373
445
  }
374
446
 
375
- await this.update(updated);
447
+ await this.update(updated, added.length === 0);
376
448
 
377
- if (add.length > 0) {
378
- await this.add(add, false);
379
- }
449
+ await this.add(added, false);
380
450
 
381
- if (add.length > 0 || (remove ?? false)) {
451
+ state.managers.event.emit(EVENT_DATA_SYNCHRONIZE, {
452
+ added,
453
+ removed,
454
+ updated,
455
+ });
456
+
457
+ if (added.length > 0 || remove) {
382
458
  this.render();
383
459
  }
384
460
  }
385
461
 
386
- async update(data: PlainObject[]): Promise<void> {
462
+ async update(data: PlainObject[], render: boolean): Promise<void> {
387
463
  const {state} = this;
388
464
 
389
465
  const {length} = data;
390
466
 
467
+ const updated: PlainObject[] = [];
468
+
391
469
  for (let index = 0; index < length; index += 1) {
392
- const object = data[index];
470
+ const item = data[index];
393
471
 
394
- const key = getValue(object, state.key) as Key;
395
- const value = state.values.mapped.get(key);
472
+ const key = getValue(item, state.key) as Key;
473
+
474
+ const existing = state.keys.original.indexOf(key);
475
+
476
+ if (existing === -1) {
477
+ continue;
478
+ }
479
+
480
+ Object.assign(state.values.array[existing], item);
396
481
 
397
- if (value != null) {
398
- state.values.mapped.set(key, {...value, ...object} as PlainObject);
482
+ updated.push(state.values.array[existing] as PlainObject);
399
483
 
484
+ if (render && state.managers.render.visible.keys.has(key)) {
400
485
  state.managers.row.update(key);
401
486
  }
402
487
  }
488
+
489
+ if (updated.length > 0) {
490
+ state.managers.event.emit(EVENT_DATA_UPDATE, updated);
491
+ }
403
492
  }
404
493
  }
@@ -1,12 +1,49 @@
1
+ import type {GenericCallback} from '@oscarpalmer/atoms/models';
1
2
  import {on} from '@oscarpalmer/toretto/event';
2
3
  import {findAncestor} from '@oscarpalmer/toretto/find';
4
+ import {isEvent} from '../helpers/misc.helpers';
5
+ import {
6
+ ATTRIBUTE_DATA_EVENT,
7
+ ATTRIBUTE_DATA_KEY,
8
+ ATTRIBUTE_DATA_SORT_DIRECTION,
9
+ } from '../models/dom.model';
10
+ import {
11
+ EVENT_GROUP,
12
+ EVENT_HEADING,
13
+ EVENT_ROW,
14
+ type EventMap,
15
+ type EventName,
16
+ type Events,
17
+ type TabelaEvents,
18
+ } from '../models/event.model';
19
+ import {CSS_TABLE} from '../models/style.model';
3
20
  import type {State} from '../models/tabela.model';
4
21
 
5
22
  export class EventManager {
23
+ events: Events = {};
24
+
25
+ handlers: TabelaEvents = {
26
+ subscribe: (name, callback) => this.subscribe(name, callback),
27
+ unsubscribe: (name, callback) => this.unsubscribe(name, callback),
28
+ };
29
+
6
30
  constructor(public state: State) {
7
31
  mapped.set(state.element, this);
8
32
  }
9
33
 
34
+ emit<Name extends EventName>(name: Name, ...parameters: Parameters<EventMap[Name]>): void {
35
+ if (this.events[name] == null) {
36
+ return;
37
+ }
38
+
39
+ const handlers = [...this.events[name]];
40
+ const {length} = handlers;
41
+
42
+ for (let index = 0; index < length; index += 1) {
43
+ (handlers[index] as GenericCallback)(...parameters);
44
+ }
45
+ }
46
+
10
47
  destroy(): void {
11
48
  mapped.delete(this.state.element);
12
49
 
@@ -14,18 +51,34 @@ export class EventManager {
14
51
  }
15
52
 
16
53
  onSort(event: MouseEvent, target: HTMLElement): void {
17
- const direction = target.getAttribute('data-sort-direction');
18
- const field = target.getAttribute('data-field');
54
+ const direction = target.getAttribute(ATTRIBUTE_DATA_SORT_DIRECTION);
55
+ const key = target.getAttribute(ATTRIBUTE_DATA_KEY);
56
+
57
+ if (key != null) {
58
+ this.state.managers.sort.toggle(event, key, direction);
59
+ }
60
+ }
19
61
 
20
- if (field != null) {
21
- this.state.managers.sort.toggle(event, field, direction);
62
+ subscribe(name: string, callback: GenericCallback): void {
63
+ if (!isEvent(name) || typeof callback !== 'function') {
64
+ return;
65
+ }
66
+
67
+ (this.events as Record<string, Set<unknown>>)[name] ??= new Set();
68
+
69
+ (this.events[name] as Set<unknown>).add(callback);
70
+ }
71
+
72
+ unsubscribe(name: string, callback: GenericCallback): void {
73
+ if (isEvent(name) && typeof callback === 'function') {
74
+ this.events[name]?.delete(callback);
22
75
  }
23
76
  }
24
77
  }
25
78
 
26
79
  function onClick(event: MouseEvent): void {
27
- const target = findAncestor(event, '[data-event]');
28
- const table = findAncestor(event, '.tabela__table');
80
+ const target = findAncestor(event, eventAttribute);
81
+ const table = findAncestor(event, tableClassName);
29
82
 
30
83
  if (!(target instanceof HTMLElement) || !(table instanceof HTMLElement)) {
31
84
  return;
@@ -37,18 +90,18 @@ function onClick(event: MouseEvent): void {
37
90
  return;
38
91
  }
39
92
 
40
- const type = target?.getAttribute('data-event');
93
+ const type = target?.getAttribute(ATTRIBUTE_DATA_EVENT);
41
94
 
42
95
  switch (type) {
43
- case 'group':
96
+ case EVENT_GROUP:
44
97
  manager.state.managers.group.handle(target);
45
98
  break;
46
99
 
47
- case 'heading':
100
+ case EVENT_HEADING:
48
101
  manager.onSort(event, target);
49
102
  break;
50
103
 
51
- case 'row':
104
+ case EVENT_ROW:
52
105
  manager.state.managers.selection.handle(event, target);
53
106
  break;
54
107
 
@@ -58,8 +111,8 @@ function onClick(event: MouseEvent): void {
58
111
  }
59
112
 
60
113
  function onKeydown(event: KeyboardEvent): void {
61
- const target = findAncestor(event, '[data-event]');
62
- const table = findAncestor(event, '.tabela__table');
114
+ const target = findAncestor(event, eventAttribute);
115
+ const table = findAncestor(event, tableClassName);
63
116
 
64
117
  if (!(target instanceof HTMLElement) || !(table instanceof HTMLElement)) {
65
118
  return;
@@ -82,7 +135,11 @@ function onKeydown(event: KeyboardEvent): void {
82
135
  manager.state.managers.navigation.handle(event);
83
136
  }
84
137
 
138
+ const eventAttribute = `[${ATTRIBUTE_DATA_EVENT}]`;
139
+
85
140
  const mapped = new WeakMap<HTMLElement, EventManager>();
86
141
 
142
+ const tableClassName = `.${CSS_TABLE}`;
143
+
87
144
  on(document, 'click', onClick);
88
145
  on(document, 'keydown', onKeydown, {passive: false});