@rokkit/data 1.0.0-next.96 → 1.0.0-next.98
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 +9 -9
- package/src/dataset.js +3 -17
- package/src/index.js +2 -1
- package/src/infer.js +4 -4
- package/src/metadata.js +3 -3
- package/src/model.js +2 -2
- package/src/parser.js +2 -1
- package/src/utils.js +9 -4
- package/src/view.js +91 -97
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rokkit/data",
|
|
3
|
-
"version": "1.0.0-next.
|
|
3
|
+
"version": "1.0.0-next.98",
|
|
4
4
|
"description": "Contains generic data manipulation functions that can be used in various components.",
|
|
5
5
|
"author": "Jerry Thomas <me@jerrythomas.name>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -12,15 +12,15 @@
|
|
|
12
12
|
"access": "public"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"@sveltejs/vite-plugin-svelte": "^3.1.
|
|
16
|
-
"@testing-library/svelte": "^5.1
|
|
17
|
-
"@types/ramda": "^0.30.
|
|
15
|
+
"@sveltejs/vite-plugin-svelte": "^3.1.2",
|
|
16
|
+
"@testing-library/svelte": "^5.2.1",
|
|
17
|
+
"@types/ramda": "^0.30.2",
|
|
18
18
|
"@vitest/coverage-v8": "^1.6.0",
|
|
19
19
|
"@vitest/ui": "~1.6.0",
|
|
20
|
-
"typescript": "^5.
|
|
21
|
-
"vite": "^5.
|
|
20
|
+
"typescript": "^5.6.2",
|
|
21
|
+
"vite": "^5.4.6",
|
|
22
22
|
"vitest": "~1.6.0",
|
|
23
|
-
"shared-config": "1.0.0-next.
|
|
23
|
+
"shared-config": "1.0.0-next.98"
|
|
24
24
|
},
|
|
25
25
|
"files": [
|
|
26
26
|
"src/**/*.js",
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"d3-array": "^3.2.4",
|
|
41
41
|
"d3-collection": "^1.0.7",
|
|
42
42
|
"ramda": "^0.30.1",
|
|
43
|
-
"svelte": "^4.2.
|
|
44
|
-
"@rokkit/core": "1.0.0-next.
|
|
43
|
+
"svelte": "^4.2.19",
|
|
44
|
+
"@rokkit/core": "1.0.0-next.98"
|
|
45
45
|
},
|
|
46
46
|
"scripts": {
|
|
47
47
|
"format": "prettier --write .",
|
package/src/dataset.js
CHANGED
|
@@ -40,7 +40,7 @@ export function dataset(data, options = {}) {
|
|
|
40
40
|
// joins
|
|
41
41
|
innerJoin: (other, condition) => dataset(innerJoin(data, other.select(), condition)),
|
|
42
42
|
leftJoin: (other, condition) => dataset(leftJoin(data, other.select(), condition)),
|
|
43
|
-
rightJoin: (other, condition) => dataset(
|
|
43
|
+
rightJoin: (other, condition) => dataset(leftJoin(other.select(), data, condition)),
|
|
44
44
|
fullJoin: (other, condition) => dataset(fullJoin(data, other.select(), condition)),
|
|
45
45
|
crossJoin: (other) => dataset(crossJoin(data, other.select())),
|
|
46
46
|
semiJoin: (other, condition) => dataset(semiJoin(data, other.select(), condition)),
|
|
@@ -179,6 +179,8 @@ function innerJoin(first, second, condition) {
|
|
|
179
179
|
* are returned. When combining the rows, the columns from the first dataset take precedence.
|
|
180
180
|
* If there is no match in the second dataset, only the row from the first dataset is returned.
|
|
181
181
|
*
|
|
182
|
+
* This can be used to perform a right join by swapping the datasets.
|
|
183
|
+
*
|
|
182
184
|
* @param {Array} first - The first dataset to join.
|
|
183
185
|
* @param {Array} second - The second dataset to join.
|
|
184
186
|
* @param {Function} condition - The condition to join the datasets on.
|
|
@@ -189,22 +191,6 @@ function leftJoin(first, second, condition) {
|
|
|
189
191
|
return inner.concat(outer)
|
|
190
192
|
}
|
|
191
193
|
|
|
192
|
-
/**
|
|
193
|
-
* Performs a right join on two datasets based on a condition. Result includes all rows from the second
|
|
194
|
-
* dataset and matching rows from the first dataset. In case of multiple matches, all combinations
|
|
195
|
-
* are returned. When combining the rows, the columns from the second dataset take precedence.
|
|
196
|
-
* If there is no match in the first dataset, only the row from the second dataset is returned.
|
|
197
|
-
*
|
|
198
|
-
* @param {Array} first - The first dataset to join.
|
|
199
|
-
* @param {Array} second - The second dataset to join.
|
|
200
|
-
* @param {Function} condition - The condition to join the datasets on.
|
|
201
|
-
* @returns {Array} - The joined dataset.
|
|
202
|
-
*/
|
|
203
|
-
function rightJoin(first, second, condition) {
|
|
204
|
-
const { inner, outer } = joinData(second, first, condition)
|
|
205
|
-
return inner.concat(outer)
|
|
206
|
-
}
|
|
207
|
-
|
|
208
194
|
/**
|
|
209
195
|
* Performs a full join on two datasets based on a condition. Result includes all rows from both
|
|
210
196
|
* datasets. In case of multiple matches, all combinations are returned. When combining the rows,
|
package/src/index.js
CHANGED
package/src/infer.js
CHANGED
|
@@ -3,7 +3,7 @@ import { compact } from '@rokkit/core'
|
|
|
3
3
|
import { omit } from 'ramda'
|
|
4
4
|
import { createFormatter } from './formatter'
|
|
5
5
|
import { defaultViewOptions, defaultActionOrder } from './constants'
|
|
6
|
-
import {
|
|
6
|
+
import { typeOf } from './utils'
|
|
7
7
|
import { deriveColumnProperties } from './metadata'
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -14,7 +14,7 @@ import { deriveColumnProperties } from './metadata'
|
|
|
14
14
|
* @throws {Error} Throws an error if the value type is not supported or the value format is invalid.
|
|
15
15
|
*/
|
|
16
16
|
export function deriveSortableColumn(value) {
|
|
17
|
-
const type =
|
|
17
|
+
const type = typeOf(value)
|
|
18
18
|
|
|
19
19
|
const configGenerators = {
|
|
20
20
|
array: arrayConfigGenerator,
|
|
@@ -166,10 +166,10 @@ export function inferDataType(values) {
|
|
|
166
166
|
return 'null'
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
let type =
|
|
169
|
+
let type = typeOf(nonNullValues[0])
|
|
170
170
|
|
|
171
171
|
for (let i = 1; i < nonNullValues.length; i++) {
|
|
172
|
-
if (
|
|
172
|
+
if (typeOf(nonNullValues[i]) !== type) {
|
|
173
173
|
type = 'mixed'
|
|
174
174
|
break
|
|
175
175
|
}
|
package/src/metadata.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defaultPathOptions } from './constants'
|
|
2
2
|
import { getDeepScanSample } from './infer'
|
|
3
|
-
import {
|
|
3
|
+
import { typeOf } from './utils'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Merges currency-related attributes into the corresponding base columns.
|
|
@@ -64,7 +64,7 @@ export function deriveColumnProperties(sample, options) {
|
|
|
64
64
|
|
|
65
65
|
let columns = Object.keys(sample).map((key) => ({
|
|
66
66
|
name: key,
|
|
67
|
-
type:
|
|
67
|
+
type: typeOf(sample[key]),
|
|
68
68
|
sortable: true,
|
|
69
69
|
filterable: true,
|
|
70
70
|
sorted: 'none',
|
|
@@ -90,7 +90,7 @@ export function deriveColumnMetadata(data, options = {}) {
|
|
|
90
90
|
if (Array.isArray(metadata) && metadata.length > 0) return metadata
|
|
91
91
|
if (data.length === 0) return []
|
|
92
92
|
const sample = deepScan ? getDeepScanSample(data) : data[0]
|
|
93
|
-
return Object.entries(sample).map(([name, value]) => ({ name, type:
|
|
93
|
+
return Object.entries(sample).map(([name, value]) => ({ name, type: typeOf(value) }))
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
/**
|
package/src/model.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { identity, clone as copy } from 'ramda'
|
|
2
2
|
import { getDeepScanSample } from './infer'
|
|
3
|
-
import {
|
|
3
|
+
import { typeOf } from './utils'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* A model is a representation of a dataset
|
|
@@ -72,7 +72,7 @@ function deriveModel(data, sparse = false) {
|
|
|
72
72
|
kv.forEach(([key, value]) => {
|
|
73
73
|
result.push({
|
|
74
74
|
name: key,
|
|
75
|
-
type:
|
|
75
|
+
type: typeOf(value)
|
|
76
76
|
})
|
|
77
77
|
})
|
|
78
78
|
return result
|
package/src/parser.js
CHANGED
|
@@ -48,7 +48,8 @@ export function parseFilters(string) {
|
|
|
48
48
|
// Iterate over the tokens
|
|
49
49
|
for (const token of tokens) {
|
|
50
50
|
// Extract the named groups from the token
|
|
51
|
-
|
|
51
|
+
const { group, column } = token.groups
|
|
52
|
+
let { operator, value } = token.groups
|
|
52
53
|
search = search.replace(group, '').trim()
|
|
53
54
|
|
|
54
55
|
operator = replaceOperators(operator)
|
package/src/utils.js
CHANGED
|
@@ -7,25 +7,30 @@ function isDateString(value) {
|
|
|
7
7
|
return !isNaN(Date.parse(value))
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
function deriveTypeFromEmptyValue(value) {
|
|
11
|
+
if (value === undefined || value === null) return 'string'
|
|
12
|
+
return typeof value
|
|
13
|
+
}
|
|
10
14
|
/**
|
|
11
15
|
* Infer the type of a value as array, date or the default type
|
|
12
16
|
* @param {any} value
|
|
13
17
|
* @returns {string}
|
|
14
18
|
*/
|
|
15
|
-
function
|
|
19
|
+
function deriveArrayAndDateTypes(value) {
|
|
16
20
|
if (Array.isArray(value)) return 'array'
|
|
17
21
|
if (value instanceof Date) return 'date'
|
|
18
22
|
|
|
19
|
-
return
|
|
23
|
+
return deriveTypeFromEmptyValue(value)
|
|
20
24
|
}
|
|
21
25
|
/**
|
|
22
26
|
* Derive the type of a value
|
|
23
27
|
* @param {any} value
|
|
24
28
|
* @returns {string}
|
|
25
29
|
*/
|
|
26
|
-
export function
|
|
27
|
-
const type =
|
|
30
|
+
export function typeOf(value) {
|
|
31
|
+
const type = deriveArrayAndDateTypes(value)
|
|
28
32
|
if (type === 'string' && isDateString(value)) return 'date'
|
|
29
33
|
if (type === 'number' && Number.isInteger(value)) return 'integer'
|
|
34
|
+
|
|
30
35
|
return type
|
|
31
36
|
}
|
package/src/view.js
CHANGED
|
@@ -10,76 +10,24 @@ import {
|
|
|
10
10
|
} from './hierarchy'
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
* as well as providing methods for sorting, selection, and toggling of data expansion.
|
|
13
|
+
* Sorts a nested array of objects based on a group of sorters.
|
|
15
14
|
*
|
|
16
|
-
* @param {Array<Object>}
|
|
17
|
-
* @param {Object} options - The configuration options used for deriving metadata and hierarchy from the data.
|
|
18
|
-
* @returns {import('svelte/store').Writable} An object representing the view, providing methods to manipulate and query the presentation state.
|
|
15
|
+
* @param {Array<Object>} elements - The array of objects to sort.
|
|
19
16
|
*/
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
*/
|
|
33
|
-
const sortBy = (name, ascending = true) => {
|
|
34
|
-
sortGroup = [...sortGroup, [name, ascending]]
|
|
35
|
-
// change the sort order in the metadata
|
|
36
|
-
const { columns, hierarchy } = get(store)
|
|
37
|
-
columns.forEach((column) => {
|
|
38
|
-
if (column.name === name) {
|
|
39
|
-
column.order = ascending ? 'ascending' : 'descending'
|
|
17
|
+
function sortNested(elements, group) {
|
|
18
|
+
elements
|
|
19
|
+
.sort((a, b) => {
|
|
20
|
+
let result = 0
|
|
21
|
+
for (let i = 0; i < group.length && result === 0; i++) {
|
|
22
|
+
result = group[i].sorter(a, b)
|
|
23
|
+
}
|
|
24
|
+
return result
|
|
25
|
+
})
|
|
26
|
+
.forEach((x) => {
|
|
27
|
+
if (Array.isArray(x.children) && x.children.length > 0) {
|
|
28
|
+
sortNested(x.children, group)
|
|
40
29
|
}
|
|
41
30
|
})
|
|
42
|
-
groupSort(hierarchy, sortGroup)
|
|
43
|
-
store.set({ columns, hierarchy })
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return {
|
|
47
|
-
...omit(['set', 'update'], store),
|
|
48
|
-
// filter: noop,
|
|
49
|
-
/**
|
|
50
|
-
* Clears the applied sort order from the hierarchy.
|
|
51
|
-
* TODO: clearing sort should clear the sort orders in columns
|
|
52
|
-
*/
|
|
53
|
-
clearSort: () => {
|
|
54
|
-
sortGroup = []
|
|
55
|
-
const { columns } = get(store)
|
|
56
|
-
columns.forEach((column) => {
|
|
57
|
-
column.order = 'none'
|
|
58
|
-
})
|
|
59
|
-
// clearSortOrder(metadata)
|
|
60
|
-
},
|
|
61
|
-
sortBy,
|
|
62
|
-
/**
|
|
63
|
-
* Toggles the selection state of a data element at the specified index.
|
|
64
|
-
*
|
|
65
|
-
* @param {number} index - The index of the element in the hierarchy to select or deselect.
|
|
66
|
-
*/
|
|
67
|
-
select: (index) => {
|
|
68
|
-
const { hierarchy } = get(store)
|
|
69
|
-
toggleSelection(hierarchy[index])
|
|
70
|
-
store.update((state) => ({ ...state, hierarchy }))
|
|
71
|
-
},
|
|
72
|
-
/**
|
|
73
|
-
* Toggles the expansion state of a data element at the specified index.
|
|
74
|
-
*
|
|
75
|
-
* @param {number} index - The index of the element in the hierarchy to expand or collapse.
|
|
76
|
-
*/
|
|
77
|
-
toggle: (index) => {
|
|
78
|
-
const { hierarchy } = get(store)
|
|
79
|
-
toggleExpansion(hierarchy[index])
|
|
80
|
-
store.update((state) => ({ ...state, hierarchy }))
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
31
|
}
|
|
84
32
|
|
|
85
33
|
/**
|
|
@@ -100,39 +48,22 @@ export function groupSort(hierarchy, sortGroup) {
|
|
|
100
48
|
}
|
|
101
49
|
|
|
102
50
|
/**
|
|
103
|
-
* Sorts
|
|
104
|
-
*
|
|
105
|
-
* @param {Array<Object>} elements - The array of objects to sort.
|
|
106
|
-
*/
|
|
107
|
-
function sortNested(elements, group) {
|
|
108
|
-
elements
|
|
109
|
-
.sort((a, b) => {
|
|
110
|
-
let result = 0
|
|
111
|
-
for (let i = 0; i < group.length && result === 0; i++) {
|
|
112
|
-
result = group[i].sorter(a, b)
|
|
113
|
-
}
|
|
114
|
-
return result
|
|
115
|
-
})
|
|
116
|
-
.forEach((x) => {
|
|
117
|
-
if (Array.isArray(x.children) && x.children.length > 0) {
|
|
118
|
-
sortNested(x.children, group)
|
|
119
|
-
}
|
|
120
|
-
})
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Toggles the selection state of a node in the hierarchy.
|
|
125
|
-
* Depending on whether the node to toggle is a parent or child,
|
|
126
|
-
* it will either select or deselect all children, or update the parent's selection state based on its children's states.
|
|
127
|
-
*
|
|
128
|
-
* @param {import('./types').Hierarchy} item - The node to toggle.
|
|
51
|
+
* Sorts the hierarchy based on a specific field name and order.
|
|
129
52
|
*
|
|
53
|
+
* @param {string} name - The field name to sort by.
|
|
54
|
+
* @param {boolean} [ascending=true] - Whether the sort should be in ascending order.
|
|
130
55
|
*/
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
56
|
+
const sortHierarchyBy = (store, sortGroup, name, ascending = true) => {
|
|
57
|
+
sortGroup = [...sortGroup, [name, ascending]]
|
|
58
|
+
// change the sort order in the metadata
|
|
59
|
+
const { columns, hierarchy } = get(store)
|
|
60
|
+
columns.forEach((column) => {
|
|
61
|
+
if (column.name === name) {
|
|
62
|
+
column.order = ascending ? 'ascending' : 'descending'
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
groupSort(hierarchy, sortGroup)
|
|
66
|
+
store.set({ columns, hierarchy })
|
|
136
67
|
}
|
|
137
68
|
|
|
138
69
|
/**
|
|
@@ -185,3 +116,66 @@ function updateParents(item) {
|
|
|
185
116
|
parent = parent.parent
|
|
186
117
|
}
|
|
187
118
|
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Toggles the selection state of a node in the hierarchy.
|
|
122
|
+
* Depending on whether the node to toggle is a parent or child,
|
|
123
|
+
* it will either select or deselect all children, or update the parent's selection state based on its children's states.
|
|
124
|
+
*
|
|
125
|
+
* @param {import('./types').Hierarchy} item - The node to toggle.
|
|
126
|
+
*
|
|
127
|
+
*/
|
|
128
|
+
export function toggleSelection(item) {
|
|
129
|
+
item.selected = item.selected === 'checked' ? 'unchecked' : 'checked'
|
|
130
|
+
|
|
131
|
+
updateParents(item)
|
|
132
|
+
updateChildren(item)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Toggles the expansion or selection state of a data element at the specified index.
|
|
137
|
+
*
|
|
138
|
+
* @param {import('svelte/store').Writable} store - The store object representing the view.
|
|
139
|
+
* @param {number} index - The index of the element in the hierarchy to expand or collapse.
|
|
140
|
+
* @param {string} mode - The mode to toggle, either 'expansion' or 'selection'.
|
|
141
|
+
*/
|
|
142
|
+
function toggleState(store, index, mode) {
|
|
143
|
+
const { hierarchy } = get(store)
|
|
144
|
+
if (mode === 'selection') toggleSelection(hierarchy[index])
|
|
145
|
+
else if (mode === 'expansion') toggleExpansion(hierarchy[index])
|
|
146
|
+
store.update((state) => ({ ...state, hierarchy }))
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Creates a view object for managing data presentation with metadata and hierarchy structures,
|
|
151
|
+
* as well as providing methods for sorting, selection, and toggling of data expansion.
|
|
152
|
+
*
|
|
153
|
+
* @param {Array<Object>} data - The source data array to be presented by the view.
|
|
154
|
+
* @param {Object} options - The configuration options used for deriving metadata and hierarchy from the data.
|
|
155
|
+
* @returns {import('svelte/store').Writable} An object representing the view, providing methods to manipulate and query the presentation state.
|
|
156
|
+
*/
|
|
157
|
+
export function dataview(data, options) {
|
|
158
|
+
const store = writable({
|
|
159
|
+
columns: deriveMetadata(data, options),
|
|
160
|
+
hierarchy: deriveHierarchy(data, options)
|
|
161
|
+
})
|
|
162
|
+
let sortGroup = []
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
...omit(['set', 'update'], store),
|
|
166
|
+
// filter: noop,
|
|
167
|
+
/**
|
|
168
|
+
* Clears the applied sort order from the hierarchy.
|
|
169
|
+
*/
|
|
170
|
+
clearSort: () => {
|
|
171
|
+
sortGroup = []
|
|
172
|
+
store.update((state) => ({
|
|
173
|
+
...state,
|
|
174
|
+
columns: state.columns.map((column) => ({ ...column, order: 'none' }))
|
|
175
|
+
}))
|
|
176
|
+
},
|
|
177
|
+
sortBy: (name, ascending) => sortHierarchyBy(store, sortGroup, name, ascending),
|
|
178
|
+
select: (index) => toggleState(store, index, 'selection'),
|
|
179
|
+
toggle: (index) => toggleState(store, index, 'expansion')
|
|
180
|
+
}
|
|
181
|
+
}
|