@rokkit/data 1.0.0-next.100
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/LICENSE +21 -0
- package/README.md +50 -0
- package/package.json +56 -0
- package/src/aggregators.js +24 -0
- package/src/constants.js +72 -0
- package/src/convert.js +36 -0
- package/src/dataset.js +428 -0
- package/src/filter.js +57 -0
- package/src/formatter.js +56 -0
- package/src/hierarchy.js +136 -0
- package/src/index.js +7 -0
- package/src/infer.js +263 -0
- package/src/list.js +232 -0
- package/src/metadata.js +106 -0
- package/src/model.js +100 -0
- package/src/parser.js +111 -0
- package/src/renamer.js +98 -0
- package/src/rollup.js +138 -0
- package/src/types.js +173 -0
- package/src/utils.js +36 -0
- package/src/view.js +181 -0
package/src/filter.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { filterOperations } from './constants'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Filters an array of objects based on a specified condition defined in the options.
|
|
5
|
+
* If a column is provided, it filters based on the value of that column. Otherwise, it filters
|
|
6
|
+
* based on a value present in any of the object's properties.
|
|
7
|
+
*
|
|
8
|
+
* @param {Object[]} data - The array of objects to filter.
|
|
9
|
+
* @param {Object} options - The filtering options that define the condition.
|
|
10
|
+
* @param {string} [options.column] - The property name in the objects to filter by. If omitted, all properties are considered.
|
|
11
|
+
* @param {string|number} [options.value] - The value to compare against when filtering.
|
|
12
|
+
* @param {string} [options.operator] - The operator to use for comparison, should match a key in the filterOperations object.
|
|
13
|
+
* @returns {Object[]} - The filtered array of objects.
|
|
14
|
+
*/
|
|
15
|
+
export function filterObjectArray(data, options) {
|
|
16
|
+
const { column, value, operator } = options
|
|
17
|
+
// If no operator or value is given, return the data unchanged.
|
|
18
|
+
if (!operator || !value) return data
|
|
19
|
+
// If a column is specified, perform the filtering on that specific column.
|
|
20
|
+
if (column) return data.filter((row) => filterOperations[operator](row[column], value))
|
|
21
|
+
|
|
22
|
+
return data.filter((row) => findMatchingRow(row, value, operator))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Checks if a row matches the given value based on the specified operator.
|
|
27
|
+
* Supports both standard and negated operators by checking the operator string
|
|
28
|
+
* for a leading '!' character, which indicates negation.
|
|
29
|
+
*
|
|
30
|
+
* @param {Object} row - The row object in which to search for the value match.
|
|
31
|
+
* @param {any} value - The value to match against the row's properties.
|
|
32
|
+
* @param {string} operator - The operator used to compare the row's properties to the value.
|
|
33
|
+
* When operator starts with '!', it negates the result of the comparison.
|
|
34
|
+
* @returns {boolean} - Returns true if the row matches the condition; otherwise, returns false.
|
|
35
|
+
*/
|
|
36
|
+
function findMatchingRow(row, value, operator) {
|
|
37
|
+
const op = operator.startsWith('!') ? operator.slice(1) : operator
|
|
38
|
+
const matched = Object.keys(row).some((key) => filterOperations[op](row[key], value))
|
|
39
|
+
|
|
40
|
+
return operator.startsWith('!') ? !matched : matched
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Applies multiple filtering conditions to an array of objects. This function iterates through each filter option,
|
|
45
|
+
* successively narrowing down the data.
|
|
46
|
+
*
|
|
47
|
+
* @param {Object[]} data - The array of objects to filter.
|
|
48
|
+
* @param {Object[]} filters - An array of filtering options to apply to the data.
|
|
49
|
+
* @returns {Object[]} - The filtered array of objects that meet all conditions specified by the filters.
|
|
50
|
+
*/
|
|
51
|
+
export function filterData(data, filters) {
|
|
52
|
+
let filteredData = data
|
|
53
|
+
filters.forEach((filter) => {
|
|
54
|
+
filteredData = filterObjectArray(filteredData, filter)
|
|
55
|
+
})
|
|
56
|
+
return filteredData
|
|
57
|
+
}
|
package/src/formatter.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { identity } from 'ramda'
|
|
2
|
+
|
|
3
|
+
const formatterMap = {
|
|
4
|
+
currency: getCurrencyFormatter,
|
|
5
|
+
integer: (language) => (val) => val.toLocaleString(language, { maximumFractionDigits: 0 }),
|
|
6
|
+
number: (language, decimalPlaces) => (val) =>
|
|
7
|
+
val.toLocaleString(language, {
|
|
8
|
+
minimumFractionDigits: decimalPlaces,
|
|
9
|
+
maximumFractionDigits: decimalPlaces
|
|
10
|
+
}),
|
|
11
|
+
date: (language) => (val) => val.toLocaleDateString(language),
|
|
12
|
+
time: (language) => (val) => val.toLocaleTimeString(language),
|
|
13
|
+
object: () => JSON.stringify,
|
|
14
|
+
array: () => JSON.stringify, // Combined formatter for 'object' and 'array'
|
|
15
|
+
ellipsis: () => () => '...' // Static formatter, no need for parameters
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Returns a currency formatter function that formats a value as a currency string.
|
|
20
|
+
* @param {string} language - The IETF language tag used for locale-specific formatting.
|
|
21
|
+
* @param {number} decimalPlaces - The number of decimal places to show with currency formatting.
|
|
22
|
+
* @returns {function} - A currency formatter function that takes a value and returns a formatted string.
|
|
23
|
+
*/
|
|
24
|
+
function getCurrencyFormatter(language, decimalPlaces) {
|
|
25
|
+
/**
|
|
26
|
+
* Formats a value as a currency string with the desired decimal places.
|
|
27
|
+
*
|
|
28
|
+
* @param {number} val - The value to format as a currency string.
|
|
29
|
+
* @param {string} [currency='USD'] - The currency code to use for formatting.
|
|
30
|
+
* @returns {string} - The formatted currency string.
|
|
31
|
+
*/
|
|
32
|
+
const formatter = (val, currency = 'USD') =>
|
|
33
|
+
val.toLocaleString(language, {
|
|
34
|
+
style: 'currency',
|
|
35
|
+
currency,
|
|
36
|
+
minimumFractionDigits: decimalPlaces,
|
|
37
|
+
maximumFractionDigits: decimalPlaces
|
|
38
|
+
})
|
|
39
|
+
return formatter
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Creates a formatter function that formats a value according to the specified type and optional locale settings.
|
|
44
|
+
* Supported types include 'default' (no formatting), 'integer', 'number', 'date', 'time',, 'object', 'array', 'currency' & 'ellipsis''
|
|
45
|
+
*
|
|
46
|
+
* @param {string} type - The type of the formatter to use (e.g., 'integer', 'number', 'date', 'time', 'currency').
|
|
47
|
+
* @param {string} [language='en-US'] - Optional IETF language tag used for locale-specific formatting.
|
|
48
|
+
* @param {number} [decimalPlaces=2] - Optional number of decimal places to show with number and currency formatting.
|
|
49
|
+
* @returns {*} - A format function that takes a value and returns a formatted string.
|
|
50
|
+
*/
|
|
51
|
+
export function createFormatter(type, language = 'en-US', decimalPlaces = 2) {
|
|
52
|
+
const formatter = formatterMap[type]
|
|
53
|
+
if (formatter) return formatter(language, decimalPlaces)
|
|
54
|
+
|
|
55
|
+
return identity
|
|
56
|
+
}
|
package/src/hierarchy.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { defaultViewOptions } from './constants'
|
|
2
|
+
/**
|
|
3
|
+
* Removes objects from an array where the parent attribute is not null.
|
|
4
|
+
* @param {Array<Object>} arr - The array of objects.
|
|
5
|
+
*/
|
|
6
|
+
export function removeChildren(arr) {
|
|
7
|
+
for (let i = arr.length - 1; i >= 0; i--) {
|
|
8
|
+
if (arr[i].parent) arr.splice(i, 1)
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Flatten nested children arrays within an array of objects.
|
|
13
|
+
* @param {Array<Object>} arr - The array of objects.
|
|
14
|
+
*/
|
|
15
|
+
export function flattenNestedChildren(arr) {
|
|
16
|
+
let index = 0
|
|
17
|
+
while (index < arr.length) {
|
|
18
|
+
if (Array.isArray(arr[index].children) && arr[index].children.length > 0) {
|
|
19
|
+
const children = arr[index].children
|
|
20
|
+
arr.splice(index + 1, 0, ...children)
|
|
21
|
+
}
|
|
22
|
+
index++
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Recursively updates parent items' flags based on the child's flags.
|
|
28
|
+
* @param {Object} item - The current object.
|
|
29
|
+
*/
|
|
30
|
+
function updateParentFlags(item) {
|
|
31
|
+
if (!item.parent) return
|
|
32
|
+
if (item.parent.excluded) {
|
|
33
|
+
item.parent.excluded = false
|
|
34
|
+
item.parent.retainedByChild = true
|
|
35
|
+
|
|
36
|
+
updateParentFlags(item.parent)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Applies a hierarchical filter to an array of objects.
|
|
42
|
+
* @param {Array<Object>} arr - The array of objects.
|
|
43
|
+
* @param {Function} filterFn - The filter function to apply on each object.
|
|
44
|
+
*/
|
|
45
|
+
export function hierarchicalFilter(arr, filterFn) {
|
|
46
|
+
arr.forEach((item) => {
|
|
47
|
+
item.excluded = !filterFn(item)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
arr.forEach((item) => {
|
|
51
|
+
if (!item.excluded) updateParentFlags(item)
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Derives the hierarchy from the data.
|
|
57
|
+
*
|
|
58
|
+
* @param {Array} data - The data to derive the hierarchy from.
|
|
59
|
+
* @param {string} path - The column name to be used as hierarchical path.
|
|
60
|
+
* @param {string} separator - The separator to be used in the path.
|
|
61
|
+
* @returns {Array<import('./types').Hierarchy>} - The derived hierarchy.
|
|
62
|
+
*/
|
|
63
|
+
export function deriveHierarchy(data, options) {
|
|
64
|
+
const { expanded, path, separator } = { ...defaultViewOptions, ...options }
|
|
65
|
+
if (!path) return data.map((row) => ({ depth: 0, row }))
|
|
66
|
+
|
|
67
|
+
const hierarchy = data.map((row) => {
|
|
68
|
+
const parts = row[path].split(separator).filter((part) => part.length > 0)
|
|
69
|
+
const depth = parts.length
|
|
70
|
+
const value = depth > 0 ? parts[depth - 1] : ''
|
|
71
|
+
return { depth, value, path: row[path], row }
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
hierarchy.forEach((row) => {
|
|
75
|
+
row.children = hierarchy.filter(
|
|
76
|
+
(child) => child.path.startsWith(row.path) && row.depth === child.depth - 1
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
row.isParent = row.children.length > 0
|
|
80
|
+
if (row.isParent) {
|
|
81
|
+
row.isExpanded = expanded
|
|
82
|
+
row.isHidden = !row.parent ? false : row.parent.isHidden || !row.parent.isExpanded
|
|
83
|
+
}
|
|
84
|
+
row.children.forEach((child) => {
|
|
85
|
+
child.parent = row
|
|
86
|
+
child.isHidden = row.isHidden || !row.isExpanded
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
return hierarchy
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Recursively sets the visibility of child nodes based on the hidden and expanded state
|
|
94
|
+
* of their parent node.
|
|
95
|
+
*
|
|
96
|
+
* @param {Object} node - The parent node whose children's visibility will be changed.
|
|
97
|
+
* The node is expected to have a 'children' property which is an
|
|
98
|
+
* array of child nodes, a 'isHidden' property indicating if the
|
|
99
|
+
* node is hidden, and a 'isExpanded' property indicating if the
|
|
100
|
+
* node is expanded.
|
|
101
|
+
*/
|
|
102
|
+
function changeVisibilityForChildren(node) {
|
|
103
|
+
if (node.children) {
|
|
104
|
+
node.children.forEach((child) => {
|
|
105
|
+
child.isHidden = node.isHidden || !node.isExpanded
|
|
106
|
+
changeVisibilityForChildren(child)
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Toggles the expansion state of a node and updates the visibility of its children accordingly.
|
|
113
|
+
* If the node is a parent (has child nodes), it will invert its 'isExpanded' property and call
|
|
114
|
+
* 'changeVisibilityForChildren' to reflect changes to its child nodes.
|
|
115
|
+
*
|
|
116
|
+
* @param {Object} node - The node to toggle expansion for. The node is expected to have an
|
|
117
|
+
* 'isParent' property indicating if the node has children, an
|
|
118
|
+
* 'isExpanded' property indicating if the node is expanded, and an array
|
|
119
|
+
* of child nodes under the 'children' property if it is a parent.
|
|
120
|
+
*/
|
|
121
|
+
export function toggleExpansion(node) {
|
|
122
|
+
if (node.isParent) {
|
|
123
|
+
node.isExpanded = !node.isExpanded
|
|
124
|
+
changeVisibilityForChildren(node)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// function hierarchy(data, options){
|
|
129
|
+
// const nodes = deriveHierarchy(data, options)
|
|
130
|
+
|
|
131
|
+
// return {
|
|
132
|
+
// nodes,
|
|
133
|
+
// filter: (fn) => hierarchicalFilter(nodes, fn),
|
|
134
|
+
// selection:
|
|
135
|
+
// }
|
|
136
|
+
// }
|
package/src/index.js
ADDED
package/src/infer.js
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { ascending, descending } from 'd3-array'
|
|
2
|
+
import { compact } from '@rokkit/core'
|
|
3
|
+
import { omit } from 'ramda'
|
|
4
|
+
import { createFormatter } from './formatter'
|
|
5
|
+
import { defaultViewOptions, defaultActionOrder } from './constants'
|
|
6
|
+
import { typeOf } from './utils'
|
|
7
|
+
import { deriveColumnProperties } from './metadata'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Derives the sorting information for a column based on the type of the input value.
|
|
11
|
+
*
|
|
12
|
+
* @param {import('./types').SortableColumn} value - The value that determines the sorting behavior of the column.
|
|
13
|
+
* @returns {import('./types').ColumnSorter} An object containing the 'name' of the column and a 'sorter' function.
|
|
14
|
+
* @throws {Error} Throws an error if the value type is not supported or the value format is invalid.
|
|
15
|
+
*/
|
|
16
|
+
export function deriveSortableColumn(value) {
|
|
17
|
+
const type = typeOf(value)
|
|
18
|
+
|
|
19
|
+
const configGenerators = {
|
|
20
|
+
array: arrayConfigGenerator,
|
|
21
|
+
object: objectConfigGenerator,
|
|
22
|
+
string: stringConfigGenerator
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const configGenerator = configGenerators[type]
|
|
26
|
+
if (!configGenerator) {
|
|
27
|
+
throw new Error(`Invalid value type: ${type}`)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return configGenerator(value)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Generates a sorting configuration for an array value.
|
|
35
|
+
*
|
|
36
|
+
* @param {Array} value - The array from which to derive a sorting configuration.
|
|
37
|
+
* @returns {Object} A validated sorting configuration based on the array.
|
|
38
|
+
*/
|
|
39
|
+
function arrayConfigGenerator(value) {
|
|
40
|
+
arrayValidator(value)
|
|
41
|
+
return { name: value[0], sorter: value[1] ? ascending : descending }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Generates a sorting configuration for an object value.
|
|
46
|
+
*
|
|
47
|
+
* @param {Object} value - The object from which to derive a sorting configuration.
|
|
48
|
+
* @returns {Object} A validated sorting configuration based on the object.
|
|
49
|
+
*/
|
|
50
|
+
function objectConfigGenerator(value) {
|
|
51
|
+
objectValidator(value)
|
|
52
|
+
return { name: value.name, sorter: value.sorter ?? ascending }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Generates a sorting configuration for a string value.
|
|
57
|
+
*
|
|
58
|
+
* @param {string} value - The string from which to derive a sorting configuration.
|
|
59
|
+
* @returns {Object} A sorting configuration using the string name and ascending sort function.
|
|
60
|
+
*/
|
|
61
|
+
function stringConfigGenerator(value) {
|
|
62
|
+
return { name: value, sorter: ascending }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Validates an array to ensure it can be used to create a sorting configuration.
|
|
67
|
+
*
|
|
68
|
+
* @param {Array} value - The array to validate.
|
|
69
|
+
* @throws {Error} Throws an error if the array does not correspond to a [string, boolean] pair.
|
|
70
|
+
*/
|
|
71
|
+
function arrayValidator(value) {
|
|
72
|
+
if (value.length !== 2 || typeof value[0] !== 'string' || typeof value[1] !== 'boolean') {
|
|
73
|
+
throw new Error('Array should be a pair of [string, boolean]')
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Validates an object to ensure it has the necessary properties for creating a sorting configuration.
|
|
79
|
+
*
|
|
80
|
+
* @param {Object} value - The object to validate.
|
|
81
|
+
* @throws {Error} Throws an error if the object does not have a 'name' property or has an invalid 'sorter'.
|
|
82
|
+
*/
|
|
83
|
+
function objectValidator(value) {
|
|
84
|
+
if (typeof value.name !== 'string') {
|
|
85
|
+
throw new Error('The property "name" is required and must be a string')
|
|
86
|
+
}
|
|
87
|
+
if (value.sorter !== undefined && typeof value.sorter !== 'function') {
|
|
88
|
+
throw new Error('Sorter should be a function')
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Creates a deep scan sample object that contains a union of all keys from all
|
|
94
|
+
* objects in the array, pulling the values from the last item that contains the key.
|
|
95
|
+
*
|
|
96
|
+
* @param {Array<Object>} data - An array of objects to deep scan and collect sample values.
|
|
97
|
+
* @returns {Object} A sample object consolidating all unique keys with their latest non-null values.
|
|
98
|
+
*/
|
|
99
|
+
export function getDeepScanSample(data) {
|
|
100
|
+
return data.reduce(
|
|
101
|
+
(acc, cur) => ({
|
|
102
|
+
...acc,
|
|
103
|
+
...compact(omit(Object.keys(acc), cur))
|
|
104
|
+
}),
|
|
105
|
+
{}
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Derives a unique list of column names from an array of data objects.
|
|
110
|
+
* Each data object represents a row and this function compiles a list of all unique property keys
|
|
111
|
+
* present across these objects.
|
|
112
|
+
*
|
|
113
|
+
* @param {Object[]} data - The array of data objects to analyze for column names.
|
|
114
|
+
* @returns {string[]} - An array of strings representing the unique column names found across all data objects.
|
|
115
|
+
*/
|
|
116
|
+
export function deriveColumns(data, options) {
|
|
117
|
+
if (data.length === 0) return []
|
|
118
|
+
|
|
119
|
+
const { scanMode } = { ...defaultViewOptions, ...options }
|
|
120
|
+
const sample = scanMode === 'fast' ? data[0] : getDeepScanSample(data)
|
|
121
|
+
|
|
122
|
+
return deriveColumnProperties(sample, options)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Derives the data types of columns based on the data provided in an array of objects.
|
|
127
|
+
* The function assumes that all values of a single field (column) are of the same type. It uses
|
|
128
|
+
* the first object in the data array as a reference for the field names. Each field is categorized
|
|
129
|
+
* as either 'string' or 'number' based on whether any of the field values in the data set are non-numeric.
|
|
130
|
+
*
|
|
131
|
+
* @param {Object[]} data - The array of data objects to assess for column data types.
|
|
132
|
+
* @returns {Object} - An object with keys being the data types ('string' & 'number') and values
|
|
133
|
+
* being arrays of field names that correspond to that data type.
|
|
134
|
+
*/
|
|
135
|
+
export function deriveDataTypes(data) {
|
|
136
|
+
const dataTypes = Object.keys(data[0])
|
|
137
|
+
.map((field) => ({
|
|
138
|
+
field,
|
|
139
|
+
type: data.map((d) => d[field]).some(isNaN) ? 'string' : 'number'
|
|
140
|
+
}))
|
|
141
|
+
.reduce((acc, cur) => ({ ...acc, [cur.type]: [...acc[cur.type], cur.field] }), {
|
|
142
|
+
string: [],
|
|
143
|
+
number: []
|
|
144
|
+
})
|
|
145
|
+
return dataTypes
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Infers the data type of a set of values by examining the values in the array.
|
|
150
|
+
* Assumes all values in the array should have the same type, unless they are 'null'.
|
|
151
|
+
* If multiple types are detected (excluding 'null'), the function will return 'mixed'.
|
|
152
|
+
* If all values are 'null', it returns 'null', and it returns 'undefined' for an empty array.
|
|
153
|
+
*
|
|
154
|
+
* @param {Array} values - An array of values for which the data type is to be inferred.
|
|
155
|
+
* @returns {string} - A string representing the inferred data type. Possible values are
|
|
156
|
+
* 'undefined', 'null', 'mixed', or any data type returned by the getType function.
|
|
157
|
+
*/
|
|
158
|
+
export function inferDataType(values) {
|
|
159
|
+
if (values.length === 0) {
|
|
160
|
+
return 'undefined'
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const nonNullValues = values.filter((value) => value !== null)
|
|
164
|
+
|
|
165
|
+
if (nonNullValues.length === 0) {
|
|
166
|
+
return 'null'
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
let type = typeOf(nonNullValues[0])
|
|
170
|
+
|
|
171
|
+
for (let i = 1; i < nonNullValues.length; i++) {
|
|
172
|
+
if (typeOf(nonNullValues[i]) !== type) {
|
|
173
|
+
type = 'mixed'
|
|
174
|
+
break
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return type
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Derives column metadata from the data to be used in a tabular component.
|
|
183
|
+
*
|
|
184
|
+
* @param {Array} data - The data to derive column metadata from.
|
|
185
|
+
* @returns {Array<import('./types').ColumnMetadata>} - The derived column metadata.
|
|
186
|
+
*/
|
|
187
|
+
export function deriveMetadata(dataArray, options = {}) {
|
|
188
|
+
let { columns = [] } = options
|
|
189
|
+
if (dataArray.length === 0) return columns
|
|
190
|
+
const { actions, language } = { ...defaultViewOptions, ...options }
|
|
191
|
+
|
|
192
|
+
if (columns.length === 0) {
|
|
193
|
+
columns = deriveColumns(dataArray, options)
|
|
194
|
+
} else {
|
|
195
|
+
columns.forEach((col) => {
|
|
196
|
+
// if (col.sortable && !col.order) col.order = 'none'
|
|
197
|
+
if (!col.fields) col.fields = { text: col.name }
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
columns = addFormatters(columns, language)
|
|
201
|
+
if (actions.length > 0) columns = deriveActions(columns, actions)
|
|
202
|
+
|
|
203
|
+
return columns
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Adds a formatter function to each column in the array based on the column type. If a formatter
|
|
208
|
+
* function is already present, it will not be overwritten. The formatter is created based on the
|
|
209
|
+
* override formatter if present, or the column type and language.
|
|
210
|
+
*
|
|
211
|
+
* @param {Array<import('./types').ColumnMetadata>} columns - The array of column metadata to add formatters to.
|
|
212
|
+
* @param {string} language - The language to use for formatting.
|
|
213
|
+
* @returns {Array<import('./types').ColumnMetadata>} - The array of column metadata with formatters added.
|
|
214
|
+
*/
|
|
215
|
+
export function addFormatters(columns, language) {
|
|
216
|
+
return columns.map((column) => {
|
|
217
|
+
const formatter = column.formatter ?? column.type
|
|
218
|
+
if (typeof formatter !== 'function') {
|
|
219
|
+
const digits = column.digits ?? (['number', 'currency'].includes(column.type) ? 2 : 0)
|
|
220
|
+
column.formatter = createFormatter(formatter, language, digits)
|
|
221
|
+
}
|
|
222
|
+
return column
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Converts an array of action names to an array of action objects.
|
|
228
|
+
*
|
|
229
|
+
* @param {Array<string|import('./types').Action>} input - The input to convert to actions.
|
|
230
|
+
* @returns {Array<import('./types').Action>} - The converted actions.
|
|
231
|
+
*/
|
|
232
|
+
export function convertToActions(input) {
|
|
233
|
+
if (!Array.isArray(input)) throw new Error('Actions must be an array')
|
|
234
|
+
const actions = input
|
|
235
|
+
.map((action) => {
|
|
236
|
+
if (typeof action === 'string') {
|
|
237
|
+
return { name: action, label: action, order: defaultActionOrder[action] }
|
|
238
|
+
}
|
|
239
|
+
return { order: defaultActionOrder[action.name], ...action }
|
|
240
|
+
})
|
|
241
|
+
.sort((a, b) => a.order - b.order)
|
|
242
|
+
return actions
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Adds actions to the column metadata. This function updates the columns array in place.
|
|
246
|
+
*
|
|
247
|
+
* @param {Array<import('./types').ColumnMetadata>} columns - The column metadata to update.
|
|
248
|
+
* @param {Array<string|import('./types').Action>} input - The actions to add to the column metadata.
|
|
249
|
+
* @returns {Array<import('./types').ColumnMetadata>} - The updated column metadata.
|
|
250
|
+
*/
|
|
251
|
+
export function deriveActions(columns, input) {
|
|
252
|
+
// convert array of strings to array of objects
|
|
253
|
+
const actions = convertToActions(input)
|
|
254
|
+
const existing = columns.filter(({ action }) => action).map(({ action }) => action)
|
|
255
|
+
|
|
256
|
+
const actionColumns = actions
|
|
257
|
+
.filter(({ name }) => !existing.includes(name))
|
|
258
|
+
.map(({ name, label }) => ({
|
|
259
|
+
label,
|
|
260
|
+
action: name
|
|
261
|
+
}))
|
|
262
|
+
return [...actionColumns, ...columns]
|
|
263
|
+
}
|