@codady/utils 0.0.31 → 0.0.33

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,6 +2,44 @@
2
2
 
3
3
  All changes to Utils including new features, updates, and removals are documented here.
4
4
 
5
+
6
+ ## [v0.0.33] - 2026-1-10
7
+
8
+ ### Distribution Files
9
+ * **JS**: https://unpkg.com/@codady/utils@0.0.33/dist/js/utils.js
10
+ * **Zip**:https://unpkg.com/@codady/utils@0.0.33/dist.zip
11
+
12
+ ### Changes
13
+
14
+ #### Fixed
15
+ * Null
16
+
17
+ #### Added
18
+ * Added the functions `parseLLMStream`.新增 `parseLLMStream`函数。
19
+
20
+
21
+ #### Removed
22
+ * Null
23
+
24
+
25
+ ## [v0.0.32] - 2026-1-7
26
+
27
+ ### Distribution Files
28
+ * **JS**: https://unpkg.com/@codady/utils@0.0.32/dist/js/utils.js
29
+ * **Zip**:https://unpkg.com/@codady/utils@0.0.32/dist.zip
30
+
31
+ ### Changes
32
+
33
+ #### Fixed
34
+ * Null
35
+
36
+ #### Added
37
+ * Added the functions `typeWriter`.新增 `typeWriter`函数。
38
+
39
+
40
+ #### Removed
41
+ * Null
42
+
5
43
  ## [v0.0.24] - 2026-1-7
6
44
 
7
45
  ### Distribution Files
package/dist/utils.cjs.js CHANGED
@@ -1,8 +1,8 @@
1
1
 
