jsly 3.0.6 → 3.1.7

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
@@ -194,4 +194,105 @@ $bus.off('test', listener)
194
194
  // 移除所有监听器
195
195
  $bus.removeAllListeners()
196
196
 
197
+ ```
198
+
199
+ ### 文本关键词高亮(highlightKeyword)
200
+ 高亮文本中匹配的关键词,支持不区分大小写匹配,并允许自定义标签与样式。
201
+
202
+ ```javascript
203
+ import { highlightKeyword } from 'jsly'
204
+
205
+ const text = '泡泡音乐是一款非常好用的音乐播放器'
206
+ const result = highlightKeyword(text, '音乐')
207
+ console.log(result)
208
+ // => '泡泡<span style="background-color: #ff0;color: #001;">音乐</span>是一款非常好用的<span style="background-color: #ff0;color: #001;">音乐</span>播放器'
209
+
210
+ ```
211
+
212
+ 自定义标签与样式
213
+ ```javascript
214
+ highlightKeyword('Hello World', 'world', {
215
+ tag: 'mark',
216
+ bgColor: '#000',
217
+ color: '#0f0'
218
+ })
219
+
220
+ ```
221
+
222
+ 配置说明
223
+ | 参数 | 类型 | 默认值 | 说明 |
224
+ |------|------|---------|------|
225
+ | `text` | `string` | 必填 | 原始文本 |
226
+ | `keyword` | `string` | 必填 | 要高亮的关键词(大小写不敏感) |
227
+ | `config.tag` | `string` | `'span'` | 包裹关键词的标签名 |
228
+ | `config.bgColor` | `string` | `'#ff0'` | 背景颜色 |
229
+ | `config.color` | `string` | `'#001'` | 文本颜色 |
230
+
231
+ ### 基于 requestAnimationFrame 的定时器(rafInterval)
232
+ 使用 requestAnimationFrame 实现类似 setInterval 的效果。
233
+ 更适合动画、轮播、游戏循环等需要与浏览器帧同步的场景。
234
+
235
+ ```javascript
236
+ import { rafInterval } from 'jsly'
237
+
238
+ // 每秒执行一次
239
+ const stop = rafInterval(() => {
240
+ console.log('tick')
241
+ }, 1000)
242
+
243
+ // 停止执行
244
+ stop()
245
+ ```
246
+
247
+ 在 Vue 中使用
248
+
249
+ ```javascript
250
+ import { rafInterval } from 'jsly'
251
+ import { onMounted, onUnmounted } from 'vue'
252
+
253
+ let stop
254
+
255
+ onMounted(() => {
256
+ stop = rafInterval(() => {
257
+ console.log('frame update')
258
+ }, 16) // 约 60fps
259
+ })
260
+
261
+ onUnmounted(() => {
262
+ stop()
263
+ })
264
+ ```
265
+
266
+ ### 基轮询函数(poll)
267
+ 用于按固定间隔重复执行任务,直到:
268
+ - 返回结果为 truthy
269
+ - 超时
270
+ - 手动停止
271
+ 适用于接口状态查询、等待资源加载、检测条件成立等场景。
272
+
273
+ ```javascript
274
+ import { poll } from 'jsly'
275
+
276
+ // 轮询接口直到 ready === true
277
+ const { promise, stop } = poll(
278
+ async () => {
279
+ const res = await fetch('/api/status').then(r => r.json())
280
+ return res.ready && res
281
+ },
282
+ {
283
+ interval: 2000, // 每 2 秒执行一次
284
+ timeout: 10000 // 10 秒超时
285
+ }
286
+ )
287
+
288
+ promise
289
+ .then(data => {
290
+ console.log('成功:', data)
291
+ })
292
+ .catch(err => {
293
+ console.error('轮询失败:', err)
294
+ })
295
+
296
+ // 如需手动停止
297
+ // stop()
197
298
  ```
package/dist/index.cjs.js CHANGED
@@ -7628,6 +7628,196 @@ class EventBus {
7628
7628
  // 导出单例
7629
7629
  const $bus = new EventBus();
7630
7630
 
7631
+ /**
7632
+ * 转义正则表达式中的特殊字符。
7633
+ *
7634
+ * @private
7635
+ * @function escapeRegExp
7636
+ * @description
7637
+ * 将字符串中的正则表达式特殊字符(如 `. * + ? ^ $ { } ( ) | [ ] \\`)全部转义,
7638
+ * 以便安全地用于构建正则表达式。通常用于用户输入的关键字过滤,避免因
7639
+ * 正则表达式语法导致的匹配异常。
7640
+ *
7641
+ * @param {string} str - 需要进行转义的原始字符串。
7642
+ * @returns {string} 已转义、可安全用于 RegExp 的字符串。
7643
+ *
7644
+ * @example
7645
+ * escapeRegExp("a+b*c") // => "a\\+b\\*c"
7646
+ */
7647
+ function escapeRegExp(str) {
7648
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7649
+ }
7650
+
7651
+ /**
7652
+ * 高亮文本中的关键词。
7653
+ *
7654
+ * @function highlightKeyword
7655
+ * @description
7656
+ * 在给定的文本中查找所有匹配的关键词(不区分大小写),并使用带样式的
7657
+ * HTML 标签(默认 `<span>`)包裹,使其以高亮方式显示。该方法适用于搜索
7658
+ * 结果展示、文本匹配提示等场景。
7659
+ *
7660
+ * @param {string} text - 原始完整文本。
7661
+ * @param {string} keyword - 需要高亮显示的关键词。如果为空,则直接返回原文本。
7662
+ * @param {Object} [config] - 配置对象,用于自定义高亮标签和样式。
7663
+ * @param {string} [config.tag="span"] - 包裹关键词的 HTML 标签名。
7664
+ * @param {string} [config.bgColor="#ff0"] - 高亮背景颜色。
7665
+ * @param {string} [config.color="#001"] - 关键词文字颜色。
7666
+ *
7667
+ * @returns {string} 返回插入带样式标签后的 HTML 字符串。
7668
+ *
7669
+ * @example
7670
+ * highlightKeyword(
7671
+ * "泡泡音乐是一款好用的播放器",
7672
+ * "音乐"
7673
+ * )
7674
+ * // => '泡泡<span style="background-color: #ff0;color: #001;">音乐</span>是一款好用的播放器'
7675
+ */
7676
+ function highlightKeyword(text, keyword, config = {
7677
+ tag: "span",
7678
+ bgColor: "#ff0",
7679
+ color: "#001"
7680
+ }) {
7681
+ if (!keyword) return text;
7682
+ const {
7683
+ tag,
7684
+ bgColor,
7685
+ color
7686
+ } = config;
7687
+ const pattern = new RegExp(escapeRegExp(keyword), "gi");
7688
+ const style = `background-color: ${bgColor};color: ${color};`;
7689
+ return text.replace(pattern, match => `<${tag} style="${style}">${match}</${tag}>`);
7690
+ }
7691
+
7692
+ /**
7693
+ * 基于 requestAnimationFrame 实现的定时器(RAF Interval)
7694
+ *
7695
+ * 功能类似于 setInterval,但内部使用 requestAnimationFrame 驱动。
7696
+ * 相比 setInterval:
7697
+ * - 与浏览器刷新率同步,更平滑
7698
+ * - 页面不可见时会自动暂停(节省性能)
7699
+ * - 不易出现掉帧或时间漂移问题
7700
+ *
7701
+ * 适用于动画、轮播、游戏循环等需要与渲染帧同步的场景。
7702
+ *
7703
+ * @template {(...args: any[]) => any} F
7704
+ * @param {F} callback - 每次间隔触发时执行的函数。
7705
+ * @param {number} delay - 执行间隔时间(毫秒)。
7706
+ * @returns {() => void} 返回一个停止函数,用于取消循环。
7707
+ *
7708
+ * @example
7709
+ * const stop = rafInterval(() => {
7710
+ * console.log('每秒执行一次')
7711
+ * }, 1000)
7712
+ *
7713
+ * // 停止
7714
+ * stop()
7715
+ *
7716
+ * @example
7717
+ * // 在 Vue 中使用
7718
+ * let stop
7719
+ * onMounted(() => {
7720
+ * stop = rafInterval(() => {
7721
+ * rotate.value += 1
7722
+ * }, 16)
7723
+ * })
7724
+ *
7725
+ * onUnmounted(() => {
7726
+ * stop()
7727
+ * })
7728
+ */
7729
+ function rafInterval(callback, delay) {
7730
+ if (typeof callback !== 'function') {
7731
+ throw new TypeError('rafInterval expected a function as the first argument');
7732
+ }
7733
+ let start = performance.now();
7734
+ let frameId = null;
7735
+ let stopped = false;
7736
+ function loop(now) {
7737
+ if (stopped) return;
7738
+ const delta = now - start;
7739
+ if (delta >= delay) {
7740
+ callback();
7741
+ // 使用 += 防止时间漂移
7742
+ start += delay;
7743
+ }
7744
+ frameId = requestAnimationFrame(loop);
7745
+ }
7746
+ frameId = requestAnimationFrame(loop);
7747
+ return function stop() {
7748
+ stopped = true;
7749
+ if (frameId) cancelAnimationFrame(frameId);
7750
+ };
7751
+ }
7752
+
7753
+ /**
7754
+ * 轮询函数(Polling)
7755
+ *
7756
+ * 按指定间隔重复执行异步任务,直到:
7757
+ * 1. 任务返回 truthy 值(成功)
7758
+ * 2. 超时
7759
+ * 3. 被手动停止
7760
+ *
7761
+ * 常用于:
7762
+ * - 接口状态查询
7763
+ * - 等待资源加载完成
7764
+ * - 检测条件成立
7765
+ *
7766
+ * @template T
7767
+ * @param {() => Promise<T> | T} task - 需要轮询执行的函数(支持同步或异步)。
7768
+ * @param {Object} [options]
7769
+ * @param {number} [options.interval=1000] - 每次轮询间隔时间(毫秒)。
7770
+ * @param {number} [options.timeout=0] - 超时时间(毫秒),0 表示不限制。
7771
+ * @returns {{ promise: Promise<T>, stop: () => void }}
7772
+ *
7773
+ * @example
7774
+ * const { promise, stop } = poll(
7775
+ * () => fetch('/api/status').then(r => r.json()),
7776
+ * { interval: 2000, timeout: 10000 }
7777
+ * )
7778
+ *
7779
+ * promise.then(res => console.log('成功:', res))
7780
+ */
7781
+ function poll(task, {
7782
+ interval = 1000,
7783
+ timeout = 0
7784
+ } = {}) {
7785
+ if (typeof task !== 'function') {
7786
+ throw new TypeError('poll expected a function as the first argument');
7787
+ }
7788
+ let stopped = false;
7789
+ let startTime = Date.now();
7790
+ let timer = null;
7791
+ const promise = new Promise((resolve, reject) => {
7792
+ async function execute() {
7793
+ if (stopped) return;
7794
+ try {
7795
+ const result = await task();
7796
+ if (result) {
7797
+ resolve(result);
7798
+ return;
7799
+ }
7800
+ if (timeout && Date.now() - startTime >= timeout) {
7801
+ reject(new Error('Polling timeout'));
7802
+ return;
7803
+ }
7804
+ timer = setTimeout(execute, interval);
7805
+ } catch (err) {
7806
+ reject(err);
7807
+ }
7808
+ }
7809
+ execute();
7810
+ });
7811
+ function stop() {
7812
+ stopped = true;
7813
+ if (timer) clearTimeout(timer);
7814
+ }
7815
+ return {
7816
+ promise,
7817
+ stop
7818
+ };
7819
+ }
7820
+
7631
7821
  exports.$bus = $bus;
7632
7822
  exports.buildObjFormData = buildObjFormData;
7633
7823
  exports.camelToSnake = camelToSnake;
@@ -7638,6 +7828,9 @@ exports.copyToClipboard = copyToClipboard;
7638
7828
  exports.debounce = debounce;
7639
7829
  exports.deepDiff = deepDiff;
7640
7830
  exports.generateBrowserId = generateBrowserId;
7831
+ exports.highlightKeyword = highlightKeyword;
7832
+ exports.poll = poll;
7833
+ exports.rafInterval = rafInterval;
7641
7834
  exports.randomHash = randomHash;
7642
7835
  exports.sha256 = sha256;
7643
7836
  exports.shallowDiff = shallowDiff;