@sankhyalabs/core 7.2.0 → 7.2.1-dev.2
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/.docs/classes/ColumnFilterManager.md +6 -6
- package/.docs/classes/MaskFormatter.md +16 -16
- package/.docs/classes/OverflowWatcher.md +71 -17
- package/.docs/enumerations/DependencyType.md +3 -3
- package/.docs/enumerations/SortMode.md +2 -2
- package/.docs/enumerations/UserInterface.md +19 -19
- package/.docs/interfaces/FieldDescriptor.md +19 -9
- package/.docs/interfaces/Filter.md +3 -3
- package/.docs/interfaces/OverFlowWatcherParams.md +6 -6
- package/.docs/interfaces/Sort.md +3 -3
- package/.docs/interfaces/SortingProvider.md +1 -1
- package/.docs/namespaces/MaskFormatter/type-aliases/MaskCharacter.md +1 -1
- package/.docs/namespaces/MaskFormatter/variables/MaskCharacter.md +1 -1
- package/dist/dataunit/metadata/UnitMetadata.d.ts +1 -0
- package/dist/dataunit/metadata/UnitMetadata.js.map +1 -1
- package/dist/utils/ColumnFilterManager.js +17 -1
- package/dist/utils/ColumnFilterManager.js.map +1 -1
- package/dist/utils/MaskFormatter.js +1 -0
- package/dist/utils/MaskFormatter.js.map +1 -1
- package/dist/utils/OverflowWatcher/index.d.ts +3 -0
- package/dist/utils/OverflowWatcher/index.js +51 -2
- package/dist/utils/OverflowWatcher/index.js.map +1 -1
- package/package.json +1 -1
- package/reports/test-report.xml +333 -330
- package/src/dataunit/metadata/UnitMetadata.ts +1 -0
- package/src/utils/ColumnFilterManager.ts +16 -2
- package/src/utils/MaskFormatter.ts +1 -0
- package/src/utils/OverflowWatcher/index.ts +249 -190
- package/test/util/ColumnFilterManager.spec.ts +59 -1
|
@@ -3,6 +3,7 @@ import DataUnit from '../dataunit/DataUnit.js';
|
|
|
3
3
|
import { Record } from '../dataunit/interfaces/dataUnit.js';
|
|
4
4
|
import { FieldComparator } from '../dataunit/sorting/FieldComparator.js';
|
|
5
5
|
import { LoadDataRequest } from '../dataunit/loading/LoadDataRequest.js';
|
|
6
|
+
import { DataType } from '../dataunit/metadata/DataType.js';
|
|
6
7
|
|
|
7
8
|
const COLUMN_FILTER_PATTERN = /FILTRO_COLUNA_(.+)/;
|
|
8
9
|
|
|
@@ -46,13 +47,26 @@ export class ColumnFilterManager {
|
|
|
46
47
|
if (!columnFilter.params || !fieldDescriptor) return false;
|
|
47
48
|
|
|
48
49
|
const fieldValue = record[columnFilter.columnName];
|
|
50
|
+
/**
|
|
51
|
+
* Casa datas pelo valor FORMATADO — a mesma granularidade que o funil exibe/agrupa
|
|
52
|
+
* (dia p/ data pura, minuto p/ DATETIME). Nem por dia nem por timestamp exato. KB-171175.
|
|
53
|
+
*/
|
|
54
|
+
const isDate = fieldDescriptor.dataType === DataType.DATE;
|
|
49
55
|
|
|
50
56
|
for (let param of columnFilter.params) {
|
|
51
57
|
const paramValue = dataUnit.valueFromString(columnFilter.columnName, param.value);
|
|
52
|
-
|
|
58
|
+
let matches: boolean;
|
|
59
|
+
if (isDate) {
|
|
60
|
+
matches = (fieldValue == undefined || paramValue == undefined)
|
|
61
|
+
? fieldValue == paramValue
|
|
62
|
+
: dataUnit.getFormattedValue(columnFilter.columnName, fieldValue)
|
|
63
|
+
=== dataUnit.getFormattedValue(columnFilter.columnName, paramValue);
|
|
64
|
+
} else {
|
|
65
|
+
matches = FieldComparator.compareValues(fieldDescriptor, fieldValue, paramValue) === 0;
|
|
66
|
+
}
|
|
67
|
+
if (matches) {
|
|
53
68
|
return true;
|
|
54
69
|
}
|
|
55
|
-
|
|
56
70
|
}
|
|
57
71
|
return false;
|
|
58
72
|
}
|
|
@@ -9,236 +9,295 @@ export * from "./types/overflow-callback.js";
|
|
|
9
9
|
export const OVERFLOWED_CLASS_NAME = 'overflowed';
|
|
10
10
|
|
|
11
11
|
export default class OverflowWatcher {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
12
|
+
private _onResize:OnOverflowCallBack;
|
|
13
|
+
private _resizeObserver:ResizeObserver;
|
|
14
|
+
private _lastContainerSize:number|undefined = undefined;
|
|
15
|
+
private _lastContainerInstance: HTMLElement | undefined = undefined;
|
|
16
|
+
private _scrollDirection = OverflowDirection.HORIZONTAL;
|
|
17
|
+
private _propSize:string;
|
|
18
|
+
private _hiddenItemsProps:Map<Element, SizeProps> = new Map();
|
|
19
|
+
private _notOverFlowPros:Map<string, SizeProps> = new Map();
|
|
20
|
+
private _deltaSize:number;
|
|
21
|
+
private _notOverFlow: string[] = [];
|
|
22
|
+
|
|
23
|
+
readonly DATA_ELEMENT_ID = 'data-element-id';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Cria uma instancia do OverflowWatcher
|
|
27
|
+
*
|
|
28
|
+
* @param element - Elemento HTML que o overflow será observado.
|
|
29
|
+
* @param callback - Função que sera chamada quando ocorrer overflow no elemento.
|
|
30
|
+
* @param overFlowDirection - Indica direção que o overflow será monitorado.
|
|
31
|
+
* @param deltaSize - Variação de tamanho que será considerada como overflow.
|
|
32
|
+
* @param debounce - Tempo até execução do callback em milissegundos.
|
|
33
|
+
* @param notOverFlow - Lista de ids ou data-element-ids dos elementos que não devem sofrer overFlow.
|
|
34
|
+
*/
|
|
35
|
+
constructor({
|
|
36
|
+
element,
|
|
37
|
+
callback,
|
|
38
|
+
overFlowDirection = OverflowDirection.HORIZONTAL,
|
|
39
|
+
debounce = 200,
|
|
40
|
+
deltaSize = 0,
|
|
41
|
+
notOverFlow = []
|
|
42
|
+
}: OverFlowWatcherParams){
|
|
43
|
+
this._onResize = callback;
|
|
44
|
+
this._scrollDirection = overFlowDirection;
|
|
45
|
+
this._propSize = (OverflowDirection.HORIZONTAL === overFlowDirection) ? "width" : "height";
|
|
46
|
+
this._resizeObserver = new ResizeObserver(JSUtils.debounce((entries: ResizeObserverEntry[]) => this.handleResize(entries), debounce));
|
|
47
|
+
this._resizeObserver.observe(element);
|
|
48
|
+
this._deltaSize = deltaSize;
|
|
49
|
+
this._notOverFlow = notOverFlow;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public addNotOverFlowElement(elementId: string){
|
|
53
|
+
if(!this._notOverFlow.includes(elementId)){
|
|
54
|
+
this._notOverFlow.push(elementId);
|
|
50
55
|
}
|
|
56
|
+
}
|
|
51
57
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
public destroy(){
|
|
59
|
-
this._resizeObserver.disconnect();
|
|
60
|
-
}
|
|
58
|
+
public destroy(){
|
|
59
|
+
this._resizeObserver.disconnect();
|
|
60
|
+
}
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
62
|
+
public forceUpdate(){
|
|
63
|
+
if(this._lastContainerSize && this._lastContainerInstance){
|
|
64
|
+
this.updateOverFlowedItems(this._lastContainerInstance, this._lastContainerSize);
|
|
66
65
|
}
|
|
66
|
+
}
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
private handleResize(entries: ResizeObserverEntry[]){
|
|
69
|
+
if(!entries || entries.length === 0) return;
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
const container = entries[0];
|
|
72
|
+
const containerSize:number = (container.contentRect as any)[this._propSize];
|
|
73
|
+
if(!containerSize) return;
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
75
|
+
if(this.hasChangedSize(containerSize)){
|
|
76
|
+
this.updateOverFlowedItems(container.target as HTMLElement, containerSize);
|
|
78
77
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private updateOverFlowedItems(container: HTMLElement, containerSize: number){
|
|
81
|
+
const children:Element[] = this.getProcessableElements(container);
|
|
82
|
+
this.registerNotOverflowProps(children);
|
|
83
|
+
this.proccessElements(containerSize, children);
|
|
84
|
+
this._lastContainerSize = containerSize;
|
|
85
|
+
this._lastContainerInstance = container;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private getProcessableElements(container: HTMLElement): Element[] {
|
|
89
|
+
const directChildren = Array.from(container.children);
|
|
90
|
+
const processableElements: Element[] = [];
|
|
91
|
+
|
|
92
|
+
for (const child of directChildren) {
|
|
93
|
+
if (child.classList.contains('taskbar-group')) {
|
|
94
|
+
processableElements.push(...Array.from(child.children));
|
|
95
|
+
} else {
|
|
96
|
+
processableElements.push(child);
|
|
97
|
+
}
|
|
86
98
|
}
|
|
87
99
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const id = childElement.id || this.getDataElementId(childElement);
|
|
91
|
-
if (this.canNotRegisterNotOverFlow(id)) return;
|
|
92
|
-
this._notOverFlowPros.set(id, this.getElementSizeProps(childElement));
|
|
93
|
-
});
|
|
94
|
-
}
|
|
100
|
+
return processableElements;
|
|
101
|
+
}
|
|
95
102
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
103
|
+
private registerNotOverflowProps(children: Element[]) {
|
|
104
|
+
children.forEach(childElement => {
|
|
105
|
+
const id = childElement.id || this.getDataElementId(childElement);
|
|
106
|
+
if (this.canNotRegisterNotOverFlow(id)) return;
|
|
107
|
+
this._notOverFlowPros.set(id, this.getElementSizeProps(childElement));
|
|
108
|
+
});
|
|
109
|
+
}
|
|
99
110
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
111
|
+
private canNotRegisterNotOverFlow(id: string) {
|
|
112
|
+
return !id || !this._notOverFlow.includes(id);
|
|
113
|
+
}
|
|
103
114
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
115
|
+
private hasChangedSize(elementSize: number):boolean{
|
|
116
|
+
if(!this._lastContainerSize) return true;
|
|
117
|
+
const variation = elementSize - this._lastContainerSize;
|
|
108
118
|
|
|
109
|
-
|
|
119
|
+
if(variation < 0){
|
|
120
|
+
const absoluteVariation = Math.abs(variation);
|
|
121
|
+
return (absoluteVariation > this._deltaSize);
|
|
110
122
|
}
|
|
111
123
|
|
|
112
|
-
|
|
113
|
-
|
|
124
|
+
return variation > 0;
|
|
125
|
+
}
|
|
114
126
|
|
|
115
|
-
|
|
116
|
-
|
|
127
|
+
private proccessElements(elementSize:number, children:Element[]){
|
|
128
|
+
if(children.length === 0) return;
|
|
117
129
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
130
|
+
const childrenSize = this.calcChildrenSize(children);
|
|
131
|
+
let diff = Number((elementSize - childrenSize).toFixed(4));
|
|
122
132
|
|
|
123
|
-
|
|
133
|
+
if(diff > 0){
|
|
134
|
+
this.clearOverFlow();
|
|
135
|
+
return;
|
|
124
136
|
}
|
|
125
137
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
this._onResize([]);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
private proccessElementsOverFlow(allElements:Element[], avaliableSize:number){
|
|
132
|
-
const elementsThatFit: Element[] = [];
|
|
133
|
-
const avaliableSizeConsideringDelta = (avaliableSize - this._deltaSize);
|
|
134
|
-
|
|
135
|
-
let sumElementsSize = 0;
|
|
138
|
+
this.proccessElementsOverFlow(children, elementSize);
|
|
139
|
+
}
|
|
136
140
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
141
|
+
private clearOverFlow(){
|
|
142
|
+
this._hiddenItemsProps = new Map();
|
|
143
|
+
this._onResize([]);
|
|
144
|
+
}
|
|
142
145
|
|
|
143
|
-
|
|
146
|
+
private proccessElementsOverFlow(allElements:Element[], avaliableSize:number){
|
|
147
|
+
const elementsThatFit: Element[] = [];
|
|
148
|
+
const avaliableSizeConsideringDelta = (avaliableSize - this._deltaSize);
|
|
144
149
|
|
|
145
|
-
|
|
146
|
-
if(!this._hiddenItemsProps.has(overFlowed)){
|
|
147
|
-
this.registerElementSize(overFlowed);
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
+
let sumElementsSize = 0;
|
|
150
151
|
|
|
151
|
-
|
|
152
|
+
for (const element of allElements) {
|
|
153
|
+
sumElementsSize += this.calcElementSize(element);
|
|
154
|
+
if(this.exceedsAvaliableSize(sumElementsSize, elementsThatFit, avaliableSizeConsideringDelta)) break;
|
|
155
|
+
elementsThatFit.push(element);
|
|
152
156
|
}
|
|
153
157
|
|
|
154
|
-
|
|
155
|
-
|
|
158
|
+
const overFlowedElements = allElements.filter(element => this.isElementOverFlowing(elementsThatFit, element));
|
|
159
|
+
|
|
160
|
+
overFlowedElements.forEach(overFlowed => {
|
|
161
|
+
if(!this._hiddenItemsProps.has(overFlowed)){
|
|
162
|
+
this.registerElementSize(overFlowed);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
this._onResize(overFlowedElements);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private isElementOverFlowing(elementsThatFit: Element[], element: Element) {
|
|
170
|
+
return !elementsThatFit.includes(element) && this.canOverFlowElement(element);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private canOverFlowElement(element: Element) {
|
|
174
|
+
return !this._notOverFlow.includes(element.id)
|
|
175
|
+
&& !this._notOverFlow.includes(this.getDataElementId(element));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private getDataElementId(element: Element): string {
|
|
179
|
+
return (element as HTMLElement).getAttribute('data-element-id') ?? "";
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private exceedsAvaliableSize(sumElementsSize: number, elements: Element[], avaliableSize: number): boolean {
|
|
183
|
+
if(!this._notOverFlow.length) return sumElementsSize > avaliableSize
|
|
184
|
+
|
|
185
|
+
const elementIdsToCalculate = this.canNotOverFlowNotIncludedIds(elements);
|
|
186
|
+
if(!elementIdsToCalculate.length) return sumElementsSize > avaliableSize
|
|
187
|
+
|
|
188
|
+
const variation = this.calculateVariation(elementIdsToCalculate);
|
|
189
|
+
const occupiedSize = sumElementsSize + variation;
|
|
190
|
+
return occupiedSize > avaliableSize;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private calculateVariation(elementIdsToCalculate: string[]) {
|
|
194
|
+
let variation = 0
|
|
195
|
+
elementIdsToCalculate.forEach(id => {
|
|
196
|
+
const sizeProps = this._notOverFlowPros.get(id);
|
|
197
|
+
variation += sizeProps?.size ?? 0;
|
|
198
|
+
variation += sizeProps?.margin ?? 0;
|
|
199
|
+
});
|
|
200
|
+
return variation;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private canNotOverFlowNotIncludedIds(elements: Element[]): string[]{
|
|
204
|
+
const elementsIdList = elements.map(el => el.id || this.getDataElementId(el)).filter(id => !!id);
|
|
205
|
+
return this._notOverFlow.filter(id => !elementsIdList.includes(id));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private registerElementSize(element: Element) {
|
|
209
|
+
const sizeProps = this.getElementSizeProps(element);
|
|
210
|
+
this._hiddenItemsProps.set(element, sizeProps);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private getElementSizeProps(element: Element) {
|
|
214
|
+
const sizeProps: SizeProps = {
|
|
215
|
+
size: (element.getBoundingClientRect() as any)[this._propSize],
|
|
216
|
+
margin: calcMarginSize(element, this._scrollDirection),
|
|
217
|
+
};
|
|
218
|
+
return sizeProps;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private calcChildrenSize(children:Element[]):number{
|
|
222
|
+
let sumChildren = 0;
|
|
223
|
+
sumChildren += this._deltaSize;
|
|
224
|
+
|
|
225
|
+
// Agrupa os elementos por container pai
|
|
226
|
+
const elementsByContainer = this.groupElementsByContainer(children);
|
|
227
|
+
|
|
228
|
+
// Calcula o tamanho considerando os containers
|
|
229
|
+
for (const [container, elements] of elementsByContainer) {
|
|
230
|
+
if (container && container.classList.contains('taskbar-group')) {
|
|
231
|
+
// Se é um grupo da taskbar, adiciona o tamanho do container mais os elementos
|
|
232
|
+
sumChildren += this.calcContainerSize(container);
|
|
233
|
+
elements.forEach(el => sumChildren += this.calcElementSize(el));
|
|
234
|
+
} else {
|
|
235
|
+
// Se não é um grupo, adiciona apenas o tamanho dos elementos
|
|
236
|
+
elements.forEach(el => sumChildren += this.calcElementSize(el));
|
|
237
|
+
}
|
|
156
238
|
}
|
|
157
239
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
&& !this._notOverFlow.includes(this.getDataElementId(element));
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
private getDataElementId(element: Element): string {
|
|
164
|
-
return (element as HTMLElement).getAttribute('data-element-id') ?? "";
|
|
165
|
-
}
|
|
240
|
+
return sumChildren;
|
|
241
|
+
}
|
|
166
242
|
|
|
167
|
-
|
|
168
|
-
|
|
243
|
+
private groupElementsByContainer(elements: Element[]): Map<Element | null, Element[]> {
|
|
244
|
+
const grouped = new Map<Element | null, Element[]>();
|
|
169
245
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
private calculateVariation(elementIdsToCalculate: string[]) {
|
|
179
|
-
let variation = 0
|
|
180
|
-
elementIdsToCalculate.forEach(id => {
|
|
181
|
-
const sizeProps = this._notOverFlowPros.get(id);
|
|
182
|
-
variation += sizeProps?.size ?? 0;
|
|
183
|
-
variation += sizeProps?.margin ?? 0;
|
|
184
|
-
});
|
|
185
|
-
return variation;
|
|
246
|
+
for (const element of elements) {
|
|
247
|
+
const container = element.parentElement;
|
|
248
|
+
if (!grouped.has(container)) {
|
|
249
|
+
grouped.set(container, []);
|
|
250
|
+
}
|
|
251
|
+
grouped.get(container)!.push(element);
|
|
186
252
|
}
|
|
187
253
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
254
|
+
return grouped;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private calcContainerSize(container: Element): number {
|
|
258
|
+
const rect = container.getBoundingClientRect();
|
|
259
|
+
const elementSize = rect[this._propSize as keyof DOMRect] as number;
|
|
260
|
+
const marginSize = calcMarginSize(container, this._scrollDirection);
|
|
261
|
+
|
|
262
|
+
// Subtrai o tamanho dos filhos para não contar em duplicidade
|
|
263
|
+
let childrenSize = 0;
|
|
264
|
+
Array.from(container.children).forEach(child => {
|
|
265
|
+
const childRect = child.getBoundingClientRect();
|
|
266
|
+
childrenSize += childRect[this._propSize as keyof DOMRect] as number;
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
return Math.max(0, elementSize - childrenSize) + marginSize;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private calcElementSize(el: Element) {
|
|
273
|
+
let size = 0
|
|
274
|
+
if (this.isOverFlowed(el)) {
|
|
275
|
+
const sizeProps = this._hiddenItemsProps.get(el);
|
|
276
|
+
size += sizeProps?.size ?? 0;
|
|
277
|
+
size += sizeProps?.margin ?? 0;
|
|
278
|
+
return size;
|
|
191
279
|
}
|
|
192
280
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
private getElementSizeProps(element: Element) {
|
|
199
|
-
const sizeProps: SizeProps = {
|
|
200
|
-
size: (element.getBoundingClientRect() as any)[this._propSize],
|
|
201
|
-
margin: calcMarginSize(element, this._scrollDirection),
|
|
202
|
-
};
|
|
203
|
-
return sizeProps;
|
|
204
|
-
}
|
|
281
|
+
size += (el.getBoundingClientRect() as any)[this._propSize];
|
|
282
|
+
size += calcMarginSize(el, this._scrollDirection);
|
|
283
|
+
return size;
|
|
284
|
+
}
|
|
205
285
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
Array.from(children).forEach(el => sumChildren += this.calcElementSize(el));
|
|
210
|
-
return sumChildren;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
private calcElementSize(el: Element) {
|
|
214
|
-
let size = 0
|
|
215
|
-
if (this.isOverFlowed(el)) {
|
|
216
|
-
const sizeProps = this._hiddenItemsProps.get(el);
|
|
217
|
-
size += sizeProps?.size ?? 0;
|
|
218
|
-
size += sizeProps?.margin ?? 0;
|
|
219
|
-
return size;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
size += (el.getBoundingClientRect() as any)[this._propSize];
|
|
223
|
-
size += calcMarginSize(el, this._scrollDirection);
|
|
224
|
-
return size;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
private isOverFlowed(el: Element) {
|
|
228
|
-
return el.classList.contains(OVERFLOWED_CLASS_NAME);
|
|
229
|
-
}
|
|
286
|
+
private isOverFlowed(el: Element) {
|
|
287
|
+
return el.classList.contains(OVERFLOWED_CLASS_NAME);
|
|
288
|
+
}
|
|
230
289
|
}
|
|
231
290
|
|
|
232
291
|
export interface OverFlowWatcherParams {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
292
|
+
element:HTMLElement,
|
|
293
|
+
callback:OnOverflowCallBack,
|
|
294
|
+
overFlowDirection?:OverflowDirection,
|
|
295
|
+
deltaSize?:number,
|
|
296
|
+
debounce?: number,
|
|
297
|
+
notOverFlow?: string[]
|
|
239
298
|
}
|
|
240
299
|
|
|
241
300
|
interface SizeProps {
|
|
242
|
-
|
|
243
|
-
|
|
301
|
+
size: number,
|
|
302
|
+
margin: number,
|
|
244
303
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ColumnFilterManager, IColumnFilter } from '../../src/utils/ColumnFilterManager.js';
|
|
2
|
-
import { FieldDescriptor, Filter, FilterParam } from '../../src/dataunit/metadata/UnitMetadata.js';
|
|
2
|
+
import { FieldDescriptor, Filter, FilterParam, UserInterface } from '../../src/dataunit/metadata/UnitMetadata.js';
|
|
3
|
+
import { DataType } from '../../src/dataunit/metadata/DataType.js';
|
|
3
4
|
import DataUnit from '../../src/dataunit/DataUnit';
|
|
4
5
|
import { Record } from '../../src/dataunit/interfaces/dataUnit';
|
|
5
6
|
|
|
@@ -82,6 +83,63 @@ describe('ColumnFilterManager', () => {
|
|
|
82
83
|
const record: Record = { field1: 'test' } as unknown as Record;
|
|
83
84
|
expect(filterFunction!(record)).toBe(false);
|
|
84
85
|
});
|
|
86
|
+
|
|
87
|
+
describe('granularidade de data (KB-171175)', () => {
|
|
88
|
+
// Formata na granularidade EXIBIDA do campo, como o funil faz via getFormattedValue:
|
|
89
|
+
// DATE -> dia; DATETIME -> minuto. Usa toISOString (sempre UTC) p/ ser estável ao fuso do runner.
|
|
90
|
+
const formatterFor = (field: FieldDescriptor) => (_col: string, value: any) => {
|
|
91
|
+
if (value == undefined) return null;
|
|
92
|
+
const iso = new Date(value).toISOString();
|
|
93
|
+
return field.userInterface === UserInterface.DATE ? iso.slice(0, 10) : iso.slice(0, 16);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const buildDataUnit = (field: FieldDescriptor): DataUnit => ({
|
|
97
|
+
getField: jest.fn().mockReturnValue(field),
|
|
98
|
+
// O funil envia a data selecionada; devolvemos como Date, como faz o DataUnit real.
|
|
99
|
+
valueFromString: jest.fn().mockImplementation((_col: string, value: any) => new Date(value)),
|
|
100
|
+
getFormattedValue: jest.fn().mockImplementation(formatterFor(field)),
|
|
101
|
+
} as unknown as DataUnit);
|
|
102
|
+
|
|
103
|
+
const buildFilter = (...values: string[]): IColumnFilter[] => ([{
|
|
104
|
+
columnName: 'dt',
|
|
105
|
+
name: 'FILTRO_COLUNA_dt',
|
|
106
|
+
expression: '',
|
|
107
|
+
params: values.map(value => ({ value } as FilterParam)),
|
|
108
|
+
}]);
|
|
109
|
+
|
|
110
|
+
it('data PURA (DATE): casa por dia (granularidade exibida) — alinhado ao onlydate da exportação', () => {
|
|
111
|
+
const dataUnit = buildDataUnit({ name: 'dt', dataType: DataType.DATE, userInterface: UserInterface.DATE } as FieldDescriptor);
|
|
112
|
+
// Seleção do funil no nível de dia (03/08).
|
|
113
|
+
const filterFunction = ColumnFilterManager.getFilterFunction(dataUnit, buildFilter('2025-08-03T00:00:00Z'));
|
|
114
|
+
|
|
115
|
+
// Registros do mesmo dia, com horas diferentes, devem casar.
|
|
116
|
+
expect(filterFunction!({ dt: new Date('2025-08-03T09:18:00Z') } as unknown as Record)).toBe(true);
|
|
117
|
+
expect(filterFunction!({ dt: new Date('2025-08-03T09:22:00Z') } as unknown as Record)).toBe(true);
|
|
118
|
+
// Dia diferente não casa.
|
|
119
|
+
expect(filterFunction!({ dt: new Date('2025-08-04T09:18:00Z') } as unknown as Record)).toBe(false);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('data+hora (DATETIME): casa pelo minuto exibido — distingue 09:18 de 09:22', () => {
|
|
123
|
+
const dataUnit = buildDataUnit({ name: 'dt', dataType: DataType.DATE, userInterface: UserInterface.DATETIME } as FieldDescriptor);
|
|
124
|
+
// Funil com apenas 09:22 marcado; o param carrega o timestamp representativo (09:22:39).
|
|
125
|
+
const filterFunction = ColumnFilterManager.getFilterFunction(dataUnit, buildFilter('2025-08-03T09:22:39Z'));
|
|
126
|
+
|
|
127
|
+
expect(filterFunction!({ dt: new Date('2025-08-03T09:22:00Z') } as unknown as Record)).toBe(true);
|
|
128
|
+
// O título das 09:18 deve sair da grade.
|
|
129
|
+
expect(filterFunction!({ dt: new Date('2025-08-03T09:18:49Z') } as unknown as Record)).toBe(false);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('data+hora (DATETIME): mantém registros IRMÃOS do mesmo minuto com segundos diferentes (regressão KB-171175)', () => {
|
|
133
|
+
const dataUnit = buildDataUnit({ name: 'dt', dataType: DataType.DATE, userInterface: UserInterface.DATETIME } as FieldDescriptor);
|
|
134
|
+
// A opção "09:22" no funil = 1 param representativo (09:22:39), mas há irmãos :51/:55.
|
|
135
|
+
const filterFunction = ColumnFilterManager.getFilterFunction(dataUnit, buildFilter('2025-08-03T09:22:39Z'));
|
|
136
|
+
|
|
137
|
+
expect(filterFunction!({ dt: new Date('2025-08-03T09:22:39Z') } as unknown as Record)).toBe(true);
|
|
138
|
+
// Casamento exato descartaria estes; por minuto exibido eles PERMANECEM.
|
|
139
|
+
expect(filterFunction!({ dt: new Date('2025-08-03T09:22:51Z') } as unknown as Record)).toBe(true);
|
|
140
|
+
expect(filterFunction!({ dt: new Date('2025-08-03T09:22:55Z') } as unknown as Record)).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
85
143
|
});
|
|
86
144
|
|
|
87
145
|
|