2
2
  /*!
3
- * @since Last modified: 2026-1-7 14:15:41
3
+ * @since Last modified: 2026-1-10 10:36:22
4
4
  * @name Utils for web front-end.
5
- * @version 0.0.25
5
+ * @version 0.0.33
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}
@@ -955,7 +955,7 @@ const COMMA = ',';
955
955
 
956
956
  const SPACE = ' ';
957
957
 
958
- const trim = (str, placement = '') => {
958
+ const trim = (str, placement = 'compress') => {
959
959
  if (typeof str !== 'string') {
960
960
  console.warn('Expected a string input');
961
961
  return '';
@@ -1063,6 +1063,103 @@ const removeClasses = (target, classes, intercept) => {
1063
1063
  return;
1064
1064
  };
1065
1065
 
1066
+ const typeWriter = (text, options) => {
1067
+ const speed = options.speed || 100; // Set typing speed (default to 100ms per character)
1068
+ return new Promise((resolve) => {
1069
+ // Callback before typing starts
1070
+ options?.onBeforeType?.(text);
1071
+ let index = 0;
1072
+ // Timer to type the text character by character at the given speed
1073
+ const timer = setInterval(() => {
1074
+ if (index < text.length) {
1075
+ const char = text.charAt(index); // Get the character at the current index
1076
+ const typedText = text.substring(0, index + 1); // The text typed so far
1077
+ // Callback during typing each character
1078
+ options?.onDuringType?.(char, typedText);
1079
+ index++;
1080
+ }
1081
+ else {
1082
+ // Clear the timer once typing is complete
1083
+ clearInterval(timer);
1084
+ // Resolve the Promise when typing is complete
1085
+ resolve(text);
1086
+ // Callback after typing is finished
1087
+ options?.onAfterType?.(text);
1088
+ }
1089
+ }, speed);
1090
+ });
1091
+ };
1092
+
1093
+ const parseLLMStream = async (response, onChunk) => {
1094
+ // Ensure the body exists before attempting to read
1095
+ if (!response.body) {
1096
+ throw new Error("Response body is null. Cannot read stream.");
1097
+ }
1098
+ const reader = response.body.getReader(), decoder = new TextDecoder("utf-8");
1099
+ // Buffer to store partial lines that haven't reached a newline character yet
1100
+ let buffer = "";
1101
+ const finalResult = {
1102
+ fullText: "",
1103
+ finishReason: null,
1104
+ usage: null,
1105
+ isCompleted: false
1106
+ };
1107
+ try {
1108
+ while (true) {
1109
+ const { done, value } = await reader.read();
1110
+ // 'done' is true when the connection closes
1111
+ if (done)
1112
+ break;
1113
+ // Decode the binary chunk and append to buffer.
1114
+ // { stream: true } ensures multi-byte characters (like Emojis/Chinese) aren't corrupted.
1115
+ buffer += decoder.decode(value, { stream: true });
1116
+ // Split buffer by newlines to process complete SSE "data: " lines
1117
+ let lines = buffer.split("\n");
1118
+ // Pop the last element: if the line is incomplete, it stays in buffer for the next chunk.
1119
+ // If the line was complete, pop() leaves an empty string in buffer.
1120
+ buffer = lines.pop() || "";
1121
+ for (const line of lines) {
1122
+ const trimmed = line.trim();
1123
+ if (!trimmed || !trimmed.startsWith("data: "))
1124
+ continue;
1125
+ const message = trimmed.substring(6); // Extract the JSON string after "data: "
1126
+ // Signal: [DONE] indicates the server has finished sending data
1127
+ if (message === "[DONE]") {
1128
+ finalResult.isCompleted = true;
1129
+ continue;
1130
+ }
1131
+ try {
1132
+ const json = JSON.parse(message), choice = json.choices?.[0],
1133
+ // 1. Accumulate Content: Delta contains the incremental text
1134
+ content = choice?.delta?.content || "";
1135
+ if (content) {
1136
+ finalResult.fullText += content;
1137
+ // Real-time callback for UI typewriter effect
1138
+ onChunk?.(content);
1139
+ }
1140
+ // 2. Capture Finish Reason: Explains why generation ended (e.g., 'stop', 'length')
1141
+ if (choice?.finish_reason) {
1142
+ finalResult.finishReason = choice.finish_reason;
1143
+ }
1144
+ // 3. Capture Usage: Token statistics usually arrive in the last few chunks
1145
+ if (json.usage) {
1146
+ finalResult.usage = json.usage;
1147
+ }
1148
+ }
1149
+ catch (e) {
1150
+ // Log parsing errors but continue processing other lines
1151
+ console.error("Error parsing SSE JSON message:", e, "Message:", message);
1152
+ }
1153
+ }
1154
+ }
1155
+ }
1156
+ catch (err) {
1157
+ console.error("Critical error while reading ReadableStream:", err);
1158
+ throw err; // Re-throw to let the caller handle network/stream failures
1159
+ }
1160
+ return finalResult;
1161
+ };
1162
+
1066
1163
  const utils = {
1067
1164
  //executeStr,
1068
1165
  getDataType,
@@ -1096,6 +1193,8 @@ const utils = {
1096
1193
  addClasses,
1097
1194
  removeClasses,
1098
1195
  createTools,
1196
+ typeWriter,
1197
+ parseLLMStream,
1099
1198
  };
1100
1199
 
1101
1200
  module.exports = utils;
@@ -1,7 +1,7 @@
1
1
  /*!
2
- * @since Last modified: 2026-1-7 14:15:41
2
+ * @since Last modified: 2026-1-10 10:36:22
3
3
  * @name Utils for web front-end.
4
- * @version 0.0.25
4
+ * @version 0.0.33
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="")=>{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)})},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};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},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.body)throw new Error("Response body is null. Cannot read stream.");const r=e.body.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},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};module.exports=utils;
package/dist/utils.esm.js CHANGED
@@ -1,8 +1,8 @@
1
1
 
2
2
  /*!
3
- * @since Last modified: 2026-1-7 14:15:41
3
+ * @since Last modified: 2026-1-10 10:36:22
4
4
  * @name Utils for web front-end.
5
- * @version 0.0.25
5
+ * @version 0.0.33
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}
@@ -953,7 +953,7 @@ const COMMA = ',';
953
953
 
954
954
  const SPACE = ' ';
955
955
 
956
- const trim = (str, placement = '') => {
956
+ const trim = (str, placement = 'compress') => {
957
957
  if (typeof str !== 'string') {
958
958
  console.warn('Expected a string input');
959
959
  return '';
@@ -1061,6 +1061,103 @@ const removeClasses = (target, classes, intercept) => {
1061
1061
  return;
1062
1062
  };
1063
1063
 
1064
+ const typeWriter = (text, options) => {
1065
+ const speed = options.speed || 100; // Set typing speed (default to 100ms per character)
1066
+ return new Promise((resolve) => {
1067
+ // Callback before typing starts
1068
+ options?.onBeforeType?.(text);
1069
+ let index = 0;
1070
+ // Timer to type the text character by character at the given speed
1071
+ const timer = setInterval(() => {
1072
+ if (index < text.length) {
1073
+ const char = text.charAt(index); // Get the character at the current index
1074
+ const typedText = text.substring(0, index + 1); // The text typed so far
1075
+ // Callback during typing each character
1076
+ options?.onDuringType?.(char, typedText);
1077
+ index++;
1078
+ }
1079
+ else {
1080
+ // Clear the timer once typing is complete
1081
+ clearInterval(timer);
1082
+ // Resolve the Promise when typing is complete
1083
+ resolve(text);
1084
+ // Callback after typing is finished
1085
+ options?.onAfterType?.(text);
1086
+ }
1087
+ }, speed);
1088
+ });
1089
+ };
1090
+
1091
+ const parseLLMStream = async (response, onChunk) => {
1092
+ // Ensure the body exists before attempting to read
1093
+ if (!response.body) {
1094
+ throw new Error("Response body is null. Cannot read stream.");
1095
+ }
1096
+ const reader = response.body.getReader(), decoder = new TextDecoder("utf-8");
1097
+ // Buffer to store partial lines that haven't reached a newline character yet
1098
+ let buffer = "";
1099
+ const finalResult = {
1100
+ fullText: "",
1101
+ finishReason: null,
1102
+ usage: null,
1103
+ isCompleted: false
1104
+ };
1105
+ try {
1106
+ while (true) {
1107
+ const { done, value } = await reader.read();
1108
+ // 'done' is true when the connection closes
1109
+ if (done)
1110
+ break;
1111
+ // Decode the binary chunk and append to buffer.
1112
+ // { stream: true } ensures multi-byte characters (like Emojis/Chinese) aren't corrupted.
1113
+ buffer += decoder.decode(value, { stream: true });
1114
+ // Split buffer by newlines to process complete SSE "data: " lines
1115
+ let lines = buffer.split("\n");
1116
+ // Pop the last element: if the line is incomplete, it stays in buffer for the next chunk.
1117
+ // If the line was complete, pop() leaves an empty string in buffer.
1118
+ buffer = lines.pop() || "";
1119
+ for (const line of lines) {
1120
+ const trimmed = line.trim();
1121
+ if (!trimmed || !trimmed.startsWith("data: "))
1122
+ continue;
1123
+ const message = trimmed.substring(6); // Extract the JSON string after "data: "
1124
+ // Signal: [DONE] indicates the server has finished sending data
1125
+ if (message === "[DONE]") {
1126
+ finalResult.isCompleted = true;
1127
+ continue;
1128
+ }
1129
+ try {
1130
+ const json = JSON.parse(message), choice = json.choices?.[0],
1131
+ // 1. Accumulate Content: Delta contains the incremental text
1132
+ content = choice?.delta?.content || "";
1133
+ if (content) {
1134
+ finalResult.fullText += content;
1135
+ // Real-time callback for UI typewriter effect
1136
+ onChunk?.(content);
1137
+ }
1138
+ // 2. Capture Finish Reason: Explains why generation ended (e.g., 'stop', 'length')
1139
+ if (choice?.finish_reason) {
1140
+ finalResult.finishReason = choice.finish_reason;
1141
+ }
1142
+ // 3. Capture Usage: Token statistics usually arrive in the last few chunks
1143
+ if (json.usage) {
1144
+ finalResult.usage = json.usage;
1145
+ }
1146
+ }
1147
+ catch (e) {
1148
+ // Log parsing errors but continue processing other lines
1149
+ console.error("Error parsing SSE JSON message:", e, "Message:", message);
1150
+ }
1151
+ }
1152
+ }
1153
+ }
1154
+ catch (err) {
1155
+ console.error("Critical error while reading ReadableStream:", err);
1156
+ throw err; // Re-throw to let the caller handle network/stream failures
1157
+ }
1158
+ return finalResult;
1159
+ };
1160
+
1064
1161
  const utils = {
1065
1162
  //executeStr,
1066
1163
  getDataType,
@@ -1094,6 +1191,8 @@ const utils = {
1094
1191
  addClasses,
1095
1192
  removeClasses,
1096
1193
  createTools,
1194
+ typeWriter,
1195
+ parseLLMStream,
1097
1196
  };
1098
1197
 
1099
1198
  export { utils as default };
@@ -1,7 +1,7 @@
1
1
  /*!
2
- * @since Last modified: 2026-1-7 14:15:41
2
+ * @since Last modified: 2026-1-10 10:36:22
3
3
  * @name Utils for web front-end.
4
- * @version 0.0.25
4
+ * @version 0.0.33
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
- 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="")=>{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)})},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};export{utils as default};
15
+ 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.body)throw new Error("Response body is null. Cannot read stream.");const r=e.body.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},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};export{utils as default};
package/dist/utils.umd.js CHANGED
@@ -1,8 +1,8 @@
1
1
 
2
2
  /*!
3
- * @since Last modified: 2026-1-7 14:15:41
3
+ * @since Last modified: 2026-1-10 10:36:22
4
4
  * @name Utils for web front-end.
5
- * @version 0.0.25
5
+ * @version 0.0.33
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}
@@ -959,7 +959,7 @@
959
959
 
960
960
  const SPACE = ' ';
961
961
 
962
- const trim = (str, placement = '') => {
962
+ const trim = (str, placement = 'compress') => {
963
963
  if (typeof str !== 'string') {
964
964
  console.warn('Expected a string input');
965
965
  return '';
@@ -1067,6 +1067,103 @@
1067
1067
  return;
1068
1068
  };
1069
1069
 
1070
+ const typeWriter = (text, options) => {
1071
+ const speed = options.speed || 100; // Set typing speed (default to 100ms per character)
1072
+ return new Promise((resolve) => {
1073
+ // Callback before typing starts
1074
+ options?.onBeforeType?.(text);
1075
+ let index = 0;
1076
+ // Timer to type the text character by character at the given speed
1077
+ const timer = setInterval(() => {
1078
+ if (index < text.length) {
1079
+ const char = text.charAt(index); // Get the character at the current index
1080
+ const typedText = text.substring(0, index + 1); // The text typed so far
1081
+ // Callback during typing each character
1082
+ options?.onDuringType?.(char, typedText);
1083
+ index++;
1084
+ }
1085
+ else {
1086
+ // Clear the timer once typing is complete
1087
+ clearInterval(timer);
1088
+ // Resolve the Promise when typing is complete
1089
+ resolve(text);
1090
+ // Callback after typing is finished
1091
+ options?.onAfterType?.(text);
1092
+ }
1093
+ }, speed);
1094
+ });
1095
+ };
1096
+
1097
+ const parseLLMStream = async (response, onChunk) => {
1098
+ // Ensure the body exists before attempting to read
1099
+ if (!response.body) {
1100
+ throw new Error("Response body is null. Cannot read stream.");
1101
+ }
1102
+ const reader = response.body.getReader(), decoder = new TextDecoder("utf-8");
1103
+ // Buffer to store partial lines that haven't reached a newline character yet
1104
+ let buffer = "";
1105
+ const finalResult = {
1106
+ fullText: "",
1107
+ finishReason: null,
1108
+ usage: null,
1109
+ isCompleted: false
1110
+ };
1111
+ try {
1112
+ while (true) {
1113
+ const { done, value } = await reader.read();
1114
+ // 'done' is true when the connection closes
1115
+ if (done)
1116
+ break;
1117
+ // Decode the binary chunk and append to buffer.
1118
+ // { stream: true } ensures multi-byte characters (like Emojis/Chinese) aren't corrupted.
1119
+ buffer += decoder.decode(value, { stream: true });
1120
+ // Split buffer by newlines to process complete SSE "data: " lines
1121
+ let lines = buffer.split("\n");
1122
+ // Pop the last element: if the line is incomplete, it stays in buffer for the next chunk.
1123
+ // If the line was complete, pop() leaves an empty string in buffer.
1124
+ buffer = lines.pop() || "";
1125
+ for (const line of lines) {
1126
+ const trimmed = line.trim();
1127
+ if (!trimmed || !trimmed.startsWith("data: "))
1128
+ continue;
1129
+ const message = trimmed.substring(6); // Extract the JSON string after "data: "
1130
+ // Signal: [DONE] indicates the server has finished sending data
1131
+ if (message === "[DONE]") {
1132
+ finalResult.isCompleted = true;
1133
+ continue;
1134
+ }
1135
+ try {
1136
+ const json = JSON.parse(message), choice = json.choices?.[0],
1137
+ // 1. Accumulate Content: Delta contains the incremental text
1138
+ content = choice?.delta?.content || "";
1139
+ if (content) {
1140
+ finalResult.fullText += content;
1141
+ // Real-time callback for UI typewriter effect
1142
+ onChunk?.(content);
1143
+ }
1144
+ // 2. Capture Finish Reason: Explains why generation ended (e.g., 'stop', 'length')
1145
+ if (choice?.finish_reason) {
1146
+ finalResult.finishReason = choice.finish_reason;
1147
+ }
1148
+ // 3. Capture Usage: Token statistics usually arrive in the last few chunks
1149
+ if (json.usage) {
1150
+ finalResult.usage = json.usage;
1151
+ }
1152
+ }
1153
+ catch (e) {
1154
+ // Log parsing errors but continue processing other lines
1155
+ console.error("Error parsing SSE JSON message:", e, "Message:", message);
1156
+ }
1157
+ }
1158
+ }
1159
+ }
1160
+ catch (err) {
1161
+ console.error("Critical error while reading ReadableStream:", err);
1162
+ throw err; // Re-throw to let the caller handle network/stream failures
1163
+ }
1164
+ return finalResult;
1165
+ };
1166
+
1070
1167
  const utils = {
1071
1168
  //executeStr,
1072
1169
  getDataType,
@@ -1100,6 +1197,8 @@
1100
1197
  addClasses,
1101
1198
  removeClasses,
1102
1199
  createTools,
1200
+ typeWriter,
1201
+ parseLLMStream,
1103
1202
  };
1104
1203
 
1105
1204
  return utils;
@@ -1,7 +1,7 @@
1
1
  /*!
2
- * @since Last modified: 2026-1-7 14:15:41
2
+ * @since Last modified: 2026-1-10 10:36:22
3
3
  * @name Utils for web front-end.
4
- * @version 0.0.25
4
+ * @version 0.0.33
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
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).utils=t()}(this,function(){"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),n=Object.assign({cloneSet:!0,cloneMap:!0,cloneObject:!0,cloneArray:!0,cloneDate:!0,cloneRegex:!0},t);if(n.interceptor&&"function"==typeof n.interceptor){let t=n.interceptor({input:e,type:r,parent:n.parent});if(t)return t}n.onBeforeClone?.({input:e,type:r,parent:n.parent});let a,o=!0;if("Object"===r&&n.cloneObject){const t={},r=Object.getOwnPropertySymbols(e);for(const r in e)t[r]=deepClone(e[r],n);if(r.length>0)for(const a of r)t[a]=deepClone(e[a],{...n,parent:e});a=t}else if("Array"===r&&n.cloneArray)a=e.map(t=>deepClone(t,{...n,parent:e}));else if("Map"===r&&n.cloneMap){const t=new Map;for(const[r,a]of e)t.set(deepClone(r,n),deepClone(a,{...n,parent:e}));a=t}else if("Set"===r&&n.cloneSet){const t=new Set;for(const r of e)t.add(deepClone(r,{...n,parent:e}));a=t}else if("Date"===r&&n.cloneDate)a=new Date(e.getTime());else if("RegExp"===r&&n.cloneRegex){const t=e;a=new RegExp(t.source,t.flags)}else a=e,o=!1;return n.onAfterClone?.({output:a,input:e,type:r,cloned:o,parent:n.parent}),a},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},e=["push","pop","shift","unshift","splice","sort","reverse","copyWithin","fill"],t=["add","delete","clear"],r=["set","delete","clear"],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},getEl=(e,t=document.body)=>{let r=getDataType(e),n=getDataType(t),a=n.includes("HTML")||"ShadowRoot"===n?t:document.querySelector(t),o=a&&a instanceof HTMLTemplateElement?a.content:a,s=null;if(e)if(r.includes("HTML"))s=e;else if("String"===r)try{s=(o||document).querySelector(e.trim())}catch{s=null}return s},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},createEl=(e,t,r)=>{let n=(e=e||"div").toUpperCase().trim(),a=document.createElement(n),o=getDataType(t);if(t&&"Object"===o)for(let e in t)t.hasOwnProperty(e)&&a.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"===n)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)})(a,r),a},n="rep",trim=(e,t="")=>{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 n=getEl(e),a=parseClasses(t);n&&0!==a.length&&a.forEach(e=>{let t;r?(t=r(e),!0===t?n.classList.add(e):"string"==typeof t&&t&&n.classList.add(t)):n.classList.add(e)})};return{getDataType:getDataType,requireTypes:(e,t,r)=>{let n=Array.isArray(t)?t:[t],a=getDataType(e),o=a.toLowerCase(),s=n.map(e=>e.toLowerCase()),l=o.includes("html")?"element":o;if(r)try{if(!s.includes(l))throw new TypeError(`Expected data type(s): [${s.join(", ")}], but got: ${l}`)}catch(e){r(e,a)}else if(!s.includes(l))throw new TypeError(`Expected data type(s): [${s.join(", ")}], but got: ${l}`);return a},deepClone:deepClone,deepCloneToJSON:deepCloneToJSON,wrapArrayMethods:({target:t,onBeforeMutate:r=()=>{},onAfterMutate:n=()=>{},allowList:a,props:o={}})=>{if(!Array.isArray(t))throw new TypeError("The 'target' parameter must be an array.");a&&!a?.length||(a=e);const s={};for(let e of a)s[e]=function(...a){const s={},l=t.length;switch(e){case"push":case"unshift":s.addedItems=[...a];break;case"pop":s.poppedItem=t[l-1];break;case"shift":s.shiftedItem=t[0];break;case"splice":const[e,r]=a,n=e<0?Math.max(l+e,0):Math.min(e,l),o=void 0===r?l-n:r;s.deletedItems=t.slice(n,n+o);break;case"sort":case"reverse":s.oldSnapshot=[...t];break;case"fill":case"copyWithin":const i=a[1]||0,c=void 0===a[2]?l:a[2];s.oldItems=t.slice(i,c),s.start=i,s.end=c}r?.(s);const i=Array.prototype[e].apply(t,a),c={value:i,key:e,args:a,context:s,target:t,...o};return n?.(c),i};return s},arrayMutableMethods:e,setMutableMethods:t,mapMutableMethods:r,wrapSetMethods:({target:e,onBeforeMutate:r=()=>{},onAfterMutate:n=()=>{},allowList:a=t,props:o={}})=>{if(!(e instanceof Set))throw new TypeError("The 'target' parameter must be a Set.");const s={},createWrappedMethod=t=>function(...a){const s={};switch(t){case"add":{const[t]=a;s.addedItem=t,s.existed=e.has(t);break}case"delete":{const[t]=a;s.existed=e.has(t),s.deletedItem=s.existed?t:void 0;break}case"clear":s.clearedItems=Array.from(e),s.previousSize=e.size}r(s);const l=e[t].apply(e,a),i={method:t,result:l,args:a,context:s,target:e,...o};return n(i),l};for(const e of a)t.includes(e)&&(s[e]=createWrappedMethod(e));return Object.defineProperty(s,"target",{get:()=>e,enumerable:!1,configurable:!1}),s},wrapMapMethods:({target:e,onBeforeMutate:t=()=>{},onAfterMutate:n=()=>{},allowList:a=r,props:o={}})=>{if(!(e instanceof Map))throw new TypeError("The 'target' parameter must be a Map.");const s={},createWrappedMethod=r=>function(...a){const s={};switch(r){case"set":{const[t,r]=a;s.key=t,s.newValue=r,s.existed=e.has(t),s.oldValue=s.existed?e.get(t):void 0;break}case"delete":{const[t]=a;s.key=t,s.existed=e.has(t),s.value=s.existed?e.get(t):void 0;break}case"clear":s.clearedItems=Array.from(e.entries()),s.previousSize=e.size}t(s);const l=e[r].apply(e,a),i={method:r,result:l,args:a,context:s,target:e,...o};return n(i),l};for(const e of a)r.includes(e)&&(s[e]=createWrappedMethod(e));return Object.defineProperty(s,"target",{get:()=>e,enumerable:!1,configurable:!1}),s},getUniqueId:(e={})=>{const t=e.prefix,r=e.suffix,n=e.base10,a=e.base36;return`${t?t+"-":""}${Date.now()}${a?"-"+Math.random().toString(36).substring(2,11):""}${n?"-"+Math.floor(1e4*Math.random()).toString().padStart(4,"0"):""}${r?"-"+r:""}`},deepMerge:(e,t,r={})=>{const n=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,n)=>{let a,o,s=getDataType(e),l=getDataType(t),i=!0;if(n.interceptor&&"function"==typeof n.interceptor){let r=n.interceptor({target:e,source:t,targetType:s,sourceType:l,parent:n.parent});if(r){if(null===r?.target||null===r?.source)return r;e=r.target,t=r.source}}return n?.onBeforeMerge?.({target:e,source:t,targetType:s,sourceType:l,parent:n.parent}),"Object"===s&&"Object"===l?(o=deepMergeObjects(e,t,n),a="Object"):"Array"===s&&"Array"===l?(o=deepMergeArrays(e,t,n),a="Array"):"Set"===s&&"Set"===l?(o=deepMergeSets(e,t,n),a="Set"):"Map"===s&&"Map"===l?(o=deepMergeMaps(e,t,n),a="Map"):(i=!1,o=e),n?.onAfterMerge?.({result:o,target:e,source:t,targetType:s,sourceType:l,mergeType:a,parent:r.parent}),{result:o,flag:i,mergeType:a}},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 n=getDataType(e),a=getDataType(t);if("Object"!==n||"Object"!==a)return e;const o=Object.assign({inheritMissing:!0,targetClone:!1,useEnable:!0},r);let s={};s=o.targetClone?shallowCopy(e):e;for(let e in t){const n=s[e],a=t[e];if(t.hasOwnProperty(e)&&s.hasOwnProperty(e)){const t=smartMerger(n,a,{...r,parent:s});if(t.flag)t.mergeType?"Object"===t.mergeType&&(s[e]=t.result):s[e]=a;else{let t=o.useEnable?mergeEnableObject(n,a):a;n!==t&&null===t?"ignore"===o.nullBehavior||("delete"===o.nullBehavior?Reflect.deleteProperty(s,e):s[e]=t):n!==t&&void 0===t?"ignore"===o.undefinedBehavior||("delete"===o.undefinedBehavior?Reflect.deleteProperty(s,e):s[e]=a):s[e]=a}}else t.hasOwnProperty(e)&&!s.hasOwnProperty(e)&&o.inheritMissing&&(s[e]=a)}if(o.useSymbol){let e=Object.getOwnPropertySymbols(t);if(e.length)for(let r of e)s[r]=t[r]}return s},deepMergeArrays=(e,t,r={})=>{if(!Array.isArray(e)||!Array.isArray(t))return e;const n=Object.assign({dataMode:"clear",inheritMissing:!0,targetClone:!1},r),a=n.targetClone?[...e]:e;if("replace"===n.dataMode)for(let e=0;e<t.length&&(n.inheritMissing||!(e>=a.length));e++){smartMerger(a[e],t[e],{...n,parent:a}).flag||(a[e]=t[e])}else"concat"===n.dataMode||(a.length=0),a.push(...t);return a},deepMergeMaps=(e,t,r={})=>{if(!(e instanceof Map&&t instanceof Map))return e;const n=Object.assign({inheritMissing:!0,targetClone:!1,useEnable:!0},r),a=n.targetClone?new Map([...e]):e;for(const[e,o]of t.entries())if(a.has(e)){const t=a.get(e),r=o,s=smartMerger(t,r,n);s.flag?"Object"===s.mergeType&&a.set(e,s.result):a.set(e,r)}else r.inheritMissing&&a.set(e,o);return a},deepMergeSets=(e,t,r={})=>{if(!(e instanceof Set&&t instanceof Set))return e;const n=Object.assign({dataMode:"clear",inheritMissing:!0,targetClone:!1,useEnable:!0},r),a=n.targetClone?new Set(...e):e;if("replace"===n.dataMode){const e=[...a],r=[...t],o=smartMerger(e,r,n);a.clear();for(let e of o.result)a.add(e)}else if("concat"===n.dataMode)for(let e of t)a.add(e);else{a.clear();for(let e of t)a.add(e)}return a};return smartMerger(e,t,n).result},shallowCopy:shallowCopy,copyObjectWithSymbol:copyObjectWithSymbol,getEl:getEl,getEls:(e,t=document.body)=>{let r=getDataType(e),n=getEl(t),a=n&&n instanceof HTMLTemplateElement?n.content:n||document,o=[];return isEmpty(e)?o:(r.includes("HTML")?o.push(e):"String"===r?o=(e=e.trim()).split(",").map(e=>[...a.querySelectorAll(e)]).flat():"Array"===r&&(o=e.map(e=>getEl(e,n))),o.filter(Boolean))},createEl:createEl,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 n=new FileReader;n.onload=()=>{"string"==typeof n.result?t(n.result):r(new Error("FileReader result is not a string"))},n.onerror=()=>{r(n.error||new Error("Unknown error occurred during file reading"))},n.readAsDataURL(e)}),NAMESPACE:"ax",ALIAS:n,COMMA:",",SPACE:" ",trim:trim,parseClasses:parseClasses,getClasses:e=>{let t=getEl(e);return t?parseClasses(t.getAttribute("class")||""):[]},addClasses:addClasses,removeClasses:(e,t,r)=>{const n=getEl(e),a=parseClasses(t);n&&0!==a.length&&a.forEach(e=>{let t;r?(t=r(e),!0===t?n.classList.remove(e):"string"==typeof t&&t&&n.classList.remove(t)):n.classList.remove(e)})},createTools:e=>{const t=createEl("span",{class:"ax-box-tools"}),renderFn=e=>{const t={},r=e.extendable?`<i ${n}="arrow"></i>`:"",a=(e.icon?`<i ${n}="icon">${e.icon}</i>`:"")+(e.disk?`<i ${n}="disk"><img src="${e.disk}"/></i>`:"")+(e.cube?`<i ${n}="cube"><img src="${e.cube}"/></i>`:"")+(e.image?`<i ${n}="image"><img src="${e.image}"/></i>`:"")+(e.label?`<i ${n}="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(`[${n}="icon"]`),e.cubeEl=e.wrapEl.querySelector(`[${n}="cube"]`),e.diskEl=e.wrapEl.querySelector(`[${n}="disk"]`),e.imageEl=e.wrapEl.querySelector(`[${n}="image"]`),e.labelEl=e.wrapEl.querySelector(`[${n}="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}}});
15
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).utils=t()}(this,function(){"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),n=Object.assign({cloneSet:!0,cloneMap:!0,cloneObject:!0,cloneArray:!0,cloneDate:!0,cloneRegex:!0},t);if(n.interceptor&&"function"==typeof n.interceptor){let t=n.interceptor({input:e,type:r,parent:n.parent});if(t)return t}n.onBeforeClone?.({input:e,type:r,parent:n.parent});let a,s=!0;if("Object"===r&&n.cloneObject){const t={},r=Object.getOwnPropertySymbols(e);for(const r in e)t[r]=deepClone(e[r],n);if(r.length>0)for(const a of r)t[a]=deepClone(e[a],{...n,parent:e});a=t}else if("Array"===r&&n.cloneArray)a=e.map(t=>deepClone(t,{...n,parent:e}));else if("Map"===r&&n.cloneMap){const t=new Map;for(const[r,a]of e)t.set(deepClone(r,n),deepClone(a,{...n,parent:e}));a=t}else if("Set"===r&&n.cloneSet){const t=new Set;for(const r of e)t.add(deepClone(r,{...n,parent:e}));a=t}else if("Date"===r&&n.cloneDate)a=new Date(e.getTime());else if("RegExp"===r&&n.cloneRegex){const t=e;a=new RegExp(t.source,t.flags)}else a=e,s=!1;return n.onAfterClone?.({output:a,input:e,type:r,cloned:s,parent:n.parent}),a},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},e=["push","pop","shift","unshift","splice","sort","reverse","copyWithin","fill"],t=["add","delete","clear"],r=["set","delete","clear"],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},getEl=(e,t=document.body)=>{let r=getDataType(e),n=getDataType(t),a=n.includes("HTML")||"ShadowRoot"===n?t:document.querySelector(t),s=a&&a instanceof HTMLTemplateElement?a.content:a,o=null;if(e)if(r.includes("HTML"))o=e;else if("String"===r)try{o=(s||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},createEl=(e,t,r)=>{let n=(e=e||"div").toUpperCase().trim(),a=document.createElement(n),s=getDataType(t);if(t&&"Object"===s)for(let e in t)t.hasOwnProperty(e)&&a.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"===n)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)})(a,r),a},n="rep",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 n=getEl(e),a=parseClasses(t);n&&0!==a.length&&a.forEach(e=>{let t;r?(t=r(e),!0===t?n.classList.add(e):"string"==typeof t&&t&&n.classList.add(t)):n.classList.add(e)})};return{getDataType:getDataType,requireTypes:(e,t,r)=>{let n=Array.isArray(t)?t:[t],a=getDataType(e),s=a.toLowerCase(),o=n.map(e=>e.toLowerCase()),l=s.includes("html")?"element":s;if(r)try{if(!o.includes(l))throw new TypeError(`Expected data type(s): [${o.join(", ")}], but got: ${l}`)}catch(e){r(e,a)}else if(!o.includes(l))throw new TypeError(`Expected data type(s): [${o.join(", ")}], but got: ${l}`);return a},deepClone:deepClone,deepCloneToJSON:deepCloneToJSON,wrapArrayMethods:({target:t,onBeforeMutate:r=()=>{},onAfterMutate:n=()=>{},allowList:a,props:s={}})=>{if(!Array.isArray(t))throw new TypeError("The 'target' parameter must be an array.");a&&!a?.length||(a=e);const o={};for(let e of a)o[e]=function(...a){const o={},l=t.length;switch(e){case"push":case"unshift":o.addedItems=[...a];break;case"pop":o.poppedItem=t[l-1];break;case"shift":o.shiftedItem=t[0];break;case"splice":const[e,r]=a,n=e<0?Math.max(l+e,0):Math.min(e,l),s=void 0===r?l-n:r;o.deletedItems=t.slice(n,n+s);break;case"sort":case"reverse":o.oldSnapshot=[...t];break;case"fill":case"copyWithin":const i=a[1]||0,c=void 0===a[2]?l:a[2];o.oldItems=t.slice(i,c),o.start=i,o.end=c}r?.(o);const i=Array.prototype[e].apply(t,a),c={value:i,key:e,args:a,context:o,target:t,...s};return n?.(c),i};return o},arrayMutableMethods:e,setMutableMethods:t,mapMutableMethods:r,wrapSetMethods:({target:e,onBeforeMutate:r=()=>{},onAfterMutate:n=()=>{},allowList:a=t,props:s={}})=>{if(!(e instanceof Set))throw new TypeError("The 'target' parameter must be a Set.");const o={},createWrappedMethod=t=>function(...a){const o={};switch(t){case"add":{const[t]=a;o.addedItem=t,o.existed=e.has(t);break}case"delete":{const[t]=a;o.existed=e.has(t),o.deletedItem=o.existed?t:void 0;break}case"clear":o.clearedItems=Array.from(e),o.previousSize=e.size}r(o);const l=e[t].apply(e,a),i={method:t,result:l,args:a,context:o,target:e,...s};return n(i),l};for(const e of a)t.includes(e)&&(o[e]=createWrappedMethod(e));return Object.defineProperty(o,"target",{get:()=>e,enumerable:!1,configurable:!1}),o},wrapMapMethods:({target:e,onBeforeMutate:t=()=>{},onAfterMutate:n=()=>{},allowList:a=r,props:s={}})=>{if(!(e instanceof Map))throw new TypeError("The 'target' parameter must be a Map.");const o={},createWrappedMethod=r=>function(...a){const o={};switch(r){case"set":{const[t,r]=a;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]=a;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[r].apply(e,a),i={method:r,result:l,args:a,context:o,target:e,...s};return n(i),l};for(const e of a)r.includes(e)&&(o[e]=createWrappedMethod(e));return Object.defineProperty(o,"target",{get:()=>e,enumerable:!1,configurable:!1}),o},getUniqueId:(e={})=>{const t=e.prefix,r=e.suffix,n=e.base10,a=e.base36;return`${t?t+"-":""}${Date.now()}${a?"-"+Math.random().toString(36).substring(2,11):""}${n?"-"+Math.floor(1e4*Math.random()).toString().padStart(4,"0"):""}${r?"-"+r:""}`},deepMerge:(e,t,r={})=>{const n=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,n)=>{let a,s,o=getDataType(e),l=getDataType(t),i=!0;if(n.interceptor&&"function"==typeof n.interceptor){let r=n.interceptor({target:e,source:t,targetType:o,sourceType:l,parent:n.parent});if(r){if(null===r?.target||null===r?.source)return r;e=r.target,t=r.source}}return n?.onBeforeMerge?.({target:e,source:t,targetType:o,sourceType:l,parent:n.parent}),"Object"===o&&"Object"===l?(s=deepMergeObjects(e,t,n),a="Object"):"Array"===o&&"Array"===l?(s=deepMergeArrays(e,t,n),a="Array"):"Set"===o&&"Set"===l?(s=deepMergeSets(e,t,n),a="Set"):"Map"===o&&"Map"===l?(s=deepMergeMaps(e,t,n),a="Map"):(i=!1,s=e),n?.onAfterMerge?.({result:s,target:e,source:t,targetType:o,sourceType:l,mergeType:a,parent:r.parent}),{result:s,flag:i,mergeType:a}},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 n=getDataType(e),a=getDataType(t);if("Object"!==n||"Object"!==a)return e;const s=Object.assign({inheritMissing:!0,targetClone:!1,useEnable:!0},r);let o={};o=s.targetClone?shallowCopy(e):e;for(let e in t){const n=o[e],a=t[e];if(t.hasOwnProperty(e)&&o.hasOwnProperty(e)){const t=smartMerger(n,a,{...r,parent:o});if(t.flag)t.mergeType?"Object"===t.mergeType&&(o[e]=t.result):o[e]=a;else{let t=s.useEnable?mergeEnableObject(n,a):a;n!==t&&null===t?"ignore"===s.nullBehavior||("delete"===s.nullBehavior?Reflect.deleteProperty(o,e):o[e]=t):n!==t&&void 0===t?"ignore"===s.undefinedBehavior||("delete"===s.undefinedBehavior?Reflect.deleteProperty(o,e):o[e]=a):o[e]=a}}else t.hasOwnProperty(e)&&!o.hasOwnProperty(e)&&s.inheritMissing&&(o[e]=a)}if(s.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 n=Object.assign({dataMode:"clear",inheritMissing:!0,targetClone:!1},r),a=n.targetClone?[...e]:e;if("replace"===n.dataMode)for(let e=0;e<t.length&&(n.inheritMissing||!(e>=a.length));e++){smartMerger(a[e],t[e],{...n,parent:a}).flag||(a[e]=t[e])}else"concat"===n.dataMode||(a.length=0),a.push(...t);return a},deepMergeMaps=(e,t,r={})=>{if(!(e instanceof Map&&t instanceof Map))return e;const n=Object.assign({inheritMissing:!0,targetClone:!1,useEnable:!0},r),a=n.targetClone?new Map([...e]):e;for(const[e,s]of t.entries())if(a.has(e)){const t=a.get(e),r=s,o=smartMerger(t,r,n);o.flag?"Object"===o.mergeType&&a.set(e,o.result):a.set(e,r)}else r.inheritMissing&&a.set(e,s);return a},deepMergeSets=(e,t,r={})=>{if(!(e instanceof Set&&t instanceof Set))return e;const n=Object.assign({dataMode:"clear",inheritMissing:!0,targetClone:!1,useEnable:!0},r),a=n.targetClone?new Set(...e):e;if("replace"===n.dataMode){const e=[...a],r=[...t],s=smartMerger(e,r,n);a.clear();for(let e of s.result)a.add(e)}else if("concat"===n.dataMode)for(let e of t)a.add(e);else{a.clear();for(let e of t)a.add(e)}return a};return smartMerger(e,t,n).result},shallowCopy:shallowCopy,copyObjectWithSymbol:copyObjectWithSymbol,getEl:getEl,getEls:(e,t=document.body)=>{let r=getDataType(e),n=getEl(t),a=n&&n instanceof HTMLTemplateElement?n.content:n||document,s=[];return isEmpty(e)?s:(r.includes("HTML")?s.push(e):"String"===r?s=(e=e.trim()).split(",").map(e=>[...a.querySelectorAll(e)]).flat():"Array"===r&&(s=e.map(e=>getEl(e,n))),s.filter(Boolean))},createEl:createEl,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 n=new FileReader;n.onload=()=>{"string"==typeof n.result?t(n.result):r(new Error("FileReader result is not a string"))},n.onerror=()=>{r(n.error||new Error("Unknown error occurred during file reading"))},n.readAsDataURL(e)}),NAMESPACE:"ax",ALIAS:n,COMMA:",",SPACE:" ",trim:trim,parseClasses:parseClasses,getClasses:e=>{let t=getEl(e);return t?parseClasses(t.getAttribute("class")||""):[]},addClasses:addClasses,removeClasses:(e,t,r)=>{const n=getEl(e),a=parseClasses(t);n&&0!==a.length&&a.forEach(e=>{let t;r?(t=r(e),!0===t?n.classList.remove(e):"string"==typeof t&&t&&n.classList.remove(t)):n.classList.remove(e)})},createTools:e=>{const t=createEl("span",{class:"ax-box-tools"}),renderFn=e=>{const t={},r=e.extendable?`<i ${n}="arrow"></i>`:"",a=(e.icon?`<i ${n}="icon">${e.icon}</i>`:"")+(e.disk?`<i ${n}="disk"><img src="${e.disk}"/></i>`:"")+(e.cube?`<i ${n}="cube"><img src="${e.cube}"/></i>`:"")+(e.image?`<i ${n}="image"><img src="${e.image}"/></i>`:"")+(e.label?`<i ${n}="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(`[${n}="icon"]`),e.cubeEl=e.wrapEl.querySelector(`[${n}="cube"]`),e.diskEl=e.wrapEl.querySelector(`[${n}="disk"]`),e.imageEl=e.wrapEl.querySelector(`[${n}="image"]`),e.labelEl=e.wrapEl.querySelector(`[${n}="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},typeWriter:(e,t)=>{const r=t.speed||100;return new Promise(n=>{t?.onBeforeType?.(e);let a=0;const s=setInterval(()=>{if(a<e.length){const r=e.charAt(a),n=e.substring(0,a+1);t?.onDuringType?.(r,n),a++}else clearInterval(s),n(e),t?.onAfterType?.(e)},r)})},parseLLMStream:async(e,t)=>{if(!e.body)throw new Error("Response body is null. Cannot read stream.");const r=e.body.getReader(),n=new TextDecoder("utf-8");let a="";const s={fullText:"",finishReason:null,usage:null,isCompleted:!1};try{for(;;){const{done:e,value:o}=await r.read();if(e)break;a+=n.decode(o,{stream:!0});let l=a.split("\n");a=l.pop()||"";for(const e of l){const r=e.trim();if(!r||!r.startsWith("data: "))continue;const n=r.substring(6);if("[DONE]"!==n)try{const e=JSON.parse(n),r=e.choices?.[0],a=r?.delta?.content||"";a&&(s.fullText+=a,t?.(a)),r?.finish_reason&&(s.finishReason=r.finish_reason),e.usage&&(s.usage=e.usage)}catch(e){}else s.isCompleted=!0}}}catch(e){throw e}return s}}});
package/dist.zip CHANGED
Binary file
package/modules.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Last modified: 2026/01/07 13:47:21
2
+ * Last modified: 2026/01/10 10:35:26
3
3
  */
