@rokkit/core 1.0.0-next.99 → 1.0.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/README.md +121 -1
- package/package.json +26 -34
- package/src/colors/extra.json +16 -0
- package/src/colors/index.ts +24 -0
- package/src/colors/syntax.json +4 -4
- package/src/colors/tailwind.json +0 -14
- package/src/connector.js +28 -23
- package/src/constants.js +113 -24
- package/src/events.js +32 -0
- package/src/field-mapper.js +147 -0
- package/src/index.js +13 -11
- package/src/key-event-map.js +35 -0
- package/src/mapped-items.js +22 -0
- package/src/mapping.js +5 -122
- package/src/nested.js +3 -31
- package/src/string.js +11 -11
- package/src/theme.ts +243 -0
- package/src/types.js +45 -0
- package/src/utils.js +203 -3
- package/src/vite/icon-collections.js +73 -0
- package/src/vite/index.js +1 -0
- package/LICENSE +0 -21
- package/src/colors/index.js +0 -19
- package/src/mapped-list.js +0 -233
- package/src/theme.js +0 -197
package/src/types.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {'child' | 'last' | 'sibling' | 'empty' | 'icon'} LineType
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {Object.<string, Function>} EventHandlers
|
|
7
|
+
* An object where keys are event names and values are event handler functions.
|
|
8
|
+
*/
|
|
9
|
+
|
|
1
10
|
/**
|
|
2
11
|
* Component map to be used to render the item.
|
|
3
12
|
* @typedef {Object<string, import('svelte').SvelteComponent>} ComponentMap
|
|
@@ -112,4 +121,40 @@
|
|
|
112
121
|
* @property {boolean} weekend - Indicates if the day is on the weekend.
|
|
113
122
|
*/
|
|
114
123
|
|
|
124
|
+
/**
|
|
125
|
+
* @typedef {Object} ColorPalette
|
|
126
|
+
* @property {string} 50 - The color for the 50 shade.
|
|
127
|
+
* @property {string} 100 - The color for the 100 shade.
|
|
128
|
+
* @property {string} 200 - The color for the 200 shade.
|
|
129
|
+
* @property {string} 300 - The color for the 300 shade.
|
|
130
|
+
* @property {string} 400 - The color for the 400 shade.
|
|
131
|
+
* @property {string} 500 - The color for the 500 shade.
|
|
132
|
+
* @property {string} 600 - The color for the 600 shade.
|
|
133
|
+
* @property {string} 700 - The color for the 700 shade.
|
|
134
|
+
* @property {string} 800 - The color for the 800 shade.
|
|
135
|
+
* @property {string} 900 - The color for the 900 shade.
|
|
136
|
+
* @property {string} 950 - The color for the 950 shade.
|
|
137
|
+
*/
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @typedef {Object<string,ColorPalette>} ColorCollection
|
|
141
|
+
*/
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @typedef {Object} ColorMapping
|
|
145
|
+
* @property {string} [primary] - The primary color.
|
|
146
|
+
* @property {string} [secondary] - The secondary color.
|
|
147
|
+
* @property {string} [tertiary] - The tertiary color.
|
|
148
|
+
* @property {string} [surface] - The surface color.
|
|
149
|
+
* @property {string} [info] - The info color.
|
|
150
|
+
* @property {string} [success] - The success color.
|
|
151
|
+
* @property {string} [warning] - The warning color.
|
|
152
|
+
* @property {string} [error] - The error color.
|
|
153
|
+
*/
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @typedef {Object} ColorTheme
|
|
157
|
+
* @property {ColorMapping} [mapping] - The color mapping.
|
|
158
|
+
* @property {ColorCollection} [colors] - The color collection.
|
|
159
|
+
*/
|
|
115
160
|
export default {}
|
package/src/utils.js
CHANGED
|
@@ -1,3 +1,67 @@
|
|
|
1
|
+
import { has, isNil } from 'ramda'
|
|
2
|
+
import { DATA_IMAGE_REGEX, ITEM_SNIPPET } from './constants'
|
|
3
|
+
|
|
4
|
+
let idCounter = 0
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* RTL language codes (ISO 639-1)
|
|
8
|
+
* @type {string[]}
|
|
9
|
+
*/
|
|
10
|
+
const RTL_LANGUAGES = [
|
|
11
|
+
'ar', // Arabic
|
|
12
|
+
'he', // Hebrew
|
|
13
|
+
'fa', // Persian/Farsi
|
|
14
|
+
'ur', // Urdu
|
|
15
|
+
'yi', // Yiddish
|
|
16
|
+
'ps', // Pashto
|
|
17
|
+
'sd', // Sindhi
|
|
18
|
+
'ug', // Uyghur
|
|
19
|
+
'ku', // Kurdish (Sorani)
|
|
20
|
+
'dv' // Divehi/Maldivian
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Checks dir/lang attributes of the html element for RTL
|
|
25
|
+
* @returns {'ltr' | 'rtl'}
|
|
26
|
+
*/
|
|
27
|
+
function dirFromHtmlElement() {
|
|
28
|
+
const htmlDir = document.documentElement.getAttribute('dir')
|
|
29
|
+
if (htmlDir === 'rtl' || htmlDir === 'ltr') return htmlDir
|
|
30
|
+
|
|
31
|
+
const lang = document.documentElement.getAttribute('lang')
|
|
32
|
+
const primaryLang = lang ? lang.split('-')[0].toLowerCase() : ''
|
|
33
|
+
return RTL_LANGUAGES.includes(primaryLang) ? 'rtl' : 'ltr'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Detects text direction based on HTML lang attribute
|
|
38
|
+
* @returns {'ltr' | 'rtl'}
|
|
39
|
+
*/
|
|
40
|
+
export function detectDirection() {
|
|
41
|
+
if (typeof document === 'undefined') return 'ltr'
|
|
42
|
+
return dirFromHtmlElement()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Checks if current document direction is RTL
|
|
47
|
+
* @returns {boolean}
|
|
48
|
+
*/
|
|
49
|
+
export function isRTL() {
|
|
50
|
+
return detectDirection() === 'rtl'
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Finds the closest ancestor of the given element that has the given attribute.
|
|
54
|
+
*
|
|
55
|
+
* @param {HTMLElement} element
|
|
56
|
+
* @param {string} attribute
|
|
57
|
+
* @returns {HTMLElement|null}
|
|
58
|
+
*/
|
|
59
|
+
export function getClosestAncestorWithAttribute(element, attribute) {
|
|
60
|
+
if (!element) return null
|
|
61
|
+
if (element.getAttribute(attribute)) return element
|
|
62
|
+
return getClosestAncestorWithAttribute(element.parentElement, attribute)
|
|
63
|
+
}
|
|
64
|
+
|
|
1
65
|
/**
|
|
2
66
|
* A function that performs no operations.
|
|
3
67
|
*/
|
|
@@ -10,8 +74,10 @@ export function noop() {
|
|
|
10
74
|
*
|
|
11
75
|
* @returns {string} A random id
|
|
12
76
|
*/
|
|
13
|
-
export function id() {
|
|
14
|
-
return Math.random().toString(36).substring(2, 9)
|
|
77
|
+
export function id(prefix = '') {
|
|
78
|
+
return [prefix, Math.random().toString(36).substring(2, 9), ++idCounter]
|
|
79
|
+
.filter((x) => !isNil(x) && x !== '')
|
|
80
|
+
.join('-')
|
|
15
81
|
}
|
|
16
82
|
|
|
17
83
|
/**
|
|
@@ -21,7 +87,7 @@ export function id() {
|
|
|
21
87
|
* @returns {boolean}
|
|
22
88
|
*/
|
|
23
89
|
export function isObject(val) {
|
|
24
|
-
return typeof val === 'object' && val
|
|
90
|
+
return typeof val === 'object' && !isNil(val) && !(val instanceof Date)
|
|
25
91
|
}
|
|
26
92
|
|
|
27
93
|
/**
|
|
@@ -70,3 +136,137 @@ export function scaledPath(size, x) {
|
|
|
70
136
|
if (Array.isArray(x)) return x.map((v) => scaledPath(size, v)).join(' ')
|
|
71
137
|
return typeof x === 'number' ? x * size : x
|
|
72
138
|
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Gets a key string from path
|
|
142
|
+
* @param {string[]} path
|
|
143
|
+
* @returns {string}
|
|
144
|
+
*/
|
|
145
|
+
export function getKeyFromPath(path) {
|
|
146
|
+
return Array.isArray(path) ? path.join('-') : [path].join('-')
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Gets a path array from key string
|
|
151
|
+
* @param {string} key
|
|
152
|
+
* @returns {string[]}
|
|
153
|
+
*/
|
|
154
|
+
export function getPathFromKey(key) {
|
|
155
|
+
return key.split('-').map(Number)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Get snippet function from an object
|
|
160
|
+
* @param {Object} obj
|
|
161
|
+
* @param {string} key
|
|
162
|
+
* @param {null|Function} defaultSnippet
|
|
163
|
+
* @returns {Function|undefined}
|
|
164
|
+
*/
|
|
165
|
+
export function getSnippet(obj, key, defaultSnippet = null) {
|
|
166
|
+
if (has(key, obj) && typeof obj[key] === 'function') {
|
|
167
|
+
return obj[key]
|
|
168
|
+
}
|
|
169
|
+
return defaultSnippet
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Resolve which snippet to render for a proxy item.
|
|
174
|
+
*
|
|
175
|
+
* Checks proxy.snippet for a per-item named override first (e.g. item.snippet = 'highlighted').
|
|
176
|
+
* Falls back to the component-level fallback snippet name (e.g. 'itemContent' / 'groupContent').
|
|
177
|
+
* Returns null if neither is found.
|
|
178
|
+
*
|
|
179
|
+
* @param {Record<string, unknown>} snippets - snippets passed to the component
|
|
180
|
+
* @param {{ snippet?: string | null }} proxy - any object with an optional .snippet property
|
|
181
|
+
* @param {string} [fallback] - fallback snippet name; defaults to ITEM_SNIPPET ('itemContent')
|
|
182
|
+
* @returns {Function | null}
|
|
183
|
+
*/
|
|
184
|
+
/**
|
|
185
|
+
* @param {unknown} value
|
|
186
|
+
* @returns {Function|null}
|
|
187
|
+
*/
|
|
188
|
+
function asSnippet(value) {
|
|
189
|
+
return typeof value === 'function' ? value : null
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function resolveSnippet(snippets, proxy, fallback = ITEM_SNIPPET) {
|
|
193
|
+
const name = proxy && proxy.snippet
|
|
194
|
+
return asSnippet(name && snippets[name]) || asSnippet(snippets[fallback])
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* convert hex string to `{r} {g} {b}`
|
|
199
|
+
* @param {string} hex
|
|
200
|
+
* @return {string}
|
|
201
|
+
*/
|
|
202
|
+
export function hex2rgb(hex) {
|
|
203
|
+
const [r, g, b] = hex.match(/\w\w/g).map((x) => parseInt(x, 16))
|
|
204
|
+
return `${r},${g},${b}`
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Convert a CSS color value to r,g,b for use in CSS variables.
|
|
209
|
+
* Hex values (#rrggbb) are converted to "r,g,b" for rgba() support.
|
|
210
|
+
* All other CSS color formats (oklch, hsl, named) are returned as-is.
|
|
211
|
+
* Non-string values are returned unchanged.
|
|
212
|
+
* Note: non-hex values will NOT work with UnoCSS opacity utilities like bg-primary/50.
|
|
213
|
+
*
|
|
214
|
+
* @param {unknown} value
|
|
215
|
+
* @returns {unknown}
|
|
216
|
+
*/
|
|
217
|
+
export function colorToRgb(value) {
|
|
218
|
+
if (typeof value === 'string' && /^#[0-9a-fA-F]{6}$/.test(value)) {
|
|
219
|
+
return hex2rgb(value)
|
|
220
|
+
}
|
|
221
|
+
return value
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Checks if a string is a valid image URL
|
|
226
|
+
*
|
|
227
|
+
* @param {string} str - The string to check
|
|
228
|
+
* @returns {boolean} - Returns true if the string is an image URL
|
|
229
|
+
*/
|
|
230
|
+
function isImageUrl(str) {
|
|
231
|
+
// Fallback regex-based validation
|
|
232
|
+
const fallbackValidation = () => {
|
|
233
|
+
const urlRegex = /^https?:\/\/.+\.(jpg|jpeg|png|gif|bmp|webp|svg|tiff)(\?.*)?$/i
|
|
234
|
+
return urlRegex.test(str)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Check if the string looks like a URL
|
|
238
|
+
try {
|
|
239
|
+
// Use browser-native URL constructor if available
|
|
240
|
+
if (typeof URL !== 'undefined') {
|
|
241
|
+
const url = new URL(str)
|
|
242
|
+
// Only accept HTTP/HTTPS protocols
|
|
243
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
244
|
+
return false
|
|
245
|
+
}
|
|
246
|
+
// Check common image extensions
|
|
247
|
+
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg', '.tiff']
|
|
248
|
+
const path = url.pathname.toLowerCase()
|
|
249
|
+
return imageExtensions.some((ext) => path.endsWith(ext))
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Fallback if URL constructor is not available
|
|
253
|
+
return fallbackValidation()
|
|
254
|
+
} catch {
|
|
255
|
+
// Fallback if URL constructor fails
|
|
256
|
+
return fallbackValidation()
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* A utility function that detects if a string is an image URL or image data (base64)
|
|
261
|
+
*
|
|
262
|
+
* @param {string} str - The string to check
|
|
263
|
+
* @returns {string|null} - Returns the original string if it's an image URL or image data, otherwise null
|
|
264
|
+
*/
|
|
265
|
+
export function getImage(str) {
|
|
266
|
+
if (DATA_IMAGE_REGEX.test(str)) return str
|
|
267
|
+
// Check if it's a URL
|
|
268
|
+
|
|
269
|
+
if (isImageUrl(str)) return str
|
|
270
|
+
|
|
271
|
+
return null
|
|
272
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createRequire } from 'module'
|
|
2
|
+
import { resolve, isAbsolute } from 'path'
|
|
3
|
+
import { readFileSync } from 'fs'
|
|
4
|
+
|
|
5
|
+
// Two require contexts: CWD (for site-specific packages like @iconify-json/*)
|
|
6
|
+
// and the module's own location (for workspace packages like @rokkit/icons).
|
|
7
|
+
const requireFromCwd = createRequire(resolve(process.cwd(), 'package.json'))
|
|
8
|
+
const requireFromModule = createRequire(import.meta.url)
|
|
9
|
+
|
|
10
|
+
function requirePackage(jsonPath) {
|
|
11
|
+
try {
|
|
12
|
+
return requireFromCwd(jsonPath)
|
|
13
|
+
} catch {
|
|
14
|
+
return requireFromModule(jsonPath)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Creates icon collection loaders for UnoCSS presetIcons from a simple config.
|
|
20
|
+
*
|
|
21
|
+
* This function transforms a map of collection names to JSON package paths
|
|
22
|
+
* into the format expected by UnoCSS's presetIcons.
|
|
23
|
+
*
|
|
24
|
+
* Supports three types of paths:
|
|
25
|
+
* - Package paths (e.g., '@rokkit/icons/ui.json') - resolved via require
|
|
26
|
+
* - Absolute paths (e.g., '/path/to/icons.json') - used directly
|
|
27
|
+
* - Relative paths (e.g., './static/icons.json') - resolved from process.cwd()
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* // uno.config.js
|
|
31
|
+
* import { iconCollections } from '@rokkit/core/vite'
|
|
32
|
+
*
|
|
33
|
+
* export default defineConfig({
|
|
34
|
+
* presets: [
|
|
35
|
+
* presetIcons({
|
|
36
|
+
* collections: iconCollections({
|
|
37
|
+
* rokkit: '@rokkit/icons/ui.json',
|
|
38
|
+
* logo: '@rokkit/icons/auth.json',
|
|
39
|
+
* solar: '@iconify-json/solar/icons.json',
|
|
40
|
+
* custom: './static/icons/custom.json'
|
|
41
|
+
* })
|
|
42
|
+
* })
|
|
43
|
+
* ]
|
|
44
|
+
* })
|
|
45
|
+
*
|
|
46
|
+
* @param {Record<string, string>} config - Map of collection alias to JSON path
|
|
47
|
+
* @returns {Record<string, () => any>} Collections object for presetIcons
|
|
48
|
+
*/
|
|
49
|
+
export function iconCollections(config) {
|
|
50
|
+
if (!config || typeof config !== 'object') {
|
|
51
|
+
return {}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return Object.entries(config).reduce((acc, [alias, jsonPath]) => {
|
|
55
|
+
acc[alias] = () => {
|
|
56
|
+
// Check if it's a relative path (starts with ./ or ../)
|
|
57
|
+
if (jsonPath.startsWith('./') || jsonPath.startsWith('../')) {
|
|
58
|
+
// Resolve relative to current working directory (where the config is)
|
|
59
|
+
const absolutePath = resolve(process.cwd(), jsonPath)
|
|
60
|
+
return JSON.parse(readFileSync(absolutePath, 'utf-8'))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Check if it's an absolute path
|
|
64
|
+
if (isAbsolute(jsonPath)) {
|
|
65
|
+
return JSON.parse(readFileSync(jsonPath, 'utf-8'))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Otherwise treat as a package path; try CWD first (site packages), then module location
|
|
69
|
+
return requirePackage(jsonPath)
|
|
70
|
+
}
|
|
71
|
+
return acc
|
|
72
|
+
}, {})
|
|
73
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { iconCollections } from './icon-collections.js'
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2022 Jerry Thomas
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
package/src/colors/index.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import defaultTailwindColors from './tailwind.json'
|
|
2
|
-
import syntaxColorPalette from './syntax.json'
|
|
3
|
-
|
|
4
|
-
export const shades = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950]
|
|
5
|
-
export const defaultPalette = [
|
|
6
|
-
'neutral',
|
|
7
|
-
'primary',
|
|
8
|
-
'secondary',
|
|
9
|
-
'accent',
|
|
10
|
-
'success',
|
|
11
|
-
'warning',
|
|
12
|
-
'danger',
|
|
13
|
-
'info'
|
|
14
|
-
]
|
|
15
|
-
|
|
16
|
-
export const syntaxColors = syntaxColorPalette
|
|
17
|
-
export const defaultColors = {
|
|
18
|
-
...defaultTailwindColors
|
|
19
|
-
}
|
package/src/mapped-list.js
DELETED
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
import { defaultFields } from './constants'
|
|
2
|
-
import { isExpanded, hasChildren, getAttribute } from './mapping'
|
|
3
|
-
import { equals } from 'ramda'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Checks if a specifc attribute of an item matches a value.
|
|
7
|
-
*
|
|
8
|
-
* @param {Object} item - The item.
|
|
9
|
-
* @param {string} attr - The attribute to check.
|
|
10
|
-
*/
|
|
11
|
-
function isMatch(item, attr, value) {
|
|
12
|
-
const itemValue = attr ? getAttribute(item, attr) : item
|
|
13
|
-
return equals(itemValue, value)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Traverses the tree to find an item by value.
|
|
18
|
-
* @param {Array} items - The items array.
|
|
19
|
-
* @param {Object} fields - The fields mapping.
|
|
20
|
-
* @param {any} value - The value to find.
|
|
21
|
-
* @param {Array} position - The current position in the tree.
|
|
22
|
-
* @returns {Object} The found item, or null if not found.
|
|
23
|
-
*/
|
|
24
|
-
function findInChildren(item, index, fields, value, attr, position) {
|
|
25
|
-
if (hasChildren(item, fields)) {
|
|
26
|
-
return findItemByValue(value, item[fields.children], fields.fields ?? fields, attr, [
|
|
27
|
-
...position,
|
|
28
|
-
index
|
|
29
|
-
])
|
|
30
|
-
}
|
|
31
|
-
return null
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Traverses the tree to find an item by value.
|
|
35
|
-
* @param {Array} items - The items array.
|
|
36
|
-
* @param {Object} fields - The fields mapping.
|
|
37
|
-
* @param {any} value - The value to find.
|
|
38
|
-
* @param {Array} position - The current position in the tree.
|
|
39
|
-
* @returns {Object} The found item, or null if not found.
|
|
40
|
-
*/
|
|
41
|
-
export function findItemByValue(value, items, fields = defaultFields, attr = null, position = []) {
|
|
42
|
-
for (let i = 0; i < items.length; i++) {
|
|
43
|
-
if (isMatch(items[i], attr, value)) {
|
|
44
|
-
return { item: items[i], position: [...position, i], fields }
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const foundInChildren = findInChildren(items[i], i, fields, value, attr, position)
|
|
48
|
-
if (foundInChildren) return foundInChildren
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return null
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Gets an item from an items array using an index array.
|
|
56
|
-
* @param {Array} indices - The index array.
|
|
57
|
-
* @param {Array} items - The items array.
|
|
58
|
-
* @param {Object} fields - The fields configuration.
|
|
59
|
-
* @returns {Object} The item.
|
|
60
|
-
*/
|
|
61
|
-
export function findItemByIndexArray(indices, items, fields) {
|
|
62
|
-
let item = items[indices[0]]
|
|
63
|
-
let levelFields = fields
|
|
64
|
-
for (let level = 1; level < indices.length; level++) {
|
|
65
|
-
if (hasChildren(item, levelFields)) {
|
|
66
|
-
item = item[levelFields.children][indices[level]]
|
|
67
|
-
levelFields = levelFields.fields ?? levelFields
|
|
68
|
-
} else {
|
|
69
|
-
return null
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return { item, position: indices, fields: levelFields }
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
*
|
|
77
|
-
* @param {Array<integer>} position
|
|
78
|
-
* @param {Array<*>} items
|
|
79
|
-
* @param {import('./types').FieldMapping} fields
|
|
80
|
-
* @returns
|
|
81
|
-
*/
|
|
82
|
-
export function findNearestItemBefore(position, items, fields) {
|
|
83
|
-
if (items.length === 0) return null
|
|
84
|
-
if (position.length === 0) return { item: items[0], position: [0], fields }
|
|
85
|
-
|
|
86
|
-
let index = position[position.length - 1]
|
|
87
|
-
let result = null
|
|
88
|
-
if (index > 0) {
|
|
89
|
-
index -= 1
|
|
90
|
-
if (position.length === 1) {
|
|
91
|
-
return findLastVisibleChild(items[index], [index], fields)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const sibling = findItemByIndexArray([...position.slice(0, -1), index], items, fields)
|
|
95
|
-
result = findLastVisibleChild(sibling.item, sibling.position, sibling.fields)
|
|
96
|
-
} else {
|
|
97
|
-
result = findItemByIndexArray(position.slice(0, -1), items, fields)
|
|
98
|
-
}
|
|
99
|
-
return result
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Returns the next sibling of the current item.
|
|
104
|
-
*
|
|
105
|
-
* @param {*} parent
|
|
106
|
-
* @param {Array<integer>} position
|
|
107
|
-
* @param {import('./types').FieldMapping} fields
|
|
108
|
-
* @returns
|
|
109
|
-
*/
|
|
110
|
-
export function findLastVisibleChild(parent, position, fields) {
|
|
111
|
-
if (isExpanded(parent, fields)) {
|
|
112
|
-
const children = parent[fields.children]
|
|
113
|
-
return findLastVisibleChild(
|
|
114
|
-
children[children.length - 1],
|
|
115
|
-
position.concat(children.length - 1),
|
|
116
|
-
fields.fields ?? fields
|
|
117
|
-
)
|
|
118
|
-
}
|
|
119
|
-
return { item: parent, position, fields }
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Returns the next item in the tree.
|
|
124
|
-
* @param {Array} position - The position of the current item.
|
|
125
|
-
* @param {Array} items - The items in the tree.
|
|
126
|
-
* @param {Object} fields - The fields mapping.
|
|
127
|
-
* @returns {Object|null} The next item or null if there is none.
|
|
128
|
-
*/
|
|
129
|
-
export function findNearestItemAfter(position, items, fields) {
|
|
130
|
-
if (items.length === 0) return null
|
|
131
|
-
if (position.length === 0) return { item: items[0], position: [0], fields }
|
|
132
|
-
|
|
133
|
-
const current = findItemByIndexArray(position, items, fields)
|
|
134
|
-
let result = null
|
|
135
|
-
if (isExpanded(current.item, current.fields)) {
|
|
136
|
-
result = getFirstChild(current, position)
|
|
137
|
-
} else if (position.length === 1) {
|
|
138
|
-
result = getNextSiblingAtRoot(position, items, fields)
|
|
139
|
-
} else {
|
|
140
|
-
result = getNextSiblingOrAncestor(position, items, fields)
|
|
141
|
-
}
|
|
142
|
-
return result
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Returns the next child of the current item.
|
|
147
|
-
* @param {Object} current - The current item.
|
|
148
|
-
* @param {Array} position - The position of the current item.
|
|
149
|
-
* @returns {Object} The next child.
|
|
150
|
-
*/
|
|
151
|
-
function getFirstChild(current, position) {
|
|
152
|
-
return {
|
|
153
|
-
item: current.item[current.fields.children][0],
|
|
154
|
-
position: position.concat(0),
|
|
155
|
-
fields: current.fields.fields ?? current.fields
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Returns the next sibling of the current item at the root level.
|
|
161
|
-
* @param {Array} position - The position of the current item.
|
|
162
|
-
* @param {Array} items - The items in the tree.
|
|
163
|
-
* @param {Object} fields - The fields mapping.
|
|
164
|
-
* @returns {Object|null} The next sibling or null if there is none.
|
|
165
|
-
*/
|
|
166
|
-
function getNextSiblingAtRoot(position, items, fields) {
|
|
167
|
-
if (position[0] < items.length - 1) {
|
|
168
|
-
return {
|
|
169
|
-
item: items[position[0] + 1],
|
|
170
|
-
position: [position[0] + 1],
|
|
171
|
-
fields
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
return null
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Returns the next sibling of the current item or the next item in the ancestor.
|
|
179
|
-
* @param {Array} position - The position of the current item.
|
|
180
|
-
* @param {Array} items - The items in the tree.
|
|
181
|
-
* @param {Object} fields - The fields mapping.
|
|
182
|
-
* @returns {Object|null} The next sibling or ancestor or null if there is none.
|
|
183
|
-
*/
|
|
184
|
-
function getNextSiblingOrAncestor(position, items, fields) {
|
|
185
|
-
let index = position[position.length - 1]
|
|
186
|
-
let parent = findItemByIndexArray(position.slice(0, -1), items, fields)
|
|
187
|
-
let children = parent.item[parent.fields.children]
|
|
188
|
-
if (index < children.length - 1) {
|
|
189
|
-
index += 1
|
|
190
|
-
const sibling = findItemByIndexArray([...position.slice(0, -1), index], items, fields)
|
|
191
|
-
return { item: sibling.item, position: sibling.position, fields }
|
|
192
|
-
} else {
|
|
193
|
-
while (index === children.length - 1) {
|
|
194
|
-
index = position[position.length - 1]
|
|
195
|
-
position = position.slice(0, -1)
|
|
196
|
-
if (position.length === 0) return null
|
|
197
|
-
parent = findItemByIndexArray(position, items, fields)
|
|
198
|
-
children = parent.item[parent.fields.children]
|
|
199
|
-
}
|
|
200
|
-
return {
|
|
201
|
-
item: children[index + 1],
|
|
202
|
-
position: [...position, index + 1],
|
|
203
|
-
fields
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Creates a mapped list from an items array and a fields mapping.
|
|
210
|
-
* @param {Array<Object>} items - The items array.
|
|
211
|
-
* @param {import('./types').FieldMapping} fields - The fields mapping.
|
|
212
|
-
* @returns {Object} The mapped list.
|
|
213
|
-
*/
|
|
214
|
-
export function mappedList(items, fields) {
|
|
215
|
-
const findByValue = (value) => findItemByValue(value, items, fields)
|
|
216
|
-
const findByAttribute = (value, attr) => findItemByValue(value, items, fields, attr)
|
|
217
|
-
const findByIndexArray = (index) => findItemByIndexArray(index, items, fields)
|
|
218
|
-
const previous = (position) => findNearestItemBefore(position, items, fields)
|
|
219
|
-
const next = (position) => findNearestItemAfter(position, items, fields)
|
|
220
|
-
|
|
221
|
-
const update = (newItems, newFields) => {
|
|
222
|
-
items = newItems
|
|
223
|
-
fields = newFields
|
|
224
|
-
}
|
|
225
|
-
return {
|
|
226
|
-
findByValue,
|
|
227
|
-
findByAttribute,
|
|
228
|
-
findByIndexArray,
|
|
229
|
-
previous,
|
|
230
|
-
next,
|
|
231
|
-
update
|
|
232
|
-
}
|
|
233
|
-
}
|