@yh-ui/utils 0.1.17 → 0.1.21

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 ADDED
@@ -0,0 +1,226 @@
1
+ # @yh-ui/utils
2
+
3
+ <p align="center">
4
+ <img src="https://raw.githubusercontent.com/1079161148/yh-ui/main/docs/public/logo.svg" width="100" height="100" alt="YH-UI Logo">
5
+ </p>
6
+
7
+ <h3 align="center">YH-UI 工具函数库</h3>
8
+
9
+ <p align="center">
10
+ YH-UI 组件库的基础工具集。高性能、零依赖、完整类型,可独立用于任意 Vue 3 项目。
11
+ </p>
12
+
13
+ <p align="center">
14
+ <a href="https://www.npmjs.com/package/@yh-ui/utils">
15
+ <img src="https://img.shields.io/npm/v/@yh-ui/utils.svg?style=flat-square&colorB=409eff" alt="npm version">
16
+ </a>
17
+ <a href="https://www.npmjs.com/package/@yh-ui/utils">
18
+ <img src="https://img.shields.io/npm/dm/@yh-ui/utils.svg?style=flat-square&colorB=409eff" alt="npm downloads">
19
+ </a>
20
+ <a href="https://github.com/1079161148/yh-ui/blob/main/LICENSE">
21
+ <img src="https://img.shields.io/npm/l/@yh-ui/utils.svg?style=flat-square" alt="license">
22
+ </a>
23
+ </p>
24
+
25
+ ---
26
+
27
+ ## ✨ 特性
28
+
29
+ - 🔒 **完整 TypeScript 类型** — 所有函数均有精确类型定义,无 `any`
30
+ - 🌿 **Tree-shaking 友好** — 每个工具函数均可单独导入
31
+ - 🌐 **SSR 安全** — 所有涉及 DOM/BOM 的操作均有服务端安全防护
32
+ - 📦 **零运行时依赖** — 不引入任何第三方库
33
+ - ⚡ **高性能** — 广泛使用记忆化、惰性求值等优化手段
34
+
35
+ ---
36
+
37
+ ## 📦 安装
38
+
39
+ ```bash
40
+ # pnpm(推荐)
41
+ pnpm add @yh-ui/utils
42
+
43
+ # npm
44
+ npm install @yh-ui/utils
45
+ ```
46
+
47
+ > **注意**:`@yh-ui/utils` 是 `@yh-ui/yh-ui` 的基础子包,若已安装主包则无需单独安装。
48
+
49
+ ---
50
+
51
+ ## 🔨 使用
52
+
53
+ ### 完整导入
54
+
55
+ ```ts
56
+ import { isString, debounce, deepClone } from '@yh-ui/utils'
57
+ ```
58
+
59
+ ### 按需导入
60
+
61
+ ```ts
62
+ import { isString } from '@yh-ui/utils/is'
63
+ import { debounce } from '@yh-ui/utils/function'
64
+ import { deepClone } from '@yh-ui/utils/object'
65
+ ```
66
+
67
+ ---
68
+
69
+ ## 📚 工具函数分类
70
+
71
+ ### 🔍 类型判断(`is`)
72
+
73
+ ```ts
74
+ import {
75
+ isString,
76
+ isNumber,
77
+ isBoolean,
78
+ isFunction,
79
+ isArray,
80
+ isObject,
81
+ isDate,
82
+ isRegExp,
83
+ isPromise,
84
+ isSymbol,
85
+ isNil,
86
+ isDef,
87
+ isClient,
88
+ isServer,
89
+ isEmpty
90
+ } from '@yh-ui/utils'
91
+
92
+ isString('hello') // true
93
+ isArray([1, 2]) // true
94
+ isClient // 是否在浏览器环境
95
+ isNil(null) // true(null 或 undefined)
96
+ isEmpty({}) // true
97
+ isEmpty([]) // true
98
+ isEmpty('') // true
99
+ ```
100
+
101
+ ### ⏱ 函数工具(`function`)
102
+
103
+ ```ts
104
+ import { debounce, throttle, once, memoize } from '@yh-ui/utils'
105
+
106
+ // 防抖:300ms 内只执行一次
107
+ const handleInput = debounce((val: string) => fetchSearch(val), 300)
108
+
109
+ // 节流:每 200ms 最多执行一次
110
+ const handleScroll = throttle(() => updatePosition(), 200)
111
+
112
+ // 只执行一次
113
+ const initOnce = once(() => setupSdk())
114
+
115
+ // 记忆化(相同参数缓存结果)
116
+ const expensiveCalc = memoize((n: number) => fibonacci(n))
117
+ ```
118
+
119
+ ### 🧮 对象工具(`object`)
120
+
121
+ ```ts
122
+ import { deepClone, deepMerge, pick, omit, flattenObject } from '@yh-ui/utils'
123
+
124
+ // 深拷贝(支持循环引用、Date、RegExp、Map、Set)
125
+ const clone = deepClone(original)
126
+
127
+ // 深合并(不可变,返回新对象)
128
+ const merged = deepMerge(defaults, overrides)
129
+
130
+ // 提取指定属性
131
+ const subset = pick(obj, ['name', 'age'])
132
+
133
+ // 排除指定属性
134
+ const without = omit(obj, ['password', 'secret'])
135
+
136
+ // 扁平化嵌套对象
137
+ // { a: { b: { c: 1 } } } => { 'a.b.c': 1 }
138
+ const flat = flattenObject(nestedObj)
139
+ ```
140
+
141
+ ### 📝 字符串工具(`string`)
142
+
143
+ ```ts
144
+ import {
145
+ capitalize,
146
+ camelCase,
147
+ kebabCase,
148
+ snakeCase,
149
+ truncate,
150
+ escapeHtml,
151
+ stripHtml,
152
+ generateId
153
+ } from '@yh-ui/utils'
154
+
155
+ capitalize('hello world') // 'Hello world'
156
+ camelCase('background-color') // 'backgroundColor'
157
+ kebabCase('backgroundColor') // 'background-color'
158
+ truncate('很长的文字内容', 10) // '很长的文字内容...'
159
+ escapeHtml('<script>alert(1)</script>') // '&lt;script&gt;...'
160
+ generateId() // 'yh-xxxxxxxx'(唯一 ID)
161
+ ```
162
+
163
+ ### 🔢 数字工具(`number`)
164
+
165
+ ```ts
166
+ import { clamp, round, formatNumber, randomInt } from '@yh-ui/utils'
167
+
168
+ clamp(150, 0, 100) // 100(限制在范围内)
169
+ round(3.14159, 2) // 3.14
170
+ formatNumber(1234567.89) // '1,234,567.89'
171
+ randomInt(1, 10) // 随机整数 [1, 10]
172
+ ```
173
+
174
+ ### 📅 日期工具(`date`)
175
+
176
+ ```ts
177
+ import { formatDate, parseDate, diffDate, isToday } from '@yh-ui/utils'
178
+
179
+ formatDate(new Date(), 'YYYY-MM-DD HH:mm:ss') // '2025-03-26 11:30:00'
180
+ isToday(new Date()) // true
181
+ diffDate(date1, date2, 'day') // 相差天数
182
+ ```
183
+
184
+ ### 🌐 DOM 工具(`dom`)
185
+
186
+ ```ts
187
+ import { getScrollTop, setScrollTop, getStyle, hasClass, addClass, removeClass } from '@yh-ui/utils'
188
+
189
+ // 所有 DOM 工具均有 SSR 安全防护
190
+ getScrollTop() // 获取页面滚动距离
191
+ setScrollTop(200) // 设置页面滚动位置
192
+ getStyle(el, 'font-size') // 获取计算样式
193
+ hasClass(el, 'active') // 是否有类名
194
+ addClass(el, 'active') // 添加类名
195
+ removeClass(el, 'active') // 移除类名
196
+ ```
197
+
198
+ ### 🎨 颜色工具(`color`)
199
+
200
+ ```ts
201
+ import { hexToRgb, rgbToHsv, lighten, darken, mix } from '@yh-ui/utils'
202
+
203
+ hexToRgb('#409eff') // { r: 64, g: 158, b: 255 }
204
+ lighten('#409eff', 0.3) // 亮化 30%
205
+ darken('#409eff', 0.3) // 暗化 30%
206
+ mix('#409eff', '#ffffff', 0.5) // 混合两种颜色
207
+ ```
208
+
209
+ ---
210
+
211
+ ## ⚠️ 注意事项
212
+
213
+ - **DOM 工具**:在 SSR 环境(Nuxt)中,`isClient` 会返回 `false`,所有 DOM 操作自动跳过
214
+ - **deepClone**:对于 `WeakMap`、`WeakSet` 等弱引用类型不做深拷贝处理
215
+ - **escapeHtml**:始终对用户输入使用此函数,防范 XSS 攻击
216
+
217
+ ---
218
+
219
+ ## 🔗 相关资源
220
+
221
+ - [📖 官方文档](https://1079161148.github.io/yh-ui/)
222
+ - [📦 GitHub 仓库](https://github.com/1079161148/yh-ui)
223
+
224
+ ## 📄 开源协议
225
+
226
+ MIT License © 2024-present YH-UI Team
package/dist/common.d.ts CHANGED
@@ -8,17 +8,20 @@ export declare const generateId: (prefix?: string) => string;
8
8
  */
9
9
  type AnyFunction = (...args: unknown[]) => unknown;
10
10
  /**
11
- * 防抖函数
11
+ * 可取消函数类型
12
12
  */
13
- export declare const debounce: <T extends AnyFunction>(fn: T, delay: number) => ((...args: Parameters<T>) => void) & {
13
+ interface CancelableFunction<T extends AnyFunction> {
14
+ (...args: Parameters<T>): ReturnType<T>;
14
15
  cancel: () => void;
15
- };
16
+ }
17
+ /**
18
+ * 防抖函数
19
+ */
20
+ export declare const debounce: <T extends AnyFunction>(fn: T, delay: number) => CancelableFunction<T>;
16
21
  /**
17
22
  * 节流函数
18
23
  */
19
- export declare const throttle: <T extends AnyFunction>(fn: T, delay: number) => ((...args: Parameters<T>) => void) & {
20
- cancel: () => void;
21
- };
24
+ export declare const throttle: <T extends AnyFunction>(fn: T, delay: number) => CancelableFunction<T>;
22
25
  /**
23
26
  * 深拷贝
24
27
  */
package/dist/common.mjs CHANGED
@@ -4,13 +4,13 @@ export const generateId = (prefix = "yh") => {
4
4
  };
5
5
  export const debounce = (fn, delay) => {
6
6
  let timer = null;
7
- const debounced = (...args) => {
7
+ const debounced = ((...args) => {
8
8
  if (timer) clearTimeout(timer);
9
9
  timer = setTimeout(() => {
10
10
  fn(...args);
11
11
  timer = null;
12
12
  }, delay);
13
- };
13
+ });
14
14
  debounced.cancel = () => {
15
15
  if (timer) {
16
16
  clearTimeout(timer);
@@ -22,7 +22,7 @@ export const debounce = (fn, delay) => {
22
22
  export const throttle = (fn, delay) => {
23
23
  let lastTime = 0;
24
24
  let timer = null;
25
- const throttled = (...args) => {
25
+ const throttled = ((...args) => {
26
26
  const now = Date.now();
27
27
  const remaining = delay - (now - lastTime);
28
28
  if (remaining <= 0) {
@@ -39,7 +39,7 @@ export const throttle = (fn, delay) => {
39
39
  timer = null;
40
40
  }, remaining);
41
41
  }
42
- };
42
+ });
43
43
  throttled.cancel = () => {
44
44
  if (timer) {
45
45
  clearTimeout(timer);
package/dist/dom.cjs CHANGED
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.toggleClass = exports.setStyle = exports.removeClass = exports.isServer = exports.isInViewport = exports.isClient = exports.hasClass = exports.getStyle = exports.getScrollContainer = exports.addClass = void 0;
6
+ exports.toggleClass = exports.setStyle = exports.removeClass = exports.isServer = exports.isInViewport = exports.isClient = exports.hasClass = exports.getStyle = exports.getScrollbarWidth = exports.getScrollContainer = exports.addClass = void 0;
7
7
  const isClient = exports.isClient = typeof window !== "undefined";
8
8
  const isServer = exports.isServer = !isClient;
9
9
  const getStyle = (element, styleName) => {
@@ -72,4 +72,25 @@ const isInViewport = el => {
72
72
  const rect = el.getBoundingClientRect();
73
73
  return rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth);
74
74
  };
75
- exports.isInViewport = isInViewport;
75
+ exports.isInViewport = isInViewport;
76
+ let scrollBarWidth;
77
+ const getScrollbarWidth = () => {
78
+ if (!isClient) return 0;
79
+ if (scrollBarWidth !== void 0) return scrollBarWidth;
80
+ const outer = document.createElement("div");
81
+ outer.style.visibility = "hidden";
82
+ outer.style.width = "100px";
83
+ outer.style.position = "absolute";
84
+ outer.style.top = "-9999px";
85
+ document.body.appendChild(outer);
86
+ const widthNoScroll = outer.offsetWidth;
87
+ outer.style.overflow = "scroll";
88
+ const inner = document.createElement("div");
89
+ inner.style.width = "100%";
90
+ outer.appendChild(inner);
91
+ const widthWithScroll = inner.offsetWidth;
92
+ outer.parentNode?.removeChild(outer);
93
+ scrollBarWidth = widthNoScroll - widthWithScroll;
94
+ return scrollBarWidth;
95
+ };
96
+ exports.getScrollbarWidth = getScrollbarWidth;
package/dist/dom.d.ts CHANGED
@@ -41,3 +41,4 @@ export declare const getScrollContainer: (el: HTMLElement, isVertical?: boolean)
41
41
  * 检查元素是否在视口中
42
42
  */
43
43
  export declare const isInViewport: (el: HTMLElement) => boolean;
44
+ export declare const getScrollbarWidth: () => number;
package/dist/dom.mjs CHANGED
@@ -59,3 +59,23 @@ export const isInViewport = (el) => {
59
59
  const rect = el.getBoundingClientRect();
60
60
  return rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth);
61
61
  };
62
+ let scrollBarWidth;
63
+ export const getScrollbarWidth = () => {
64
+ if (!isClient) return 0;
65
+ if (scrollBarWidth !== void 0) return scrollBarWidth;
66
+ const outer = document.createElement("div");
67
+ outer.style.visibility = "hidden";
68
+ outer.style.width = "100px";
69
+ outer.style.position = "absolute";
70
+ outer.style.top = "-9999px";
71
+ document.body.appendChild(outer);
72
+ const widthNoScroll = outer.offsetWidth;
73
+ outer.style.overflow = "scroll";
74
+ const inner = document.createElement("div");
75
+ inner.style.width = "100%";
76
+ outer.appendChild(inner);
77
+ const widthWithScroll = inner.offsetWidth;
78
+ outer.parentNode?.removeChild(outer);
79
+ scrollBarWidth = widthNoScroll - widthWithScroll;
80
+ return scrollBarWidth;
81
+ };