@xxkatt/utils 0.0.2 → 0.0.3

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/index.d.ts CHANGED
@@ -108,4 +108,65 @@ export declare function isUrl(value: unknown): boolean;
108
108
  export declare function isEmail(value: unknown): boolean;
109
109
  /** 中国大陆 11 位手机号格式:以 1 开头,第二位为 3–9;不验证号段分配或号码存在性。 */
110
110
  export declare function isPhone(value: unknown): boolean;
111
+ /** 读取 JSON 值;不存在时返回 null。T 仅提供类型提示,不校验实际数据。 */
112
+ export declare function getLocalStorage<T = unknown>(key: string): T | null;
113
+ /** 按 JSON.stringify 的规则写入值;无法序列化时抛错,不覆盖已有数据。 */
114
+ export declare function setLocalStorage(key: string, value: unknown): void;
115
+ /** 删除指定键;键不存在时不做任何操作。 */
116
+ export declare function removeLocalStorage(key: string): void;
117
+ export interface CookieOptions {
118
+ /** 默认 /。删除 cookie 时需使用与写入时一致的路径。 */
119
+ path?: string;
120
+ /** 省略时使用当前主机。 */
121
+ domain?: string;
122
+ /** 绝对过期时间;与 maxAge 同时设置时,maxAge 优先。 */
123
+ expires?: Date;
124
+ /** 有效期,单位为秒,必须是有限整数。 */
125
+ maxAge?: number;
126
+ secure?: boolean;
127
+ sameSite?: "strict" | "lax" | "none";
128
+ }
129
+ /** 读取并解码 cookie;不存在时返回 null,非法百分号编码保留原值。 */
130
+ export declare function getCookie(name: string): string | null;
131
+ /** 编码并写入字符串 cookie;省略有效期时创建会话 cookie。 */
132
+ export declare function setCookie(name: string, value: string, options?: CookieOptions): void;
133
+ /** 通过 Max-Age=0 删除 cookie;path 和 domain 需与写入时一致。 */
134
+ export declare function removeCookie(name: string, options?: Pick<CookieOptions, "path" | "domain">): void;
135
+ export type QueryValue = string | number | boolean;
136
+ export type QueryParams = Record<string, QueryValue | readonly QueryValue[] | null | undefined>;
137
+ export type ParsedQuery = Record<string, string | string[]>;
138
+ /** 解析查询串(可带前导 ?);重复键返回数组,所有值保持字符串。 */
139
+ export declare function parseQuery(query: string): ParsedQuery;
140
+ /** 序列化为不带 ? 的查询串;数组使用重复键,省略 null、undefined 和空数组。 */
141
+ export declare function stringifyQuery(params: QueryParams): string;
142
+ /** 读取 URL 中第一个同名查询参数;不存在时返回 null,不读取 hash 内的参数。 */
143
+ export declare function getQueryParam(url: string, name: string): string | null;
144
+ /** 读取 URL 的全部查询参数,重复键返回数组。 */
145
+ export declare function getQueryParams(url: string): ParsedQuery;
146
+ /** 合并查询参数并返回新 URL;null 或空数组删除该键,undefined 保留原值。 */
147
+ export declare function setQueryParams(url: string, params: QueryParams): string;
148
+ /** 删除一个或多个查询参数的全部同名项,返回新 URL。 */
149
+ export declare function removeQueryParams(url: string, names: string | readonly string[]): string;
150
+ /** 获取第一个 # 后的原始 hash,不含 #,不解码;不存在时返回空字符串。 */
151
+ export declare function getUrlHash(url: string): string;
152
+ /** 设置原始 hash(可带一个前导 #),空字符串移除 hash;不进行编码。 */
153
+ export declare function setUrlHash(url: string, hash: string): string;
154
+ export type TreeId = string | number;
155
+ /** 保留业务字段,并将指定子节点字段替换为递归节点数组。 */
156
+ export type TreeNode<T, ChildrenKey extends string = "children"> = Omit<T, ChildrenKey> & { [Key in ChildrenKey]: TreeNode<T, ChildrenKey>[]; };
157
+ export interface TreeOptions<ChildrenKey extends string = "children"> {
158
+ childrenKey?: ChildrenKey;
159
+ }
160
+ export interface ListToTreeOptions<IdKey extends string = "id", ParentIdKey extends string = "parentId", ChildrenKey extends string = "children"> extends TreeOptions<ChildrenKey> {
161
+ idKey?: IdKey;
162
+ parentIdKey?: ParentIdKey;
163
+ }
164
+ /** 按深度优先前序展开树;浅拷贝节点并移除子节点字段,不补充 parentId。 */
165
+ export declare function treeToList<T extends object, ChildrenKey extends string = "children">(tree: readonly T[], options?: TreeOptions<ChildrenKey>): Omit<T, ChildrenKey>[];
166
+ /** 列表转树;父 ID 为空或不存在时作为根节点,重复 ID 和循环关系抛错。 */
167
+ export declare function listToTree<T extends Record<IdKey, TreeId> & Partial<Record<ParentIdKey, TreeId | null | undefined>>, IdKey extends string = "id", ParentIdKey extends string = "parentId", ChildrenKey extends string = "children">(list: readonly T[], options?: ListToTreeOptions<IdKey, ParentIdKey, ChildrenKey>): TreeNode<T, ChildrenKey>[];
168
+ /** 按深度优先前序查找首个匹配节点,返回原引用;未找到时返回 undefined。 */
169
+ export declare function findTreeNode<T extends object, ChildrenKey extends string = "children">(tree: readonly T[], predicate: (node: T) => boolean, options?: TreeOptions<ChildrenKey>): T | undefined;
170
+ /** 查找首个匹配节点从根到自身的路径,保留节点引用;未找到时返回 undefined。 */
171
+ export declare function findTreePath<T extends object, ChildrenKey extends string = "children">(tree: readonly T[], predicate: (node: T) => boolean, options?: TreeOptions<ChildrenKey>): T[] | undefined;
111
172
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -265,6 +265,221 @@ function isEmail(value) {
265
265
  function isPhone(value) {
266
266
  return isString(value) && /^1[3-9]\d{9}$/.test(value);
267
267
  }
268
- export { debounce, download, is, isAllEmpty, isArray, isBigInt, isBoolean, isDate, isDefined, isEmail, isEmpty, isFunction, isJSON, isMap, isNil, isNull, isNumber, isObject, isPhone, isPlainObject, isPromise, isRegExp, isSet, isString, isSymbol, isUndefined, isUrl, randomId, throttle, uuid };
268
+ // src/storage/index.ts
269
+ function getStorage() {
270
+ if (typeof window === "undefined") throw new Error("localStorage helpers require a browser environment");
271
+ return window.localStorage;
272
+ }
273
+ /** 读取 JSON 值;不存在时返回 null。T 仅提供类型提示,不校验实际数据。 */
274
+ function getLocalStorage(key) {
275
+ const value = getStorage().getItem(key);
276
+ return value === null ? null : JSON.parse(value);
277
+ }
278
+ /** 按 JSON.stringify 的规则写入值;无法序列化时抛错,不覆盖已有数据。 */
279
+ function setLocalStorage(key, value) {
280
+ const storage = getStorage();
281
+ const serialized = JSON.stringify(value);
282
+ if (serialized === void 0) throw new TypeError("The value cannot be serialized as JSON");
283
+ storage.setItem(key, serialized);
284
+ }
285
+ /** 删除指定键;键不存在时不做任何操作。 */
286
+ function removeLocalStorage(key) {
287
+ getStorage().removeItem(key);
288
+ }
289
+ // src/cookie/index.ts
290
+ function getCookieDocument() {
291
+ if (typeof document === "undefined") throw new Error("cookie helpers require a browser environment");
292
+ return document;
293
+ }
294
+ /** 读取并解码 cookie;不存在时返回 null,非法百分号编码保留原值。 */
295
+ function getCookie(name) {
296
+ const prefix = `${encodeURIComponent(name)}=`;
297
+ for (const cookie of getCookieDocument().cookie.split(";")) {
298
+ const entry = cookie.trim();
299
+ if (!entry.startsWith(prefix)) continue;
300
+ const value = entry.slice(prefix.length);
301
+ try {
302
+ return decodeURIComponent(value);
303
+ } catch {
304
+ return value;
305
+ }
306
+ }
307
+ return null;
308
+ }
309
+ /** 编码并写入字符串 cookie;省略有效期时创建会话 cookie。 */
310
+ function setCookie(name, value, options = {}) {
311
+ const cookieDocument = getCookieDocument();
312
+ const { path = "/", domain, expires, maxAge, secure, sameSite } = options;
313
+ const attributes = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
314
+ for (const attribute of [path, domain]) if (attribute !== void 0 && /[;\u0000-\u001f\u007f]/.test(attribute)) throw new TypeError("Cookie path and domain cannot contain semicolons or control characters");
315
+ attributes.push(`Path=${path}`);
316
+ if (domain !== void 0) attributes.push(`Domain=${domain}`);
317
+ if (expires !== void 0) {
318
+ if (!Number.isFinite(expires.getTime())) throw new TypeError("Invalid cookie expiration date");
319
+ attributes.push(`Expires=${expires.toUTCString()}`);
320
+ }
321
+ if (maxAge !== void 0) {
322
+ if (!Number.isInteger(maxAge)) throw new TypeError("Cookie maxAge must be a finite integer");
323
+ attributes.push(`Max-Age=${maxAge}`);
324
+ }
325
+ if (secure) attributes.push("Secure");
326
+ if (sameSite !== void 0) attributes.push(`SameSite=${sameSite}`);
327
+ cookieDocument.cookie = attributes.join("; ");
328
+ }
329
+ /** 通过 Max-Age=0 删除 cookie;path 和 domain 需与写入时一致。 */
330
+ function removeCookie(name, options = {}) {
331
+ setCookie(name, "", {
332
+ ...options,
333
+ maxAge: 0
334
+ });
335
+ }
336
+ // src/url/index.ts
337
+ function splitUrl(url) {
338
+ const hashIndex = url.indexOf("#");
339
+ const hash = hashIndex < 0 ? "" : url.slice(hashIndex);
340
+ const beforeHash = hashIndex < 0 ? url : url.slice(0, hashIndex);
341
+ const queryIndex = beforeHash.indexOf("?");
342
+ return {
343
+ path: queryIndex < 0 ? beforeHash : beforeHash.slice(0, queryIndex),
344
+ query: queryIndex < 0 ? "" : beforeHash.slice(queryIndex),
345
+ hash
346
+ };
347
+ }
348
+ function appendQueryValue(search, name, value) {
349
+ for (const item of Array.isArray(value) ? value : [value]) search.append(name, String(item));
350
+ }
351
+ /** 解析查询串(可带前导 ?);重复键返回数组,所有值保持字符串。 */
352
+ function parseQuery(query) {
353
+ const entries = /* @__PURE__ */ new Map();
354
+ for (const [name, value] of new URLSearchParams(query)) {
355
+ const previous = entries.get(name);
356
+ if (previous === void 0) entries.set(name, value);
357
+ else if (typeof previous === "string") entries.set(name, [previous, value]);
358
+ else previous.push(value);
359
+ }
360
+ return Object.fromEntries(entries);
361
+ }
362
+ /** 序列化为不带 ? 的查询串;数组使用重复键,省略 null、undefined 和空数组。 */
363
+ function stringifyQuery(params) {
364
+ const search = new URLSearchParams();
365
+ for (const [name, value] of Object.entries(params)) if (value !== null && value !== void 0) appendQueryValue(search, name, value);
366
+ return search.toString();
367
+ }
368
+ /** 读取 URL 中第一个同名查询参数;不存在时返回 null,不读取 hash 内的参数。 */
369
+ function getQueryParam(url, name) {
370
+ return new URLSearchParams(splitUrl(url).query).get(name);
371
+ }
372
+ /** 读取 URL 的全部查询参数,重复键返回数组。 */
373
+ function getQueryParams(url) {
374
+ return parseQuery(splitUrl(url).query);
375
+ }
376
+ /** 合并查询参数并返回新 URL;null 或空数组删除该键,undefined 保留原值。 */
377
+ function setQueryParams(url, params) {
378
+ const { path, query, hash } = splitUrl(url);
379
+ const search = new URLSearchParams(query);
380
+ for (const [name, value] of Object.entries(params)) {
381
+ if (value === void 0) continue;
382
+ search.delete(name);
383
+ if (value !== null) appendQueryValue(search, name, value);
384
+ }
385
+ const serialized = search.toString();
386
+ return path + (serialized ? `?${serialized}` : "") + hash;
387
+ }
388
+ /** 删除一个或多个查询参数的全部同名项,返回新 URL。 */
389
+ function removeQueryParams(url, names) {
390
+ return setQueryParams(url, Object.fromEntries((typeof names === "string" ? [names] : names).map((name) => [name, null])));
391
+ }
392
+ /** 获取第一个 # 后的原始 hash,不含 #,不解码;不存在时返回空字符串。 */
393
+ function getUrlHash(url) {
394
+ return splitUrl(url).hash.slice(1);
395
+ }
396
+ /** 设置原始 hash(可带一个前导 #),空字符串移除 hash;不进行编码。 */
397
+ function setUrlHash(url, hash) {
398
+ const hashIndex = url.indexOf("#");
399
+ const beforeHash = hashIndex < 0 ? url : url.slice(0, hashIndex);
400
+ const value = hash.startsWith("#") ? hash.slice(1) : hash;
401
+ return beforeHash + (value ? `#${value}` : "");
402
+ }
403
+ // src/tree/index.ts
404
+ function* walkTree(tree, childrenKey) {
405
+ const stack = [tree[Symbol.iterator]()];
406
+ const path = [];
407
+ while (stack.length > 0) {
408
+ const iterator = stack[stack.length - 1];
409
+ if (!iterator) break;
410
+ const result = iterator.next();
411
+ if (result.done) {
412
+ stack.pop();
413
+ path.pop();
414
+ continue;
415
+ }
416
+ const node = result.value;
417
+ path.push(node);
418
+ yield {
419
+ node,
420
+ path
421
+ };
422
+ const children = node[childrenKey];
423
+ if (children?.length) stack.push(children[Symbol.iterator]());
424
+ else path.pop();
425
+ }
426
+ }
427
+ /** 按深度优先前序展开树;浅拷贝节点并移除子节点字段,不补充 parentId。 */
428
+ function treeToList(tree, options = {}) {
429
+ const childrenKey = options.childrenKey ?? "children";
430
+ const list = [];
431
+ for (const { node } of walkTree(tree, childrenKey)) {
432
+ const item = { ...node };
433
+ delete item[childrenKey];
434
+ list.push(item);
435
+ }
436
+ return list;
437
+ }
438
+ /** 列表转树;父 ID 为空或不存在时作为根节点,重复 ID 和循环关系抛错。 */
439
+ function listToTree(list, options = {}) {
440
+ const idKey = options.idKey ?? "id";
441
+ const parentIdKey = options.parentIdKey ?? "parentId";
442
+ const childrenKey = options.childrenKey ?? "children";
443
+ const nodes = /* @__PURE__ */ new Map();
444
+ for (const item of list) {
445
+ const id = item[idKey];
446
+ if (nodes.has(id)) throw new Error(`Duplicate tree node ID: ${id}`);
447
+ const children = [];
448
+ const node = {
449
+ ...item,
450
+ [childrenKey]: children
451
+ };
452
+ nodes.set(id, {
453
+ node,
454
+ children,
455
+ parentId: item[parentIdKey]
456
+ });
457
+ }
458
+ const roots = [];
459
+ const visited = /* @__PURE__ */ new Set();
460
+ for (const [id, entry] of nodes) {
461
+ const path = /* @__PURE__ */ new Set();
462
+ let current = id;
463
+ while (current !== null && current !== void 0 && !visited.has(current)) {
464
+ if (path.has(current)) throw new Error(`Cycle detected in tree at ID: ${current}`);
465
+ path.add(current);
466
+ current = nodes.get(current)?.parentId;
467
+ }
468
+ for (const ancestorId of path) visited.add(ancestorId);
469
+ const parent = entry.parentId == null ? void 0 : nodes.get(entry.parentId);
470
+ if (parent) parent.children.push(entry.node);
471
+ else roots.push(entry.node);
472
+ }
473
+ return roots;
474
+ }
475
+ /** 按深度优先前序查找首个匹配节点,返回原引用;未找到时返回 undefined。 */
476
+ function findTreeNode(tree, predicate, options = {}) {
477
+ for (const { node } of walkTree(tree, options.childrenKey ?? "children")) if (predicate(node)) return node;
478
+ }
479
+ /** 查找首个匹配节点从根到自身的路径,保留节点引用;未找到时返回 undefined。 */
480
+ function findTreePath(tree, predicate, options = {}) {
481
+ for (const { node, path } of walkTree(tree, options.childrenKey ?? "children")) if (predicate(node)) return [...path];
482
+ }
483
+ export { debounce, download, findTreeNode, findTreePath, getCookie, getLocalStorage, getQueryParam, getQueryParams, getUrlHash, is, isAllEmpty, isArray, isBigInt, isBoolean, isDate, isDefined, isEmail, isEmpty, isFunction, isJSON, isMap, isNil, isNull, isNumber, isObject, isPhone, isPlainObject, isPromise, isRegExp, isSet, isString, isSymbol, isUndefined, isUrl, listToTree, parseQuery, randomId, removeCookie, removeLocalStorage, removeQueryParams, setCookie, setLocalStorage, setQueryParams, setUrlHash, stringifyQuery, throttle, treeToList, uuid };
269
484
 
270
485
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/uuid/index.ts","../src/download/index.ts","../src/debounce/index.ts","../src/throttle/index.ts","../src/is/index.ts"],"sourcesContent":["const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'\n\n/**\n * 生成 UUID v4,默认返回 32 个小写十六进制字符。\n *\n * @param prefix 原样拼接在 UUID 前的前缀,分隔符需自行提供。\n * @param hyphens 是否保留标准 UUID 中的横杠,默认 false。\n * @example\n * uuid()\n * uuid('', true)\n * uuid('user_')\n * uuid('user_', true)\n */\nexport function uuid(prefix: string = '', hyphens: boolean = false): string {\n const crypto = globalThis.crypto\n let value: string\n\n if (typeof crypto?.randomUUID === 'function') {\n value = crypto.randomUUID()\n } else {\n if (typeof crypto?.getRandomValues !== 'function') {\n throw new Error('uuid requires crypto.randomUUID or crypto.getRandomValues')\n }\n\n const bytes = new Uint8Array(16)\n crypto.getRandomValues(bytes)\n // 设置 UUID v4 的版本位和 RFC 变体位,保留其余随机位。\n bytes[6] = (bytes[6]! & 0x0f) | 0x40\n bytes[8] = (bytes[8]! & 0x3f) | 0x80\n const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('')\n value = `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`\n }\n\n return prefix + (hyphens ? value : value.replace(/-/g, ''))\n}\n\n/**\n * 使用加密安全随机数生成 ID;自定义长度的 ID 不遵循 UUID 格式。\n * 字符表依次为 0–9、a–z、A–Z,使用前 radix 个字符。\n *\n * @param length 字符数量,必须为非负安全整数,默认 32;0 返回空字符串。\n * @param radix 基数,必须为 2–62 的整数,默认 16。\n * @example\n * randomId(8, 10)\n * randomId(21, 62)\n */\nexport function randomId(length: number = 32, radix: number = 16): string {\n if (!Number.isSafeInteger(length) || length < 0) {\n throw new RangeError('length must be a non-negative safe integer')\n }\n if (!Number.isInteger(radix) || radix < 2 || radix > ALPHABET.length) {\n throw new RangeError('radix must be an integer between 2 and 62')\n }\n if (length === 0) return ''\n\n const bytes = new Uint8Array(Math.min(length, 65_536))\n const limit = 256 - (256 % radix)\n let value = ''\n\n while (value.length < length) {\n globalThis.crypto.getRandomValues(bytes)\n for (const byte of bytes) {\n // 丢弃无法均分到字符表的尾部区间,避免取模导致分布偏差。\n if (byte >= limit) continue\n value += ALPHABET.charAt(byte % radix)\n if (value.length === length) break\n }\n }\n\n return value\n}\n","/**\n * 在浏览器中下载 URL、完整 Data URL 或 Blob/File。\n *\n * @param source 文件地址或文件内容;字符串始终作为地址处理。\n * @param filename 建议保存的完整文件名,包含扩展名。\n * @returns 触发下载后完成,不代表文件已保存到磁盘。\n * @example\n * await download('/files/report.pdf', 'report.pdf')\n * await download(new Blob(['hello'], { type: 'text/plain' }), 'hello.txt')\n */\nexport async function download(source: string | Blob, filename: string): Promise<void> {\n if (typeof document === 'undefined' || !document.body) {\n throw new Error('download requires a browser environment')\n }\n\n let blob: Blob\n if (typeof source === 'string') {\n const response = await fetch(source)\n if (!response.ok) {\n throw new Error(`Download failed: HTTP ${response.status}`)\n }\n blob = await response.blob()\n } else {\n blob = source\n }\n\n const anchor = document.createElement('a')\n const url = URL.createObjectURL(blob)\n try {\n anchor.href = url\n anchor.download = filename\n anchor.hidden = true\n document.body.append(anchor)\n anchor.click()\n } finally {\n anchor.remove()\n // 留出浏览器开始读取文件的时间,避免同步释放导致下载失败。\n setTimeout(() => URL.revokeObjectURL(url), 1000)\n }\n}\n","export interface DebouncedFunction<This, Args extends unknown[]> {\n (this: This, ...args: Args): void\n cancel(): void\n}\n\n/**\n * 防抖:停止调用 wait 毫秒后,使用最后一次调用的 this 和参数执行回调。\n *\n * @param fn 要延迟执行的回调。\n * @param wait 等待时间,单位毫秒,遵循原生 setTimeout 的时间规则。\n * @returns 带有 cancel() 方法的防抖函数;cancel() 取消尚未执行的回调。\n * @example\n * const search = debounce((keyword: string) => console.log(keyword), 300)\n * search('hello')\n * search.cancel()\n */\nexport function debounce<This, Args extends unknown[]>(\n fn: (this: This, ...args: Args) => unknown,\n wait: number,\n): DebouncedFunction<This, Args> {\n let timer: ReturnType<typeof setTimeout> | undefined\n\n function debounced(this: This, ...args: Args): void {\n if (timer !== undefined) clearTimeout(timer)\n timer = setTimeout(() => {\n timer = undefined\n fn.apply(this, args)\n }, wait)\n }\n\n debounced.cancel = (): void => {\n if (timer !== undefined) clearTimeout(timer)\n timer = undefined\n }\n\n return debounced\n}\n","export interface ThrottledFunction<This, Args extends unknown[]> {\n (this: This, ...args: Args): void\n cancel(): void\n}\n\n/**\n * 节流:首次调用立即执行,间隔内的调用在 wait 毫秒后以最后一次的 this 和参数执行。\n * 持续调用时,每隔 wait 毫秒执行一次;仅调用一次时不会重复执行。\n *\n * @param fn 要限制执行频率的回调。\n * @param wait 执行间隔,单位毫秒,遵循原生 setTimeout 的时间规则。\n * @returns 带有 cancel() 方法的节流函数;cancel() 取消待执行回调并重置间隔。\n * @example\n * const update = throttle((position: number) => console.log(position), 100)\n * update(10)\n * update.cancel()\n */\nexport function throttle<This, Args extends unknown[]>(\n fn: (this: This, ...args: Args) => unknown,\n wait: number,\n): ThrottledFunction<This, Args> {\n let timer: ReturnType<typeof setTimeout> | undefined\n let pending: { thisArg: This; args: Args } | undefined\n\n function onTimeout(): void {\n timer = undefined\n if (!pending) return\n\n const { thisArg, args } = pending\n pending = undefined\n // 在执行用户回调前建立下一轮间隔,支持回调内再次调用或取消。\n timer = setTimeout(onTimeout, wait)\n fn.apply(thisArg, args)\n }\n\n function throttled(this: This, ...args: Args): void {\n if (timer === undefined) {\n timer = setTimeout(onTimeout, wait)\n fn.apply(this, args)\n } else {\n pending = { thisArg: this, args }\n }\n }\n\n throttled.cancel = (): void => {\n if (timer !== undefined) clearTimeout(timer)\n timer = undefined\n pending = undefined\n }\n\n return throttled\n}\n","/** 按 Object.prototype.toString 的标签判断类型,如 'Date'、'Map';区分大小写。 */\nexport function is(value: unknown, type: string): boolean {\n return Object.prototype.toString.call(value) === `[object ${type}]`\n}\n\n/** 仅判断原始字符串,不包含 String 包装对象。 */\nexport function isString(value: unknown): value is string {\n return typeof value === 'string'\n}\n\n/** 判断原始 number,包含 NaN 和 Infinity,不进行数值有效性检查。 */\nexport function isNumber(value: unknown): value is number {\n return typeof value === 'number'\n}\n\n/** 仅判断原始布尔值。 */\nexport function isBoolean(value: unknown): value is boolean {\n return typeof value === 'boolean'\n}\n\nexport function isBigInt(value: unknown): value is bigint {\n return typeof value === 'bigint'\n}\n\nexport function isSymbol(value: unknown): value is symbol {\n return typeof value === 'symbol'\n}\n\nexport function isUndefined(value: unknown): value is undefined {\n return value === undefined\n}\n\n/** 排除 undefined,保留 null。 */\nexport function isDefined<T>(value: T): value is Exclude<T, undefined> {\n return value !== undefined\n}\n\nexport function isNull(value: unknown): value is null {\n return value === null\n}\n\n/** 判断 null 或 undefined。 */\nexport function isNil(value: unknown): value is null | undefined {\n return value === null || value === undefined\n}\n\n/** 判断非 null 的对象,包含数组、内置对象和类实例,不包含函数。 */\nexport function isObject(value: unknown): value is object {\n return value !== null && typeof value === 'object'\n}\n\n/** 判断普通对象,支持对象字面量、跨 realm 对象和 Object.create(null)。 */\nexport function isPlainObject(value: unknown): value is Record<PropertyKey, unknown> {\n if (!is(value, 'Object')) return false\n const prototype = Object.getPrototypeOf(value)\n if (prototype === null) return true\n const constructor = Object.hasOwn(prototype, 'constructor') && prototype.constructor\n return (\n typeof constructor === 'function' &&\n Function.prototype.toString.call(constructor) === Function.prototype.toString.call(Object)\n )\n}\n\n/** 判断 Date 类型,包含 Invalid Date。 */\nexport function isDate(value: unknown): value is Date {\n return is(value, 'Date')\n}\n\nexport function isRegExp(value: unknown): value is RegExp {\n return is(value, 'RegExp')\n}\n\n/** 包含普通函数、异步函数和生成器函数;不检查参数或返回值类型。 */\nexport function isFunction(value: unknown): value is (...args: never[]) => unknown {\n return typeof value === 'function'\n}\n\n/** 仅判断数组,不检查元素类型。 */\nexport function isArray(value: unknown): value is unknown[] {\n return Array.isArray(value)\n}\n\nexport function isMap(value: unknown): value is Map<unknown, unknown> {\n return is(value, 'Map')\n}\n\nexport function isSet(value: unknown): value is Set<unknown> {\n return is(value, 'Set')\n}\n\n/** 判断 Promise 或具有可调用 then 方法的对象/函数,收窄为 PromiseLike。 */\nexport function isPromise(value: unknown): value is PromiseLike<unknown> {\n return (isObject(value) || isFunction(value)) && 'then' in value && isFunction(value.then)\n}\n\n/** 判断可被 JSON.parse 解析的字符串,包含 JSON 原始值。 */\nexport function isJSON(value: unknown): boolean {\n if (!isString(value)) return false\n try {\n JSON.parse(value)\n return true\n } catch {\n return false\n }\n}\n\n/** 判断空字符串、数组、Map、Set 或无自有可枚举属性的普通对象。 */\nexport function isEmpty(value: unknown): boolean {\n if (isString(value) || isArray(value)) return value.length === 0\n if (isMap(value) || isSet(value)) return value.size === 0\n if (isPlainObject(value)) {\n return !Reflect.ownKeys(value).some((key) =>\n Object.prototype.propertyIsEnumerable.call(value, key),\n )\n }\n return false\n}\n\n/** 在 isEmpty 的基础上,将 null、undefined 也视为空。 */\nexport function isAllEmpty(value: unknown): boolean {\n return isNil(value) || isEmpty(value)\n}\n\n/** 判断绝对 HTTP(S) URL,拒绝空白字符、反斜杠和相对地址。 */\nexport function isUrl(value: unknown): boolean {\n if (!isString(value) || !/^https?:\\/\\//i.test(value) || /[\\s\\\\]/.test(value)) return false\n try {\n const url = new URL(value)\n return (url.protocol === 'http:' || url.protocol === 'https:') && url.hostname !== ''\n } catch {\n return false\n }\n}\n\n/** 常用邮箱格式校验,不验证邮箱是否存在,不覆盖完整 RFC 邮箱语法。 */\nexport function isEmail(value: unknown): boolean {\n return (\n isString(value) &&\n /^[a-z\\d!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z\\d!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z\\d](?:[a-z\\d-]*[a-z\\d])?\\.)+[a-z]{2,}$/i.test(\n value,\n )\n )\n}\n\n/** 中国大陆 11 位手机号格式:以 1 开头,第二位为 3–9;不验证号段分配或号码存在性。 */\nexport function isPhone(value: unknown): boolean {\n return isString(value) && /^1[3-9]\\d{9}$/.test(value)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/uuid/index.ts","../src/download/index.ts","../src/debounce/index.ts","../src/throttle/index.ts","../src/is/index.ts","../src/storage/index.ts","../src/cookie/index.ts","../src/url/index.ts","../src/tree/index.ts"],"sourcesContent":["const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'\n\n/**\n * 生成 UUID v4,默认返回 32 个小写十六进制字符。\n *\n * @param prefix 原样拼接在 UUID 前的前缀,分隔符需自行提供。\n * @param hyphens 是否保留标准 UUID 中的横杠,默认 false。\n * @example\n * uuid()\n * uuid('', true)\n * uuid('user_')\n * uuid('user_', true)\n */\nexport function uuid(prefix: string = '', hyphens: boolean = false): string {\n const crypto = globalThis.crypto\n let value: string\n\n if (typeof crypto?.randomUUID === 'function') {\n value = crypto.randomUUID()\n } else {\n if (typeof crypto?.getRandomValues !== 'function') {\n throw new Error('uuid requires crypto.randomUUID or crypto.getRandomValues')\n }\n\n const bytes = new Uint8Array(16)\n crypto.getRandomValues(bytes)\n // 设置 UUID v4 的版本位和 RFC 变体位,保留其余随机位。\n bytes[6] = (bytes[6]! & 0x0f) | 0x40\n bytes[8] = (bytes[8]! & 0x3f) | 0x80\n const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('')\n value = `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`\n }\n\n return prefix + (hyphens ? value : value.replace(/-/g, ''))\n}\n\n/**\n * 使用加密安全随机数生成 ID;自定义长度的 ID 不遵循 UUID 格式。\n * 字符表依次为 0–9、a–z、A–Z,使用前 radix 个字符。\n *\n * @param length 字符数量,必须为非负安全整数,默认 32;0 返回空字符串。\n * @param radix 基数,必须为 2–62 的整数,默认 16。\n * @example\n * randomId(8, 10)\n * randomId(21, 62)\n */\nexport function randomId(length: number = 32, radix: number = 16): string {\n if (!Number.isSafeInteger(length) || length < 0) {\n throw new RangeError('length must be a non-negative safe integer')\n }\n if (!Number.isInteger(radix) || radix < 2 || radix > ALPHABET.length) {\n throw new RangeError('radix must be an integer between 2 and 62')\n }\n if (length === 0) return ''\n\n const bytes = new Uint8Array(Math.min(length, 65_536))\n const limit = 256 - (256 % radix)\n let value = ''\n\n while (value.length < length) {\n globalThis.crypto.getRandomValues(bytes)\n for (const byte of bytes) {\n // 丢弃无法均分到字符表的尾部区间,避免取模导致分布偏差。\n if (byte >= limit) continue\n value += ALPHABET.charAt(byte % radix)\n if (value.length === length) break\n }\n }\n\n return value\n}\n","/**\n * 在浏览器中下载 URL、完整 Data URL 或 Blob/File。\n *\n * @param source 文件地址或文件内容;字符串始终作为地址处理。\n * @param filename 建议保存的完整文件名,包含扩展名。\n * @returns 触发下载后完成,不代表文件已保存到磁盘。\n * @example\n * await download('/files/report.pdf', 'report.pdf')\n * await download(new Blob(['hello'], { type: 'text/plain' }), 'hello.txt')\n */\nexport async function download(source: string | Blob, filename: string): Promise<void> {\n if (typeof document === 'undefined' || !document.body) {\n throw new Error('download requires a browser environment')\n }\n\n let blob: Blob\n if (typeof source === 'string') {\n const response = await fetch(source)\n if (!response.ok) {\n throw new Error(`Download failed: HTTP ${response.status}`)\n }\n blob = await response.blob()\n } else {\n blob = source\n }\n\n const anchor = document.createElement('a')\n const url = URL.createObjectURL(blob)\n try {\n anchor.href = url\n anchor.download = filename\n anchor.hidden = true\n document.body.append(anchor)\n anchor.click()\n } finally {\n anchor.remove()\n // 留出浏览器开始读取文件的时间,避免同步释放导致下载失败。\n setTimeout(() => URL.revokeObjectURL(url), 1000)\n }\n}\n","export interface DebouncedFunction<This, Args extends unknown[]> {\n (this: This, ...args: Args): void\n cancel(): void\n}\n\n/**\n * 防抖:停止调用 wait 毫秒后,使用最后一次调用的 this 和参数执行回调。\n *\n * @param fn 要延迟执行的回调。\n * @param wait 等待时间,单位毫秒,遵循原生 setTimeout 的时间规则。\n * @returns 带有 cancel() 方法的防抖函数;cancel() 取消尚未执行的回调。\n * @example\n * const search = debounce((keyword: string) => console.log(keyword), 300)\n * search('hello')\n * search.cancel()\n */\nexport function debounce<This, Args extends unknown[]>(\n fn: (this: This, ...args: Args) => unknown,\n wait: number,\n): DebouncedFunction<This, Args> {\n let timer: ReturnType<typeof setTimeout> | undefined\n\n function debounced(this: This, ...args: Args): void {\n if (timer !== undefined) clearTimeout(timer)\n timer = setTimeout(() => {\n timer = undefined\n fn.apply(this, args)\n }, wait)\n }\n\n debounced.cancel = (): void => {\n if (timer !== undefined) clearTimeout(timer)\n timer = undefined\n }\n\n return debounced\n}\n","export interface ThrottledFunction<This, Args extends unknown[]> {\n (this: This, ...args: Args): void\n cancel(): void\n}\n\n/**\n * 节流:首次调用立即执行,间隔内的调用在 wait 毫秒后以最后一次的 this 和参数执行。\n * 持续调用时,每隔 wait 毫秒执行一次;仅调用一次时不会重复执行。\n *\n * @param fn 要限制执行频率的回调。\n * @param wait 执行间隔,单位毫秒,遵循原生 setTimeout 的时间规则。\n * @returns 带有 cancel() 方法的节流函数;cancel() 取消待执行回调并重置间隔。\n * @example\n * const update = throttle((position: number) => console.log(position), 100)\n * update(10)\n * update.cancel()\n */\nexport function throttle<This, Args extends unknown[]>(\n fn: (this: This, ...args: Args) => unknown,\n wait: number,\n): ThrottledFunction<This, Args> {\n let timer: ReturnType<typeof setTimeout> | undefined\n let pending: { thisArg: This; args: Args } | undefined\n\n function onTimeout(): void {\n timer = undefined\n if (!pending) return\n\n const { thisArg, args } = pending\n pending = undefined\n // 在执行用户回调前建立下一轮间隔,支持回调内再次调用或取消。\n timer = setTimeout(onTimeout, wait)\n fn.apply(thisArg, args)\n }\n\n function throttled(this: This, ...args: Args): void {\n if (timer === undefined) {\n timer = setTimeout(onTimeout, wait)\n fn.apply(this, args)\n } else {\n pending = { thisArg: this, args }\n }\n }\n\n throttled.cancel = (): void => {\n if (timer !== undefined) clearTimeout(timer)\n timer = undefined\n pending = undefined\n }\n\n return throttled\n}\n","/** 按 Object.prototype.toString 的标签判断类型,如 'Date'、'Map';区分大小写。 */\nexport function is(value: unknown, type: string): boolean {\n return Object.prototype.toString.call(value) === `[object ${type}]`\n}\n\n/** 仅判断原始字符串,不包含 String 包装对象。 */\nexport function isString(value: unknown): value is string {\n return typeof value === 'string'\n}\n\n/** 判断原始 number,包含 NaN 和 Infinity,不进行数值有效性检查。 */\nexport function isNumber(value: unknown): value is number {\n return typeof value === 'number'\n}\n\n/** 仅判断原始布尔值。 */\nexport function isBoolean(value: unknown): value is boolean {\n return typeof value === 'boolean'\n}\n\nexport function isBigInt(value: unknown): value is bigint {\n return typeof value === 'bigint'\n}\n\nexport function isSymbol(value: unknown): value is symbol {\n return typeof value === 'symbol'\n}\n\nexport function isUndefined(value: unknown): value is undefined {\n return value === undefined\n}\n\n/** 排除 undefined,保留 null。 */\nexport function isDefined<T>(value: T): value is Exclude<T, undefined> {\n return value !== undefined\n}\n\nexport function isNull(value: unknown): value is null {\n return value === null\n}\n\n/** 判断 null 或 undefined。 */\nexport function isNil(value: unknown): value is null | undefined {\n return value === null || value === undefined\n}\n\n/** 判断非 null 的对象,包含数组、内置对象和类实例,不包含函数。 */\nexport function isObject(value: unknown): value is object {\n return value !== null && typeof value === 'object'\n}\n\n/** 判断普通对象,支持对象字面量、跨 realm 对象和 Object.create(null)。 */\nexport function isPlainObject(value: unknown): value is Record<PropertyKey, unknown> {\n if (!is(value, 'Object')) return false\n const prototype = Object.getPrototypeOf(value)\n if (prototype === null) return true\n const constructor = Object.hasOwn(prototype, 'constructor') && prototype.constructor\n return (\n typeof constructor === 'function' &&\n Function.prototype.toString.call(constructor) === Function.prototype.toString.call(Object)\n )\n}\n\n/** 判断 Date 类型,包含 Invalid Date。 */\nexport function isDate(value: unknown): value is Date {\n return is(value, 'Date')\n}\n\nexport function isRegExp(value: unknown): value is RegExp {\n return is(value, 'RegExp')\n}\n\n/** 包含普通函数、异步函数和生成器函数;不检查参数或返回值类型。 */\nexport function isFunction(value: unknown): value is (...args: never[]) => unknown {\n return typeof value === 'function'\n}\n\n/** 仅判断数组,不检查元素类型。 */\nexport function isArray(value: unknown): value is unknown[] {\n return Array.isArray(value)\n}\n\nexport function isMap(value: unknown): value is Map<unknown, unknown> {\n return is(value, 'Map')\n}\n\nexport function isSet(value: unknown): value is Set<unknown> {\n return is(value, 'Set')\n}\n\n/** 判断 Promise 或具有可调用 then 方法的对象/函数,收窄为 PromiseLike。 */\nexport function isPromise(value: unknown): value is PromiseLike<unknown> {\n return (isObject(value) || isFunction(value)) && 'then' in value && isFunction(value.then)\n}\n\n/** 判断可被 JSON.parse 解析的字符串,包含 JSON 原始值。 */\nexport function isJSON(value: unknown): boolean {\n if (!isString(value)) return false\n try {\n JSON.parse(value)\n return true\n } catch {\n return false\n }\n}\n\n/** 判断空字符串、数组、Map、Set 或无自有可枚举属性的普通对象。 */\nexport function isEmpty(value: unknown): boolean {\n if (isString(value) || isArray(value)) return value.length === 0\n if (isMap(value) || isSet(value)) return value.size === 0\n if (isPlainObject(value)) {\n return !Reflect.ownKeys(value).some((key) =>\n Object.prototype.propertyIsEnumerable.call(value, key),\n )\n }\n return false\n}\n\n/** 在 isEmpty 的基础上,将 null、undefined 也视为空。 */\nexport function isAllEmpty(value: unknown): boolean {\n return isNil(value) || isEmpty(value)\n}\n\n/** 判断绝对 HTTP(S) URL,拒绝空白字符、反斜杠和相对地址。 */\nexport function isUrl(value: unknown): boolean {\n if (!isString(value) || !/^https?:\\/\\//i.test(value) || /[\\s\\\\]/.test(value)) return false\n try {\n const url = new URL(value)\n return (url.protocol === 'http:' || url.protocol === 'https:') && url.hostname !== ''\n } catch {\n return false\n }\n}\n\n/** 常用邮箱格式校验,不验证邮箱是否存在,不覆盖完整 RFC 邮箱语法。 */\nexport function isEmail(value: unknown): boolean {\n return (\n isString(value) &&\n /^[a-z\\d!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z\\d!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z\\d](?:[a-z\\d-]*[a-z\\d])?\\.)+[a-z]{2,}$/i.test(\n value,\n )\n )\n}\n\n/** 中国大陆 11 位手机号格式:以 1 开头,第二位为 3–9;不验证号段分配或号码存在性。 */\nexport function isPhone(value: unknown): boolean {\n return isString(value) && /^1[3-9]\\d{9}$/.test(value)\n}\n","function getStorage(): Storage {\n if (typeof window === 'undefined') {\n throw new Error('localStorage helpers require a browser environment')\n }\n return window.localStorage\n}\n\n/** 读取 JSON 值;不存在时返回 null。T 仅提供类型提示,不校验实际数据。 */\nexport function getLocalStorage<T = unknown>(key: string): T | null {\n const value = getStorage().getItem(key)\n return value === null ? null : (JSON.parse(value) as T)\n}\n\n/** 按 JSON.stringify 的规则写入值;无法序列化时抛错,不覆盖已有数据。 */\nexport function setLocalStorage(key: string, value: unknown): void {\n const storage = getStorage()\n const serialized = JSON.stringify(value)\n if (serialized === undefined) {\n throw new TypeError('The value cannot be serialized as JSON')\n }\n storage.setItem(key, serialized)\n}\n\n/** 删除指定键;键不存在时不做任何操作。 */\nexport function removeLocalStorage(key: string): void {\n getStorage().removeItem(key)\n}\n","export interface CookieOptions {\n /** 默认 /。删除 cookie 时需使用与写入时一致的路径。 */\n path?: string\n /** 省略时使用当前主机。 */\n domain?: string\n /** 绝对过期时间;与 maxAge 同时设置时,maxAge 优先。 */\n expires?: Date\n /** 有效期,单位为秒,必须是有限整数。 */\n maxAge?: number\n secure?: boolean\n sameSite?: 'strict' | 'lax' | 'none'\n}\n\nfunction getCookieDocument(): Document {\n if (typeof document === 'undefined') {\n throw new Error('cookie helpers require a browser environment')\n }\n return document\n}\n\n/** 读取并解码 cookie;不存在时返回 null,非法百分号编码保留原值。 */\nexport function getCookie(name: string): string | null {\n const prefix = `${encodeURIComponent(name)}=`\n for (const cookie of getCookieDocument().cookie.split(';')) {\n const entry = cookie.trim()\n if (!entry.startsWith(prefix)) continue\n const value = entry.slice(prefix.length)\n try {\n return decodeURIComponent(value)\n } catch {\n return value\n }\n }\n return null\n}\n\n/** 编码并写入字符串 cookie;省略有效期时创建会话 cookie。 */\nexport function setCookie(name: string, value: string, options: CookieOptions = {}): void {\n const cookieDocument = getCookieDocument()\n const { path = '/', domain, expires, maxAge, secure, sameSite } = options\n const attributes = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`]\n // 路径和域名直接用于属性值,禁止分隔符与控制字符注入其他属性。\n for (const attribute of [path, domain]) {\n // oxlint-disable-next-line no-control-regex -- 此处用于拒绝控制字符。\n if (attribute !== undefined && /[;\\u0000-\\u001f\\u007f]/.test(attribute)) {\n throw new TypeError('Cookie path and domain cannot contain semicolons or control characters')\n }\n }\n attributes.push(`Path=${path}`)\n if (domain !== undefined) attributes.push(`Domain=${domain}`)\n if (expires !== undefined) {\n if (!Number.isFinite(expires.getTime())) throw new TypeError('Invalid cookie expiration date')\n attributes.push(`Expires=${expires.toUTCString()}`)\n }\n if (maxAge !== undefined) {\n if (!Number.isInteger(maxAge)) throw new TypeError('Cookie maxAge must be a finite integer')\n attributes.push(`Max-Age=${maxAge}`)\n }\n if (secure) attributes.push('Secure')\n if (sameSite !== undefined) attributes.push(`SameSite=${sameSite}`)\n cookieDocument.cookie = attributes.join('; ')\n}\n\n/** 通过 Max-Age=0 删除 cookie;path 和 domain 需与写入时一致。 */\nexport function removeCookie(\n name: string,\n options: Pick<CookieOptions, 'path' | 'domain'> = {},\n): void {\n setCookie(name, '', { ...options, maxAge: 0 })\n}\n","export type QueryValue = string | number | boolean\n\nexport type QueryParams = Record<string, QueryValue | readonly QueryValue[] | null | undefined>\n\nexport type ParsedQuery = Record<string, string | string[]>\n\nfunction splitUrl(url: string): { path: string; query: string; hash: string } {\n const hashIndex = url.indexOf('#')\n const hash = hashIndex < 0 ? '' : url.slice(hashIndex)\n const beforeHash = hashIndex < 0 ? url : url.slice(0, hashIndex)\n const queryIndex = beforeHash.indexOf('?')\n return {\n path: queryIndex < 0 ? beforeHash : beforeHash.slice(0, queryIndex),\n query: queryIndex < 0 ? '' : beforeHash.slice(queryIndex),\n hash,\n }\n}\n\nfunction appendQueryValue(\n search: URLSearchParams,\n name: string,\n value: QueryValue | readonly QueryValue[],\n): void {\n for (const item of Array.isArray(value) ? value : [value]) {\n search.append(name, String(item))\n }\n}\n\n/** 解析查询串(可带前导 ?);重复键返回数组,所有值保持字符串。 */\nexport function parseQuery(query: string): ParsedQuery {\n const entries = new Map<string, string | string[]>()\n for (const [name, value] of new URLSearchParams(query)) {\n const previous = entries.get(name)\n if (previous === undefined) entries.set(name, value)\n else if (typeof previous === 'string') entries.set(name, [previous, value])\n else previous.push(value)\n }\n return Object.fromEntries(entries)\n}\n\n/** 序列化为不带 ? 的查询串;数组使用重复键,省略 null、undefined 和空数组。 */\nexport function stringifyQuery(params: QueryParams): string {\n const search = new URLSearchParams()\n for (const [name, value] of Object.entries(params)) {\n if (value !== null && value !== undefined) appendQueryValue(search, name, value)\n }\n return search.toString()\n}\n\n/** 读取 URL 中第一个同名查询参数;不存在时返回 null,不读取 hash 内的参数。 */\nexport function getQueryParam(url: string, name: string): string | null {\n return new URLSearchParams(splitUrl(url).query).get(name)\n}\n\n/** 读取 URL 的全部查询参数,重复键返回数组。 */\nexport function getQueryParams(url: string): ParsedQuery {\n return parseQuery(splitUrl(url).query)\n}\n\n/** 合并查询参数并返回新 URL;null 或空数组删除该键,undefined 保留原值。 */\nexport function setQueryParams(url: string, params: QueryParams): string {\n const { path, query, hash } = splitUrl(url)\n const search = new URLSearchParams(query)\n for (const [name, value] of Object.entries(params)) {\n if (value === undefined) continue\n search.delete(name)\n if (value !== null) appendQueryValue(search, name, value)\n }\n const serialized = search.toString()\n return path + (serialized ? `?${serialized}` : '') + hash\n}\n\n/** 删除一个或多个查询参数的全部同名项,返回新 URL。 */\nexport function removeQueryParams(url: string, names: string | readonly string[]): string {\n const params = Object.fromEntries(\n (typeof names === 'string' ? [names] : names).map((name) => [name, null]),\n )\n return setQueryParams(url, params)\n}\n\n/** 获取第一个 # 后的原始 hash,不含 #,不解码;不存在时返回空字符串。 */\nexport function getUrlHash(url: string): string {\n return splitUrl(url).hash.slice(1)\n}\n\n/** 设置原始 hash(可带一个前导 #),空字符串移除 hash;不进行编码。 */\nexport function setUrlHash(url: string, hash: string): string {\n const hashIndex = url.indexOf('#')\n const beforeHash = hashIndex < 0 ? url : url.slice(0, hashIndex)\n const value = hash.startsWith('#') ? hash.slice(1) : hash\n return beforeHash + (value ? `#${value}` : '')\n}\n","export type TreeId = string | number\n\n/** 保留业务字段,并将指定子节点字段替换为递归节点数组。 */\nexport type TreeNode<T, ChildrenKey extends string = 'children'> = Omit<T, ChildrenKey> & {\n [Key in ChildrenKey]: TreeNode<T, ChildrenKey>[]\n}\n\nexport interface TreeOptions<ChildrenKey extends string = 'children'> {\n childrenKey?: ChildrenKey\n}\n\nexport interface ListToTreeOptions<\n IdKey extends string = 'id',\n ParentIdKey extends string = 'parentId',\n ChildrenKey extends string = 'children',\n> extends TreeOptions<ChildrenKey> {\n idKey?: IdKey\n parentIdKey?: ParentIdKey\n}\n\nfunction* walkTree<T extends object>(\n tree: readonly T[],\n childrenKey: string,\n): Generator<{ node: T; path: readonly T[] }> {\n const stack = [tree[Symbol.iterator]()]\n const path: T[] = []\n\n while (stack.length > 0) {\n const iterator = stack[stack.length - 1]\n if (!iterator) break\n const result = iterator.next()\n if (result.done) {\n stack.pop()\n path.pop()\n continue\n }\n\n const node = result.value\n path.push(node)\n yield { node, path }\n const children = (node as Record<string, readonly T[] | null | undefined>)[childrenKey]\n if (children?.length) stack.push(children[Symbol.iterator]())\n else path.pop()\n }\n}\n\n/** 按深度优先前序展开树;浅拷贝节点并移除子节点字段,不补充 parentId。 */\nexport function treeToList<T extends object, ChildrenKey extends string = 'children'>(\n tree: readonly T[],\n options: TreeOptions<ChildrenKey> = {},\n): Omit<T, ChildrenKey>[] {\n const childrenKey = options.childrenKey ?? 'children'\n const list: Omit<T, ChildrenKey>[] = []\n for (const { node } of walkTree(tree, childrenKey)) {\n const item = { ...node }\n delete (item as Record<string, unknown>)[childrenKey]\n list.push(item)\n }\n return list\n}\n\n/** 列表转树;父 ID 为空或不存在时作为根节点,重复 ID 和循环关系抛错。 */\nexport function listToTree<\n T extends Record<IdKey, TreeId> & Partial<Record<ParentIdKey, TreeId | null | undefined>>,\n IdKey extends string = 'id',\n ParentIdKey extends string = 'parentId',\n ChildrenKey extends string = 'children',\n>(\n list: readonly T[],\n options: ListToTreeOptions<IdKey, ParentIdKey, ChildrenKey> = {},\n): TreeNode<T, ChildrenKey>[] {\n const idKey = options.idKey ?? ('id' as IdKey)\n const parentIdKey = options.parentIdKey ?? ('parentId' as ParentIdKey)\n const childrenKey = options.childrenKey ?? 'children'\n const nodes = new Map<\n TreeId,\n {\n node: TreeNode<T, ChildrenKey>\n children: TreeNode<T, ChildrenKey>[]\n parentId: TreeId | null | undefined\n }\n >()\n\n for (const item of list) {\n const id = item[idKey]\n if (nodes.has(id)) throw new Error(`Duplicate tree node ID: ${id}`)\n const children: TreeNode<T, ChildrenKey>[] = []\n const node = { ...item, [childrenKey]: children } as unknown as TreeNode<T, ChildrenKey>\n nodes.set(id, { node, children, parentId: item[parentIdKey] })\n }\n\n const roots: TreeNode<T, ChildrenKey>[] = []\n const visited = new Set<TreeId>()\n for (const [id, entry] of nodes) {\n // 每条父链只检查一次,避免深层列表退化为平方复杂度。\n const path = new Set<TreeId>()\n let current: TreeId | null | undefined = id\n while (current !== null && current !== undefined && !visited.has(current)) {\n if (path.has(current)) throw new Error(`Cycle detected in tree at ID: ${current}`)\n path.add(current)\n current = nodes.get(current)?.parentId\n }\n for (const ancestorId of path) visited.add(ancestorId)\n\n const parent = entry.parentId == null ? undefined : nodes.get(entry.parentId)\n if (parent) parent.children.push(entry.node)\n else roots.push(entry.node)\n }\n return roots\n}\n\n/** 按深度优先前序查找首个匹配节点,返回原引用;未找到时返回 undefined。 */\nexport function findTreeNode<T extends object, ChildrenKey extends string = 'children'>(\n tree: readonly T[],\n predicate: (node: T) => boolean,\n options: TreeOptions<ChildrenKey> = {},\n): T | undefined {\n for (const { node } of walkTree(tree, options.childrenKey ?? 'children')) {\n if (predicate(node)) return node\n }\n return undefined\n}\n\n/** 查找首个匹配节点从根到自身的路径,保留节点引用;未找到时返回 undefined。 */\nexport function findTreePath<T extends object, ChildrenKey extends string = 'children'>(\n tree: readonly T[],\n predicate: (node: T) => boolean,\n options: TreeOptions<ChildrenKey> = {},\n): T[] | undefined {\n for (const { node, path } of walkTree(tree, options.childrenKey ?? 'children')) {\n if (predicate(node)) return [...path]\n }\n return undefined\n}\n"],"mappings":";AAAA,MAAM,WAAW;;;;;;;;;;;;AAajB,SAAgB,KAAK,SAAiB,IAAI,UAAmB,OAAe;CAC1E,MAAM,SAAS,WAAW;CAC1B,IAAI;CAEJ,IAAI,OAAO,QAAQ,eAAe,YAChC,QAAQ,OAAO,WAAW;MACrB;EACL,IAAI,OAAO,QAAQ,oBAAoB,YACrC,MAAM,IAAI,MAAM,2DAA2D;EAG7E,MAAM,wBAAQ,IAAI,WAAW,EAAE;EAC/B,OAAO,gBAAgB,KAAK;EAE5B,MAAM,KAAM,MAAM,KAAM,KAAQ;EAChC,MAAM,KAAM,MAAM,KAAM,KAAQ;EAChC,MAAM,MAAM,MAAM,KAAK,QAAQ,SAAS,KAAK,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE;EACnF,QAAQ,GAAG,IAAI,MAAM,GAAG,CAAC,EAAE,GAAG,IAAI,MAAM,GAAG,EAAE,EAAE,GAAG,IAAI,MAAM,IAAI,EAAE,EAAE,GAAG,IAAI,MAAM,IAAI,EAAE,EAAE,GAAG,IAAI,MAAM,EAAE;CAC1G;CAEA,OAAO,UAAU,UAAU,QAAQ,MAAM,QAAQ,MAAM,EAAE;AAC3D;;;;;;;;;;;AAYA,SAAgB,SAAS,SAAiB,IAAI,QAAgB,IAAY;CACxE,IAAI,CAAC,OAAO,cAAc,MAAM,KAAK,SAAS,GAC5C,MAAM,IAAI,WAAW,4CAA4C;CAEnE,IAAI,CAAC,OAAO,UAAU,KAAK,KAAK,QAAQ,KAAK,QAAQ,IACnD,MAAM,IAAI,WAAW,2CAA2C;CAElE,IAAI,WAAW,GAAG,OAAO;CAEzB,MAAM,QAAQ,IAAI,WAAW,KAAK,IAAI,QAAQ,KAAM,CAAC;CACrD,MAAM,QAAQ,MAAO,MAAM;CAC3B,IAAI,QAAQ;CAEZ,OAAO,MAAM,SAAS,QAAQ;EAC5B,WAAW,OAAO,gBAAgB,KAAK;EACvC,KAAK,MAAM,QAAQ,OAAO;GAExB,IAAI,QAAQ,OAAO;GACnB,SAAS,SAAS,OAAO,OAAO,KAAK;GACrC,IAAI,MAAM,WAAW,QAAQ;EAC/B;CACF;CAEA,OAAO;AACT;;;;;;;;;;;;AC5DA,eAAsB,SAAS,QAAuB,UAAiC;CACrF,IAAI,OAAO,aAAa,eAAe,CAAC,SAAS,MAC/C,MAAM,IAAI,MAAM,yCAAyC;CAG3D,IAAI;CACJ,IAAI,OAAO,WAAW,UAAU;EAC9B,MAAM,WAAW,MAAM,MAAM,MAAM;EACnC,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MAAM,yBAAyB,SAAS,QAAQ;EAE5D,OAAO,MAAM,SAAS,KAAK;CAC7B,OACE,OAAO;CAGT,MAAM,SAAS,SAAS,cAAc,GAAG;CACzC,MAAM,MAAM,IAAI,gBAAgB,IAAI;CACpC,IAAI;EACF,OAAO,OAAO;EACd,OAAO,WAAW;EAClB,OAAO,SAAS;EAChB,SAAS,KAAK,OAAO,MAAM;EAC3B,OAAO,MAAM;CACf,UAAU;EACR,OAAO,OAAO;EAEd,iBAAiB,IAAI,gBAAgB,GAAG,GAAG,GAAI;CACjD;AACF;;;;;;;;;;;;;ACvBA,SAAgB,SACd,IACA,MAC+B;CAC/B,IAAI;CAEJ,SAAS,UAAsB,GAAG,MAAkB;EAClD,IAAI,UAAU,KAAA,GAAW,aAAa,KAAK;EAC3C,QAAQ,iBAAiB;GACvB,QAAQ,KAAA;GACR,GAAG,MAAM,MAAM,IAAI;EACrB,GAAG,IAAI;CACT;CAEA,UAAU,eAAqB;EAC7B,IAAI,UAAU,KAAA,GAAW,aAAa,KAAK;EAC3C,QAAQ,KAAA;CACV;CAEA,OAAO;AACT;;;;;;;;;;;;;;ACnBA,SAAgB,SACd,IACA,MAC+B;CAC/B,IAAI;CACJ,IAAI;CAEJ,SAAS,YAAkB;EACzB,QAAQ,KAAA;EACR,IAAI,CAAC,SAAS;EAEd,MAAM,EAAE,SAAS,SAAS;EAC1B,UAAU,KAAA;EAEV,QAAQ,WAAW,WAAW,IAAI;EAClC,GAAG,MAAM,SAAS,IAAI;CACxB;CAEA,SAAS,UAAsB,GAAG,MAAkB;EAClD,IAAI,UAAU,KAAA,GAAW;GACvB,QAAQ,WAAW,WAAW,IAAI;GAClC,GAAG,MAAM,MAAM,IAAI;EACrB,OACE,UAAU;GAAE,SAAS;GAAM;EAAK;CAEpC;CAEA,UAAU,eAAqB;EAC7B,IAAI,UAAU,KAAA,GAAW,aAAa,KAAK;EAC3C,QAAQ,KAAA;EACR,UAAU,KAAA;CACZ;CAEA,OAAO;AACT;;;AClDA,SAAgB,GAAG,OAAgB,MAAuB;CACxD,OAAO,OAAO,UAAU,SAAS,KAAK,KAAK,MAAM,WAAW,KAAK;AACnE;;AAGA,SAAgB,SAAS,OAAiC;CACxD,OAAO,OAAO,UAAU;AAC1B;;AAGA,SAAgB,SAAS,OAAiC;CACxD,OAAO,OAAO,UAAU;AAC1B;;AAGA,SAAgB,UAAU,OAAkC;CAC1D,OAAO,OAAO,UAAU;AAC1B;AAEA,SAAgB,SAAS,OAAiC;CACxD,OAAO,OAAO,UAAU;AAC1B;AAEA,SAAgB,SAAS,OAAiC;CACxD,OAAO,OAAO,UAAU;AAC1B;AAEA,SAAgB,YAAY,OAAoC;CAC9D,OAAO,UAAU,KAAA;AACnB;;AAGA,SAAgB,UAAa,OAA0C;CACrE,OAAO,UAAU,KAAA;AACnB;AAEA,SAAgB,OAAO,OAA+B;CACpD,OAAO,UAAU;AACnB;;AAGA,SAAgB,MAAM,OAA2C;CAC/D,OAAO,UAAU,QAAQ,UAAU,KAAA;AACrC;;AAGA,SAAgB,SAAS,OAAiC;CACxD,OAAO,UAAU,QAAQ,OAAO,UAAU;AAC5C;;AAGA,SAAgB,cAAc,OAAuD;CACnF,IAAI,CAAC,GAAG,OAAO,QAAQ,GAAG,OAAO;CACjC,MAAM,YAAY,OAAO,eAAe,KAAK;CAC7C,IAAI,cAAc,MAAM,OAAO;CAC/B,MAAM,cAAc,OAAO,OAAO,WAAW,aAAa,KAAK,UAAU;CACzE,OACE,OAAO,gBAAgB,cACvB,SAAS,UAAU,SAAS,KAAK,WAAW,MAAM,SAAS,UAAU,SAAS,KAAK,MAAM;AAE7F;;AAGA,SAAgB,OAAO,OAA+B;CACpD,OAAO,GAAG,OAAO,MAAM;AACzB;AAEA,SAAgB,SAAS,OAAiC;CACxD,OAAO,GAAG,OAAO,QAAQ;AAC3B;;AAGA,SAAgB,WAAW,OAAwD;CACjF,OAAO,OAAO,UAAU;AAC1B;;AAGA,SAAgB,QAAQ,OAAoC;CAC1D,OAAO,MAAM,QAAQ,KAAK;AAC5B;AAEA,SAAgB,MAAM,OAAgD;CACpE,OAAO,GAAG,OAAO,KAAK;AACxB;AAEA,SAAgB,MAAM,OAAuC;CAC3D,OAAO,GAAG,OAAO,KAAK;AACxB;;AAGA,SAAgB,UAAU,OAA+C;CACvE,QAAQ,SAAS,KAAK,KAAK,WAAW,KAAK,MAAM,UAAU,SAAS,WAAW,MAAM,IAAI;AAC3F;;AAGA,SAAgB,OAAO,OAAyB;CAC9C,IAAI,CAAC,SAAS,KAAK,GAAG,OAAO;CAC7B,IAAI;EACF,KAAK,MAAM,KAAK;EAChB,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;;AAGA,SAAgB,QAAQ,OAAyB;CAC/C,IAAI,SAAS,KAAK,KAAK,QAAQ,KAAK,GAAG,OAAO,MAAM,WAAW;CAC/D,IAAI,MAAM,KAAK,KAAK,MAAM,KAAK,GAAG,OAAO,MAAM,SAAS;CACxD,IAAI,cAAc,KAAK,GACrB,OAAO,CAAC,QAAQ,QAAQ,KAAK,CAAC,CAAC,MAAM,QACnC,OAAO,UAAU,qBAAqB,KAAK,OAAO,GAAG,CACvD;CAEF,OAAO;AACT;;AAGA,SAAgB,WAAW,OAAyB;CAClD,OAAO,MAAM,KAAK,KAAK,QAAQ,KAAK;AACtC;;AAGA,SAAgB,MAAM,OAAyB;CAC7C,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,gBAAgB,KAAK,KAAK,KAAK,SAAS,KAAK,KAAK,GAAG,OAAO;CACrF,IAAI;EACF,MAAM,MAAM,IAAI,IAAI,KAAK;EACzB,QAAQ,IAAI,aAAa,WAAW,IAAI,aAAa,aAAa,IAAI,aAAa;CACrF,QAAQ;EACN,OAAO;CACT;AACF;;AAGA,SAAgB,QAAQ,OAAyB;CAC/C,OACE,SAAS,KAAK,KACd,gHAAgH,KAC9G,KACF;AAEJ;;AAGA,SAAgB,QAAQ,OAAyB;CAC/C,OAAO,SAAS,KAAK,KAAK,gBAAgB,KAAK,KAAK;AACtD;;ACnJA,SAAS,aAAsB;CAC7B,IAAI,OAAO,WAAW,aACpB,MAAM,IAAI,MAAM,oDAAoD;CAEtE,OAAO,OAAO;AAChB;;AAGA,SAAgB,gBAA6B,KAAuB;CAClE,MAAM,QAAQ,WAAW,CAAC,CAAC,QAAQ,GAAG;CACtC,OAAO,UAAU,OAAO,OAAQ,KAAK,MAAM,KAAK;AAClD;;AAGA,SAAgB,gBAAgB,KAAa,OAAsB;CACjE,MAAM,UAAU,WAAW;CAC3B,MAAM,aAAa,KAAK,UAAU,KAAK;CACvC,IAAI,eAAe,KAAA,GACjB,MAAM,IAAI,UAAU,wCAAwC;CAE9D,QAAQ,QAAQ,KAAK,UAAU;AACjC;;AAGA,SAAgB,mBAAmB,KAAmB;CACpD,WAAW,CAAC,CAAC,WAAW,GAAG;AAC7B;;ACbA,SAAS,oBAA8B;CACrC,IAAI,OAAO,aAAa,aACtB,MAAM,IAAI,MAAM,8CAA8C;CAEhE,OAAO;AACT;;AAGA,SAAgB,UAAU,MAA6B;CACrD,MAAM,SAAS,GAAG,mBAAmB,IAAI,EAAE;CAC3C,KAAK,MAAM,UAAU,kBAAkB,CAAC,CAAC,OAAO,MAAM,GAAG,GAAG;EAC1D,MAAM,QAAQ,OAAO,KAAK;EAC1B,IAAI,CAAC,MAAM,WAAW,MAAM,GAAG;EAC/B,MAAM,QAAQ,MAAM,MAAM,OAAO,MAAM;EACvC,IAAI;GACF,OAAO,mBAAmB,KAAK;EACjC,QAAQ;GACN,OAAO;EACT;CACF;CACA,OAAO;AACT;;AAGA,SAAgB,UAAU,MAAc,OAAe,UAAyB,CAAC,GAAS;CACxF,MAAM,iBAAiB,kBAAkB;CACzC,MAAM,EAAE,OAAO,KAAK,QAAQ,SAAS,QAAQ,QAAQ,aAAa;CAClE,MAAM,aAAa,CAAC,GAAG,mBAAmB,IAAI,EAAE,GAAG,mBAAmB,KAAK,GAAG;CAE9E,KAAK,MAAM,aAAa,CAAC,MAAM,MAAM,GAEnC,IAAI,cAAc,KAAA,KAAa,yBAAyB,KAAK,SAAS,GACpE,MAAM,IAAI,UAAU,wEAAwE;CAGhG,WAAW,KAAK,QAAQ,MAAM;CAC9B,IAAI,WAAW,KAAA,GAAW,WAAW,KAAK,UAAU,QAAQ;CAC5D,IAAI,YAAY,KAAA,GAAW;EACzB,IAAI,CAAC,OAAO,SAAS,QAAQ,QAAQ,CAAC,GAAG,MAAM,IAAI,UAAU,gCAAgC;EAC7F,WAAW,KAAK,WAAW,QAAQ,YAAY,GAAG;CACpD;CACA,IAAI,WAAW,KAAA,GAAW;EACxB,IAAI,CAAC,OAAO,UAAU,MAAM,GAAG,MAAM,IAAI,UAAU,wCAAwC;EAC3F,WAAW,KAAK,WAAW,QAAQ;CACrC;CACA,IAAI,QAAQ,WAAW,KAAK,QAAQ;CACpC,IAAI,aAAa,KAAA,GAAW,WAAW,KAAK,YAAY,UAAU;CAClE,eAAe,SAAS,WAAW,KAAK,IAAI;AAC9C;;AAGA,SAAgB,aACd,MACA,UAAkD,CAAC,GAC7C;CACN,UAAU,MAAM,IAAI;EAAE,GAAG;EAAS,QAAQ;CAAE,CAAC;AAC/C;;AC/DA,SAAS,SAAS,KAA4D;CAC5E,MAAM,YAAY,IAAI,QAAQ,GAAG;CACjC,MAAM,OAAO,YAAY,IAAI,KAAK,IAAI,MAAM,SAAS;CACrD,MAAM,aAAa,YAAY,IAAI,MAAM,IAAI,MAAM,GAAG,SAAS;CAC/D,MAAM,aAAa,WAAW,QAAQ,GAAG;CACzC,OAAO;EACL,MAAM,aAAa,IAAI,aAAa,WAAW,MAAM,GAAG,UAAU;EAClE,OAAO,aAAa,IAAI,KAAK,WAAW,MAAM,UAAU;EACxD;CACF;AACF;AAEA,SAAS,iBACP,QACA,MACA,OACM;CACN,KAAK,MAAM,QAAQ,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK,GACtD,OAAO,OAAO,MAAM,OAAO,IAAI,CAAC;AAEpC;;AAGA,SAAgB,WAAW,OAA4B;CACrD,MAAM,0BAAU,IAAI,IAA+B;CACnD,KAAK,MAAM,CAAC,MAAM,UAAU,IAAI,gBAAgB,KAAK,GAAG;EACtD,MAAM,WAAW,QAAQ,IAAI,IAAI;EACjC,IAAI,aAAa,KAAA,GAAW,QAAQ,IAAI,MAAM,KAAK;OAC9C,IAAI,OAAO,aAAa,UAAU,QAAQ,IAAI,MAAM,CAAC,UAAU,KAAK,CAAC;OACrE,SAAS,KAAK,KAAK;CAC1B;CACA,OAAO,OAAO,YAAY,OAAO;AACnC;;AAGA,SAAgB,eAAe,QAA6B;CAC1D,MAAM,SAAS,IAAI,gBAAgB;CACnC,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,MAAM,GAC/C,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,iBAAiB,QAAQ,MAAM,KAAK;CAEjF,OAAO,OAAO,SAAS;AACzB;;AAGA,SAAgB,cAAc,KAAa,MAA6B;CACtE,OAAO,IAAI,gBAAgB,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,IAAI;AAC1D;;AAGA,SAAgB,eAAe,KAA0B;CACvD,OAAO,WAAW,SAAS,GAAG,CAAC,CAAC,KAAK;AACvC;;AAGA,SAAgB,eAAe,KAAa,QAA6B;CACvE,MAAM,EAAE,MAAM,OAAO,SAAS,SAAS,GAAG;CAC1C,MAAM,SAAS,IAAI,gBAAgB,KAAK;CACxC,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,MAAM,GAAG;EAClD,IAAI,UAAU,KAAA,GAAW;EACzB,OAAO,OAAO,IAAI;EAClB,IAAI,UAAU,MAAM,iBAAiB,QAAQ,MAAM,KAAK;CAC1D;CACA,MAAM,aAAa,OAAO,SAAS;CACnC,OAAO,QAAQ,aAAa,IAAI,eAAe,MAAM;AACvD;;AAGA,SAAgB,kBAAkB,KAAa,OAA2C;CAIxF,OAAO,eAAe,KAHP,OAAO,aACnB,OAAO,UAAU,WAAW,CAAC,KAAK,IAAI,MAAA,CAAO,KAAK,SAAS,CAAC,MAAM,IAAI,CAAC,CAE1C,CAAC;AACnC;;AAGA,SAAgB,WAAW,KAAqB;CAC9C,OAAO,SAAS,GAAG,CAAC,CAAC,KAAK,MAAM,CAAC;AACnC;;AAGA,SAAgB,WAAW,KAAa,MAAsB;CAC5D,MAAM,YAAY,IAAI,QAAQ,GAAG;CACjC,MAAM,aAAa,YAAY,IAAI,MAAM,IAAI,MAAM,GAAG,SAAS;CAC/D,MAAM,QAAQ,KAAK,WAAW,GAAG,IAAI,KAAK,MAAM,CAAC,IAAI;CACrD,OAAO,cAAc,QAAQ,IAAI,UAAU;AAC7C;;ACvEA,UAAU,SACR,MACA,aAC4C;CAC5C,MAAM,QAAQ,CAAC,KAAK,OAAO,SAAS,CAAC,CAAC;CACtC,MAAM,OAAY,CAAC;CAEnB,OAAO,MAAM,SAAS,GAAG;EACvB,MAAM,WAAW,MAAM,MAAM,SAAS;EACtC,IAAI,CAAC,UAAU;EACf,MAAM,SAAS,SAAS,KAAK;EAC7B,IAAI,OAAO,MAAM;GACf,MAAM,IAAI;GACV,KAAK,IAAI;GACT;EACF;EAEA,MAAM,OAAO,OAAO;EACpB,KAAK,KAAK,IAAI;EACd,MAAM;GAAE;GAAM;EAAK;EACnB,MAAM,WAAY,KAAyD;EAC3E,IAAI,UAAU,QAAQ,MAAM,KAAK,SAAS,OAAO,SAAS,CAAC,CAAC;OACvD,KAAK,IAAI;CAChB;AACF;;AAGA,SAAgB,WACd,MACA,UAAoC,CAAC,GACb;CACxB,MAAM,cAAc,QAAQ,eAAe;CAC3C,MAAM,OAA+B,CAAC;CACtC,KAAK,MAAM,EAAE,UAAU,SAAS,MAAM,WAAW,GAAG;EAClD,MAAM,OAAO,EAAE,GAAG,KAAK;EACvB,OAAQ,KAAiC;EACzC,KAAK,KAAK,IAAI;CAChB;CACA,OAAO;AACT;;AAGA,SAAgB,WAMd,MACA,UAA8D,CAAC,GACnC;CAC5B,MAAM,QAAQ,QAAQ,SAAU;CAChC,MAAM,cAAc,QAAQ,eAAgB;CAC5C,MAAM,cAAc,QAAQ,eAAe;CAC3C,MAAM,wBAAQ,IAAI,IAOhB;CAEF,KAAK,MAAM,QAAQ,MAAM;EACvB,MAAM,KAAK,KAAK;EAChB,IAAI,MAAM,IAAI,EAAE,GAAG,MAAM,IAAI,MAAM,2BAA2B,IAAI;EAClE,MAAM,WAAuC,CAAC;EAC9C,MAAM,OAAO;GAAE,GAAG;IAAO,cAAc;EAAS;EAChD,MAAM,IAAI,IAAI;GAAE;GAAM;GAAU,UAAU,KAAK;EAAa,CAAC;CAC/D;CAEA,MAAM,QAAoC,CAAC;CAC3C,MAAM,0BAAU,IAAI,IAAY;CAChC,KAAK,MAAM,CAAC,IAAI,UAAU,OAAO;EAE/B,MAAM,uBAAO,IAAI,IAAY;EAC7B,IAAI,UAAqC;EACzC,OAAO,YAAY,QAAQ,YAAY,KAAA,KAAa,CAAC,QAAQ,IAAI,OAAO,GAAG;GACzE,IAAI,KAAK,IAAI,OAAO,GAAG,MAAM,IAAI,MAAM,iCAAiC,SAAS;GACjF,KAAK,IAAI,OAAO;GAChB,UAAU,MAAM,IAAI,OAAO,CAAC,EAAE;EAChC;EACA,KAAK,MAAM,cAAc,MAAM,QAAQ,IAAI,UAAU;EAErD,MAAM,SAAS,MAAM,YAAY,OAAO,KAAA,IAAY,MAAM,IAAI,MAAM,QAAQ;EAC5E,IAAI,QAAQ,OAAO,SAAS,KAAK,MAAM,IAAI;OACtC,MAAM,KAAK,MAAM,IAAI;CAC5B;CACA,OAAO;AACT;;AAGA,SAAgB,aACd,MACA,WACA,UAAoC,CAAC,GACtB;CACf,KAAK,MAAM,EAAE,UAAU,SAAS,MAAM,QAAQ,eAAe,UAAU,GACrE,IAAI,UAAU,IAAI,GAAG,OAAO;AAGhC;;AAGA,SAAgB,aACd,MACA,WACA,UAAoC,CAAC,GACpB;CACjB,KAAK,MAAM,EAAE,MAAM,UAAU,SAAS,MAAM,QAAQ,eAAe,UAAU,GAC3E,IAAI,UAAU,IAAI,GAAG,OAAO,CAAC,GAAG,IAAI;AAGxC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xxkatt/utils",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "常用的 JavaScript / TypeScript 工具函数",
5
5
  "license": "MIT",
6
6
  "repository": {