@movk/core 0.0.3 → 0.0.4

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 CHANGED
@@ -1,6 +1,7 @@
1
1
  # @movk/core
2
2
 
3
3
  [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
4
+ [![code style](https://antfu.me/badge-code-style.svg)](https://github.com/antfu/eslint-config)
4
5
  [![npm version][npm-version-src]][npm-version-href]
5
6
  [![npm downloads][npm-downloads-src]][npm-downloads-href]
6
7
  [![bundle][bundle-src]][bundle-href]
package/dist/index.d.mts CHANGED
@@ -58,35 +58,6 @@ interface AppStorageReturn<T> {
58
58
  removeItem: () => void;
59
59
  }
60
60
 
61
- declare const _TreeNodeBaseSchema: z.ZodRecord<z.ZodString, z.ZodAny>;
62
- type TreeNodeBase = z.infer<typeof _TreeNodeBaseSchema>;
63
- type TreeNode<T extends TreeNodeBase = TreeNodeBase> = T & {
64
- [K in TreeConfig['children']]: TreeNode<T>[];
65
- };
66
- declare const TreeConfigSchema: z.ZodObject<{
67
- id: z.ZodDefault<z.ZodString>;
68
- pid: z.ZodDefault<z.ZodString>;
69
- children: z.ZodDefault<z.ZodString>;
70
- }, z.core.$strip>;
71
- type TreeConfig = z.infer<typeof TreeConfigSchema>;
72
- type TreeConfigInput = z.input<typeof TreeConfigSchema>;
73
- declare const TreeStatsSchema: z.ZodObject<{
74
- total: z.ZodNumber;
75
- leaves: z.ZodNumber;
76
- depth: z.ZodNumber;
77
- branches: z.ZodNumber;
78
- }, z.core.$strip>;
79
- type TreeStats = z.infer<typeof TreeStatsSchema>;
80
- interface TreeNodeResult<T extends TreeNodeBase = TreeNodeBase> {
81
- readonly node: TreeNode<T>;
82
- readonly path: readonly TreeNode<T>[];
83
- readonly depth: number;
84
- readonly index: number;
85
- }
86
- type TreePredicate<T extends TreeNodeBase = TreeNodeBase> = (node: TreeNode<T>, depth: number, path: readonly TreeNode<T>[]) => boolean;
87
- type TreeTransformer<T extends TreeNodeBase = TreeNodeBase, R extends TreeNodeBase = TreeNodeBase> = (node: TreeNode<T>, depth: number, path: readonly TreeNode<T>[]) => R;
88
- type TreeVisitor<T extends TreeNodeBase = TreeNodeBase> = (node: TreeNode<T>, depth: number, path: readonly TreeNode<T>[]) => void | boolean;
89
-
90
61
  /**
91
62
  * 应用存储管理的组合式函数,支持localStorage和sessionStorage
92
63
  *
@@ -170,36 +141,32 @@ declare function useAppStorage<T = unknown>(config: StorageConfigInput<T>): AppS
170
141
  */
171
142
  declare function useCopyCode(text: string): Promise<boolean>;
172
143
 
173
- /**
174
- * 树形数据结构操作类,提供树形数据的各种操作方法
175
- *
176
- * @category Data Structures
177
- * @example
178
- * ```ts
179
- * // 从扁平数组创建树形结构
180
- * const flatList = [
181
- * { id: '1', name: '根节点', pid: null },
182
- * { id: '2', name: '子节点1', pid: '1' },
183
- * { id: '3', name: '子节点2', pid: '1' },
184
- * { id: '4', name: '孙节点', pid: '2' }
185
- * ]
186
- *
187
- * const tree = Tree.fromList(flatList)
188
- * console.log(tree) // 树形结构
189
- *
190
- * // 查找节点
191
- * const found = Tree.find(tree, (node) => node.name === '子节点1')
192
- *
193
- * // 过滤节点
194
- * const filtered = Tree.filter(tree, (node) => node.name.includes('子'))
195
- *
196
- * // 转换树形结构
197
- * const transformed = Tree.transform(tree, (node) => ({
198
- * ...node,
199
- * displayName: `[${node.name}]`
200
- * }))
201
- * ```
202
- */
144
+ type TreeNode<T = any> = T & {
145
+ children?: TreeNode<T>[];
146
+ [key: string]: any;
147
+ };
148
+ declare const TreeConfigSchema: z.ZodObject<{
149
+ id: z.ZodDefault<z.ZodString>;
150
+ pid: z.ZodDefault<z.ZodString>;
151
+ children: z.ZodDefault<z.ZodString>;
152
+ }, z.core.$strip>;
153
+ type TreeConfigInput = z.input<typeof TreeConfigSchema>;
154
+ declare const _TreeStatsSchema: z.ZodObject<{
155
+ total: z.ZodNumber;
156
+ leaves: z.ZodNumber;
157
+ depth: z.ZodNumber;
158
+ branches: z.ZodNumber;
159
+ }, z.core.$strip>;
160
+ type TreeStats = z.infer<typeof _TreeStatsSchema>;
161
+ interface TreeNodeResult<T = any> {
162
+ readonly node: TreeNode<T>;
163
+ readonly path: readonly TreeNode<T>[];
164
+ readonly depth: number;
165
+ readonly index: number;
166
+ }
167
+ type TreePredicate<T = any> = (node: TreeNode<T>, depth: number, path: readonly TreeNode<T>[]) => boolean;
168
+ type TreeTransformer<T = any, R = any> = (node: TreeNode<T>, depth: number, path: readonly TreeNode<T>[]) => R;
169
+ type TreeVisitor<T = any> = (node: TreeNode<T>, depth: number, path: readonly TreeNode<T>[]) => void | boolean;
203
170
  declare class Tree {
204
171
  private static dfsGenerator;
205
172
  private static bfsGenerator;
@@ -229,7 +196,7 @@ declare class Tree {
229
196
  * console.log(tree) // 转换为树形结构
230
197
  * ```
231
198
  */
232
- static fromList<T extends TreeNodeBase>(list: T[], config?: TreeConfigInput): TreeNode<T>[];
199
+ static fromList<T = any>(list: T[], config?: TreeConfigInput): TreeNode<T>[];
233
200
  /**
234
201
  * 将树形结构转换为扁平数组
235
202
  *
@@ -254,124 +221,328 @@ declare class Tree {
254
221
  * console.log(flatList) // [{ id: '1', name: '根节点' }, { id: '2', name: '子节点1' }, ...]
255
222
  * ```
256
223
  */
257
- static toList<T extends TreeNodeBase>(tree: TreeNode<T> | TreeNode<T>[], config?: TreeConfigInput): T[];
258
- static estimateSize<T extends TreeNodeBase>(tree: TreeNode<T> | TreeNode<T>[], config?: TreeConfigInput): number;
259
- static find<T extends TreeNodeBase>(tree: TreeNode<T> | TreeNode<T>[], predicate: TreePredicate<T>, config?: TreeConfigInput): TreeNodeResult<T> | undefined;
260
- static findAll<T extends TreeNodeBase>(tree: TreeNode<T> | TreeNode<T>[], predicate: TreePredicate<T>, config?: TreeConfigInput): TreeNodeResult<T>[];
261
- static findById<T extends TreeNodeBase>(tree: TreeNode<T> | TreeNode<T>[], id: string, config?: TreeConfigInput): TreeNodeResult<T> | undefined;
262
- static getStats<T extends TreeNodeBase>(tree: TreeNode<T> | TreeNode<T>[], config?: TreeConfigInput): TreeStats;
263
- static filter<T extends TreeNodeBase>(tree: TreeNode<T> | TreeNode<T>[], predicate: TreePredicate<T>, config?: TreeConfigInput): TreeNode<T>[];
264
- static transform<T extends TreeNodeBase, R extends TreeNodeBase>(tree: TreeNode<T> | TreeNode<T>[], transformer: TreeTransformer<T, R>, config?: TreeConfigInput): TreeNode<R>[];
265
- static forEach<T extends TreeNodeBase>(tree: TreeNode<T> | TreeNode<T>[], visitor: TreeVisitor<T>, config?: TreeConfigInput): void;
266
- static insertBefore<T extends TreeNodeBase>(tree: TreeNode<T>[], targetId: string, newNode: T, config?: TreeConfigInput): boolean;
267
- static insertAfter<T extends TreeNodeBase>(tree: TreeNode<T>[], targetId: string, newNode: T, config?: TreeConfigInput): boolean;
268
- static remove<T extends TreeNodeBase>(tree: TreeNode<T>[], targetId: string, config?: TreeConfigInput): TreeNode<T> | undefined;
269
- static validate<T extends TreeNodeBase>(tree: TreeNode<T> | TreeNode<T>[], config?: TreeConfigInput): {
224
+ static toList<T = any>(tree: TreeNode<T> | TreeNode<T>[], config?: TreeConfigInput): T[];
225
+ /**
226
+ * 估算树形结构的节点数量
227
+ *
228
+ * @category Data Structures
229
+ * @param tree 树形结构(单个节点或节点数组)
230
+ * @param config 树形配置选项
231
+ * @returns 节点总数量
232
+ * @example
233
+ * ```ts
234
+ * const tree = [
235
+ * {
236
+ * id: '1',
237
+ * name: '根节点',
238
+ * children: [
239
+ * { id: '2', name: '子节点1', children: [] },
240
+ * { id: '3', name: '子节点2', children: [] }
241
+ * ]
242
+ * }
243
+ * ]
244
+ *
245
+ * const size = Tree.estimateSize(tree)
246
+ * console.log(size) // 3
247
+ * ```
248
+ */
249
+ static estimateSize<T = any>(tree: TreeNode<T> | TreeNode<T>[], config?: TreeConfigInput): number;
250
+ /**
251
+ * 查找树中第一个满足条件的节点
252
+ *
253
+ * @category Data Structures
254
+ * @param tree 树形结构(单个节点或节点数组)
255
+ * @param predicate 查找条件函数
256
+ * @param config 树形配置选项
257
+ * @returns 匹配的节点结果,包含节点、路径、深度和索引信息;未找到时返回undefined
258
+ * @example
259
+ * ```ts
260
+ * const tree = [
261
+ * {
262
+ * id: '1',
263
+ * name: '部门1',
264
+ * children: [
265
+ * { id: '2', name: '部门1-1', children: [] }
266
+ * ]
267
+ * }
268
+ * ]
269
+ *
270
+ * const result = Tree.find(tree, (node) => node.name === '部门1-1')
271
+ * console.log(result?.node.id) // '2'
272
+ * console.log(result?.depth) // 1
273
+ * ```
274
+ */
275
+ static find<T = any>(tree: TreeNode<T> | TreeNode<T>[], predicate: TreePredicate<T>, config?: TreeConfigInput): TreeNodeResult<T> | undefined;
276
+ /**
277
+ * 查找树中所有满足条件的节点
278
+ *
279
+ * @category Data Structures
280
+ * @param tree 树形结构(单个节点或节点数组)
281
+ * @param predicate 查找条件函数
282
+ * @param config 树形配置选项
283
+ * @returns 所有匹配的节点结果数组,每个结果包含节点、路径、深度和索引信息
284
+ * @example
285
+ * ```ts
286
+ * const tree = [
287
+ * {
288
+ * id: '1',
289
+ * type: 'folder',
290
+ * name: '根目录',
291
+ * children: [
292
+ * { id: '2', type: 'file', name: '文件1', children: [] },
293
+ * { id: '3', type: 'file', name: '文件2', children: [] }
294
+ * ]
295
+ * }
296
+ * ]
297
+ *
298
+ * const files = Tree.findAll(tree, (node) => node.type === 'file')
299
+ * console.log(files.length) // 2
300
+ * ```
301
+ */
302
+ static findAll<T = any>(tree: TreeNode<T> | TreeNode<T>[], predicate: TreePredicate<T>, config?: TreeConfigInput): TreeNodeResult<T>[];
303
+ /**
304
+ * 根据ID查找树中的节点
305
+ *
306
+ * @category Data Structures
307
+ * @param tree 树形结构(单个节点或节点数组)
308
+ * @param id 要查找的节点ID
309
+ * @param config 树形配置选项
310
+ * @returns 匹配的节点结果,包含节点、路径、深度和索引信息;未找到时返回undefined
311
+ * @example
312
+ * ```ts
313
+ * const tree = [
314
+ * {
315
+ * id: '1',
316
+ * name: '根节点',
317
+ * children: [
318
+ * { id: '2', name: '子节点', children: [] }
319
+ * ]
320
+ * }
321
+ * ]
322
+ *
323
+ * const result = Tree.findById(tree, '2')
324
+ * console.log(result?.node.name) // '子节点'
325
+ * ```
326
+ */
327
+ static findById<T = any>(tree: TreeNode<T> | TreeNode<T>[], id: string, config?: TreeConfigInput): TreeNodeResult<T> | undefined;
328
+ /**
329
+ * 获取树形结构的统计信息
330
+ *
331
+ * @category Data Structures
332
+ * @param tree 树形结构(单个节点或节点数组)
333
+ * @param config 树形配置选项
334
+ * @returns 树的统计信息,包含总节点数、叶子节点数、最大深度和分支节点数
335
+ * @example
336
+ * ```ts
337
+ * const tree = [
338
+ * {
339
+ * id: '1',
340
+ * name: '根节点',
341
+ * children: [
342
+ * { id: '2', name: '子节点1', children: [] },
343
+ * { id: '3', name: '子节点2', children: [
344
+ * { id: '4', name: '孙节点', children: [] }
345
+ * ] }
346
+ * ]
347
+ * }
348
+ * ]
349
+ *
350
+ * const stats = Tree.getStats(tree)
351
+ * console.log(stats) // { total: 4, leaves: 2, depth: 3, branches: 2 }
352
+ * ```
353
+ */
354
+ static getStats<T = any>(tree: TreeNode<T> | TreeNode<T>[], config?: TreeConfigInput): TreeStats;
355
+ /**
356
+ * 过滤树形结构,保留满足条件的节点及其祖先和后代
357
+ *
358
+ * @category Data Structures
359
+ * @param tree 树形结构(单个节点或节点数组)
360
+ * @param predicate 过滤条件函数
361
+ * @param config 树形配置选项
362
+ * @returns 过滤后的树形结构数组
363
+ * @example
364
+ * ```ts
365
+ * const tree = [
366
+ * {
367
+ * id: '1',
368
+ * type: 'folder',
369
+ * name: '根目录',
370
+ * children: [
371
+ * { id: '2', type: 'file', name: '文档.txt', children: [] },
372
+ * { id: '3', type: 'folder', name: '子目录', children: [
373
+ * { id: '4', type: 'file', name: '图片.jpg', children: [] }
374
+ * ] }
375
+ * ]
376
+ * }
377
+ * ]
378
+ *
379
+ * const filtered = Tree.filter(tree, (node) => node.type === 'file')
380
+ * // 返回包含所有文件节点及其父级路径的树结构
381
+ * ```
382
+ */
383
+ static filter<T = any>(tree: TreeNode<T> | TreeNode<T>[], predicate: TreePredicate<T>, config?: TreeConfigInput): TreeNode<T>[];
384
+ /**
385
+ * 转换树形结构,将每个节点转换为新的结构
386
+ *
387
+ * @category Data Structures
388
+ * @param tree 树形结构(单个节点或节点数组)
389
+ * @param transformer 节点转换函数
390
+ * @param config 树形配置选项
391
+ * @returns 转换后的树形结构数组
392
+ * @example
393
+ * ```ts
394
+ * const tree = [
395
+ * {
396
+ * id: '1',
397
+ * name: '部门1',
398
+ * children: [
399
+ * { id: '2', name: '部门1-1', children: [] }
400
+ * ]
401
+ * }
402
+ * ]
403
+ *
404
+ * const transformed = Tree.transform(tree, (node, depth) => ({
405
+ * key: node.id,
406
+ * title: node.name,
407
+ * level: depth
408
+ * }))
409
+ * // 转换为新的数据结构
410
+ * ```
411
+ */
412
+ static transform<T = any, R = any>(tree: TreeNode<T> | TreeNode<T>[], transformer: TreeTransformer<T, R>, config?: TreeConfigInput): TreeNode<R>[];
413
+ /**
414
+ * 遍历树形结构的每个节点
415
+ *
416
+ * @category Data Structures
417
+ * @param tree 树形结构(单个节点或节点数组)
418
+ * @param visitor 访问者函数,返回false可以跳过子节点的遍历
419
+ * @param config 树形配置选项
420
+ * @example
421
+ * ```ts
422
+ * const tree = [
423
+ * {
424
+ * id: '1',
425
+ * name: '根节点',
426
+ * children: [
427
+ * { id: '2', name: '子节点', children: [] }
428
+ * ]
429
+ * }
430
+ * ]
431
+ *
432
+ * Tree.forEach(tree, (node, depth) => {
433
+ * console.log(`${' '.repeat(depth * 2)}${node.name}`)
434
+ * // 输出缩进的树结构
435
+ * })
436
+ * ```
437
+ */
438
+ static forEach<T = any>(tree: TreeNode<T> | TreeNode<T>[], visitor: TreeVisitor<T>, config?: TreeConfigInput): void;
439
+ /**
440
+ * 在指定节点前插入新节点
441
+ *
442
+ * @category Data Structures
443
+ * @param tree 树形结构数组
444
+ * @param targetId 目标节点的ID
445
+ * @param newNode 要插入的新节点数据
446
+ * @param config 树形配置选项
447
+ * @returns 是否成功插入
448
+ * @example
449
+ * ```ts
450
+ * const tree = [
451
+ * {
452
+ * id: '1',
453
+ * name: '节点1',
454
+ * children: [
455
+ * { id: '2', name: '节点2', children: [] }
456
+ * ]
457
+ * }
458
+ * ]
459
+ *
460
+ * const success = Tree.insertBefore(tree, '2', { id: '1.5', name: '新节点' })
461
+ * console.log(success) // true
462
+ * ```
463
+ */
464
+ static insertBefore<T = any>(tree: TreeNode<T>[], targetId: string, newNode: T, config?: TreeConfigInput): boolean;
465
+ /**
466
+ * 在指定节点后插入新节点
467
+ *
468
+ * @category Data Structures
469
+ * @param tree 树形结构数组
470
+ * @param targetId 目标节点的ID
471
+ * @param newNode 要插入的新节点数据
472
+ * @param config 树形配置选项
473
+ * @returns 是否成功插入
474
+ * @example
475
+ * ```ts
476
+ * const tree = [
477
+ * {
478
+ * id: '1',
479
+ * name: '节点1',
480
+ * children: [
481
+ * { id: '2', name: '节点2', children: [] }
482
+ * ]
483
+ * }
484
+ * ]
485
+ *
486
+ * const success = Tree.insertAfter(tree, '2', { id: '3', name: '新节点' })
487
+ * console.log(success) // true
488
+ * ```
489
+ */
490
+ static insertAfter<T = any>(tree: TreeNode<T>[], targetId: string, newNode: T, config?: TreeConfigInput): boolean;
491
+ /**
492
+ * 从树中删除指定节点
493
+ *
494
+ * @category Data Structures
495
+ * @param tree 树形结构数组
496
+ * @param targetId 要删除的节点ID
497
+ * @param config 树形配置选项
498
+ * @returns 被删除的节点,未找到时返回undefined
499
+ * @example
500
+ * ```ts
501
+ * const tree = [
502
+ * {
503
+ * id: '1',
504
+ * name: '根节点',
505
+ * children: [
506
+ * { id: '2', name: '子节点', children: [] }
507
+ * ]
508
+ * }
509
+ * ]
510
+ *
511
+ * const removed = Tree.remove(tree, '2')
512
+ * console.log(removed?.name) // '子节点'
513
+ * ```
514
+ */
515
+ static remove<T = any>(tree: TreeNode<T>[], targetId: string, config?: TreeConfigInput): TreeNode<T> | undefined;
516
+ /**
517
+ * 验证树形结构的有效性
518
+ *
519
+ * @category Data Structures
520
+ * @param tree 树形结构(单个节点或节点数组)
521
+ * @param config 树形配置选项
522
+ * @returns 验证结果,包含是否有效和错误信息数组
523
+ * @example
524
+ * ```ts
525
+ * const tree = [
526
+ * {
527
+ * id: '1',
528
+ * name: '根节点',
529
+ * children: [
530
+ * { id: '2', name: '子节点', children: [] }
531
+ * ]
532
+ * }
533
+ * ]
534
+ *
535
+ * const result = Tree.validate(tree)
536
+ * console.log(result.isValid) // true
537
+ * console.log(result.errors) // []
538
+ * ```
539
+ */
540
+ static validate<T = any>(tree: TreeNode<T> | TreeNode<T>[], config?: TreeConfigInput): {
270
541
  isValid: boolean;
271
542
  errors: string[];
272
543
  };
273
544
  }
274
545
 
275
- /**
276
- * UnoCSS Flex布局预设,提供便捷的flex布局工具类
277
- *
278
- * @category Framework
279
- * @returns UnoCSS预设配置对象
280
- * @example
281
- * ```ts
282
- * // 在unocss.config.ts中使用
283
- * import { presetFlex } from '@movk/core'
284
- *
285
- * export default defineConfig({
286
- * presets: [
287
- * presetFlex(),
288
- * // 其他预设...
289
- * ]
290
- * })
291
- *
292
- * // 在HTML中使用生成的类名
293
- * <div class="flex-row-center-center">居中的flex容器</div>
294
- * <div class="flex-col-between-start">纵向分散对齐</div>
295
- * <div class="flex-center">完全居中</div>
296
- * <div class="flex-x-center">水平居中</div>
297
- * <div class="flex-y-center">垂直居中</div>
298
- * ```
299
- */
300
- declare function presetFlex(): {
301
- name: string;
302
- rules: ((RegExp | (([, d, j, a]: string[], { rawSelector }: {
303
- rawSelector: string;
304
- }) => Record<string, string>) | {
305
- autocomplete: string;
306
- })[] | (string | {
307
- display: string;
308
- 'justify-content': string;
309
- 'align-items': string;
310
- })[])[];
311
- shortcuts: {
312
- 'flex-x-center': string;
313
- 'flex-y-center': string;
314
- 'inline-flex-x-center': string;
315
- 'inline-flex-y-center': string;
316
- }[];
317
- };
318
-
319
- interface RuleContext {
320
- rawSelector: string;
321
- }
322
- /**
323
- * UnoCSS 猫头鹰选择器预设,提供基于相邻兄弟选择器的间距和分隔符工具类
324
- *
325
- * @category Framework
326
- * @returns UnoCSS预设配置对象
327
- * @example
328
- * ```ts
329
- * // 在unocss.config.ts中使用
330
- * import { presetOwl } from '@movk/core'
331
- *
332
- * export default defineConfig({
333
- * presets: [
334
- * presetOwl(),
335
- * // 其他预设...
336
- * ]
337
- * })
338
- *
339
- * // 在HTML中使用生成的类名
340
- * <div class="owl-y-4">
341
- * <div>项目1</div>
342
- * <div>项目2</div> <!-- 上边距 1rem -->
343
- * <div>项目3</div> <!-- 上边距 1rem -->
344
- * </div>
345
- *
346
- * <div class="owl-x-2">
347
- * <span>按钮1</span>
348
- * <span>按钮2</span> <!-- 左边距 0.5rem -->
349
- * <span>按钮3</span> <!-- 左边距 0.5rem -->
350
- * </div>
351
- *
352
- * <div class="owl-divide-gray-200">
353
- * <div>列表项1</div>
354
- * <div>列表项2</div> <!-- 上边框分隔线 -->
355
- * <div>列表项3</div> <!-- 上边框分隔线 -->
356
- * </div>
357
- * ```
358
- */
359
- declare function presetOwl(): {
360
- name: string;
361
- rules: (RegExp | (([, value]: string[], { rawSelector }: RuleContext) => string | undefined) | {
362
- autocomplete: string;
363
- })[][];
364
- shortcuts: {
365
- 'owl-stack': string;
366
- 'owl-stack-tight': string;
367
- 'owl-stack-loose': string;
368
- 'owl-list': string;
369
- 'owl-list-tight': string;
370
- 'owl-nav': string;
371
- 'owl-card-stack': string;
372
- }[];
373
- };
374
-
375
546
  /**
376
547
  * 数组去重,返回去除重复元素后的新数组
377
548
  *
@@ -1001,5 +1172,5 @@ declare function isFunction(value: any): value is (...args: any[]) => any;
1001
1172
  */
1002
1173
  declare function isEmpty(value: any): boolean;
1003
1174
 
1004
- export { StorageTypeSchema, Tree, TreeConfigSchema, TreeStatsSchema, camelToKebab, capitalize, chunk, convertSvgToPng, convertToKebabCase, createStorageConfigSchema, debounce, deepClone, extractFilename, flatten, formatFileSize, getRandomUUID, isArray, isEmpty, isFunction, isNumber, isObject, isString, kebabToCamel, omit, omitUndefined, pick, presetFlex, presetOwl, replaceCurrentColor, separate, simpleHash, sleep, sleepWithCancel, throttle, triggerDownload, unique, useAppStorage, useCopyCode };
1005
- export type { AnyObject, AppStorageReturn, DeepPartial, FirstParam, FirstParameter, GetObjectField, MutableByKeys, OmitByKey, PartialByKeys, PickByKey, ReadonlyByKeys, RenameKeys, RequiredByKeys, StorageConfig, StorageConfigInput, StorageType, StringOrVNode, TreeConfig, TreeConfigInput, TreeNode, TreeNodeBase, TreeNodeResult, TreePredicate, TreeStats, TreeTransformer, TreeVisitor, UnionToIntersection };
1175
+ export { StorageTypeSchema, Tree, camelToKebab, capitalize, chunk, convertSvgToPng, convertToKebabCase, createStorageConfigSchema, debounce, deepClone, extractFilename, flatten, formatFileSize, getRandomUUID, isArray, isEmpty, isFunction, isNumber, isObject, isString, kebabToCamel, omit, omitUndefined, pick, replaceCurrentColor, separate, simpleHash, sleep, sleepWithCancel, throttle, triggerDownload, unique, useAppStorage, useCopyCode };
1176
+ export type { AnyObject, AppStorageReturn, DeepPartial, FirstParam, FirstParameter, GetObjectField, MutableByKeys, OmitByKey, PartialByKeys, PickByKey, ReadonlyByKeys, RenameKeys, RequiredByKeys, StorageConfig, StorageConfigInput, StorageType, StringOrVNode, UnionToIntersection };
package/dist/index.d.ts CHANGED
@@ -58,35 +58,6 @@ interface AppStorageReturn<T> {
58
58
  removeItem: () => void;
59
59
  }
60
60
 
61
- declare const _TreeNodeBaseSchema: z.ZodRecord<z.ZodString, z.ZodAny>;
62
- type TreeNodeBase = z.infer<typeof _TreeNodeBaseSchema>;
63
- type TreeNode<T extends TreeNodeBase = TreeNodeBase> = T & {
64
- [K in TreeConfig['children']]: TreeNode<T>[];
65
- };
66
- declare const TreeConfigSchema: z.ZodObject<{
67
- id: z.ZodDefault<z.ZodString>;
68
- pid: z.ZodDefault<z.ZodString>;
69
- children: z.ZodDefault<z.ZodString>;
70
- }, z.core.$strip>;
71
- type TreeConfig = z.infer<typeof TreeConfigSchema>;
72
- type TreeConfigInput = z.input<typeof TreeConfigSchema>;
73
- declare const TreeStatsSchema: z.ZodObject<{
74
- total: z.ZodNumber;
75
- leaves: z.ZodNumber;
76
- depth: z.ZodNumber;
77
- branches: z.ZodNumber;
78
- }, z.core.$strip>;
79
- type TreeStats = z.infer<typeof TreeStatsSchema>;
80
- interface TreeNodeResult<T extends TreeNodeBase = TreeNodeBase> {
81
- readonly node: TreeNode<T>;
82
- readonly path: readonly TreeNode<T>[];
83
- readonly depth: number;
84
- readonly index: number;
85
- }
86
- type TreePredicate<T extends TreeNodeBase = TreeNodeBase> = (node: TreeNode<T>, depth: number, path: readonly TreeNode<T>[]) => boolean;
87
- type TreeTransformer<T extends TreeNodeBase = TreeNodeBase, R extends TreeNodeBase = TreeNodeBase> = (node: TreeNode<T>, depth: number, path: readonly TreeNode<T>[]) => R;
88
- type TreeVisitor<T extends TreeNodeBase = TreeNodeBase> = (node: TreeNode<T>, depth: number, path: readonly TreeNode<T>[]) => void | boolean;
89
-
90
61
  /**
91
62
  * 应用存储管理的组合式函数,支持localStorage和sessionStorage
92
63
  *
@@ -170,36 +141,32 @@ declare function useAppStorage<T = unknown>(config: StorageConfigInput<T>): AppS
170
141
  */
171
142
  declare function useCopyCode(text: string): Promise<boolean>;
172
143
 
173
- /**
174
- * 树形数据结构操作类,提供树形数据的各种操作方法
175
- *
176
- * @category Data Structures
177
- * @example
178
- * ```ts
179
- * // 从扁平数组创建树形结构
180
- * const flatList = [
181
- * { id: '1', name: '根节点', pid: null },
182
- * { id: '2', name: '子节点1', pid: '1' },
183
- * { id: '3', name: '子节点2', pid: '1' },
184
- * { id: '4', name: '孙节点', pid: '2' }
185
- * ]
186
- *
187
- * const tree = Tree.fromList(flatList)
188
- * console.log(tree) // 树形结构
189
- *
190
- * // 查找节点
191
- * const found = Tree.find(tree, (node) => node.name === '子节点1')
192
- *
193
- * // 过滤节点
194
- * const filtered = Tree.filter(tree, (node) => node.name.includes('子'))
195
- *
196
- * // 转换树形结构
197
- * const transformed = Tree.transform(tree, (node) => ({
198
- * ...node,
199
- * displayName: `[${node.name}]`
200
- * }))
201
- * ```
202
- */
144
+ type TreeNode<T = any> = T & {
145
+ children?: TreeNode<T>[];
146
+ [key: string]: any;
147
+ };
148
+ declare const TreeConfigSchema: z.ZodObject<{
149
+ id: z.ZodDefault<z.ZodString>;
150
+ pid: z.ZodDefault<z.ZodString>;
151
+ children: z.ZodDefault<z.ZodString>;
152
+ }, z.core.$strip>;
153
+ type TreeConfigInput = z.input<typeof TreeConfigSchema>;
154
+ declare const _TreeStatsSchema: z.ZodObject<{
155
+ total: z.ZodNumber;
156
+ leaves: z.ZodNumber;
157
+ depth: z.ZodNumber;
158
+ branches: z.ZodNumber;
159
+ }, z.core.$strip>;
160
+ type TreeStats = z.infer<typeof _TreeStatsSchema>;
161
+ interface TreeNodeResult<T = any> {
162
+ readonly node: TreeNode<T>;
163
+ readonly path: readonly TreeNode<T>[];
164
+ readonly depth: number;
165
+ readonly index: number;
166
+ }
167
+ type TreePredicate<T = any> = (node: TreeNode<T>, depth: number, path: readonly TreeNode<T>[]) => boolean;
168
+ type TreeTransformer<T = any, R = any> = (node: TreeNode<T>, depth: number, path: readonly TreeNode<T>[]) => R;
169
+ type TreeVisitor<T = any> = (node: TreeNode<T>, depth: number, path: readonly TreeNode<T>[]) => void | boolean;
203
170
  declare class Tree {
204
171
  private static dfsGenerator;
205
172
  private static bfsGenerator;
@@ -229,7 +196,7 @@ declare class Tree {
229
196
  * console.log(tree) // 转换为树形结构
230
197
  * ```
231
198
  */
232
- static fromList<T extends TreeNodeBase>(list: T[], config?: TreeConfigInput): TreeNode<T>[];
199
+ static fromList<T = any>(list: T[], config?: TreeConfigInput): TreeNode<T>[];
233
200
  /**
234
201
  * 将树形结构转换为扁平数组
235
202
  *
@@ -254,124 +221,328 @@ declare class Tree {
254
221
  * console.log(flatList) // [{ id: '1', name: '根节点' }, { id: '2', name: '子节点1' }, ...]
255
222
  * ```
256
223
  */
257
- static toList<T extends TreeNodeBase>(tree: TreeNode<T> | TreeNode<T>[], config?: TreeConfigInput): T[];
258
- static estimateSize<T extends TreeNodeBase>(tree: TreeNode<T> | TreeNode<T>[], config?: TreeConfigInput): number;
259
- static find<T extends TreeNodeBase>(tree: TreeNode<T> | TreeNode<T>[], predicate: TreePredicate<T>, config?: TreeConfigInput): TreeNodeResult<T> | undefined;
260
- static findAll<T extends TreeNodeBase>(tree: TreeNode<T> | TreeNode<T>[], predicate: TreePredicate<T>, config?: TreeConfigInput): TreeNodeResult<T>[];
261
- static findById<T extends TreeNodeBase>(tree: TreeNode<T> | TreeNode<T>[], id: string, config?: TreeConfigInput): TreeNodeResult<T> | undefined;
262
- static getStats<T extends TreeNodeBase>(tree: TreeNode<T> | TreeNode<T>[], config?: TreeConfigInput): TreeStats;
263
- static filter<T extends TreeNodeBase>(tree: TreeNode<T> | TreeNode<T>[], predicate: TreePredicate<T>, config?: TreeConfigInput): TreeNode<T>[];
264
- static transform<T extends TreeNodeBase, R extends TreeNodeBase>(tree: TreeNode<T> | TreeNode<T>[], transformer: TreeTransformer<T, R>, config?: TreeConfigInput): TreeNode<R>[];
265
- static forEach<T extends TreeNodeBase>(tree: TreeNode<T> | TreeNode<T>[], visitor: TreeVisitor<T>, config?: TreeConfigInput): void;
266
- static insertBefore<T extends TreeNodeBase>(tree: TreeNode<T>[], targetId: string, newNode: T, config?: TreeConfigInput): boolean;
267
- static insertAfter<T extends TreeNodeBase>(tree: TreeNode<T>[], targetId: string, newNode: T, config?: TreeConfigInput): boolean;
268
- static remove<T extends TreeNodeBase>(tree: TreeNode<T>[], targetId: string, config?: TreeConfigInput): TreeNode<T> | undefined;
269
- static validate<T extends TreeNodeBase>(tree: TreeNode<T> | TreeNode<T>[], config?: TreeConfigInput): {
224
+ static toList<T = any>(tree: TreeNode<T> | TreeNode<T>[], config?: TreeConfigInput): T[];
225
+ /**
226
+ * 估算树形结构的节点数量
227
+ *
228
+ * @category Data Structures
229
+ * @param tree 树形结构(单个节点或节点数组)
230
+ * @param config 树形配置选项
231
+ * @returns 节点总数量
232
+ * @example
233
+ * ```ts
234
+ * const tree = [
235
+ * {
236
+ * id: '1',
237
+ * name: '根节点',
238
+ * children: [
239
+ * { id: '2', name: '子节点1', children: [] },
240
+ * { id: '3', name: '子节点2', children: [] }
241
+ * ]
242
+ * }
243
+ * ]
244
+ *
245
+ * const size = Tree.estimateSize(tree)
246
+ * console.log(size) // 3
247
+ * ```
248
+ */
249
+ static estimateSize<T = any>(tree: TreeNode<T> | TreeNode<T>[], config?: TreeConfigInput): number;
250
+ /**
251
+ * 查找树中第一个满足条件的节点
252
+ *
253
+ * @category Data Structures
254
+ * @param tree 树形结构(单个节点或节点数组)
255
+ * @param predicate 查找条件函数
256
+ * @param config 树形配置选项
257
+ * @returns 匹配的节点结果,包含节点、路径、深度和索引信息;未找到时返回undefined
258
+ * @example
259
+ * ```ts
260
+ * const tree = [
261
+ * {
262
+ * id: '1',
263
+ * name: '部门1',
264
+ * children: [
265
+ * { id: '2', name: '部门1-1', children: [] }
266
+ * ]
267
+ * }
268
+ * ]
269
+ *
270
+ * const result = Tree.find(tree, (node) => node.name === '部门1-1')
271
+ * console.log(result?.node.id) // '2'
272
+ * console.log(result?.depth) // 1
273
+ * ```
274
+ */
275
+ static find<T = any>(tree: TreeNode<T> | TreeNode<T>[], predicate: TreePredicate<T>, config?: TreeConfigInput): TreeNodeResult<T> | undefined;
276
+ /**
277
+ * 查找树中所有满足条件的节点
278
+ *
279
+ * @category Data Structures
280
+ * @param tree 树形结构(单个节点或节点数组)
281
+ * @param predicate 查找条件函数
282
+ * @param config 树形配置选项
283
+ * @returns 所有匹配的节点结果数组,每个结果包含节点、路径、深度和索引信息
284
+ * @example
285
+ * ```ts
286
+ * const tree = [
287
+ * {
288
+ * id: '1',
289
+ * type: 'folder',
290
+ * name: '根目录',
291
+ * children: [
292
+ * { id: '2', type: 'file', name: '文件1', children: [] },
293
+ * { id: '3', type: 'file', name: '文件2', children: [] }
294
+ * ]
295
+ * }
296
+ * ]
297
+ *
298
+ * const files = Tree.findAll(tree, (node) => node.type === 'file')
299
+ * console.log(files.length) // 2
300
+ * ```
301
+ */
302
+ static findAll<T = any>(tree: TreeNode<T> | TreeNode<T>[], predicate: TreePredicate<T>, config?: TreeConfigInput): TreeNodeResult<T>[];
303
+ /**
304
+ * 根据ID查找树中的节点
305
+ *
306
+ * @category Data Structures
307
+ * @param tree 树形结构(单个节点或节点数组)
308
+ * @param id 要查找的节点ID
309
+ * @param config 树形配置选项
310
+ * @returns 匹配的节点结果,包含节点、路径、深度和索引信息;未找到时返回undefined
311
+ * @example
312
+ * ```ts
313
+ * const tree = [
314
+ * {
315
+ * id: '1',
316
+ * name: '根节点',
317
+ * children: [
318
+ * { id: '2', name: '子节点', children: [] }
319
+ * ]
320
+ * }
321
+ * ]
322
+ *
323
+ * const result = Tree.findById(tree, '2')
324
+ * console.log(result?.node.name) // '子节点'
325
+ * ```
326
+ */
327
+ static findById<T = any>(tree: TreeNode<T> | TreeNode<T>[], id: string, config?: TreeConfigInput): TreeNodeResult<T> | undefined;
328
+ /**
329
+ * 获取树形结构的统计信息
330
+ *
331
+ * @category Data Structures
332
+ * @param tree 树形结构(单个节点或节点数组)
333
+ * @param config 树形配置选项
334
+ * @returns 树的统计信息,包含总节点数、叶子节点数、最大深度和分支节点数
335
+ * @example
336
+ * ```ts
337
+ * const tree = [
338
+ * {
339
+ * id: '1',
340
+ * name: '根节点',
341
+ * children: [
342
+ * { id: '2', name: '子节点1', children: [] },
343
+ * { id: '3', name: '子节点2', children: [
344
+ * { id: '4', name: '孙节点', children: [] }
345
+ * ] }
346
+ * ]
347
+ * }
348
+ * ]
349
+ *
350
+ * const stats = Tree.getStats(tree)
351
+ * console.log(stats) // { total: 4, leaves: 2, depth: 3, branches: 2 }
352
+ * ```
353
+ */
354
+ static getStats<T = any>(tree: TreeNode<T> | TreeNode<T>[], config?: TreeConfigInput): TreeStats;
355
+ /**
356
+ * 过滤树形结构,保留满足条件的节点及其祖先和后代
357
+ *
358
+ * @category Data Structures
359
+ * @param tree 树形结构(单个节点或节点数组)
360
+ * @param predicate 过滤条件函数
361
+ * @param config 树形配置选项
362
+ * @returns 过滤后的树形结构数组
363
+ * @example
364
+ * ```ts
365
+ * const tree = [
366
+ * {
367
+ * id: '1',
368
+ * type: 'folder',
369
+ * name: '根目录',
370
+ * children: [
371
+ * { id: '2', type: 'file', name: '文档.txt', children: [] },
372
+ * { id: '3', type: 'folder', name: '子目录', children: [
373
+ * { id: '4', type: 'file', name: '图片.jpg', children: [] }
374
+ * ] }
375
+ * ]
376
+ * }
377
+ * ]
378
+ *
379
+ * const filtered = Tree.filter(tree, (node) => node.type === 'file')
380
+ * // 返回包含所有文件节点及其父级路径的树结构
381
+ * ```
382
+ */
383
+ static filter<T = any>(tree: TreeNode<T> | TreeNode<T>[], predicate: TreePredicate<T>, config?: TreeConfigInput): TreeNode<T>[];
384
+ /**
385
+ * 转换树形结构,将每个节点转换为新的结构
386
+ *
387
+ * @category Data Structures
388
+ * @param tree 树形结构(单个节点或节点数组)
389
+ * @param transformer 节点转换函数
390
+ * @param config 树形配置选项
391
+ * @returns 转换后的树形结构数组
392
+ * @example
393
+ * ```ts
394
+ * const tree = [
395
+ * {
396
+ * id: '1',
397
+ * name: '部门1',
398
+ * children: [
399
+ * { id: '2', name: '部门1-1', children: [] }
400
+ * ]
401
+ * }
402
+ * ]
403
+ *
404
+ * const transformed = Tree.transform(tree, (node, depth) => ({
405
+ * key: node.id,
406
+ * title: node.name,
407
+ * level: depth
408
+ * }))
409
+ * // 转换为新的数据结构
410
+ * ```
411
+ */
412
+ static transform<T = any, R = any>(tree: TreeNode<T> | TreeNode<T>[], transformer: TreeTransformer<T, R>, config?: TreeConfigInput): TreeNode<R>[];
413
+ /**
414
+ * 遍历树形结构的每个节点
415
+ *
416
+ * @category Data Structures
417
+ * @param tree 树形结构(单个节点或节点数组)
418
+ * @param visitor 访问者函数,返回false可以跳过子节点的遍历
419
+ * @param config 树形配置选项
420
+ * @example
421
+ * ```ts
422
+ * const tree = [
423
+ * {
424
+ * id: '1',
425
+ * name: '根节点',
426
+ * children: [
427
+ * { id: '2', name: '子节点', children: [] }
428
+ * ]
429
+ * }
430
+ * ]
431
+ *
432
+ * Tree.forEach(tree, (node, depth) => {
433
+ * console.log(`${' '.repeat(depth * 2)}${node.name}`)
434
+ * // 输出缩进的树结构
435
+ * })
436
+ * ```
437
+ */
438
+ static forEach<T = any>(tree: TreeNode<T> | TreeNode<T>[], visitor: TreeVisitor<T>, config?: TreeConfigInput): void;
439
+ /**
440
+ * 在指定节点前插入新节点
441
+ *
442
+ * @category Data Structures
443
+ * @param tree 树形结构数组
444
+ * @param targetId 目标节点的ID
445
+ * @param newNode 要插入的新节点数据
446
+ * @param config 树形配置选项
447
+ * @returns 是否成功插入
448
+ * @example
449
+ * ```ts
450
+ * const tree = [
451
+ * {
452
+ * id: '1',
453
+ * name: '节点1',
454
+ * children: [
455
+ * { id: '2', name: '节点2', children: [] }
456
+ * ]
457
+ * }
458
+ * ]
459
+ *
460
+ * const success = Tree.insertBefore(tree, '2', { id: '1.5', name: '新节点' })
461
+ * console.log(success) // true
462
+ * ```
463
+ */
464
+ static insertBefore<T = any>(tree: TreeNode<T>[], targetId: string, newNode: T, config?: TreeConfigInput): boolean;
465
+ /**
466
+ * 在指定节点后插入新节点
467
+ *
468
+ * @category Data Structures
469
+ * @param tree 树形结构数组
470
+ * @param targetId 目标节点的ID
471
+ * @param newNode 要插入的新节点数据
472
+ * @param config 树形配置选项
473
+ * @returns 是否成功插入
474
+ * @example
475
+ * ```ts
476
+ * const tree = [
477
+ * {
478
+ * id: '1',
479
+ * name: '节点1',
480
+ * children: [
481
+ * { id: '2', name: '节点2', children: [] }
482
+ * ]
483
+ * }
484
+ * ]
485
+ *
486
+ * const success = Tree.insertAfter(tree, '2', { id: '3', name: '新节点' })
487
+ * console.log(success) // true
488
+ * ```
489
+ */
490
+ static insertAfter<T = any>(tree: TreeNode<T>[], targetId: string, newNode: T, config?: TreeConfigInput): boolean;
491
+ /**
492
+ * 从树中删除指定节点
493
+ *
494
+ * @category Data Structures
495
+ * @param tree 树形结构数组
496
+ * @param targetId 要删除的节点ID
497
+ * @param config 树形配置选项
498
+ * @returns 被删除的节点,未找到时返回undefined
499
+ * @example
500
+ * ```ts
501
+ * const tree = [
502
+ * {
503
+ * id: '1',
504
+ * name: '根节点',
505
+ * children: [
506
+ * { id: '2', name: '子节点', children: [] }
507
+ * ]
508
+ * }
509
+ * ]
510
+ *
511
+ * const removed = Tree.remove(tree, '2')
512
+ * console.log(removed?.name) // '子节点'
513
+ * ```
514
+ */
515
+ static remove<T = any>(tree: TreeNode<T>[], targetId: string, config?: TreeConfigInput): TreeNode<T> | undefined;
516
+ /**
517
+ * 验证树形结构的有效性
518
+ *
519
+ * @category Data Structures
520
+ * @param tree 树形结构(单个节点或节点数组)
521
+ * @param config 树形配置选项
522
+ * @returns 验证结果,包含是否有效和错误信息数组
523
+ * @example
524
+ * ```ts
525
+ * const tree = [
526
+ * {
527
+ * id: '1',
528
+ * name: '根节点',
529
+ * children: [
530
+ * { id: '2', name: '子节点', children: [] }
531
+ * ]
532
+ * }
533
+ * ]
534
+ *
535
+ * const result = Tree.validate(tree)
536
+ * console.log(result.isValid) // true
537
+ * console.log(result.errors) // []
538
+ * ```
539
+ */
540
+ static validate<T = any>(tree: TreeNode<T> | TreeNode<T>[], config?: TreeConfigInput): {
270
541
  isValid: boolean;
271
542
  errors: string[];
272
543
  };
273
544
  }
274
545
 
275
- /**
276
- * UnoCSS Flex布局预设,提供便捷的flex布局工具类
277
- *
278
- * @category Framework
279
- * @returns UnoCSS预设配置对象
280
- * @example
281
- * ```ts
282
- * // 在unocss.config.ts中使用
283
- * import { presetFlex } from '@movk/core'
284
- *
285
- * export default defineConfig({
286
- * presets: [
287
- * presetFlex(),
288
- * // 其他预设...
289
- * ]
290
- * })
291
- *
292
- * // 在HTML中使用生成的类名
293
- * <div class="flex-row-center-center">居中的flex容器</div>
294
- * <div class="flex-col-between-start">纵向分散对齐</div>
295
- * <div class="flex-center">完全居中</div>
296
- * <div class="flex-x-center">水平居中</div>
297
- * <div class="flex-y-center">垂直居中</div>
298
- * ```
299
- */
300
- declare function presetFlex(): {
301
- name: string;
302
- rules: ((RegExp | (([, d, j, a]: string[], { rawSelector }: {
303
- rawSelector: string;
304
- }) => Record<string, string>) | {
305
- autocomplete: string;
306
- })[] | (string | {
307
- display: string;
308
- 'justify-content': string;
309
- 'align-items': string;
310
- })[])[];
311
- shortcuts: {
312
- 'flex-x-center': string;
313
- 'flex-y-center': string;
314
- 'inline-flex-x-center': string;
315
- 'inline-flex-y-center': string;
316
- }[];
317
- };
318
-
319
- interface RuleContext {
320
- rawSelector: string;
321
- }
322
- /**
323
- * UnoCSS 猫头鹰选择器预设,提供基于相邻兄弟选择器的间距和分隔符工具类
324
- *
325
- * @category Framework
326
- * @returns UnoCSS预设配置对象
327
- * @example
328
- * ```ts
329
- * // 在unocss.config.ts中使用
330
- * import { presetOwl } from '@movk/core'
331
- *
332
- * export default defineConfig({
333
- * presets: [
334
- * presetOwl(),
335
- * // 其他预设...
336
- * ]
337
- * })
338
- *
339
- * // 在HTML中使用生成的类名
340
- * <div class="owl-y-4">
341
- * <div>项目1</div>
342
- * <div>项目2</div> <!-- 上边距 1rem -->
343
- * <div>项目3</div> <!-- 上边距 1rem -->
344
- * </div>
345
- *
346
- * <div class="owl-x-2">
347
- * <span>按钮1</span>
348
- * <span>按钮2</span> <!-- 左边距 0.5rem -->
349
- * <span>按钮3</span> <!-- 左边距 0.5rem -->
350
- * </div>
351
- *
352
- * <div class="owl-divide-gray-200">
353
- * <div>列表项1</div>
354
- * <div>列表项2</div> <!-- 上边框分隔线 -->
355
- * <div>列表项3</div> <!-- 上边框分隔线 -->
356
- * </div>
357
- * ```
358
- */
359
- declare function presetOwl(): {
360
- name: string;
361
- rules: (RegExp | (([, value]: string[], { rawSelector }: RuleContext) => string | undefined) | {
362
- autocomplete: string;
363
- })[][];
364
- shortcuts: {
365
- 'owl-stack': string;
366
- 'owl-stack-tight': string;
367
- 'owl-stack-loose': string;
368
- 'owl-list': string;
369
- 'owl-list-tight': string;
370
- 'owl-nav': string;
371
- 'owl-card-stack': string;
372
- }[];
373
- };
374
-
375
546
  /**
376
547
  * 数组去重,返回去除重复元素后的新数组
377
548
  *
@@ -1001,5 +1172,5 @@ declare function isFunction(value: any): value is (...args: any[]) => any;
1001
1172
  */
1002
1173
  declare function isEmpty(value: any): boolean;
1003
1174
 
1004
- export { StorageTypeSchema, Tree, TreeConfigSchema, TreeStatsSchema, camelToKebab, capitalize, chunk, convertSvgToPng, convertToKebabCase, createStorageConfigSchema, debounce, deepClone, extractFilename, flatten, formatFileSize, getRandomUUID, isArray, isEmpty, isFunction, isNumber, isObject, isString, kebabToCamel, omit, omitUndefined, pick, presetFlex, presetOwl, replaceCurrentColor, separate, simpleHash, sleep, sleepWithCancel, throttle, triggerDownload, unique, useAppStorage, useCopyCode };
1005
- export type { AnyObject, AppStorageReturn, DeepPartial, FirstParam, FirstParameter, GetObjectField, MutableByKeys, OmitByKey, PartialByKeys, PickByKey, ReadonlyByKeys, RenameKeys, RequiredByKeys, StorageConfig, StorageConfigInput, StorageType, StringOrVNode, TreeConfig, TreeConfigInput, TreeNode, TreeNodeBase, TreeNodeResult, TreePredicate, TreeStats, TreeTransformer, TreeVisitor, UnionToIntersection };
1175
+ export { StorageTypeSchema, Tree, camelToKebab, capitalize, chunk, convertSvgToPng, convertToKebabCase, createStorageConfigSchema, debounce, deepClone, extractFilename, flatten, formatFileSize, getRandomUUID, isArray, isEmpty, isFunction, isNumber, isObject, isString, kebabToCamel, omit, omitUndefined, pick, replaceCurrentColor, separate, simpleHash, sleep, sleepWithCancel, throttle, triggerDownload, unique, useAppStorage, useCopyCode };
1176
+ export type { AnyObject, AppStorageReturn, DeepPartial, FirstParam, FirstParameter, GetObjectField, MutableByKeys, OmitByKey, PartialByKeys, PickByKey, ReadonlyByKeys, RenameKeys, RequiredByKeys, StorageConfig, StorageConfigInput, StorageType, StringOrVNode, UnionToIntersection };
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{useStorage as D}from"@vueuse/core";import{z as g}from"zod/v4";const k=g.enum(["localStorage","sessionStorage"]);function j(e){return g.object({key:g.string().min(1,{message:"Key cannot be empty"}),schema:g.custom(t=>t instanceof g.ZodType,{message:"Schema must be a valid Zod schema"}),defaultValue:g.custom(t=>e.safeParse(t).success,{message:"Default value must match the provided schema"}),prefix:g.string().default("movk"),storage:k.default("localStorage")})}g.record(g.string(),g.any());const y=g.object({id:g.string().default("id"),pid:g.string().default("pid"),children:g.string().default("children")}),P=g.object({total:g.number().int().nonnegative(),leaves:g.number().int().nonnegative(),depth:g.number().int().nonnegative(),branches:g.number().int().nonnegative()});function M(e){const t=j(e.schema).parse(e),{key:r,defaultValue:n,schema:o,storage:s,prefix:a}=t,i=`${a}:${r}`,c=(()=>{if(!(typeof window>"u"))return s==="localStorage"?localStorage:sessionStorage})();function l(h){if(h===null)return n;try{const m=JSON.parse(h),b=o.safeParse(m);return b.success?b.data:(console.warn(`[AppStorage] Validation failed for key "${i}". Using default value.`,b.error.issues),n)}catch(m){return console.warn(`[AppStorage] Failed to parse value for key "${i}". Using default value.`,m),n}}const u=D(i,n,c,{mergeDefaults:!0,serializer:{read:l,write:h=>JSON.stringify(h)}});function d(){if(!c)return n;const h=c.getItem(i);return l(h)}function p(h){const m=o.safeParse(h);if(!m.success){console.warn(`[AppStorage] Invalid value for key "${i}". Aborting setItem.`,m.error.issues);return}u.value=m.data}function f(){c&&(u.value=null)}return{state:u,getItem:d,setItem:p,removeItem:f}}async function U(e){if(typeof e!="string")throw new TypeError("Text must be a string");if(typeof window>"u")return console.warn("useCopyCode: Not available in server environment"),!1;if(navigator.clipboard&&window.isSecureContext)try{return await navigator.clipboard.writeText(e),!0}catch(t){console.warn("Clipboard API failed, falling back to legacy method:",t)}try{return L(e)}catch(t){return console.error("Failed to copy text:",t),!1}}function L(e){if(typeof document>"u")return console.warn("copyTextLegacy: Document not available"),!1;const t=document.createElement("textarea"),r=document.activeElement,n=document.getSelection(),o=n&&n.rangeCount>0?n.getRangeAt(0):null;try{return t.value=e,t.setAttribute("readonly",""),t.setAttribute("contenteditable","true"),Object.assign(t.style,{contain:"strict",position:"absolute",left:"-9999px",top:"-9999px",fontSize:"12pt",border:"0",padding:"0",margin:"0",outline:"none",boxShadow:"none",background:"transparent"}),document.body.appendChild(t),t.focus(),t.select(),t.setSelectionRange&&t.setSelectionRange(0,e.length),document.execCommand("copy")}catch(s){return console.error("Legacy copy method failed:",s),!1}finally{t.parentNode&&document.body.removeChild(t),o&&n&&(n.removeAllRanges(),n.addRange(o)),r instanceof HTMLElement&&r.focus()}}class w{static*dfsGenerator(t,r,n=[]){const{children:o}=y.parse(r);let s=0;for(const a of t){const i=[...n,a];yield{node:a,path:i,depth:n.length,index:s++};const c=a[o];c&&c.length>0&&(yield*w.dfsGenerator(c,r,i))}}static*bfsGenerator(t,r){const{children:n}=y.parse(r),o=t.map((a,i)=>({node:a,path:[a],depth:0,index:i}));let s=t.length;for(;o.length>0;){const a=o.shift();yield a;const i=a.node[n];i&&i.length>0&&i.forEach(c=>{const l=[...a.path,c];o.push({node:c,path:l,depth:a.depth+1,index:s++})})}}static selectStrategy(t){switch(t){case"find":case"findAll":case"filter":case"transform":case"forEach":case"stats":case"validate":return"dfs";default:return"dfs"}}static fromList(t,r={}){const n=y.parse(r),{id:o,pid:s,children:a}=n;if(!Array.isArray(t)||t.length===0)return[];const i=new Map,c=[];return t.forEach(l=>{const u={...l,[a]:[]};i.set(l[o],u)}),t.forEach(l=>{const u=i.get(l[o]),d=l[s];d&&i.has(d)?i.get(d)[a].push(u):c.push(u)}),c}static toList(t,r={}){const n=y.parse(r),{children:o}=n,s=[],a=Array.isArray(t)?t:[t],i=c=>{const{[o]:l,...u}=c;s.push(u),l&&l.length>0&&l.forEach(i)};return a.forEach(i),s}static estimateSize(t,r={}){const n=y.parse(r),{children:o}=n,s=Array.isArray(t)?t:[t];let a=0;const i=c=>{a++;const l=c[o];l&&l.length>0&&l.forEach(i)};return s.forEach(i),a}static find(t,r,n={}){const o=Array.isArray(t)?t:[t],s=w.selectStrategy("find")==="dfs"?w.dfsGenerator(o,n):w.bfsGenerator(o,n);for(const a of s)if(r(a.node,a.depth,a.path))return a}static findAll(t,r,n={}){const o=Array.isArray(t)?t:[t],s=w.selectStrategy("findAll"),a=[],i=s==="dfs"?w.dfsGenerator(o,n):w.bfsGenerator(o,n);for(const c of i)r(c.node,c.depth,c.path)&&a.push(c);return a}static findById(t,r,n={}){const o=y.parse(n),{id:s}=o;return this.find(t,a=>a[s]===r,n)}static getStats(t,r={}){const n=y.parse(r),{children:o}=n,s=Array.isArray(t)?t:[t];let a=0,i=0,c=0,l=0;const u=(d,p)=>{a++,c=Math.max(c,p);const f=d[o];f&&f.length>0?(l++,f.forEach(h=>u(h,p+1))):i++};return s.forEach(d=>u(d,1)),{total:a,leaves:i,depth:c,branches:l}}static filter(t,r,n={}){const o=y.parse(n),{children:s}=o,a=Array.isArray(t)?t:[t],i=[],c=(l,u,d)=>{const p=l[s],f=[];if(p&&p.length>0){const h=[...d,l];p.forEach(m=>{const b=c(m,u+1,h);b&&f.push(b)})}return r(l,u,d)||f.length>0?{...l,[s]:f}:null};return a.forEach(l=>{const u=c(l,0,[]);u&&i.push(u)}),i}static transform(t,r,n={}){const o=y.parse(n),{children:s}=o,a=Array.isArray(t)?t:[t],i=[],c=(l,u,d)=>{const p=l[s],f=[];if(p&&p.length>0){const h=[...d,l];p.forEach(m=>{f.push(c(m,u+1,h))})}return{...r(l,u,d),[s]:f}};return a.forEach(l=>{i.push(c(l,0,[]))}),i}static forEach(t,r,n={}){const o=y.parse(n),{children:s}=o,a=Array.isArray(t)?t:[t],i=(c,l,u)=>{if(r(c,l,u)!==!1){const d=c[s];if(d&&d.length>0){const p=[...u,c];d.forEach(f=>{i(f,l+1,p)})}}};a.forEach(c=>i(c,0,[]))}static insertBefore(t,r,n,o={}){const s=y.parse(o),{id:a,children:i}=s,c={...n,[i]:[]},l=(u,d)=>{for(let p=0;p<u.length;p++){const f=u[p];if(f[a]===r)return u.splice(p,0,c),!0;const h=f[i];if(h&&h.length>0){const m=[...d,f];if(l(h,m))return!0}}return!1};return l(t,[])}static insertAfter(t,r,n,o={}){const s=y.parse(o),{id:a,children:i}=s,c={...n,[i]:[]},l=(u,d)=>{for(let p=0;p<u.length;p++){const f=u[p];if(f[a]===r)return u.splice(p+1,0,c),!0;const h=f[i];if(h&&h.length>0){const m=[...d,f];if(l(h,m))return!0}}return!1};return l(t,[])}static remove(t,r,n={}){const o=y.parse(n),{id:s,children:a}=o,i=c=>{for(let l=0;l<c.length;l++){const u=c[l];if(u[s]===r)return c.splice(l,1)[0];const d=u[a];if(d&&d.length>0){const p=i(d);if(p)return p}}};return i(t)}static validate(t,r={}){const n=y.parse(r),{id:o,children:s}=n,a=Array.isArray(t)?t:[t],i=[],c=new Set,l=(u,d,p)=>{const f=u[o];if(!f||typeof f!="string"){i.push(`Node at depth ${d} has invalid or missing ID`);return}if(c.has(f)){i.push(`Duplicate ID found: ${f}`);return}if(c.add(f),p.some(m=>m[o]===f)){i.push(`Circular reference detected for ID: ${f}`);return}const h=u[s];if(h!==void 0&&!Array.isArray(h)){i.push(`Node ${f} has invalid children property (not an array)`);return}if(h&&h.length>0){const m=[...p,u];h.forEach(b=>{l(b,d+1,m)})}};return a.forEach(u=>l(u,0,[])),{isValid:i.length===0,errors:i}}}const S={row:"row",col:"column","row-reverse":"row-reverse","col-reverse":"column-reverse"},$={start:"flex-start",end:"flex-end",center:"center",between:"space-between",around:"space-around",evenly:"space-evenly"},A={start:"flex-start",end:"flex-end",center:"center",stretch:"stretch",baseline:"baseline"},C=Object.keys(S),O=Object.keys($),N=Object.keys(A),R=[new RegExp(`^(?:inline-)?flex-(${C.join("|")})(?:-(${O.join("|")}))?(?:-(${N.join("|")}))?$`),([,e,t,r],{rawSelector:n})=>{const o={display:n.startsWith("inline-")?"inline-flex":"flex"};return e&&S[e]&&(o["flex-direction"]=S[e]),t&&$[t]&&(o["justify-content"]=$[t]),r&&A[r]&&(o["align-items"]=A[r]),o},{autocomplete:`(inline-)?flex-(${C.join("|")})(-(${O.join("|")}))?(-(${N.join("|")}))?`}];function V(){return{name:"@movk/preset-flex",rules:[R,["flex-center",{display:"flex","justify-content":"center","align-items":"center"}],["inline-flex-center",{display:"inline-flex","justify-content":"center","align-items":"center"}]],shortcuts:[{"flex-x-center":"flex justify-center","flex-y-center":"flex items-center","inline-flex-x-center":"inline-flex justify-center","inline-flex-y-center":"inline-flex items-center"}]}}function x(e){return e.replace(/[.:#[\]()]/g,"\\$&")}function z(){return{name:"@movk/preset-owl",rules:[[/^owl-y-(.+)$/,([,e],{rawSelector:t})=>{if(!e)return;const r=v(e);return r?`${x(t)} > * + * { margin-block-start: ${r}; }`:void 0},{autocomplete:"owl-y-<num>"}],[/^owl-x-(.+)$/,([,e],{rawSelector:t})=>{if(!e)return;const r=v(e);return r?`${x(t)} > * + * { margin-inline-start: ${r}; }`:void 0},{autocomplete:"owl-x-<num>"}],[/^owl-block-(.+)$/,([,e],{rawSelector:t})=>{if(!e)return;const r=v(e);return r?`${x(t)} > * + * { margin-block-start: ${r}; }`:void 0},{autocomplete:"owl-block-<num>"}],[/^owl-inline-(.+)$/,([,e],{rawSelector:t})=>{if(!e)return;const r=v(e);return r?`${x(t)} > * + * { margin-inline-start: ${r}; }`:void 0},{autocomplete:"owl-inline-<num>"}],[/^owl-recursive-(.+)$/,([,e],{rawSelector:t})=>{if(!e)return;const r=v(e);return r?`${x(t)} * + * { margin-block-start: ${r}; }`:void 0},{autocomplete:"owl-recursive-<num>"}],[/^owl-divide-(.+)$/,([,e],{rawSelector:t})=>{if(!e)return;const r=B(e)||"#e5e7eb";return`${x(t)} > * + * { border-block-start: 1px solid ${r}; }`},{autocomplete:"owl-divide-<color>"}],[/^owl-divide-style-(.+)$/,([,e],{rawSelector:t})=>!e||!["solid","dashed","dotted","double","none"].includes(e)?void 0:`${x(t)} > * + * { border-block-start-style: ${e}; }`,{autocomplete:"owl-divide-style-<style>"}],[/^owl-divide-width-(.+)$/,([,e],{rawSelector:t})=>{if(!e)return;const r=v(e)||e;return`${x(t)} > * + * { border-block-start-width: ${r}; }`},{autocomplete:"owl-divide-width-<num>"}]],shortcuts:[{"owl-stack":"owl-y-4","owl-stack-tight":"owl-y-2","owl-stack-loose":"owl-y-8","owl-list":"owl-x-4","owl-list-tight":"owl-x-2","owl-nav":"owl-x-6","owl-card-stack":"owl-y-4 owl-divide-gray-200"}]}}function v(e){return/^\d+(?:\.\d+)?$/.test(e)?`${Number.parseFloat(e)*.25}rem`:/^\d+(?:\.\d+)?(?:px|rem|em|%|vh|vw|ch|ex)$/.test(e)?e:{auto:"auto",px:"1px",0:"0",1:"0.25rem",2:"0.5rem",3:"0.75rem",4:"1rem",6:"1.5rem",8:"2rem",12:"3rem",16:"4rem",24:"6rem"}[e]}function B(e){return/^#[0-9a-f]{3,8}$/i.test(e)||/^rgba?(?:\(|$)/.test(e)?e:{transparent:"transparent",current:"currentColor",black:"#000000",white:"#ffffff","gray-200":"#e5e7eb","gray-300":"#d1d5db","gray-500":"#6b7280","gray-700":"#374151","red-500":"#ef4444","blue-500":"#3b82f6","green-500":"#10b981"}[e]}function K(e){return[...new Set(e)]}function Z(e,t){const r=[];for(let n=0;n<e.length;n+=t)r.push(e.slice(n,n+t));return r}function q(e,t=1){return t===1?e.flat():e.flat(t)}function H(e,t){let r;return(...n)=>{clearTimeout(r),r=setTimeout(()=>e(...n),t)}}function J(e){return new Promise(t=>setTimeout(t,e))}function W(e){let t,r;return{promise:new Promise((n,o)=>{r=o,t=setTimeout(n,e)}),cancel:()=>{clearTimeout(t),r(new Error("Sleep was cancelled"))}}}function X(e,t){let r=!1;return function(...n){r||(e.apply(this,n),r=!0,setTimeout(()=>{r=!1},t))}}async function Q(e){if(!e||typeof e!="string")throw new Error("Invalid SVG string provided");if(typeof window>"u"||typeof document>"u")throw new TypeError("convertSvgToPng is only available in browser environment");return new Promise((t,r)=>{const n=new Image,o=document.createElement("canvas"),s=o.getContext("2d");if(!s){r(new Error("Canvas context not available"));return}n.onload=()=>{try{o.width=n.width,o.height=n.height,s.drawImage(n,0,0),o.toBlob(a=>{a?t(a):r(new Error("Failed to convert canvas to Blob"))},"image/png")}catch(a){r(new Error(`Error during canvas conversion: ${a}`))}},n.onerror=()=>{r(new Error("Failed to load SVG image"))};try{n.src=`data:image/svg+xml;base64,${btoa(e)}`}catch(a){r(new Error(`Failed to encode SVG: ${a}`))}})}function Y(e,t="file"){if(!e)return t;const r=e.get("content-disposition");if(r){const n=r.match(/filename\*?=['"]?([^'";]+)['"]?/i);if(n){let o=n[1];if(r.includes("filename*=")){const s=o.split("''");s.length===2&&(o=decodeURIComponent(s[1]))}return o}}return t}function _(e,t){if(typeof window>"u"||typeof document>"u"){console.warn("triggerDownload: Not available in server environment");return}const r=URL.createObjectURL(e),n=document.createElement("a");n.href=r,n.download=t,n.style.display="none",document.body.appendChild(n),n.click(),document.body.removeChild(n),URL.revokeObjectURL(r)}function ee(e){if(!Number.isFinite(e)||e<0||e===0)return"0 Bytes";const t=1024,r=["Bytes","KB","MB","GB","TB","PB"],n=Math.floor(Math.log(e)/Math.log(t));return n>=r.length?`${Number.parseFloat((e/t**(r.length-1)).toFixed(2))} ${r[r.length-1]}`:`${Number.parseFloat((e/t**n).toFixed(2))} ${r[n]}`}async function te(e,t){if(!e||typeof e!="string")throw new Error("Invalid SVG path provided");try{const r=await fetch(e);if(!r.ok)throw new Error(`Failed to fetch SVG file: ${r.status} ${r.statusText}`);const n=await r.text();if(!t)return n;if(typeof window>"u"||typeof DOMParser>"u")return console.warn("replaceCurrentColor: DOM manipulation not available in server environment, returning original SVG"),n;const o=new DOMParser().parseFromString(n,"image/svg+xml");if(o.querySelector("parsererror"))throw new Error("Invalid SVG content");const s=o.querySelector("svg");if(!s)throw new Error("No SVG element found in the document");s.hasAttribute("fill")||s.setAttribute("fill","currentColor");const a=i=>{i.getAttribute("fill")==="currentColor"&&i.setAttribute("fill",t),i.getAttribute("stroke")==="currentColor"&&i.setAttribute("stroke",t),Array.from(i.children).forEach(c=>{a(c)})};return a(s),new XMLSerializer().serializeToString(o)}catch(r){throw r instanceof Error?r:new Error(`Unexpected error occurred: ${r}`)}}function I(e,t=!1){if(!e||typeof e!="object"||Array.isArray(e))return e;const r=o=>o.replace(/([a-z\d])([A-Z])/g,"$1-$2").toLowerCase(),n={};for(const o in e)if(Object.prototype.hasOwnProperty.call(e,o)){const s=r(o),a=e[o];t&&a&&typeof a=="object"&&!Array.isArray(a)?n[s]=I(a,!0):n[s]=a}return n}function E(e){if(e===null||typeof e!="object")return e;if(e instanceof Date)return new Date(e.getTime());if(Array.isArray(e))return e.map(t=>E(t));if(typeof e=="object"){const t={};for(const r in e)t[r]=E(e[r]);return t}return e}function re(e,t){if(!e||typeof e!="object")return{};const r=new Set(t),n={};for(const o in e)Object.prototype.hasOwnProperty.call(e,o)&&!r.has(o)&&(n[o]=e[o]);return n}function ne(e){return!e||typeof e!="object"||Array.isArray(e)?e:Object.fromEntries(Object.entries(e).filter(([,t])=>t!==void 0))}function oe(e,t){if(!e||typeof e!="object")return{};const r={};for(const n of t)Object.prototype.hasOwnProperty.call(e,n)&&(r[n]=e[n]);return r}function ae(e,t){if(!e||typeof e!="object")return{picked:{},omitted:{}};if(t.length===0)return{picked:{},omitted:{...e}};const r=new Set(t),n={},o={};for(const s in e)if(Object.hasOwn(e,s)){const a=s;r.has(a)?n[s]=e[a]:o[s]=e[a]}return{picked:n,omitted:o}}function ie(e){return e.charAt(0).toUpperCase()+e.slice(1)}function se(e){return e.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()}function ce(e){return e.replace(/-([a-z])/g,(t,r)=>r.toUpperCase())}function le(e){let t=0;for(let r=0;r<e.length;r++){const n=e.charCodeAt(r);t=(t<<5)-t+n,t=t&t}return Math.abs(t).toString(36)}function ue(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{const t=Math.random()*16|0;return(e==="x"?t:t&3|8).toString(16)})}function T(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function F(e){return Array.isArray(e)}function G(e){return typeof e=="string"}function fe(e){return typeof e=="number"&&!Number.isNaN(e)}function de(e){return typeof e=="function"}function pe(e){return e==null?!0:F(e)||G(e)?e.length===0:T(e)?Object.keys(e).length===0:!1}export{k as StorageTypeSchema,w as Tree,y as TreeConfigSchema,P as TreeStatsSchema,se as camelToKebab,ie as capitalize,Z as chunk,Q as convertSvgToPng,I as convertToKebabCase,j as createStorageConfigSchema,H as debounce,E as deepClone,Y as extractFilename,q as flatten,ee as formatFileSize,ue as getRandomUUID,F as isArray,pe as isEmpty,de as isFunction,fe as isNumber,T as isObject,G as isString,ce as kebabToCamel,re as omit,ne as omitUndefined,oe as pick,V as presetFlex,z as presetOwl,te as replaceCurrentColor,ae as separate,le as simpleHash,J as sleep,W as sleepWithCancel,X as throttle,_ as triggerDownload,K as unique,M as useAppStorage,U as useCopyCode};
1
+ import{useStorage as $}from"@vueuse/core";import{z as y}from"zod/v4";const x=y.enum(["localStorage","sessionStorage"]);function A(e){return y.object({key:y.string().min(1,{message:"Key cannot be empty"}),schema:y.custom(t=>t instanceof y.ZodType,{message:"Schema must be a valid Zod schema"}),defaultValue:y.custom(t=>e.safeParse(t).success,{message:"Default value must match the provided schema"}),prefix:y.string().default("movk"),storage:x.default("localStorage")})}function k(e){const t=A(e.schema).parse(e),{key:n,defaultValue:r,schema:o,storage:s,prefix:a}=t,i=`${a}:${n}`,c=(()=>{if(!(typeof window>"u"))return s==="localStorage"?localStorage:sessionStorage})();function l(p){if(p===null)return r;try{const g=JSON.parse(p),w=o.safeParse(g);return w.success?w.data:(console.warn(`[AppStorage] Validation failed for key "${i}". Using default value.`,w.error.issues),r)}catch(g){return console.warn(`[AppStorage] Failed to parse value for key "${i}". Using default value.`,g),r}}const u=$(i,r,c,{mergeDefaults:!0,serializer:{read:l,write:p=>JSON.stringify(p)}});function d(){if(!c)return r;const p=c.getItem(i);return l(p)}function h(p){const g=o.safeParse(p);if(!g.success){console.warn(`[AppStorage] Invalid value for key "${i}". Aborting setItem.`,g.error.issues);return}u.value=g.data}function f(){c&&(u.value=null)}return{state:u,getItem:d,setItem:h,removeItem:f}}async function O(e){if(typeof e!="string")throw new TypeError("Text must be a string");if(typeof window>"u")return console.warn("useCopyCode: Not available in server environment"),!1;if(navigator.clipboard&&window.isSecureContext)try{return await navigator.clipboard.writeText(e),!0}catch(t){console.warn("Clipboard API failed, falling back to legacy method:",t)}try{return I(e)}catch(t){return console.error("Failed to copy text:",t),!1}}function I(e){if(typeof document>"u")return console.warn("copyTextLegacy: Document not available"),!1;const t=document.createElement("textarea"),n=document.activeElement,r=document.getSelection(),o=r&&r.rangeCount>0?r.getRangeAt(0):null;try{return t.value=e,t.setAttribute("readonly",""),t.setAttribute("contenteditable","true"),Object.assign(t.style,{contain:"strict",position:"absolute",left:"-9999px",top:"-9999px",fontSize:"12pt",border:"0",padding:"0",margin:"0",outline:"none",boxShadow:"none",background:"transparent"}),document.body.appendChild(t),t.focus(),t.select(),t.setSelectionRange&&t.setSelectionRange(0,e.length),document.execCommand("copy")}catch(s){return console.error("Legacy copy method failed:",s),!1}finally{t.parentNode&&document.body.removeChild(t),o&&r&&(r.removeAllRanges(),r.addRange(o)),n instanceof HTMLElement&&n.focus()}}const m=y.object({id:y.string().default("id"),pid:y.string().default("pid"),children:y.string().default("children")});y.object({total:y.number().int().nonnegative(),leaves:y.number().int().nonnegative(),depth:y.number().int().nonnegative(),branches:y.number().int().nonnegative()});class b{static*dfsGenerator(t,n={},r=[]){const{children:o}=m.parse(n);let s=0;for(const a of t){const i=[...r,a];yield{node:a,path:i,depth:r.length,index:s++};const c=a[o];c&&c.length>0&&(yield*b.dfsGenerator(c,n,i))}}static*bfsGenerator(t,n={}){const{children:r}=m.parse(n),o=t.map((a,i)=>({node:a,path:[a],depth:0,index:i}));let s=t.length;for(;o.length>0;){const a=o.shift();yield a;const i=a.node[r];i&&i.length>0&&i.forEach(c=>{const l=[...a.path,c];o.push({node:c,path:l,depth:a.depth+1,index:s++})})}}static selectStrategy(t){switch(t){case"find":case"findAll":case"filter":case"transform":case"forEach":case"stats":case"validate":return"dfs";default:return"dfs"}}static fromList(t,n={}){const r=m.parse(n),{id:o,pid:s,children:a}=r;if(!Array.isArray(t)||t.length===0)return[];const i=new Map,c=[];return t.forEach(l=>{const u={...l,[a]:[]};i.set(l[o],u)}),t.forEach(l=>{const u=i.get(l[o]),d=l[s];d&&i.has(d)?i.get(d)[a].push(u):c.push(u)}),c}static toList(t,n={}){const r=m.parse(n),{children:o}=r,s=[],a=Array.isArray(t)?t:[t],i=c=>{const{[o]:l,...u}=c;s.push(u),l&&l.length>0&&l.forEach(i)};return a.forEach(i),s}static estimateSize(t,n={}){const r=m.parse(n),{children:o}=r,s=Array.isArray(t)?t:[t];let a=0;const i=c=>{a++;const l=c[o];l&&l.length>0&&l.forEach(i)};return s.forEach(i),a}static find(t,n,r={}){const o=Array.isArray(t)?t:[t],s=b.selectStrategy("find")==="dfs"?b.dfsGenerator(o,r):b.bfsGenerator(o,r);for(const a of s)if(n(a.node,a.depth,a.path))return a}static findAll(t,n,r={}){const o=Array.isArray(t)?t:[t],s=b.selectStrategy("findAll"),a=[],i=s==="dfs"?b.dfsGenerator(o,r):b.bfsGenerator(o,r);for(const c of i)n(c.node,c.depth,c.path)&&a.push(c);return a}static findById(t,n,r={}){const o=m.parse(r),{id:s}=o;return this.find(t,a=>a[s]===n,r)}static getStats(t,n={}){const r=m.parse(n),{children:o}=r,s=Array.isArray(t)?t:[t];let a=0,i=0,c=0,l=0;const u=(d,h)=>{a++,c=Math.max(c,h);const f=d[o];f&&f.length>0?(l++,f.forEach(p=>u(p,h+1))):i++};return s.forEach(d=>u(d,1)),{total:a,leaves:i,depth:c,branches:l}}static filter(t,n,r={}){const o=m.parse(r),{children:s}=o,a=Array.isArray(t)?t:[t],i=[],c=(l,u,d)=>{const h=l[s],f=[];if(h&&h.length>0){const p=[...d,l];h.forEach(g=>{const w=c(g,u+1,p);w&&f.push(w)})}return n(l,u,d)||f.length>0?{...l,[s]:f}:null};return a.forEach(l=>{const u=c(l,0,[]);u&&i.push(u)}),i}static transform(t,n,r={}){const o=m.parse(r),{children:s}=o,a=Array.isArray(t)?t:[t],i=[],c=(l,u,d)=>{const h=l[s],f=[];if(h&&h.length>0){const p=[...d,l];h.forEach(g=>{f.push(c(g,u+1,p))})}return{...n(l,u,d),[s]:f}};return a.forEach(l=>{i.push(c(l,0,[]))}),i}static forEach(t,n,r={}){const o=m.parse(r),{children:s}=o,a=Array.isArray(t)?t:[t],i=(c,l,u)=>{if(n(c,l,u)!==!1){const d=c[s];if(d&&d.length>0){const h=[...u,c];d.forEach(f=>{i(f,l+1,h)})}}};a.forEach(c=>i(c,0,[]))}static insertBefore(t,n,r,o={}){const s=m.parse(o),{id:a,children:i}=s,c={...r,[i]:[]},l=(u,d)=>{for(let h=0;h<u.length;h++){const f=u[h];if(f[a]===n)return u.splice(h,0,c),!0;const p=f[i];if(p&&p.length>0){const g=[...d,f];if(l(p,g))return!0}}return!1};return l(t,[])}static insertAfter(t,n,r,o={}){const s=m.parse(o),{id:a,children:i}=s,c={...r,[i]:[]},l=(u,d)=>{for(let h=0;h<u.length;h++){const f=u[h];if(f[a]===n)return u.splice(h+1,0,c),!0;const p=f[i];if(p&&p.length>0){const g=[...d,f];if(l(p,g))return!0}}return!1};return l(t,[])}static remove(t,n,r={}){const o=m.parse(r),{id:s,children:a}=o,i=c=>{for(let l=0;l<c.length;l++){const u=c[l];if(u[s]===n)return c.splice(l,1)[0];const d=u[a];if(d&&d.length>0){const h=i(d);if(h)return h}}};return i(t)}static validate(t,n={}){const r=m.parse(n),{id:o,children:s}=r,a=Array.isArray(t)?t:[t],i=[],c=new Set,l=(u,d,h)=>{const f=u[o];if(!f||typeof f!="string"){i.push(`Node at depth ${d} has invalid or missing ID`);return}if(c.has(f)){i.push(`Duplicate ID found: ${f}`);return}if(c.add(f),h.some(g=>g[o]===f)){i.push(`Circular reference detected for ID: ${f}`);return}const p=u[s];if(p!==void 0&&!Array.isArray(p)){i.push(`Node ${f} has invalid children property (not an array)`);return}if(p&&p.length>0){const g=[...h,u];p.forEach(w=>{l(w,d+1,g)})}};return a.forEach(u=>l(u,0,[])),{isValid:i.length===0,errors:i}}}function G(e){return[...new Set(e)]}function N(e,t){const n=[];for(let r=0;r<e.length;r+=t)n.push(e.slice(r,r+t));return n}function T(e,t=1){return t===1?e.flat():e.flat(t)}function D(e,t){let n;return(...r)=>{clearTimeout(n),n=setTimeout(()=>e(...r),t)}}function F(e){return new Promise(t=>setTimeout(t,e))}function P(e){let t,n;return{promise:new Promise((r,o)=>{n=o,t=setTimeout(r,e)}),cancel:()=>{clearTimeout(t),n(new Error("Sleep was cancelled"))}}}function M(e,t){let n=!1;return function(...r){n||(e.apply(this,r),n=!0,setTimeout(()=>{n=!1},t))}}async function U(e){if(!e||typeof e!="string")throw new Error("Invalid SVG string provided");if(typeof window>"u"||typeof document>"u")throw new TypeError("convertSvgToPng is only available in browser environment");return new Promise((t,n)=>{const r=new Image,o=document.createElement("canvas"),s=o.getContext("2d");if(!s){n(new Error("Canvas context not available"));return}r.onload=()=>{try{o.width=r.width,o.height=r.height,s.drawImage(r,0,0),o.toBlob(a=>{a?t(a):n(new Error("Failed to convert canvas to Blob"))},"image/png")}catch(a){n(new Error(`Error during canvas conversion: ${a}`))}},r.onerror=()=>{n(new Error("Failed to load SVG image"))};try{r.src=`data:image/svg+xml;base64,${btoa(e)}`}catch(a){n(new Error(`Failed to encode SVG: ${a}`))}})}function L(e,t="file"){if(!e)return t;const n=e.get("content-disposition");if(n){const r=n.match(/filename\*?=['"]?([^'";]+)['"]?/i);if(r){let o=r[1];if(n.includes("filename*=")){const s=o.split("''");s.length===2&&(o=decodeURIComponent(s[1]))}return o}}return t}function V(e,t){if(typeof window>"u"||typeof document>"u"){console.warn("triggerDownload: Not available in server environment");return}const n=URL.createObjectURL(e),r=document.createElement("a");r.href=n,r.download=t,r.style.display="none",document.body.appendChild(r),r.click(),document.body.removeChild(r),URL.revokeObjectURL(n)}function z(e){if(!Number.isFinite(e)||e<0||e===0)return"0 Bytes";const t=1024,n=["Bytes","KB","MB","GB","TB","PB"],r=Math.floor(Math.log(e)/Math.log(t));return r>=n.length?`${Number.parseFloat((e/t**(n.length-1)).toFixed(2))} ${n[n.length-1]}`:`${Number.parseFloat((e/t**r).toFixed(2))} ${n[r]}`}async function B(e,t){if(!e||typeof e!="string")throw new Error("Invalid SVG path provided");try{const n=await fetch(e);if(!n.ok)throw new Error(`Failed to fetch SVG file: ${n.status} ${n.statusText}`);const r=await n.text();if(!t)return r;if(typeof window>"u"||typeof DOMParser>"u")return console.warn("replaceCurrentColor: DOM manipulation not available in server environment, returning original SVG"),r;const o=new DOMParser().parseFromString(r,"image/svg+xml");if(o.querySelector("parsererror"))throw new Error("Invalid SVG content");const s=o.querySelector("svg");if(!s)throw new Error("No SVG element found in the document");s.hasAttribute("fill")||s.setAttribute("fill","currentColor");const a=i=>{i.getAttribute("fill")==="currentColor"&&i.setAttribute("fill",t),i.getAttribute("stroke")==="currentColor"&&i.setAttribute("stroke",t),Array.from(i.children).forEach(c=>{a(c)})};return a(s),new XMLSerializer().serializeToString(o)}catch(n){throw n instanceof Error?n:new Error(`Unexpected error occurred: ${n}`)}}function S(e,t=!1){if(!e||typeof e!="object"||Array.isArray(e))return e;const n=o=>o.replace(/([a-z\d])([A-Z])/g,"$1-$2").toLowerCase(),r={};for(const o in e)if(Object.prototype.hasOwnProperty.call(e,o)){const s=n(o),a=e[o];t&&a&&typeof a=="object"&&!Array.isArray(a)?r[s]=S(a,!0):r[s]=a}return r}function v(e){if(e===null||typeof e!="object")return e;if(e instanceof Date)return new Date(e.getTime());if(Array.isArray(e))return e.map(t=>v(t));if(typeof e=="object"){const t={};for(const n in e)t[n]=v(e[n]);return t}return e}function R(e,t){if(!e||typeof e!="object")return{};const n=new Set(t),r={};for(const o in e)Object.prototype.hasOwnProperty.call(e,o)&&!n.has(o)&&(r[o]=e[o]);return r}function K(e){return!e||typeof e!="object"||Array.isArray(e)?e:Object.fromEntries(Object.entries(e).filter(([,t])=>t!==void 0))}function Z(e,t){if(!e||typeof e!="object")return{};const n={};for(const r of t)Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n}function q(e,t){if(!e||typeof e!="object")return{picked:{},omitted:{}};if(t.length===0)return{picked:{},omitted:{...e}};const n=new Set(t),r={},o={};for(const s in e)if(Object.hasOwn(e,s)){const a=s;n.has(a)?r[s]=e[a]:o[s]=e[a]}return{picked:r,omitted:o}}function H(e){return e.charAt(0).toUpperCase()+e.slice(1)}function J(e){return e.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()}function W(e){return e.replace(/-([a-z])/g,(t,n)=>n.toUpperCase())}function X(e){let t=0;for(let n=0;n<e.length;n++){const r=e.charCodeAt(n);t=(t<<5)-t+r,t=t&t}return Math.abs(t).toString(36)}function Q(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{const t=Math.random()*16|0;return(e==="x"?t:t&3|8).toString(16)})}function E(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function C(e){return Array.isArray(e)}function j(e){return typeof e=="string"}function Y(e){return typeof e=="number"&&!Number.isNaN(e)}function _(e){return typeof e=="function"}function tt(e){return e==null?!0:C(e)||j(e)?e.length===0:E(e)?Object.keys(e).length===0:!1}export{x as StorageTypeSchema,b as Tree,J as camelToKebab,H as capitalize,N as chunk,U as convertSvgToPng,S as convertToKebabCase,A as createStorageConfigSchema,D as debounce,v as deepClone,L as extractFilename,T as flatten,z as formatFileSize,Q as getRandomUUID,C as isArray,tt as isEmpty,_ as isFunction,Y as isNumber,E as isObject,j as isString,W as kebabToCamel,R as omit,K as omitUndefined,Z as pick,B as replaceCurrentColor,q as separate,X as simpleHash,F as sleep,P as sleepWithCancel,M as throttle,V as triggerDownload,G as unique,k as useAppStorage,O as useCopyCode};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@movk/core",
3
3
  "type": "module",
4
- "version": "0.0.3",
4
+ "version": "0.0.4",
5
5
  "description": "Modern Vue.js utilities and composables collection with TypeScript support",
6
6
  "author": "yixuan",
7
7
  "license": "MIT",
@@ -37,39 +37,32 @@
37
37
  "dist"
38
38
  ],
39
39
  "peerDependencies": {
40
- "vue": "^3.5.17"
40
+ "vue": "^3.5.18"
41
41
  },
42
42
  "dependencies": {
43
- "@vueuse/core": "^13.5.0",
44
- "zod": "^4.0.5"
43
+ "@vueuse/core": "^13.6.0",
44
+ "zod": "^4.0.14"
45
45
  },
46
46
  "devDependencies": {
47
- "@antfu/eslint-config": "^4.17.0",
48
- "@antfu/ni": "^25.0.0",
49
- "@types/node": "^24.0.14",
50
- "bumpp": "^10.2.0",
51
- "eslint": "^9.31.0",
52
- "lint-staged": "^15.2.10",
53
- "simple-git-hooks": "^2.13.0",
47
+ "@antfu/eslint-config": "^5.0.0",
48
+ "@release-it/conventional-changelog": "^10.0.1",
49
+ "@types/node": "^24.1.0",
50
+ "bumpp": "^10.2.1",
51
+ "eslint": "^9.32.0",
52
+ "release-it": "^19.0.4",
54
53
  "taze": "^19.1.0",
55
54
  "tsx": "^4.20.3",
56
55
  "typescript": "^5.8.3",
57
- "unbuild": "^3.5.0",
58
- "vite": "^7.0.5",
56
+ "unbuild": "^3.6.0",
57
+ "vite": "^7.0.6",
59
58
  "vitest": "^3.2.4"
60
59
  },
61
- "simple-git-hooks": {
62
- "pre-commit": "pnpm lint-staged"
63
- },
64
- "lint-staged": {
65
- "*": "eslint --fix"
66
- },
67
60
  "scripts": {
68
61
  "build": "unbuild",
69
62
  "dev": "unbuild --stub",
70
63
  "lint": "eslint .",
71
- "lint-fix": "nr lint --fix",
72
- "release": "bumpp",
64
+ "lint:fix": "pnpm run lint --fix",
65
+ "release": "release-it --verbose",
73
66
  "start": "tsx src/index.ts",
74
67
  "typecheck": "tsc --noEmit",
75
68
  "test": "vitest",