@rokkit/core 1.0.0-next.99 → 1.0.1

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/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 !== null && !(val instanceof Date)
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.
@@ -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
- }
@@ -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
- }