@rokkit/core 1.0.0-next.125 → 1.0.0-next.129
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/dist/colors/index.d.ts +6 -366
- package/dist/constants.d.ts +44 -9
- package/dist/field-mapper.d.ts +0 -3
- package/dist/index.d.ts +1 -1
- package/dist/mapping.d.ts +0 -61
- package/dist/nested.d.ts +0 -9
- package/dist/theme.d.ts +55 -796
- package/dist/utils.d.ts +25 -1
- package/dist/vite/icon-collections.d.ts +32 -0
- package/dist/vite/index.d.ts +1 -0
- package/package.json +10 -2
- package/src/constants.js +75 -16
- package/src/field-mapper.js +5 -6
- package/src/index.js +1 -1
- package/src/key-event-map.js +1 -1
- package/src/mapping.js +2 -115
- package/src/nested.js +3 -31
- package/src/utils.js +64 -14
- package/src/vite/icon-collections.js +73 -0
- package/src/vite/index.js +1 -0
- package/src/colors/index.js +0 -22
- package/src/theme.js +0 -174
package/dist/utils.d.ts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detects text direction based on HTML lang attribute
|
|
3
|
+
* @returns {'ltr' | 'rtl'}
|
|
4
|
+
*/
|
|
5
|
+
export function detectDirection(): "ltr" | "rtl";
|
|
6
|
+
/**
|
|
7
|
+
* Checks if current document direction is RTL
|
|
8
|
+
* @returns {boolean}
|
|
9
|
+
*/
|
|
10
|
+
export function isRTL(): boolean;
|
|
1
11
|
/**
|
|
2
12
|
* Finds the closest ancestor of the given element that has the given attribute.
|
|
3
13
|
*
|
|
@@ -67,6 +77,21 @@ export function getPathFromKey(key: string): string[];
|
|
|
67
77
|
* @returns {Function|undefined}
|
|
68
78
|
*/
|
|
69
79
|
export function getSnippet(obj: Object, key: string, defaultSnippet?: null | Function): Function | undefined;
|
|
80
|
+
/**
|
|
81
|
+
* Resolve which snippet to render for a proxy item.
|
|
82
|
+
*
|
|
83
|
+
* Checks proxy.snippet for a per-item named override first (e.g. item.snippet = 'highlighted').
|
|
84
|
+
* Falls back to the component-level fallback snippet name (e.g. 'itemContent' / 'groupContent').
|
|
85
|
+
* Returns null if neither is found.
|
|
86
|
+
*
|
|
87
|
+
* @param {Record<string, unknown>} snippets - snippets passed to the component
|
|
88
|
+
* @param {{ snippet?: string | null }} proxy - any object with an optional .snippet property
|
|
89
|
+
* @param {string} [fallback] - fallback snippet name; defaults to ITEM_SNIPPET ('itemContent')
|
|
90
|
+
* @returns {Function | null}
|
|
91
|
+
*/
|
|
92
|
+
export function resolveSnippet(snippets: Record<string, unknown>, proxy: {
|
|
93
|
+
snippet?: string | null;
|
|
94
|
+
}, fallback?: string): Function | null;
|
|
70
95
|
/**
|
|
71
96
|
* convert hex string to `{r} {g} {b}`
|
|
72
97
|
* @param {string} hex
|
|
@@ -80,4 +105,3 @@ export function hex2rgb(hex: string): string;
|
|
|
80
105
|
* @returns {string|null} - Returns the original string if it's an image URL or image data, otherwise null
|
|
81
106
|
*/
|
|
82
107
|
export function getImage(str: string): string | null;
|
|
83
|
-
export function importIcons(icons: Object): Object;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates icon collection loaders for UnoCSS presetIcons from a simple config.
|
|
3
|
+
*
|
|
4
|
+
* This function transforms a map of collection names to JSON package paths
|
|
5
|
+
* into the format expected by UnoCSS's presetIcons.
|
|
6
|
+
*
|
|
7
|
+
* Supports three types of paths:
|
|
8
|
+
* - Package paths (e.g., '@rokkit/icons/ui.json') - resolved via require
|
|
9
|
+
* - Absolute paths (e.g., '/path/to/icons.json') - used directly
|
|
10
|
+
* - Relative paths (e.g., './static/icons.json') - resolved from process.cwd()
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* // uno.config.js
|
|
14
|
+
* import { iconCollections } from '@rokkit/core/vite'
|
|
15
|
+
*
|
|
16
|
+
* export default defineConfig({
|
|
17
|
+
* presets: [
|
|
18
|
+
* presetIcons({
|
|
19
|
+
* collections: iconCollections({
|
|
20
|
+
* rokkit: '@rokkit/icons/ui.json',
|
|
21
|
+
* logo: '@rokkit/icons/auth.json',
|
|
22
|
+
* solar: '@iconify-json/solar/icons.json',
|
|
23
|
+
* custom: './static/icons/custom.json'
|
|
24
|
+
* })
|
|
25
|
+
* })
|
|
26
|
+
* ]
|
|
27
|
+
* })
|
|
28
|
+
*
|
|
29
|
+
* @param {Record<string, string>} config - Map of collection alias to JSON path
|
|
30
|
+
* @returns {Record<string, () => any>} Collections object for presetIcons
|
|
31
|
+
*/
|
|
32
|
+
export function iconCollections(config: Record<string, string>): Record<string, () => any>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { iconCollections } from "./icon-collections.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rokkit/core",
|
|
3
|
-
"version": "1.0.0-next.
|
|
3
|
+
"version": "1.0.0-next.129",
|
|
4
4
|
"description": "Contains core utility functions and classes that can be used in various components.",
|
|
5
5
|
"author": "Jerry Thomas <me@jerrythomas.name>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,10 +27,18 @@
|
|
|
27
27
|
"types": "./dist/index.d.ts",
|
|
28
28
|
"import": "./src/index.js",
|
|
29
29
|
"svelte": "./src/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./vite": {
|
|
32
|
+
"types": "./dist/vite/index.d.ts",
|
|
33
|
+
"import": "./src/vite/index.js"
|
|
30
34
|
}
|
|
31
35
|
},
|
|
32
36
|
"dependencies": {
|
|
37
|
+
"@unocss/preset-mini": "^66.6.1",
|
|
33
38
|
"date-fns": "^4.1.0",
|
|
34
|
-
"ramda": "^0.
|
|
39
|
+
"ramda": "^0.32.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@rokkit/icons": "1.0.0-next.129"
|
|
35
43
|
}
|
|
36
44
|
}
|
package/src/constants.js
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
|
-
export { defaultColors, syntaxColors, shades, defaultPalette } from './colors/index
|
|
1
|
+
export { defaultColors, syntaxColors, shades, defaultPalette } from './colors/index'
|
|
2
2
|
export const DATA_IMAGE_REGEX = /^data:image\/(jpeg|png|gif|bmp|webp|svg\+xml)/i
|
|
3
|
+
|
|
4
|
+
// ─── Snippet names ────────────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
export const ITEM_SNIPPET = 'itemContent'
|
|
7
|
+
export const GROUP_SNIPPET = 'groupContent'
|
|
3
8
|
/**
|
|
9
|
+
* @deprecated Use BASE_FIELDS from @rokkit/core instead.
|
|
10
|
+
* Retained for legacy ListController/FieldMapper/Proxy consumers (Toolbar, Table).
|
|
11
|
+
* Will be removed when those components migrate to Wrapper+Navigator.
|
|
4
12
|
* @type {import('./types).FieldMapping} Fields
|
|
5
13
|
*/
|
|
6
|
-
export const
|
|
14
|
+
export const DEFAULT_FIELDS = {
|
|
7
15
|
id: 'id',
|
|
8
16
|
href: 'href',
|
|
9
17
|
icon: 'icon',
|
|
@@ -24,26 +32,83 @@ export const defaultFields = {
|
|
|
24
32
|
label: 'label',
|
|
25
33
|
component: 'component', // to be deprecated in favour of snippet
|
|
26
34
|
snippet: 'snippet',
|
|
35
|
+
disabled: 'disabled',
|
|
27
36
|
/* flag fields */
|
|
28
37
|
deleted: '_deleted',
|
|
29
38
|
expanded: '_expanded',
|
|
30
39
|
selected: '_selected'
|
|
31
40
|
}
|
|
32
41
|
|
|
33
|
-
|
|
42
|
+
// ─── BASE_FIELDS ─────────────────────────────────────────────────────────────
|
|
43
|
+
// Canonical field mapping for ProxyItem and all Wrapper+Navigator components.
|
|
44
|
+
// Semantic keys map to common raw data keys for backward compatibility.
|
|
45
|
+
|
|
46
|
+
export const BASE_FIELDS = {
|
|
47
|
+
// Identity
|
|
48
|
+
id: 'id',
|
|
49
|
+
value: 'value',
|
|
50
|
+
// Display
|
|
51
|
+
label: 'label',
|
|
52
|
+
icon: 'icon',
|
|
53
|
+
avatar: 'image',
|
|
54
|
+
subtext: 'description',
|
|
55
|
+
tooltip: 'title',
|
|
56
|
+
badge: 'badge',
|
|
57
|
+
shortcut: 'shortcut',
|
|
58
|
+
// Structure
|
|
59
|
+
children: 'children',
|
|
60
|
+
type: 'type',
|
|
61
|
+
snippet: 'snippet',
|
|
62
|
+
href: 'href',
|
|
63
|
+
hrefTarget: 'target',
|
|
64
|
+
// State
|
|
65
|
+
disabled: 'disabled',
|
|
66
|
+
expanded: 'expanded',
|
|
67
|
+
selected: 'selected',
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const LEGACY_KEY_MAP = { description: 'subtext', title: 'tooltip', image: 'avatar', target: 'hrefTarget' }
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Remap legacy field-override keys to their BASE_FIELDS semantic equivalents.
|
|
74
|
+
* e.g. { text: 'name' } → { label: 'name' }
|
|
75
|
+
*
|
|
76
|
+
* @param {Record<string, string> | null | undefined} fields
|
|
77
|
+
* @returns {Record<string, string>}
|
|
78
|
+
*/
|
|
79
|
+
export function normalizeFields(fields) {
|
|
80
|
+
if (!fields || typeof fields !== 'object') return {}
|
|
81
|
+
const result = {}
|
|
82
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
83
|
+
result[LEGACY_KEY_MAP[key] ?? key] = value
|
|
84
|
+
}
|
|
85
|
+
return result
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const DEFAULT_ICONS = [
|
|
34
89
|
'accordion-opened',
|
|
35
90
|
'accordion-closed',
|
|
36
91
|
'action-remove',
|
|
92
|
+
'action-cancel',
|
|
93
|
+
'action-retry',
|
|
37
94
|
'action-add',
|
|
38
95
|
'action-clear',
|
|
39
96
|
'action-search',
|
|
40
97
|
'action-close',
|
|
41
98
|
'action-copy',
|
|
42
99
|
'action-copysuccess',
|
|
100
|
+
'action-check',
|
|
101
|
+
'action-pin',
|
|
102
|
+
'action-save',
|
|
103
|
+
'action-unpin',
|
|
43
104
|
'node-opened',
|
|
44
105
|
'node-closed',
|
|
106
|
+
'folder-opened',
|
|
107
|
+
'folder-closed',
|
|
45
108
|
'selector-opened',
|
|
46
109
|
'selector-closed',
|
|
110
|
+
'menu-opened',
|
|
111
|
+
'menu-closed',
|
|
47
112
|
'checkbox-checked',
|
|
48
113
|
'checkbox-unchecked',
|
|
49
114
|
'checkbox-unknown',
|
|
@@ -54,10 +119,13 @@ export const defaultIcons = [
|
|
|
54
119
|
'radio-on',
|
|
55
120
|
'mode-dark',
|
|
56
121
|
'mode-light',
|
|
122
|
+
'mode-system',
|
|
57
123
|
'navigate-left',
|
|
58
124
|
'navigate-right',
|
|
59
125
|
'navigate-up',
|
|
60
126
|
'navigate-down',
|
|
127
|
+
'palette-hex',
|
|
128
|
+
'palette-presets',
|
|
61
129
|
'state-error',
|
|
62
130
|
'state-warning',
|
|
63
131
|
'state-success',
|
|
@@ -84,14 +152,14 @@ export const defaultIcons = [
|
|
|
84
152
|
'align-vertical-middle'
|
|
85
153
|
]
|
|
86
154
|
|
|
87
|
-
export const
|
|
155
|
+
export const DEFAULT_OPTIONS = {
|
|
88
156
|
id: 'id',
|
|
89
157
|
label: 'label',
|
|
90
158
|
value: 'value',
|
|
91
159
|
checked: 'checked'
|
|
92
160
|
}
|
|
93
161
|
|
|
94
|
-
export const
|
|
162
|
+
export const DEFAULT_KEYMAP = {
|
|
95
163
|
ArrowRight: 'open',
|
|
96
164
|
ArrowLeft: 'close',
|
|
97
165
|
ArrowDown: 'down',
|
|
@@ -100,7 +168,7 @@ export const defaultKeyMap = {
|
|
|
100
168
|
Escape: 'deselect'
|
|
101
169
|
}
|
|
102
170
|
|
|
103
|
-
export const
|
|
171
|
+
export const DEFAULT_THEME_MAPPING = {
|
|
104
172
|
surface: 'slate',
|
|
105
173
|
primary: 'orange',
|
|
106
174
|
secondary: 'pink',
|
|
@@ -124,15 +192,6 @@ export const TONE_MAP = {
|
|
|
124
192
|
z8: 800,
|
|
125
193
|
z9: 900,
|
|
126
194
|
z10: 950
|
|
127
|
-
// base: 50,
|
|
128
|
-
// inset: 100,
|
|
129
|
-
// subtle: 200,
|
|
130
|
-
// muted: 300,
|
|
131
|
-
// raised: 400,
|
|
132
|
-
// elevated: 600,
|
|
133
|
-
// floating: 700,
|
|
134
|
-
// contrast: 800,
|
|
135
|
-
// overlay: 900
|
|
136
195
|
}
|
|
137
196
|
/**
|
|
138
197
|
* Splits an icon name into its group and key components.
|
|
@@ -161,4 +220,4 @@ export function stateIconsFromNames(icons) {
|
|
|
161
220
|
)
|
|
162
221
|
}
|
|
163
222
|
|
|
164
|
-
export const
|
|
223
|
+
export const DEFAULT_STATE_ICONS = stateIconsFromNames(DEFAULT_ICONS)
|
package/src/field-mapper.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { isNil, has, omit } from 'ramda'
|
|
2
|
-
import {
|
|
2
|
+
import { DEFAULT_FIELDS } from './constants.js'
|
|
3
3
|
import { isObject } from './utils.js'
|
|
4
4
|
|
|
5
5
|
export class FieldMapper {
|
|
6
|
-
#fields = { ...
|
|
6
|
+
#fields = { ...DEFAULT_FIELDS }
|
|
7
7
|
#childMapper = null
|
|
8
8
|
|
|
9
|
-
constructor(fields =
|
|
9
|
+
constructor(fields = DEFAULT_FIELDS) {
|
|
10
10
|
this.#updateFields(fields)
|
|
11
11
|
}
|
|
12
12
|
|
|
@@ -16,7 +16,6 @@ export class FieldMapper {
|
|
|
16
16
|
})
|
|
17
17
|
this.hasIcon = has(this.#fields.icon)
|
|
18
18
|
this.hasImage = has(this.#fields.image)
|
|
19
|
-
this.hasText = has(this.#fields.text)
|
|
20
19
|
this.hasValue = has(this.#fields.value)
|
|
21
20
|
this.hasLabel = has(this.#fields.label)
|
|
22
21
|
this.hasComponent = has(this.#fields.component)
|
|
@@ -47,7 +46,7 @@ export class FieldMapper {
|
|
|
47
46
|
return value[this.fields[fieldName]]
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
return fieldName === '
|
|
49
|
+
return fieldName === 'label' ? value : null
|
|
51
50
|
}
|
|
52
51
|
|
|
53
52
|
/**
|
|
@@ -83,7 +82,7 @@ export class FieldMapper {
|
|
|
83
82
|
}
|
|
84
83
|
|
|
85
84
|
getFormattedText(value, formatter) {
|
|
86
|
-
const text = this.get('
|
|
85
|
+
const text = this.get('label', value)
|
|
87
86
|
|
|
88
87
|
if (isNil(text)) return ''
|
|
89
88
|
|
package/src/index.js
CHANGED
|
@@ -16,4 +16,4 @@ export { createEmitter } from './events.js'
|
|
|
16
16
|
export { getLineTypes } from './connector.js'
|
|
17
17
|
export { weekdays, getCalendarDays } from './calendar.js'
|
|
18
18
|
export { generateTicks } from './ticks.js'
|
|
19
|
-
export {
|
|
19
|
+
export { getNestedFields } from './mapping.js'
|
package/src/key-event-map.js
CHANGED
|
@@ -26,7 +26,7 @@ export class KeyEventMap {
|
|
|
26
26
|
* @returns {string|null} - The event name or null if no match is found.
|
|
27
27
|
*/
|
|
28
28
|
getEventForKey(key) {
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
const matchEvent = ([_, keys]) =>
|
|
31
31
|
(Array.isArray(keys) && keys.includes(key)) || (keys instanceof RegExp && keys.test(key))
|
|
32
32
|
|
package/src/mapping.js
CHANGED
|
@@ -1,73 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { toString, isObject } from './utils.js'
|
|
3
|
-
import { has } from 'ramda'
|
|
4
|
-
/**
|
|
5
|
-
* Get the component to be used to render the item.
|
|
6
|
-
* If the component is null or undefined, it will return the default component.
|
|
7
|
-
*
|
|
8
|
-
* @param {object|string} value
|
|
9
|
-
* @param {import('./types.js').FieldMapping} fields
|
|
10
|
-
* @param {import('./types.js').ComponentMap} using
|
|
11
|
-
*/
|
|
12
|
-
export function getComponent(value, fields, using) {
|
|
13
|
-
return fields.component && isObject(value)
|
|
14
|
-
? (using[value[fields.component]] ?? using.default)
|
|
15
|
-
: using.default
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Get the icon for the item. If the icon is an object, it will use the state to determine which icon to use.
|
|
20
|
-
*
|
|
21
|
-
* @param {object} value
|
|
22
|
-
* @param {import('./types.js').FieldMapping} fields
|
|
23
|
-
* @returns {string}
|
|
24
|
-
*/
|
|
25
|
-
function getIconFromObject(value, fields) {
|
|
26
|
-
if (!value) return null
|
|
27
|
-
if (typeof value[fields.icon] === 'object') return value[fields.icon][value[fields.state]]
|
|
28
|
-
return value[fields.icon]
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Get the icon for the item. If the icon is an object, it will use the state to determine which icon to use.
|
|
33
|
-
*
|
|
34
|
-
* @param {object|string} value
|
|
35
|
-
* @param {import('./types.js').FieldMapping} fields
|
|
36
|
-
* @returns {string}
|
|
37
|
-
*/
|
|
38
|
-
export function getIcon(value, fields = defaultFields) {
|
|
39
|
-
if (fields.icon === undefined || typeof value !== 'object') return null
|
|
40
|
-
|
|
41
|
-
const name = getIconFromObject(value, fields)
|
|
42
|
-
return name ? [fields.iconPrefix, name].join('-').replace(/^-+/g, '') : null
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Get the value for the item. If the value is an object,
|
|
47
|
-
* it will use the field mapping to determine which attribute to get.
|
|
48
|
-
*
|
|
49
|
-
* @param {*} node
|
|
50
|
-
* @param {import('./types').FieldMapping} fields
|
|
51
|
-
* @returns {*}
|
|
52
|
-
*/
|
|
53
|
-
export function getValue(node, fields = defaultFields) {
|
|
54
|
-
return typeof node === 'object' && node !== null
|
|
55
|
-
? (node[fields.value] ?? node[fields.text])
|
|
56
|
-
: node
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Get the text for the item. If the text is an object,
|
|
61
|
-
* it will use the field mapping to determine which attribute to get.
|
|
62
|
-
*
|
|
63
|
-
* @param {*} node
|
|
64
|
-
* @param {import('./types').FieldMapping} fields
|
|
65
|
-
* @returns {string}
|
|
66
|
-
*/
|
|
67
|
-
export function getText(node, fields = defaultFields) {
|
|
68
|
-
const value = isObject(node) ? node[fields.text] : node
|
|
69
|
-
return value
|
|
70
|
-
}
|
|
1
|
+
import { DEFAULT_FIELDS } from './constants.js'
|
|
71
2
|
|
|
72
3
|
/**
|
|
73
4
|
* Gets the attribute from the node
|
|
@@ -79,50 +10,6 @@ export function getAttribute(node, attr) {
|
|
|
79
10
|
return typeof node === 'object' && node !== null && attr !== null ? node[attr] : null
|
|
80
11
|
}
|
|
81
12
|
|
|
82
|
-
/**
|
|
83
|
-
* Get the formatted text for the item. If the text is an object, use the field mapping to determine
|
|
84
|
-
* which attribute to get currency. Use the formatter or identity function to format the text.
|
|
85
|
-
*
|
|
86
|
-
* @param {*} node
|
|
87
|
-
* @param {import('./types').FieldMapping} fields
|
|
88
|
-
* @param {Function} formatter
|
|
89
|
-
* @returns {Function}
|
|
90
|
-
*/
|
|
91
|
-
export function getFormattedText(node, fields = defaultFields, formatter = toString) {
|
|
92
|
-
const value = getText(node, fields)
|
|
93
|
-
const currency = getAttribute(node, fields.currency)
|
|
94
|
-
const formatValue = typeof formatter === 'function' ? formatter : toString
|
|
95
|
-
|
|
96
|
-
return currency ? formatValue(value, currency) : formatValue(value)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Check if the current item is a parent
|
|
101
|
-
*
|
|
102
|
-
* @param {*} item
|
|
103
|
-
* @param {import('./types').FieldMapping} fields
|
|
104
|
-
* @returns {boolean}
|
|
105
|
-
*/
|
|
106
|
-
export function hasChildren(item, fields) {
|
|
107
|
-
return has(fields.children, item) && Array.isArray(item[fields.children])
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Check if the current item is a parent and is expanded
|
|
112
|
-
*
|
|
113
|
-
* @param {*} item
|
|
114
|
-
* @param {import('./types').FieldMapping} fields
|
|
115
|
-
* @returns {boolean}
|
|
116
|
-
*/
|
|
117
|
-
export function isExpanded(item, fields) {
|
|
118
|
-
if (item === null) return false
|
|
119
|
-
if (!hasChildren(item, fields)) return false
|
|
120
|
-
if (has(fields.expanded, item)) {
|
|
121
|
-
return item[fields.expanded]
|
|
122
|
-
}
|
|
123
|
-
return false
|
|
124
|
-
}
|
|
125
|
-
|
|
126
13
|
/**
|
|
127
14
|
* Fetches the fieldmapping for a child node
|
|
128
15
|
*
|
|
@@ -130,5 +17,5 @@ export function isExpanded(item, fields) {
|
|
|
130
17
|
* @returns {import('./types').FieldMapping}
|
|
131
18
|
*/
|
|
132
19
|
export function getNestedFields(fields) {
|
|
133
|
-
return { ...
|
|
20
|
+
return { ...DEFAULT_FIELDS, ...(fields.fields ?? fields) }
|
|
134
21
|
}
|
package/src/nested.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { omit } from 'ramda'
|
|
2
|
-
import {
|
|
2
|
+
import { DEFAULT_FIELDS } from './constants.js'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Flattens a nested list of items
|
|
@@ -9,8 +9,8 @@ import { defaultFields } from './constants.js'
|
|
|
9
9
|
* @param {number} level
|
|
10
10
|
* @returns {Array}
|
|
11
11
|
*/
|
|
12
|
-
export function flattenNestedList(items, fields =
|
|
13
|
-
fields = { ...
|
|
12
|
+
export function flattenNestedList(items, fields = DEFAULT_FIELDS, level = 0) {
|
|
13
|
+
fields = { ...DEFAULT_FIELDS, ...fields }
|
|
14
14
|
let data = []
|
|
15
15
|
items.forEach((item) => {
|
|
16
16
|
const children = item[fields.children] ?? []
|
|
@@ -26,31 +26,3 @@ export function flattenNestedList(items, fields = defaultFields, level = 0) {
|
|
|
26
26
|
})
|
|
27
27
|
return data
|
|
28
28
|
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Matches a path slug to a value in the menu
|
|
32
|
-
*
|
|
33
|
-
* @param {string} slug
|
|
34
|
-
* @param {Array} data
|
|
35
|
-
* @param {import('./types').FieldMapping} fields
|
|
36
|
-
* @returns {any}
|
|
37
|
-
*/
|
|
38
|
-
export function findValueFromPath(slug, data, fields) {
|
|
39
|
-
fields = { ...defaultFields, ...fields }
|
|
40
|
-
const keys = slug.split('/')
|
|
41
|
-
let items = data
|
|
42
|
-
let value = null
|
|
43
|
-
|
|
44
|
-
keys.forEach((key, index) => {
|
|
45
|
-
const match = items.find((item) => item[fields.key] === key)
|
|
46
|
-
if (match) {
|
|
47
|
-
if (index < keys.length - 1) {
|
|
48
|
-
match[fields.isOpen] = true
|
|
49
|
-
items = match[fields.children]
|
|
50
|
-
} else {
|
|
51
|
-
value = match
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
})
|
|
55
|
-
return value
|
|
56
|
-
}
|
package/src/utils.js
CHANGED
|
@@ -1,7 +1,54 @@
|
|
|
1
1
|
import { has, isNil } from 'ramda'
|
|
2
|
-
import { DATA_IMAGE_REGEX } from './constants'
|
|
2
|
+
import { DATA_IMAGE_REGEX, ITEM_SNIPPET } from './constants'
|
|
3
3
|
|
|
4
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
|
+
* Detects text direction based on HTML lang attribute
|
|
25
|
+
* @returns {'ltr' | 'rtl'}
|
|
26
|
+
*/
|
|
27
|
+
export function detectDirection() {
|
|
28
|
+
if (typeof document === 'undefined') return 'ltr'
|
|
29
|
+
|
|
30
|
+
// Check dir attribute first (explicit override)
|
|
31
|
+
const htmlDir = document.documentElement.getAttribute('dir')
|
|
32
|
+
if (htmlDir === 'rtl' || htmlDir === 'ltr') return htmlDir
|
|
33
|
+
|
|
34
|
+
// Detect from lang attribute
|
|
35
|
+
const lang = document.documentElement.getAttribute('lang')
|
|
36
|
+
if (lang) {
|
|
37
|
+
// Extract primary language code (e.g., 'ar-SA' -> 'ar')
|
|
38
|
+
const primaryLang = lang.split('-')[0].toLowerCase()
|
|
39
|
+
if (RTL_LANGUAGES.includes(primaryLang)) return 'rtl'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return 'ltr'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Checks if current document direction is RTL
|
|
47
|
+
* @returns {boolean}
|
|
48
|
+
*/
|
|
49
|
+
export function isRTL() {
|
|
50
|
+
return detectDirection() === 'rtl'
|
|
51
|
+
}
|
|
5
52
|
/**
|
|
6
53
|
* Finds the closest ancestor of the given element that has the given attribute.
|
|
7
54
|
*
|
|
@@ -123,19 +170,22 @@ export function getSnippet(obj, key, defaultSnippet = null) {
|
|
|
123
170
|
}
|
|
124
171
|
|
|
125
172
|
/**
|
|
126
|
-
*
|
|
173
|
+
* Resolve which snippet to render for a proxy item.
|
|
127
174
|
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
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}
|
|
130
183
|
*/
|
|
131
|
-
export
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
import(/* @vite-ignore */ value, { with: { type: 'json' } }).then((i) => i.default)
|
|
137
|
-
return acc
|
|
138
|
-
}, {})
|
|
184
|
+
export function resolveSnippet(snippets, proxy, fallback = ITEM_SNIPPET) {
|
|
185
|
+
const name = proxy?.snippet
|
|
186
|
+
if (name && typeof snippets[name] === 'function') return snippets[name]
|
|
187
|
+
const fb = snippets[fallback]
|
|
188
|
+
return typeof fb === 'function' ? fb : null
|
|
139
189
|
}
|
|
140
190
|
|
|
141
191
|
/**
|
|
@@ -178,8 +228,8 @@ function isImageUrl(str) {
|
|
|
178
228
|
|
|
179
229
|
// Fallback if URL constructor is not available
|
|
180
230
|
return fallbackValidation()
|
|
181
|
-
|
|
182
|
-
} catch
|
|
231
|
+
|
|
232
|
+
} catch {
|
|
183
233
|
// Fallback if URL constructor fails
|
|
184
234
|
return fallbackValidation()
|
|
185
235
|
}
|
|
@@ -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'
|