@vuu-ui/vuu-utils 2.1.19-beta.1 → 2.1.19-beta.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/package.json +8 -9
- package/src/Clock.js +43 -0
- package/src/PageVisibilityObserver.js +42 -0
- package/src/ScaledDecimal.js +32 -0
- package/src/ShellContext.js +5 -0
- package/src/ThemeProvider.js +56 -0
- package/src/array-utils.js +51 -0
- package/src/box-utils.js +51 -0
- package/src/broadcast-channel.js +0 -0
- package/src/column-utils.js +790 -0
- package/src/common-types.js +11 -0
- package/src/component-registry.js +83 -0
- package/src/context-definitions/DataContext.js +15 -0
- package/src/context-definitions/DataProvider.js +13 -0
- package/src/context-definitions/DataSourceProvider.js +18 -0
- package/src/context-definitions/WorkspaceContext.js +14 -0
- package/src/cookie-utils.js +15 -0
- package/src/css-utils.js +5 -0
- package/src/data-editing/DataEditingProvider.js +13 -0
- package/src/data-editing/EditButtons.js +32 -0
- package/src/data-editing/EditModeProvider.js +18 -0
- package/src/data-editing/EditSession.js +149 -0
- package/src/data-editing/edit-utils.js +37 -0
- package/src/data-editing/useEditableTable.js +76 -0
- package/src/data-utils.js +55 -0
- package/src/datasource/BaseDataSource.js +233 -0
- package/src/datasource/datasource-action-utils.js +6 -0
- package/src/datasource/datasource-filter-utils.js +27 -0
- package/src/datasource/datasource-utils.js +148 -0
- package/src/date/date-utils.js +80 -0
- package/src/date/dateTimePattern.js +17 -0
- package/src/date/formatter.js +101 -0
- package/src/date/index.js +4 -0
- package/src/date/types.js +27 -0
- package/src/debug-utils.js +25 -0
- package/src/event-emitter.js +78 -0
- package/src/feature-utils.js +86 -0
- package/src/filters/filter-utils.js +136 -0
- package/src/filters/filterAsQuery.js +70 -0
- package/src/filters/index.js +2 -0
- package/src/form-utils.js +69 -0
- package/src/formatting-utils.js +42 -0
- package/src/getUniqueId.js +2 -0
- package/src/group-utils.js +16 -0
- package/src/html-utils.js +108 -0
- package/src/index.js +77 -0
- package/src/input-utils.js +3 -0
- package/src/invariant.js +9 -0
- package/src/itemToString.js +9 -0
- package/src/json-types.js +0 -0
- package/src/json-utils.js +108 -0
- package/src/keyboard-utils.js +14 -0
- package/src/keyset.js +57 -0
- package/src/layout-types.js +0 -0
- package/src/list-utils.js +5 -0
- package/src/local-storage-utils.js +23 -0
- package/src/logging-utils.js +52 -0
- package/src/module-utils.js +2 -0
- package/src/nanoid/index.js +13 -0
- package/src/perf-utils.js +28 -0
- package/src/promise-utils.js +26 -0
- package/src/protocol-message-utils.js +68 -0
- package/src/range-utils.js +109 -0
- package/src/react-utils.js +64 -0
- package/src/round-decimal.js +83 -0
- package/src/row-utils.js +43 -0
- package/src/selection-utils.js +25 -0
- package/src/shell-layout-types.js +9 -0
- package/src/sort-utils.js +75 -0
- package/src/table-schema-utils.js +11 -0
- package/src/text-utils.js +13 -0
- package/src/theme-utils.js +21 -0
- package/src/tree-types.js +0 -0
- package/src/tree-utils.js +96 -0
- package/src/ts-utils.js +6 -0
- package/src/typeahead-utils.js +4 -0
- package/src/url-utils.js +12 -0
- package/src/useId.js +6 -0
- package/src/useLayoutEffectSkipFirst.js +9 -0
- package/src/useResizeObserver.js +122 -0
- package/src/useStateRef.js +21 -0
- package/src/user-types.js +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
function isArrayOfListeners(listeners) {
|
|
2
|
+
return Array.isArray(listeners);
|
|
3
|
+
}
|
|
4
|
+
function isOnlyListener(listeners) {
|
|
5
|
+
return !Array.isArray(listeners);
|
|
6
|
+
}
|
|
7
|
+
class EventEmitter {
|
|
8
|
+
#events = new Map();
|
|
9
|
+
addListener(event, listener) {
|
|
10
|
+
const listeners = this.#events.get(event);
|
|
11
|
+
if (listeners) {
|
|
12
|
+
if (isArrayOfListeners(listeners)) listeners.push(listener);
|
|
13
|
+
else if (isOnlyListener(listeners)) this.#events.set(event, [
|
|
14
|
+
listeners,
|
|
15
|
+
listener
|
|
16
|
+
]);
|
|
17
|
+
} else this.#events.set(event, listener);
|
|
18
|
+
}
|
|
19
|
+
removeListener(event, listener) {
|
|
20
|
+
if (!this.#events.has(event)) return;
|
|
21
|
+
const listenerOrListeners = this.#events.get(event);
|
|
22
|
+
let position = -1;
|
|
23
|
+
if (listenerOrListeners === listener) this.#events.delete(event);
|
|
24
|
+
else if (Array.isArray(listenerOrListeners)) {
|
|
25
|
+
for(let i = listenerOrListeners.length; i-- > 0;)if (listenerOrListeners[i] === listener) {
|
|
26
|
+
position = i;
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
if (position < 0) return;
|
|
30
|
+
if (1 === listenerOrListeners.length) {
|
|
31
|
+
listenerOrListeners.length = 0;
|
|
32
|
+
this.#events.delete(event);
|
|
33
|
+
} else listenerOrListeners.splice(position, 1);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
removeAllListeners(event) {
|
|
37
|
+
if (event && this.#events.has(event)) this.#events.delete(event);
|
|
38
|
+
else if (void 0 === event) this.#events.clear();
|
|
39
|
+
}
|
|
40
|
+
emit(event, ...args) {
|
|
41
|
+
if (this.#events) {
|
|
42
|
+
const handler = this.#events.get(event);
|
|
43
|
+
if (handler) this.invokeHandler(handler, args);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
once(event, listener) {
|
|
47
|
+
const handler = (...args)=>{
|
|
48
|
+
this.removeListener(event, handler);
|
|
49
|
+
listener(...args);
|
|
50
|
+
};
|
|
51
|
+
this.on(event, handler);
|
|
52
|
+
}
|
|
53
|
+
on(event, listener) {
|
|
54
|
+
this.addListener(event, listener);
|
|
55
|
+
}
|
|
56
|
+
hasListener(event, listener) {
|
|
57
|
+
const listeners = this.#events.get(event);
|
|
58
|
+
if (Array.isArray(listeners)) return listeners.includes(listener);
|
|
59
|
+
return listeners === listener;
|
|
60
|
+
}
|
|
61
|
+
invokeHandler(handler, args) {
|
|
62
|
+
if (isArrayOfListeners(handler)) handler.slice().forEach((listener)=>this.invokeHandler(listener, args));
|
|
63
|
+
else switch(args.length){
|
|
64
|
+
case 0:
|
|
65
|
+
handler();
|
|
66
|
+
break;
|
|
67
|
+
case 1:
|
|
68
|
+
handler(args[0]);
|
|
69
|
+
break;
|
|
70
|
+
case 2:
|
|
71
|
+
handler(args[0], args[1]);
|
|
72
|
+
break;
|
|
73
|
+
default:
|
|
74
|
+
handler.call(null, ...args);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export { EventEmitter };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { partition } from "./array-utils.js";
|
|
2
|
+
import { wordify } from "./text-utils.js";
|
|
3
|
+
import react from "react";
|
|
4
|
+
import { getLayoutComponent } from "./component-registry.js";
|
|
5
|
+
const env = process.env.NODE_ENV;
|
|
6
|
+
const isStaticFeature = (feature)=>null !== feature && "object" == typeof feature && "type" in feature;
|
|
7
|
+
const isStaticFeatures = (features)=>Array.isArray(features) && features.every(isStaticFeature);
|
|
8
|
+
function featureFromJson({ type }) {
|
|
9
|
+
const componentType = type.match(/^[a-z]/) ? type : getLayoutComponent(type);
|
|
10
|
+
if (void 0 === componentType) throw Error(`layoutUtils unable to create feature component from JSON, unknown type ${type}`);
|
|
11
|
+
return react.createElement(componentType);
|
|
12
|
+
}
|
|
13
|
+
const isCustomFeature = (feature)=>"vuu-features" === feature.leftNavLocation;
|
|
14
|
+
const isWildcardSchema = (vuuTables)=>"*" === vuuTables;
|
|
15
|
+
const isVuuTables = (vuuTables)=>Array.isArray(vuuTables);
|
|
16
|
+
const hasFilterTableFeatureProps = (props)=>"object" == typeof props.ComponentProps && null !== props.ComponentProps && "tableSchema" in props.ComponentProps;
|
|
17
|
+
const isSameTable = (t1, t2)=>{
|
|
18
|
+
t1.module === t2.module && (t1.table, t2.table);
|
|
19
|
+
};
|
|
20
|
+
const byModule = (schema1, schema2)=>{
|
|
21
|
+
const m1 = schema1.table.module.toLowerCase();
|
|
22
|
+
const m2 = schema2.table.module.toLowerCase();
|
|
23
|
+
if (m1 < m2) return -1;
|
|
24
|
+
if (m1 > m2) return 1;
|
|
25
|
+
if (schema1.table.table < schema2.table.table) return -1;
|
|
26
|
+
if (schema1.table.table > schema2.table.table) return 1;
|
|
27
|
+
return 0;
|
|
28
|
+
};
|
|
29
|
+
const getFilterTableFeatures = (schemas, getFeaturePath)=>schemas.sort(byModule).map((schema)=>({
|
|
30
|
+
...getFeaturePath({
|
|
31
|
+
env: env,
|
|
32
|
+
fileName: "FilterTable"
|
|
33
|
+
}),
|
|
34
|
+
ComponentProps: {
|
|
35
|
+
tableSchema: schema
|
|
36
|
+
},
|
|
37
|
+
ViewProps: {
|
|
38
|
+
allowRename: true
|
|
39
|
+
},
|
|
40
|
+
title: `${schema.table.module} ${schema.table.table}`
|
|
41
|
+
}));
|
|
42
|
+
const assertComponentRegistered = (componentName, component)=>{
|
|
43
|
+
if ("function" != typeof component) console.warn(`${componentName} module not loaded, will be unabale to deserialize from layout JSON`);
|
|
44
|
+
};
|
|
45
|
+
const assertComponentsRegistered = (componentList)=>{
|
|
46
|
+
for (const { componentName, component } of componentList)assertComponentRegistered(componentName, component);
|
|
47
|
+
};
|
|
48
|
+
const getCustomAndTableFeatures = (dynamicFeatures, tableSchemas)=>{
|
|
49
|
+
const [customFeatureConfig, tableFeaturesConfig] = partition(dynamicFeatures, isCustomFeature);
|
|
50
|
+
const customFeatures = [];
|
|
51
|
+
const tableFeatures = [];
|
|
52
|
+
for (const { featureProps = {}, viewProps, ...feature } of tableFeaturesConfig){
|
|
53
|
+
const { vuuTables } = featureProps;
|
|
54
|
+
if (isWildcardSchema(vuuTables)) {
|
|
55
|
+
if (tableSchemas) for (const tableSchema of tableSchemas)tableFeatures.push({
|
|
56
|
+
...feature,
|
|
57
|
+
ComponentProps: {
|
|
58
|
+
tableSchema
|
|
59
|
+
},
|
|
60
|
+
title: `${tableSchema.table.module} ${wordify(tableSchema.table.table)}`,
|
|
61
|
+
ViewProps: {
|
|
62
|
+
...viewProps,
|
|
63
|
+
allowRename: true
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
for (const { featureProps = {}, viewProps, ...feature } of customFeatureConfig){
|
|
69
|
+
const { vuuTables } = featureProps;
|
|
70
|
+
if (isVuuTables(vuuTables)) {
|
|
71
|
+
if (tableSchemas) customFeatures.push({
|
|
72
|
+
...feature,
|
|
73
|
+
ComponentProps: vuuTables.reduce((map, vuuTable)=>{
|
|
74
|
+
map[`${vuuTable.table}Schema`] = tableSchemas.find((tableSchema)=>isSameTable(vuuTable, tableSchema.table));
|
|
75
|
+
return map;
|
|
76
|
+
}, {}),
|
|
77
|
+
ViewProps: viewProps
|
|
78
|
+
});
|
|
79
|
+
} else customFeatures.push(feature);
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
dynamicFeatures: customFeatures,
|
|
83
|
+
tableFeatures: tableFeatures
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
export { assertComponentRegistered, assertComponentsRegistered, byModule, env, featureFromJson, getCustomAndTableFeatures, getFilterTableFeatures, hasFilterTableFeatureProps, isCustomFeature, isSameTable, isStaticFeatures, isVuuTables, isWildcardSchema };
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { ScaledDecimal } from "../ScaledDecimal.js";
|
|
2
|
+
const singleValueFilterOps = new Set([
|
|
3
|
+
"=",
|
|
4
|
+
"!=",
|
|
5
|
+
">",
|
|
6
|
+
">=",
|
|
7
|
+
"<",
|
|
8
|
+
"<=",
|
|
9
|
+
"contains",
|
|
10
|
+
"starts",
|
|
11
|
+
"ends"
|
|
12
|
+
]);
|
|
13
|
+
const isBetweenOperator = (op)=>"between" === op || "between-inclusive" === op;
|
|
14
|
+
const isValidFilterClauseOp = (op)=>"in" === op || singleValueFilterOps.has(op);
|
|
15
|
+
const isNamedFilter = (f)=>void 0 !== f && void 0 !== f.name;
|
|
16
|
+
const isSingleValueFilter = (f)=>void 0 !== f && singleValueFilterOps.has(f.op);
|
|
17
|
+
const isScaledDecimalFilterClause = (f)=>f.value instanceof ScaledDecimal;
|
|
18
|
+
const isSerializableFilter = (f)=>isSingleValueFilter(f) && "asQuery" in f && "function" == typeof f["asQuery"];
|
|
19
|
+
const isFilter = (f)=>"object" == typeof f && null !== f && (isFilterClause(f) || isMultiClauseFilter(f));
|
|
20
|
+
const isFilterClause = (f)=>void 0 !== f && (isSingleValueFilter(f) || isMultiValueFilter(f));
|
|
21
|
+
const isMultiValueFilter = (f)=>void 0 !== f && "in" === f.op;
|
|
22
|
+
const isInFilter = (f)=>"in" === f.op;
|
|
23
|
+
const isAndFilter = (f)=>f?.op === "and";
|
|
24
|
+
const isBetweenFilter = (f)=>isAndFilter(f) && 2 === f.filters.length && f.filters[0].column === f.filters[1].column && (">" === f.filters[0].op && "<" === f.filters[1].op || ">=" === f.filters[0].op && "<=" === f.filters[1].op);
|
|
25
|
+
const isOrFilter = (f)=>f?.op === "or";
|
|
26
|
+
const isCompleteFilter = (filter)=>isSingleValueFilter(filter) && void 0 !== filter.column && void 0 !== filter.op && void 0 !== filter.value;
|
|
27
|
+
function isMultiClauseFilter(f) {
|
|
28
|
+
return void 0 !== f && ("and" === f.op || "or" === f.op);
|
|
29
|
+
}
|
|
30
|
+
const isExtendedFilter = (f)=>"object" == typeof f.extendedOptions;
|
|
31
|
+
const applyFilterToColumns = (columns, { filterStruct })=>columns.map((column)=>{
|
|
32
|
+
const filter = extractFilterForColumn(filterStruct, column.name);
|
|
33
|
+
if (void 0 !== filter) return {
|
|
34
|
+
...column,
|
|
35
|
+
filter
|
|
36
|
+
};
|
|
37
|
+
if (column.filter) return {
|
|
38
|
+
...column,
|
|
39
|
+
filter: void 0
|
|
40
|
+
};
|
|
41
|
+
return column;
|
|
42
|
+
});
|
|
43
|
+
const isFilteredColumn = (column)=>void 0 !== column.filter;
|
|
44
|
+
const stripFilterFromColumns = (columns)=>columns.map((col)=>{
|
|
45
|
+
const { filter, ...rest } = col;
|
|
46
|
+
return filter ? rest : col;
|
|
47
|
+
});
|
|
48
|
+
const extractFilterForColumn = (filter, columnName)=>{
|
|
49
|
+
if (isMultiClauseFilter(filter)) return collectFiltersForColumn(filter, columnName);
|
|
50
|
+
if (isFilterClause(filter)) return filter.column === columnName ? filter : void 0;
|
|
51
|
+
};
|
|
52
|
+
const collectFiltersForColumn = (filter, columnName)=>{
|
|
53
|
+
const { filters, op } = filter;
|
|
54
|
+
const results = [];
|
|
55
|
+
filters.forEach((filter)=>{
|
|
56
|
+
const ffc = extractFilterForColumn(filter, columnName);
|
|
57
|
+
if (ffc) results.push(ffc);
|
|
58
|
+
});
|
|
59
|
+
if (0 === results.length) return;
|
|
60
|
+
if (1 === results.length) return results[0];
|
|
61
|
+
return {
|
|
62
|
+
op,
|
|
63
|
+
filters: results
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
const stringifyBoolean = (value)=>"boolean" == typeof value ? "${filter.value}" : value;
|
|
67
|
+
const getColumnValueFromFilter = (column, operator, filter)=>{
|
|
68
|
+
if (isSingleValueFilter(filter)) {
|
|
69
|
+
if (filter.column === column.name) {
|
|
70
|
+
if (!operator.startsWith("between")) return stringifyBoolean(filter.value);
|
|
71
|
+
else if ("=" === filter.op) return [
|
|
72
|
+
`${filter.value}`,
|
|
73
|
+
""
|
|
74
|
+
];
|
|
75
|
+
else if ("<" === filter.op) return [
|
|
76
|
+
"",
|
|
77
|
+
`${filter.value}`
|
|
78
|
+
];
|
|
79
|
+
}
|
|
80
|
+
} else if (isBetweenFilter(filter)) {
|
|
81
|
+
if (filter.filters[0].column === column.name) {
|
|
82
|
+
const [{ value: v1 }, { value: v2 }] = filter.filters;
|
|
83
|
+
return [
|
|
84
|
+
`${v1}`,
|
|
85
|
+
`${v2}`
|
|
86
|
+
];
|
|
87
|
+
}
|
|
88
|
+
} else if (isAndFilter(filter)) {
|
|
89
|
+
const filterForColumn = filter.filters.find((f)=>isBetweenFilter(f) ? f.filters[0].column === column.name : f.column === column.name);
|
|
90
|
+
if (isSingleValueFilter(filterForColumn)) return stringifyBoolean(filterForColumn.value);
|
|
91
|
+
if (operator.startsWith("between") && isBetweenFilter(filterForColumn)) {
|
|
92
|
+
const [{ value: v1 }, { value: v2 }] = filterForColumn.filters;
|
|
93
|
+
return [
|
|
94
|
+
`${v1}`,
|
|
95
|
+
`${v2}`
|
|
96
|
+
];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (!operator.startsWith("between")) return "";
|
|
100
|
+
if ("time" === column.type) return [
|
|
101
|
+
"00:00:00",
|
|
102
|
+
"23:59:59"
|
|
103
|
+
];
|
|
104
|
+
return [
|
|
105
|
+
"",
|
|
106
|
+
""
|
|
107
|
+
];
|
|
108
|
+
};
|
|
109
|
+
const betweenFiltersAreEqual = (f1, f2)=>{
|
|
110
|
+
const [from1, to1] = f1.filters;
|
|
111
|
+
const [from2, to2] = f2.filters;
|
|
112
|
+
return filtersAreEqual(from1, from2) && filtersAreEqual(to1, to2);
|
|
113
|
+
};
|
|
114
|
+
const singleValueFilterClausesAreEqual = (f1, f2)=>f1.column === f2.column && f1.op === f2.op && f1.value === f2.value;
|
|
115
|
+
const findEqualFilter = (filters)=>(filter)=>{
|
|
116
|
+
if (isBetweenFilter(filter)) {
|
|
117
|
+
const target = filters.find((f)=>isBetweenFilter(f) && betweenFiltersAreEqual(f, filter));
|
|
118
|
+
return void 0 !== target;
|
|
119
|
+
}
|
|
120
|
+
{
|
|
121
|
+
const target = filters.find((f)=>f.column === filter.column);
|
|
122
|
+
if (target) return target.op === filter.op && target.value === filter.value;
|
|
123
|
+
}
|
|
124
|
+
return false;
|
|
125
|
+
};
|
|
126
|
+
const filtersAreEqual = (f1, f2)=>{
|
|
127
|
+
if (null == f1 && null == f2) return true;
|
|
128
|
+
if (null == f1 || null == f2) ;
|
|
129
|
+
else if (isSingleValueFilter(f1) && isSingleValueFilter(f2)) return singleValueFilterClausesAreEqual(f1, f2);
|
|
130
|
+
else if (isBetweenFilter(f1) && isBetweenFilter(f2)) return betweenFiltersAreEqual(f1, f2);
|
|
131
|
+
else if (isAndFilter(f1) && isAndFilter(f2)) {
|
|
132
|
+
if (f1.filters.length === f2.filters.length) return f1.filters.every(findEqualFilter(f2.filters));
|
|
133
|
+
}
|
|
134
|
+
return false;
|
|
135
|
+
};
|
|
136
|
+
export { applyFilterToColumns, extractFilterForColumn, filtersAreEqual, getColumnValueFromFilter, isAndFilter, isBetweenFilter, isBetweenOperator, isCompleteFilter, isExtendedFilter, isFilter, isFilterClause, isFilteredColumn, isInFilter, isMultiClauseFilter, isMultiValueFilter, isNamedFilter, isOrFilter, isScaledDecimalFilterClause, isSerializableFilter, isSingleValueFilter, isValidFilterClauseOp, stripFilterFromColumns };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { isDateTimeDataValue } from "../column-utils.js";
|
|
2
|
+
import { isMultiClauseFilter, isMultiValueFilter, isSerializableFilter } from "./filter-utils.js";
|
|
3
|
+
import { ScaledDecimal } from "../ScaledDecimal.js";
|
|
4
|
+
const filterValue = (value)=>"string" == typeof value ? `"${value}"` : value instanceof ScaledDecimal ? value.toString() : value;
|
|
5
|
+
const quotedStrings = (value)=>"string" == typeof value ? `"${value}"` : value;
|
|
6
|
+
const removeOuterMostParentheses = (s)=>s.replace(/^\((.*)\)$/, "$1");
|
|
7
|
+
const filterAsQuery = (f, opts)=>removeOuterMostParentheses(filterAsQueryCore(f, opts));
|
|
8
|
+
const filterAsQueryCore = (f, opts)=>{
|
|
9
|
+
if (isMultiClauseFilter(f)) {
|
|
10
|
+
const multiClauseFilter = f.filters.map((filter)=>filterAsQueryCore(filter, opts)).join(` ${f.op} `);
|
|
11
|
+
return `(${multiClauseFilter})`;
|
|
12
|
+
}
|
|
13
|
+
if (isMultiValueFilter(f)) return `${f.column} ${f.op} [${f.values.map(quotedStrings).join(",")}]`;
|
|
14
|
+
return singleValueFilterAsQuery(f, opts);
|
|
15
|
+
};
|
|
16
|
+
function singleValueFilterAsQuery(f, opts) {
|
|
17
|
+
if (isSerializableFilter(f)) return f.asQuery();
|
|
18
|
+
{
|
|
19
|
+
const column = opts?.columnsByName?.[f.column];
|
|
20
|
+
if (column && isDateTimeDataValue(column)) return dateFilterAsQuery(f);
|
|
21
|
+
return defaultSingleValueFilterAsQuery(f);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const ONE_DAY_IN_MILLIS = 86400000;
|
|
25
|
+
function dateFilterAsQuery(f) {
|
|
26
|
+
switch(f.op){
|
|
27
|
+
case "=":
|
|
28
|
+
{
|
|
29
|
+
const filters = [
|
|
30
|
+
{
|
|
31
|
+
op: ">=",
|
|
32
|
+
column: f.column,
|
|
33
|
+
value: f.value
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
op: "<",
|
|
37
|
+
column: f.column,
|
|
38
|
+
value: f.value + ONE_DAY_IN_MILLIS
|
|
39
|
+
}
|
|
40
|
+
];
|
|
41
|
+
return filterAsQueryCore({
|
|
42
|
+
op: "and",
|
|
43
|
+
filters
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
case "!=":
|
|
47
|
+
{
|
|
48
|
+
const filters = [
|
|
49
|
+
{
|
|
50
|
+
op: "<",
|
|
51
|
+
column: f.column,
|
|
52
|
+
value: f.value
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
op: ">=",
|
|
56
|
+
column: f.column,
|
|
57
|
+
value: f.value + ONE_DAY_IN_MILLIS
|
|
58
|
+
}
|
|
59
|
+
];
|
|
60
|
+
return filterAsQueryCore({
|
|
61
|
+
op: "or",
|
|
62
|
+
filters
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
default:
|
|
66
|
+
return defaultSingleValueFilterAsQuery(f);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const defaultSingleValueFilterAsQuery = (f)=>`${f.column} ${f.op} ${filterValue(f.value)}`;
|
|
70
|
+
export { ONE_DAY_IN_MILLIS, dateFilterAsQuery, filterAsQuery };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { stringIsValidDecimal, stringIsValidInt } from "./data-utils.js";
|
|
2
|
+
import { Time, isValidTimeString } from "./date/index.js";
|
|
3
|
+
import { queryClosest } from "./html-utils.js";
|
|
4
|
+
import { ScaledDecimal2, ScaledDecimal4, ScaledDecimal6, ScaledDecimal8 } from "./ScaledDecimal.js";
|
|
5
|
+
class DataValidationError extends Error {
|
|
6
|
+
actualType;
|
|
7
|
+
expectedType;
|
|
8
|
+
constructor(message, expectedType, actualType){
|
|
9
|
+
super(message);
|
|
10
|
+
this.actualType = actualType;
|
|
11
|
+
this.expectedType = expectedType;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const getFieldName = (target)=>{
|
|
15
|
+
const saltFormField = queryClosest(target, "[data-field]");
|
|
16
|
+
const fieldName = saltFormField?.dataset.field;
|
|
17
|
+
if (fieldName) return fieldName;
|
|
18
|
+
throw Error("named form field not found");
|
|
19
|
+
};
|
|
20
|
+
const isNumber = (type, value)=>"number" === type;
|
|
21
|
+
const isValidRange = ([val1, val2])=>{
|
|
22
|
+
if (isValidTimeString(val1) && isValidTimeString(val2)) return val2 > val1;
|
|
23
|
+
return true;
|
|
24
|
+
};
|
|
25
|
+
function getTypedRange([value1, value2], dataType, options) {
|
|
26
|
+
return [
|
|
27
|
+
getTypedValue(value1, dataType, false, options),
|
|
28
|
+
getTypedValue(value2, dataType, false, options)
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
const getActualType = (value)=>{
|
|
32
|
+
if ("boolean" == typeof value) return "boolean";
|
|
33
|
+
if ("number" == typeof value) return "number";
|
|
34
|
+
return "string";
|
|
35
|
+
};
|
|
36
|
+
function getTypedValue(value, type, throwIfInvalid = false, options) {
|
|
37
|
+
switch(type){
|
|
38
|
+
case "int":
|
|
39
|
+
case "long":
|
|
40
|
+
if (stringIsValidInt(value)) return parseInt(value, 10);
|
|
41
|
+
if (isValidTimeString(value)) return value;
|
|
42
|
+
if (!throwIfInvalid) return;
|
|
43
|
+
else throw Error(`value ${value} is not a valid ${type}`);
|
|
44
|
+
case "scaleddecimal2":
|
|
45
|
+
return ScaledDecimal2(value);
|
|
46
|
+
case "scaleddecimal4":
|
|
47
|
+
return ScaledDecimal4(value);
|
|
48
|
+
case "scaleddecimal6":
|
|
49
|
+
return ScaledDecimal6(value);
|
|
50
|
+
case "scaleddecimal8":
|
|
51
|
+
return ScaledDecimal8(value);
|
|
52
|
+
case "double":
|
|
53
|
+
case "number":
|
|
54
|
+
if (stringIsValidDecimal(value)) return parseFloat(value);
|
|
55
|
+
if (!throwIfInvalid) return;
|
|
56
|
+
throw new DataValidationError(`value ${value} is not a valid ${type}`, "decimal", getActualType(value));
|
|
57
|
+
case "boolean":
|
|
58
|
+
return "true" === value;
|
|
59
|
+
case "time":
|
|
60
|
+
if (isValidTimeString(value)) if (options?.type === "TimeString") return value;
|
|
61
|
+
else return +Time(value).asDate();
|
|
62
|
+
if (value.length > 0 && Time.isDateInMillis(value)) return Number(value);
|
|
63
|
+
if (!throwIfInvalid) return;
|
|
64
|
+
else throw Error(`value ${value} is not a valid ${type}`);
|
|
65
|
+
default:
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export { DataValidationError, getFieldName, getTypedRange, getTypedValue, isNumber, isValidRange };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { isDateTimeDataValue, isMappedValueTypeRenderer, isTimeDataValue, isTypeDescriptor } from "./column-utils.js";
|
|
2
|
+
import { dateTimePattern, formatDate } from "./date/index.js";
|
|
3
|
+
import { roundDecimal, roundScaledDecimal } from "./round-decimal.js";
|
|
4
|
+
import { isNumericType } from "./protocol-message-utils.js";
|
|
5
|
+
const DEFAULT_NUMERIC_FORMAT = {};
|
|
6
|
+
const defaultValueFormatter = (value)=>null == value ? "" : "string" == typeof value ? value : value.toString();
|
|
7
|
+
const dateFormatter = (column)=>{
|
|
8
|
+
const pattern = dateTimePattern(column.type);
|
|
9
|
+
const formatter = formatDate(pattern);
|
|
10
|
+
return (value)=>{
|
|
11
|
+
if ("number" == typeof value && 0 !== value) return formatter(new Date(value));
|
|
12
|
+
return "";
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
const numericFormatter = ({ align = "right", serverDataType, type })=>{
|
|
16
|
+
if (void 0 === type || "string" == typeof type) return defaultValueFormatter;
|
|
17
|
+
{
|
|
18
|
+
const { alignOnDecimals = false, decimals, roundingRule, useLocaleString, zeroPad = false } = type.formatting ?? DEFAULT_NUMERIC_FORMAT;
|
|
19
|
+
return (value)=>{
|
|
20
|
+
if (serverDataType?.startsWith("scaleddecimal")) if ("string" == typeof value) return roundScaledDecimal(value, align, decimals, zeroPad, alignOnDecimals, useLocaleString, roundingRule);
|
|
21
|
+
else throw Error(`[formatting-utils] numericFormatter, invalid data for ${serverDataType}: '${value}'`);
|
|
22
|
+
if ("string" == typeof value && (value.startsWith("Σ") || value.startsWith("["))) return value;
|
|
23
|
+
const number = "number" == typeof value ? value : "string" == typeof value ? parseFloat(value) : void 0;
|
|
24
|
+
return roundDecimal(number, align, decimals, zeroPad, alignOnDecimals, useLocaleString, roundingRule);
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const mapFormatter = (map)=>(value)=>map[value] ?? "";
|
|
29
|
+
const NumericTypes = [
|
|
30
|
+
"decimal",
|
|
31
|
+
"number"
|
|
32
|
+
];
|
|
33
|
+
const getValueFormatter = (column, serverDataType = column.serverDataType)=>{
|
|
34
|
+
if (isDateTimeDataValue(column, serverDataType) || isTimeDataValue(column)) return dateFormatter(column);
|
|
35
|
+
const { type } = column;
|
|
36
|
+
if (isTypeDescriptor(type) && isMappedValueTypeRenderer(type?.renderer)) return mapFormatter(type.renderer.map);
|
|
37
|
+
if (isNumericType(serverDataType) || isTypeDescriptor(type) && NumericTypes.includes(type.name)) return numericFormatter(column);
|
|
38
|
+
if ("string" === serverDataType || "char" === serverDataType) return (value)=>value;
|
|
39
|
+
return defaultValueFormatter;
|
|
40
|
+
};
|
|
41
|
+
const lowerCase = (str)=>str.toLowerCase();
|
|
42
|
+
export { defaultValueFormatter, getValueFormatter, lowerCase, numericFormatter };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
function addGroupColumn(groupBy, column) {
|
|
2
|
+
if (groupBy.length > 0) return groupBy.concat(column.name);
|
|
3
|
+
return [
|
|
4
|
+
column.name
|
|
5
|
+
];
|
|
6
|
+
}
|
|
7
|
+
const removeGroupColumn = (groupBy, column)=>groupBy.filter((colName)=>colName !== column.name);
|
|
8
|
+
const getGroupStatus = (columnName, groupBy)=>{
|
|
9
|
+
if (void 0 === groupBy || 0 === groupBy.length) return "no-groupby";
|
|
10
|
+
{
|
|
11
|
+
const indexPos = groupBy.indexOf(columnName);
|
|
12
|
+
if (-1 === indexPos) return 1 === groupBy.length ? "single-groupby-other-column" : "multi-groupby-other-columns";
|
|
13
|
+
return 1 === groupBy.length ? "single-groupby" : "single-groupby-other-column";
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
export { addGroupColumn, getGroupStatus, removeGroupColumn };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const createEl = (elementType, className, textContent)=>{
|
|
2
|
+
const el = document.createElement(elementType);
|
|
3
|
+
if (className) el.className = className;
|
|
4
|
+
if (textContent) el.textContent = textContent;
|
|
5
|
+
return el;
|
|
6
|
+
};
|
|
7
|
+
const getFocusableElement = (el, tabIndex)=>{
|
|
8
|
+
if (el) {
|
|
9
|
+
if (el.hasAttribute("tabindex")) {
|
|
10
|
+
const rootTabIndex = parseInt(el.getAttribute("tabindex"));
|
|
11
|
+
if (!isNaN(rootTabIndex) && (void 0 === tabIndex || rootTabIndex === tabIndex)) return el;
|
|
12
|
+
}
|
|
13
|
+
const focusableEl = "number" == typeof tabIndex ? el.querySelector(`[tabindex="${tabIndex}"]`) : el.querySelector("[tabindex]");
|
|
14
|
+
if (focusableEl) return focusableEl;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const getElementDataIndex = (el)=>{
|
|
18
|
+
if (el) {
|
|
19
|
+
const index = parseInt(el.dataset.index || "");
|
|
20
|
+
if (!isNaN(index)) return index;
|
|
21
|
+
}
|
|
22
|
+
return -1;
|
|
23
|
+
};
|
|
24
|
+
function queryClosest(el, cssQueryString, throwIfNotFound) {
|
|
25
|
+
if (el) {
|
|
26
|
+
const result = el.closest(cssQueryString);
|
|
27
|
+
if (result) return result;
|
|
28
|
+
}
|
|
29
|
+
if (!throwIfNotFound) return null;
|
|
30
|
+
throw Error(`no element found to match '${cssQueryString}'`);
|
|
31
|
+
}
|
|
32
|
+
const getClosest = (el, dataProperty)=>queryClosest(el, `[data-${dataProperty}]`);
|
|
33
|
+
const getClosestIndexItem = (el)=>getClosest(el, "index");
|
|
34
|
+
function getElementByDataIndex(container, index, throwIfNotFound = false) {
|
|
35
|
+
if (null == container && throwIfNotFound) throw Error("html-utils getElementByDataIndex, container is null");
|
|
36
|
+
const element = container?.querySelector(`[data-index="${index}"]`);
|
|
37
|
+
if (element) return element;
|
|
38
|
+
if (!throwIfNotFound) return;
|
|
39
|
+
throw Error("html-utils getElementByDataIndex, Item not found with data-index='${index}'");
|
|
40
|
+
}
|
|
41
|
+
const focusFirstFocusableElement = (el, tabIndex)=>{
|
|
42
|
+
requestAnimationFrame(()=>{
|
|
43
|
+
const focusableElement = getFocusableElement(el, tabIndex);
|
|
44
|
+
if (focusableElement) focusableElement.focus();
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
const isSelectableElement = (el)=>{
|
|
48
|
+
const item = el?.closest("[data-index]");
|
|
49
|
+
if (!item || item.ariaDisabled || "false" === item.dataset.selectable || item.querySelector('[data-selectable="false"],[aria-disabled="true"]')) return false;
|
|
50
|
+
return true;
|
|
51
|
+
};
|
|
52
|
+
const isInputElement = (el)=>{
|
|
53
|
+
if (null === el) return false;
|
|
54
|
+
return "INPUT" === el.tagName;
|
|
55
|
+
};
|
|
56
|
+
const isDateInput = (el)=>isInputElement(el) && el.classList.contains("saltDateInput-input");
|
|
57
|
+
const hasOpenOptionList = (el)=>{
|
|
58
|
+
if (null !== el) return "true" === el.ariaExpanded;
|
|
59
|
+
};
|
|
60
|
+
let size;
|
|
61
|
+
function getScrollbarSize() {
|
|
62
|
+
if (void 0 === size) {
|
|
63
|
+
let outer = document.createElement("div");
|
|
64
|
+
outer.className = "scrollable-content";
|
|
65
|
+
outer.style.width = "50px";
|
|
66
|
+
outer.style.height = "50px";
|
|
67
|
+
outer.style.overflowY = "scroll";
|
|
68
|
+
outer.style.position = "absolute";
|
|
69
|
+
outer.style.top = "-200px";
|
|
70
|
+
outer.style.left = "-200px";
|
|
71
|
+
const inner = document.createElement("div");
|
|
72
|
+
inner.style.height = "100px";
|
|
73
|
+
inner.style.width = "100%";
|
|
74
|
+
outer.appendChild(inner);
|
|
75
|
+
document.body.appendChild(outer);
|
|
76
|
+
const outerWidth = outer.offsetWidth;
|
|
77
|
+
const innerWidth = inner.offsetWidth;
|
|
78
|
+
document.body.removeChild(outer);
|
|
79
|
+
size = outerWidth - innerWidth;
|
|
80
|
+
outer = null;
|
|
81
|
+
}
|
|
82
|
+
return size;
|
|
83
|
+
}
|
|
84
|
+
const dispatchMouseEvent = (el, type)=>{
|
|
85
|
+
const evt = new MouseEvent(type, {
|
|
86
|
+
view: window,
|
|
87
|
+
bubbles: true,
|
|
88
|
+
cancelable: true
|
|
89
|
+
});
|
|
90
|
+
el.dispatchEvent(evt);
|
|
91
|
+
};
|
|
92
|
+
const dispatchKeyboardEvent = (el, type, key)=>{
|
|
93
|
+
const evt = new KeyboardEvent(type, {
|
|
94
|
+
key,
|
|
95
|
+
view: window,
|
|
96
|
+
bubbles: true,
|
|
97
|
+
cancelable: true
|
|
98
|
+
});
|
|
99
|
+
el.dispatchEvent(evt);
|
|
100
|
+
};
|
|
101
|
+
const dispatchCustomEvent = (el, type)=>{
|
|
102
|
+
const evt = new Event(type, {
|
|
103
|
+
bubbles: true,
|
|
104
|
+
cancelable: true
|
|
105
|
+
});
|
|
106
|
+
el.dispatchEvent(evt);
|
|
107
|
+
};
|
|
108
|
+
export { createEl, dispatchCustomEvent, dispatchKeyboardEvent, dispatchMouseEvent, focusFirstFocusableElement, getClosest, getClosestIndexItem, getElementByDataIndex, getElementDataIndex, getFocusableElement, getScrollbarSize, hasOpenOptionList, isDateInput, isInputElement, isSelectableElement, queryClosest };
|