4
4
  'use strict';
5
5
  import deepClone from './src/deepClone';
@@ -31,6 +31,8 @@ import createTools from './src/createTools';
31
31
  import getClasses from './src/getClasses';
32
32
  import addClasses from './src/addClasses';
33
33
  import removeClasses from './src/removeClasses';
34
+ import typeWriter from './src/typeWriter';
35
+ import parseLLMStream from './src/parseLLMStream';
34
36
  const utils = {
35
37
  //executeStr,
36
38
  getDataType,
@@ -64,5 +66,7 @@ const utils = {
64
66
  addClasses,
65
67
  removeClasses,
66
68
  createTools,
69
+ typeWriter,
70
+ parseLLMStream,
67
71
  };
68
72
  export default utils;
package/modules.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Last modified: 2026/01/07 13:47:21
2
+ * Last modified: 2026/01/10 10:35:26
3
3
  */
4
4
  'use strict'
5
5
  import deepClone from './src/deepClone';
@@ -35,13 +35,12 @@ import COMMA from './src/comma';
35
35
  import SPACE from './src/space';
36
36
  import trim from './src/trim';
37
37
  import parseClasses from './src/parseClasses';
38
- import classes from './src/classes';
39
38
  import createTools from './src/createTools';
40
39
  import getClasses from './src/getClasses';
41
40
  import addClasses from './src/addClasses';
42
41
  import removeClasses from './src/removeClasses';
43
-
44
-
42
+ import typeWriter from './src/typeWriter';
43
+ import parseLLMStream from './src/parseLLMStream';
45
44
 
46
45
  const utils = {
47
46
  //executeStr,
@@ -76,6 +75,8 @@ const utils = {
76
75
  addClasses,
77
76
  removeClasses,
78
77
  createTools,
78
+ typeWriter,
79
+ parseLLMStream,
79
80
 
80
81
  };
81
82
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codady/utils",
3
- "version": "0.0.31",
3
+ "version": "0.0.33",
4
4
  "author": "AXUI Development Team",
5
5
  "license": "MIT",
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.",
@@ -0,0 +1,78 @@
1
+ /**
2
+ * @since Last modified: 2026/01/10 10:35:28
3
+ * @function parseLLMStream
4
+ * High-level Stream Parser: Aggregates content, tracks status, and collects final statistics.
5
+ * * @param response - The Fetch API Response object containing the ReadableStream.
6
+ * @param onChunk - Callback triggered whenever a new text fragment is received.
7
+ * @returns A promise that resolves to the full aggregated StreamResult.
8
+ */
9
+ const parseLLMStream = async (response, onChunk) => {
10
+ // Ensure the body exists before attempting to read
11
+ if (!response.body) {
12
+ throw new Error("Response body is null. Cannot read stream.");
13
+ }
14
+ const reader = response.body.getReader(), decoder = new TextDecoder("utf-8");
15
+ // Buffer to store partial lines that haven't reached a newline character yet
16
+ let buffer = "";
17
+ const finalResult = {
18
+ fullText: "",
19
+ finishReason: null,
20
+ usage: null,
21
+ isCompleted: false
22
+ };
23
+ try {
24
+ while (true) {
25
+ const { done, value } = await reader.read();
26
+ // 'done' is true when the connection closes
27
+ if (done)
28
+ break;
29
+ // Decode the binary chunk and append to buffer.
30
+ // { stream: true } ensures multi-byte characters (like Emojis/Chinese) aren't corrupted.
31
+ buffer += decoder.decode(value, { stream: true });
32
+ // Split buffer by newlines to process complete SSE "data: " lines
33
+ let lines = buffer.split("\n");
34
+ // Pop the last element: if the line is incomplete, it stays in buffer for the next chunk.
35
+ // If the line was complete, pop() leaves an empty string in buffer.
36
+ buffer = lines.pop() || "";
37
+ for (const line of lines) {
38
+ const trimmed = line.trim();
39
+ if (!trimmed || !trimmed.startsWith("data: "))
40
+ continue;
41
+ const message = trimmed.substring(6); // Extract the JSON string after "data: "
42
+ // Signal: [DONE] indicates the server has finished sending data
43
+ if (message === "[DONE]") {
44
+ finalResult.isCompleted = true;
45
+ continue;
46
+ }
47
+ try {
48
+ const json = JSON.parse(message), choice = json.choices?.[0],
49
+ // 1. Accumulate Content: Delta contains the incremental text
50
+ content = choice?.delta?.content || "";
51
+ if (content) {
52
+ finalResult.fullText += content;
53
+ // Real-time callback for UI typewriter effect
54
+ onChunk?.(content);
55
+ }
56
+ // 2. Capture Finish Reason: Explains why generation ended (e.g., 'stop', 'length')
57
+ if (choice?.finish_reason) {
58
+ finalResult.finishReason = choice.finish_reason;
59
+ }
60
+ // 3. Capture Usage: Token statistics usually arrive in the last few chunks
61
+ if (json.usage) {
62
+ finalResult.usage = json.usage;
63
+ }
64
+ }
65
+ catch (e) {
66
+ // Log parsing errors but continue processing other lines
67
+ console.error("Error parsing SSE JSON message:", e, "Message:", message);
68
+ }
69
+ }
70
+ }
71
+ }
72
+ catch (err) {
73
+ console.error("Critical error while reading ReadableStream:", err);
74
+ throw err; // Re-throw to let the caller handle network/stream failures
75
+ }
76
+ return finalResult;
77
+ };
78
+ export default parseLLMStream;
@@ -0,0 +1,121 @@
1
+ /**
2
+ * @since Last modified: 2026/01/10 10:35:28
3
+ * @function parseLLMStream
4
+ * High-level Stream Parser: Aggregates content, tracks status, and collects final statistics.
5
+ * * @param response - The Fetch API Response object containing the ReadableStream.
6
+ * @param onChunk - Callback triggered whenever a new text fragment is received.
7
+ * @returns A promise that resolves to the full aggregated StreamResult.
8
+ */
9
+
10
+ /**
11
+ * Interface representing the token usage statistics returned by the LLM.
12
+ */
13
+ interface StreamUsage {
14
+ prompt_tokens: number;
15
+ completion_tokens: number;
16
+ total_tokens: number;
17
+ }
18
+
19
+ /**
20
+ * Interface representing the final aggregated result of the stream.
21
+ */
22
+ interface StreamResult {
23
+ /** The full concatenated string of all content chunks received. */
24
+ fullText: string;
25
+ /** The reason why the model stopped generating (e.g., 'stop', 'length'). */
26
+ finishReason: string | null;
27
+ /** The final token usage statistics, if provided by the stream. */
28
+ usage: StreamUsage | null;
29
+ /** Indicates if the stream reached the explicit [DONE] signal. */
30
+ isCompleted: boolean;
31
+ }
32
+
33
+
34
+ const parseLLMStream = async (
35
+ response: Response,
36
+ onChunk?: (content: string) => void
37
+ ): Promise<StreamResult> => {
38
+ // Ensure the body exists before attempting to read
39
+ if (!response.body) {
40
+ throw new Error("Response body is null. Cannot read stream.");
41
+ }
42
+
43
+ const reader = response.body.getReader(),
44
+ decoder = new TextDecoder("utf-8");
45
+
46
+ // Buffer to store partial lines that haven't reached a newline character yet
47
+ let buffer = "";
48
+
49
+ const finalResult: StreamResult = {
50
+ fullText: "",
51
+ finishReason: null,
52
+ usage: null,
53
+ isCompleted: false
54
+ };
55
+
56
+ try {
57
+ while (true) {
58
+ const { done, value } = await reader.read();
59
+
60
+ // 'done' is true when the connection closes
61
+ if (done) break;
62
+
63
+ // Decode the binary chunk and append to buffer.
64
+ // { stream: true } ensures multi-byte characters (like Emojis/Chinese) aren't corrupted.
65
+ buffer += decoder.decode(value, { stream: true });
66
+
67
+ // Split buffer by newlines to process complete SSE "data: " lines
68
+ let lines = buffer.split("\n");
69
+
70
+ // Pop the last element: if the line is incomplete, it stays in buffer for the next chunk.
71
+ // If the line was complete, pop() leaves an empty string in buffer.
72
+ buffer = lines.pop() || "";
73
+
74
+ for (const line of lines) {
75
+ const trimmed = line.trim();
76
+ if (!trimmed || !trimmed.startsWith("data: ")) continue;
77
+
78
+ const message = trimmed.substring(6); // Extract the JSON string after "data: "
79
+
80
+ // Signal: [DONE] indicates the server has finished sending data
81
+ if (message === "[DONE]") {
82
+ finalResult.isCompleted = true;
83
+ continue;
84
+ }
85
+
86
+ try {
87
+ const json = JSON.parse(message),
88
+ choice = json.choices?.[0],
89
+
90
+ // 1. Accumulate Content: Delta contains the incremental text
91
+ content = choice?.delta?.content || "";
92
+ if (content) {
93
+ finalResult.fullText += content;
94
+ // Real-time callback for UI typewriter effect
95
+ onChunk?.(content);
96
+ }
97
+
98
+ // 2. Capture Finish Reason: Explains why generation ended (e.g., 'stop', 'length')
99
+ if (choice?.finish_reason) {
100
+ finalResult.finishReason = choice.finish_reason;
101
+ }
102
+
103
+ // 3. Capture Usage: Token statistics usually arrive in the last few chunks
104
+ if (json.usage) {
105
+ finalResult.usage = json.usage;
106
+ }
107
+ } catch (e) {
108
+ // Log parsing errors but continue processing other lines
109
+ console.error("Error parsing SSE JSON message:", e, "Message:", message);
110
+ }
111
+ }
112
+ }
113
+ } catch (err) {
114
+ console.error("Critical error while reading ReadableStream:", err);
115
+ throw err; // Re-throw to let the caller handle network/stream failures
116
+ }
117
+
118
+ return finalResult;
119
+ };
120
+
121
+ export default parseLLMStream;
package/src/trim.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @since Last modified: 2026/01/07 14:15:24
2
+ * @since Last modified: 2026/01/09 08:12:11
3
3
  * @function trim
4
4
  * @description Removes spaces, newlines, and carriage returns from the string.
5
5
  * Supports removing spaces at specific positions (start, end, or both) or globally within the string.
@@ -10,7 +10,7 @@
10
10
  * - 'end' to remove spaces from the end.
11
11
  * - 'both' to remove spaces from both the start and the end.
12
12
  * - 'global' to remove all spaces, newlines, and carriage returns globally.
13
- * If omitted, the default is ''.
13
+ * If omitted, the default is 'compress'.
14
14
  * @returns {string} - The string after trimming, without altering the original string.
15
15
  * @example
16
16
  * trim(' My name is Lily '); // Returns 'My name is Lily'
@@ -20,7 +20,7 @@
20
20
  * trim(' My name is Lily ', 'global'); // Returns 'MynameisLily'
21
21
  */
22
22
  'use strict';
23
- const trim = (str, placement = '') => {
23
+ const trim = (str, placement = 'compress') => {
24
24
  if (typeof str !== 'string') {
25
25
  console.warn('Expected a string input');
26
26
  return '';
package/src/trim.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @since Last modified: 2026/01/07 14:15:24
2
+ * @since Last modified: 2026/01/09 08:12:11
3
3
  * @function trim
4
4
  * @description Removes spaces, newlines, and carriage returns from the string.
5
5
  * Supports removing spaces at specific positions (start, end, or both) or globally within the string.
@@ -10,7 +10,7 @@
10
10
  * - 'end' to remove spaces from the end.
11
11
  * - 'both' to remove spaces from both the start and the end.
12
12
  * - 'global' to remove all spaces, newlines, and carriage returns globally.
13
- * If omitted, the default is ''.
13
+ * If omitted, the default is 'compress'.
14
14
  * @returns {string} - The string after trimming, without altering the original string.
15
15
  * @example
16
16
  * trim(' My name is Lily '); // Returns 'My name is Lily'
@@ -21,7 +21,7 @@
21
21
  */
22
22
  'use strict';
23
23
 
24
- const trim = (str: string, placement: 'start' | 'end' | 'both' | 'global' | ''=''): string => {
24
+ const trim = (str: string, placement: 'start' | 'end' | 'both' | 'global' | 'compress'='compress'): string => {
25
25
  if (typeof str !== 'string') {
26
26
  console.warn('Expected a string input');
27
27
  return '';
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Simulates a typewriter effect by typing text character by character with a customizable speed.
3
+ * @param text - The text to be typed.
4
+ * @param options - Configuration options for the typewriter effect.
5
+ * @returns A Promise that resolves when the typing is complete.
6
+ */
7
+ const typeWriter = (text, options) => {
8
+ const speed = options.speed || 100; // Set typing speed (default to 100ms per character)
9
+ return new Promise((resolve) => {
10
+ // Callback before typing starts
11
+ options?.onBeforeType?.(text);
12
+ let index = 0;
13
+ // Timer to type the text character by character at the given speed
14
+ const timer = setInterval(() => {
15
+ if (index < text.length) {
16
+ const char = text.charAt(index); // Get the character at the current index
17
+ const typedText = text.substring(0, index + 1); // The text typed so far
18
+ // Callback during typing each character
19
+ options?.onDuringType?.(char, typedText);
20
+ index++;
21
+ }
22
+ else {
23
+ // Clear the timer once typing is complete
24
+ clearInterval(timer);
25
+ // Resolve the Promise when typing is complete
26
+ resolve(text);
27
+ // Callback after typing is finished
28
+ options?.onAfterType?.(text);
29
+ }
30
+ }, speed);
31
+ });
32
+ };
33
+ export default typeWriter;
@@ -0,0 +1,43 @@
1
+ interface TypeWriterOptions {
2
+ speed?: number; // Speed of typing in milliseconds (default is 100ms)
3
+ onBeforeType?: (text: string) => void; // Callback before typing starts
4
+ onDuringType?: (char: string, typedText: string) => void; // Callback during typing each character
5
+ onAfterType?: (text: string) => void; // Callback after typing is finished
6
+ }
7
+
8
+ /**
9
+ * Simulates a typewriter effect by typing text character by character with a customizable speed.
10
+ * @param text - The text to be typed.
11
+ * @param options - Configuration options for the typewriter effect.
12
+ * @returns A Promise that resolves when the typing is complete.
13
+ */
14
+ const typeWriter = (text: string, options: TypeWriterOptions): Promise<string> => {
15
+ const speed = options.speed || 100; // Set typing speed (default to 100ms per character)
16
+
17
+ return new Promise((resolve) => {
18
+ // Callback before typing starts
19
+ options?.onBeforeType?.(text);
20
+
21
+ let index = 0;
22
+
23
+ // Timer to type the text character by character at the given speed
24
+ const timer = setInterval(() => {
25
+ if (index < text.length) {
26
+ const char = text.charAt(index); // Get the character at the current index
27
+ const typedText = text.substring(0, index + 1); // The text typed so far
28
+ // Callback during typing each character
29
+ options?.onDuringType?.(char, typedText);
30
+ index++;
31
+ } else {
32
+ // Clear the timer once typing is complete
33
+ clearInterval(timer);
34
+ // Resolve the Promise when typing is complete
35
+ resolve(text);
36
+ // Callback after typing is finished
37
+ options?.onAfterType?.(text);
38
+ }
39
+ }, speed);
40
+ });
41
+ }
42
+
43
+ export default typeWriter;