@rokkit/core 1.8.0 → 1.8.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/dist/colors/index.d.ts +2 -2
- package/dist/dom.d.ts +18 -0
- package/dist/icons.d.ts +29 -0
- package/dist/keys.d.ts +13 -0
- package/dist/oklch.d.ts +23 -0
- package/dist/snippets.d.ts +24 -0
- package/dist/theme.d.ts +23 -40
- package/dist/utils.d.ts +6 -122
- package/dist/values.d.ts +32 -0
- package/package.json +1 -1
- package/src/colors/index.ts +3 -4
- package/src/dom.js +60 -0
- package/src/icons.js +94 -0
- package/src/keys.js +19 -0
- package/src/oklch.js +124 -0
- package/src/snippets.js +45 -0
- package/src/theme.ts +61 -35
- package/src/utils.js +9 -388
- package/src/values.js +56 -0
package/src/utils.js
CHANGED
|
@@ -1,390 +1,11 @@
|
|
|
1
|
-
import { has, isNil } from 'ramda'
|
|
2
|
-
import { DATA_IMAGE_REGEX, ITEM_SNIPPET } from './constants'
|
|
3
|
-
|
|
4
|
-
let idCounter = 0
|
|
5
|
-
|
|
6
1
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
2
|
+
* A barrel over single-job modules — kept so `export * from './utils.js'` (the package index)
|
|
3
|
+
* and deep imports of `@rokkit/core/src/utils.js` keep their names. New helpers go in the
|
|
4
|
+
* module for their job, not here. The export list is pinned by spec/utils-modules.spec.js.
|
|
9
5
|
*/
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
65
|
-
/**
|
|
66
|
-
* A function that performs no operations.
|
|
67
|
-
*/
|
|
68
|
-
export function noop() {
|
|
69
|
-
// intentionally empty to support default actions
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Generates a random id
|
|
74
|
-
*
|
|
75
|
-
* @returns {string} A random id
|
|
76
|
-
*/
|
|
77
|
-
export function id(prefix = '') {
|
|
78
|
-
return [prefix, Math.random().toString(36).substring(2, 9), ++idCounter]
|
|
79
|
-
.filter((x) => !isNil(x) && x !== '')
|
|
80
|
-
.join('-')
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Check if a value is a json object
|
|
85
|
-
*
|
|
86
|
-
* @param {*} val
|
|
87
|
-
* @returns {boolean}
|
|
88
|
-
*/
|
|
89
|
-
export function isObject(val) {
|
|
90
|
-
return typeof val === 'object' && !isNil(val) && !(val instanceof Date)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Converts the value to a string. If the value is an object, it will convert it to a JSON string.
|
|
95
|
-
*
|
|
96
|
-
* @param {*} value
|
|
97
|
-
* @returns {string}
|
|
98
|
-
*/
|
|
99
|
-
export function toString(value) {
|
|
100
|
-
if (value === null || value === undefined) return value
|
|
101
|
-
if (isObject(value)) return JSON.stringify(value, null, 2)
|
|
102
|
-
return value.toString()
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Generates icon shortcuts for a collection of icons
|
|
107
|
-
*
|
|
108
|
-
* @param {string[]} icons
|
|
109
|
-
* @param {string} collection
|
|
110
|
-
* @param {string} variants
|
|
111
|
-
* @returns {Object}
|
|
112
|
-
*/
|
|
113
|
-
export function iconShortcuts(icons, collection, variants) {
|
|
114
|
-
const suffix = variants ? `-${variants}` : ''
|
|
115
|
-
const shortcuts = !collection
|
|
116
|
-
? {}
|
|
117
|
-
: icons.reduce(
|
|
118
|
-
(acc, name) => ({
|
|
119
|
-
...acc,
|
|
120
|
-
[name]: [collection, name].join(':') + suffix
|
|
121
|
-
}),
|
|
122
|
-
{}
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
return shortcuts
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Scales the path by the size
|
|
130
|
-
*
|
|
131
|
-
* @param {number} size
|
|
132
|
-
* @param {string|number} x
|
|
133
|
-
* @returns {string|number}
|
|
134
|
-
*/
|
|
135
|
-
export function scaledPath(size, x) {
|
|
136
|
-
if (Array.isArray(x)) return x.map((v) => scaledPath(size, v)).join(' ')
|
|
137
|
-
return typeof x === 'number' ? x * size : x
|
|
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
|
-
* Reads the proxy's `snippet` field (via proxy.get('snippet'), honoring fields.snippet)
|
|
176
|
-
* for a per-item named override first (e.g. item.snippet = 'highlighted').
|
|
177
|
-
* Falls back to the component-level fallback snippet name (e.g. 'itemContent' / 'groupContent').
|
|
178
|
-
* Returns null if neither is found.
|
|
179
|
-
*
|
|
180
|
-
* @param {Record<string, unknown>} snippets - snippets passed to the component
|
|
181
|
-
* @param {{ get: (key: string) => unknown }} proxy - a ProxyItem; its snippet field is read via proxy.get('snippet')
|
|
182
|
-
* @param {string} [fallback] - fallback snippet name; defaults to ITEM_SNIPPET ('itemContent')
|
|
183
|
-
* @returns {Function | null}
|
|
184
|
-
*/
|
|
185
|
-
/**
|
|
186
|
-
* @param {unknown} value
|
|
187
|
-
* @returns {Function|null}
|
|
188
|
-
*/
|
|
189
|
-
function asSnippet(value) {
|
|
190
|
-
return typeof value === 'function' ? value : null
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
export function resolveSnippet(snippets, proxy, fallback = ITEM_SNIPPET) {
|
|
194
|
-
// `snippet` is a field-mapped prop like icon/href — read it via the proxy's
|
|
195
|
-
// field accessor (honors fields.snippet), not as a direct property.
|
|
196
|
-
const name = proxy?.get('snippet')
|
|
197
|
-
return asSnippet(name && snippets[name]) || asSnippet(snippets[fallback])
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* convert hex string to `{r},{g},{b}`
|
|
202
|
-
* @param {string} hex
|
|
203
|
-
* @return {string}
|
|
204
|
-
*/
|
|
205
|
-
export function hex2rgb(hex) {
|
|
206
|
-
const [r, g, b] = hex.match(/\w\w/g).map((x) => parseInt(x, 16))
|
|
207
|
-
return `${r},${g},${b}`
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Convert sRGB component (0-255) to linear RGB.
|
|
212
|
-
* @param {number} c - sRGB value 0-255
|
|
213
|
-
* @returns {number}
|
|
214
|
-
*/
|
|
215
|
-
function srgbToLinear(c) {
|
|
216
|
-
const s = c / 255
|
|
217
|
-
return s <= 0.04045 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4)
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Convert linear RGB to sRGB component (0-255).
|
|
222
|
-
* @param {number} c - linear value 0-1
|
|
223
|
-
* @returns {number}
|
|
224
|
-
*/
|
|
225
|
-
function linearToSrgb(c) {
|
|
226
|
-
const s = c <= 0.0031308 ? c * 12.92 : 1.055 * Math.pow(c, 1 / 2.4) - 0.055
|
|
227
|
-
return Math.round(Math.max(0, Math.min(255, s * 255)))
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// CSS Color Level 4 two-step matrices (via XYZ D65) for round-trip fidelity.
|
|
231
|
-
// Linear sRGB → XYZ D65
|
|
232
|
-
const SRGB_TO_XYZ = [
|
|
233
|
-
[0.4123907993, 0.3575843394, 0.1804807884],
|
|
234
|
-
[0.2126390059, 0.7151686788, 0.0721923154],
|
|
235
|
-
[0.0193308187, 0.1191947798, 0.9505321522]
|
|
236
|
-
]
|
|
237
|
-
// XYZ D65 → LMS
|
|
238
|
-
const XYZ_TO_LMS = [
|
|
239
|
-
[0.8189330101, 0.3618667424, -0.1288597137],
|
|
240
|
-
[0.0329845436, 0.9293118715, 0.0361456387],
|
|
241
|
-
[0.0482003018, 0.2643662691, 0.633851707]
|
|
242
|
-
]
|
|
243
|
-
// LMS^(1/3) → OKLab
|
|
244
|
-
const LMS3_TO_OKLAB = [
|
|
245
|
-
[0.2104542553, 0.793617785, -0.0040720468],
|
|
246
|
-
[1.9779984951, -2.428592205, 0.4505937099],
|
|
247
|
-
[0.0259040371, 0.7827717662, -0.808675766]
|
|
248
|
-
]
|
|
249
|
-
// OKLab → LMS^(1/3)
|
|
250
|
-
const OKLAB_TO_LMS3 = [
|
|
251
|
-
[1.0, 0.3963377774, 0.2158037573],
|
|
252
|
-
[1.0, -0.1055613458, -0.0638541728],
|
|
253
|
-
[1.0, -0.0894841775, -1.291485548]
|
|
254
|
-
]
|
|
255
|
-
// LMS → XYZ D65
|
|
256
|
-
const LMS_TO_XYZ = [
|
|
257
|
-
[1.2270138511, -0.5577999807, 0.281256149],
|
|
258
|
-
[-0.0405801784, 1.1122568696, -0.0716766787],
|
|
259
|
-
[-0.0763812845, -0.4214819784, 1.5861632204]
|
|
260
|
-
]
|
|
261
|
-
// XYZ D65 → linear sRGB
|
|
262
|
-
const XYZ_TO_SRGB = [
|
|
263
|
-
[3.2409699419, -1.5373831776, -0.4986107603],
|
|
264
|
-
[-0.9692436363, 1.8759675015, 0.0415550574],
|
|
265
|
-
[0.0556300797, -0.2039769589, 1.0569715142]
|
|
266
|
-
]
|
|
267
|
-
|
|
268
|
-
/** @param {number[][]} m @param {number[]} v */
|
|
269
|
-
function matMul(m, v) {
|
|
270
|
-
return m.map((row) => row[0] * v[0] + row[1] * v[1] + row[2] * v[2])
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Convert hex string to OKLCH components: `{L} {C} {H}`
|
|
275
|
-
* Uses CSS Color Level 4 two-step conversion via XYZ D65 for precision.
|
|
276
|
-
* @param {string} hex - 6-digit hex color (with or without #)
|
|
277
|
-
* @returns {string} space-separated OKLCH components for CSS `oklch(L C H / alpha)`
|
|
278
|
-
*/
|
|
279
|
-
export function hex2oklch(hex) {
|
|
280
|
-
const [r, g, b] = hex.match(/\w\w/g).map((x) => parseInt(x, 16))
|
|
281
|
-
const lin = [srgbToLinear(r), srgbToLinear(g), srgbToLinear(b)]
|
|
282
|
-
|
|
283
|
-
const xyz = matMul(SRGB_TO_XYZ, lin)
|
|
284
|
-
const lms = matMul(XYZ_TO_LMS, xyz)
|
|
285
|
-
const lms3 = [Math.cbrt(lms[0]), Math.cbrt(lms[1]), Math.cbrt(lms[2])]
|
|
286
|
-
const [L, a, bk] = matMul(LMS3_TO_OKLAB, lms3)
|
|
287
|
-
|
|
288
|
-
const C = Math.sqrt(a * a + bk * bk)
|
|
289
|
-
let H = (Math.atan2(bk, a) * 180) / Math.PI
|
|
290
|
-
if (H < 0) H += 360
|
|
291
|
-
|
|
292
|
-
const Lr = Math.round(L * 1000000) / 1000000
|
|
293
|
-
const Cr = Math.round(C * 1000000) / 1000000
|
|
294
|
-
const Hr = Math.round(H * 10000) / 10000
|
|
295
|
-
|
|
296
|
-
return `${Lr} ${Cr} ${Hr}`
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Convert OKLCH components back to hex string.
|
|
301
|
-
* Uses CSS Color Level 4 two-step conversion via XYZ D65 for precision.
|
|
302
|
-
* @param {number} L - Lightness 0-1
|
|
303
|
-
* @param {number} C - Chroma >= 0
|
|
304
|
-
* @param {number} H - Hue 0-360 degrees
|
|
305
|
-
* @returns {string} hex color string (#rrggbb)
|
|
306
|
-
*/
|
|
307
|
-
export function oklch2hex(L, C, H) {
|
|
308
|
-
const hRad = (H * Math.PI) / 180
|
|
309
|
-
const lab = [L, C * Math.cos(hRad), C * Math.sin(hRad)]
|
|
310
|
-
|
|
311
|
-
const lms3 = matMul(OKLAB_TO_LMS3, lab)
|
|
312
|
-
const lms = [lms3[0] ** 3, lms3[1] ** 3, lms3[2] ** 3]
|
|
313
|
-
const xyz = matMul(LMS_TO_XYZ, lms)
|
|
314
|
-
const lin = matMul(XYZ_TO_SRGB, xyz)
|
|
315
|
-
|
|
316
|
-
const toHex = (n) => {
|
|
317
|
-
const hex = linearToSrgb(n).toString(16)
|
|
318
|
-
return hex.length === 1 ? `0${hex}` : hex
|
|
319
|
-
}
|
|
320
|
-
return `#${toHex(lin[0])}${toHex(lin[1])}${toHex(lin[2])}`
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/**
|
|
324
|
-
* Detects whether an icon value is a CSS class or a literal character
|
|
325
|
-
* (kanji, emoji, single letter, etc.).
|
|
326
|
-
*
|
|
327
|
-
* Anything more than one grapheme is treated as a class — including bare
|
|
328
|
-
* semantic names like `file-svelte` which UnoCSS expands to the configured
|
|
329
|
-
* icon collection via `iconShortcuts(DEFAULT_ICONS, …)`. Single characters
|
|
330
|
-
* (kanji, emoji, single letters) render as literal text.
|
|
331
|
-
*
|
|
332
|
-
* @param {string | null | undefined} icon
|
|
333
|
-
* @returns {boolean} true if icon should render as a CSS class
|
|
334
|
-
*/
|
|
335
|
-
export function isIconClass(icon) {
|
|
336
|
-
if (!icon || typeof icon !== 'string') return false
|
|
337
|
-
// Spread to count graphemes, not UTF-16 code units, so multi-codepoint
|
|
338
|
-
// emoji (e.g. 👨👩👧👦) and single CJK characters don't get misread.
|
|
339
|
-
return [...icon].length > 1
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
/**
|
|
343
|
-
* Checks if a string is a valid image URL
|
|
344
|
-
*
|
|
345
|
-
* @param {string} str - The string to check
|
|
346
|
-
* @returns {boolean} - Returns true if the string is an image URL
|
|
347
|
-
*/
|
|
348
|
-
function isImageUrl(str) {
|
|
349
|
-
// Fallback regex-based validation
|
|
350
|
-
const fallbackValidation = () => {
|
|
351
|
-
const urlRegex = /^https?:\/\/.+\.(jpg|jpeg|png|gif|bmp|webp|svg|tiff)(\?.*)?$/i
|
|
352
|
-
return urlRegex.test(str)
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// Check if the string looks like a URL
|
|
356
|
-
try {
|
|
357
|
-
// Use browser-native URL constructor if available
|
|
358
|
-
if (typeof URL !== 'undefined') {
|
|
359
|
-
const url = new URL(str)
|
|
360
|
-
// Only accept HTTP/HTTPS protocols
|
|
361
|
-
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
362
|
-
return false
|
|
363
|
-
}
|
|
364
|
-
// Check common image extensions
|
|
365
|
-
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg', '.tiff']
|
|
366
|
-
const path = url.pathname.toLowerCase()
|
|
367
|
-
return imageExtensions.some((ext) => path.endsWith(ext))
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// Fallback if URL constructor is not available
|
|
371
|
-
return fallbackValidation()
|
|
372
|
-
} catch {
|
|
373
|
-
// Fallback if URL constructor fails
|
|
374
|
-
return fallbackValidation()
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
/**
|
|
378
|
-
* A utility function that detects if a string is an image URL or image data (base64)
|
|
379
|
-
*
|
|
380
|
-
* @param {string} str - The string to check
|
|
381
|
-
* @returns {string|null} - Returns the original string if it's an image URL or image data, otherwise null
|
|
382
|
-
*/
|
|
383
|
-
export function getImage(str) {
|
|
384
|
-
if (DATA_IMAGE_REGEX.test(str)) return str
|
|
385
|
-
// Check if it's a URL
|
|
386
|
-
|
|
387
|
-
if (isImageUrl(str)) return str
|
|
388
|
-
|
|
389
|
-
return null
|
|
390
|
-
}
|
|
6
|
+
export * from './dom.js'
|
|
7
|
+
export * from './values.js'
|
|
8
|
+
export * from './icons.js'
|
|
9
|
+
export * from './keys.js'
|
|
10
|
+
export * from './snippets.js'
|
|
11
|
+
export * from './oklch.js'
|
package/src/values.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** Small value helpers: no-op, ids, object checks, stringifying, scaling. */
|
|
2
|
+
import { isNil } from 'ramda'
|
|
3
|
+
|
|
4
|
+
let idCounter = 0
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A function that performs no operations.
|
|
8
|
+
*/
|
|
9
|
+
export function noop() {
|
|
10
|
+
// intentionally empty to support default actions
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Generates a random id
|
|
15
|
+
*
|
|
16
|
+
* @returns {string} A random id
|
|
17
|
+
*/
|
|
18
|
+
export function id(prefix = '') {
|
|
19
|
+
return [prefix, Math.random().toString(36).substring(2, 9), ++idCounter]
|
|
20
|
+
.filter((x) => !isNil(x) && x !== '')
|
|
21
|
+
.join('-')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Check if a value is a json object
|
|
26
|
+
*
|
|
27
|
+
* @param {*} val
|
|
28
|
+
* @returns {boolean}
|
|
29
|
+
*/
|
|
30
|
+
export function isObject(val) {
|
|
31
|
+
return typeof val === 'object' && !isNil(val) && !(val instanceof Date)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Converts the value to a string. If the value is an object, it will convert it to a JSON string.
|
|
36
|
+
*
|
|
37
|
+
* @param {*} value
|
|
38
|
+
* @returns {string}
|
|
39
|
+
*/
|
|
40
|
+
export function toString(value) {
|
|
41
|
+
if (value === null || value === undefined) return value
|
|
42
|
+
if (isObject(value)) return JSON.stringify(value, null, 2)
|
|
43
|
+
return value.toString()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Scales the path by the size
|
|
48
|
+
*
|
|
49
|
+
* @param {number} size
|
|
50
|
+
* @param {string|number} x
|
|
51
|
+
* @returns {string|number}
|
|
52
|
+
*/
|
|
53
|
+
export function scaledPath(size, x) {
|
|
54
|
+
if (Array.isArray(x)) return x.map((v) => scaledPath(size, v)).join(' ')
|
|
55
|
+
return typeof x === 'number' ? x * size : x
|
|
56
|
+
}
|