├── src ├── asserts │ ├── index.ts │ ├── assertNotExists.ts │ ├── assertExists.ts │ └── assertEquals.ts ├── flex │ └── index.ts ├── iframe │ ├── index.ts │ └── postMessage.ts ├── https │ ├── index.ts │ └── httpsRedirect.ts ├── qrcode │ └── index.ts ├── theme │ ├── index.ts │ └── useSystemColor.ts ├── compress │ ├── index.ts │ └── compressCss.ts ├── parse │ ├── index.ts │ ├── parseCss.ts │ └── parse.ts ├── request │ ├── index.ts │ ├── sendBeacon.ts │ └── asyncPool.ts ├── speak │ └── index.ts ├── is │ ├── isArray.ts │ ├── isOnline.ts │ ├── isBrowser.ts │ ├── isDate.ts │ ├── isFalse.ts │ ├── isFn.ts │ ├── isNaN.ts │ ├── isStr.ts │ ├── isBool.ts │ ├── isNull.ts │ ├── isUndef.ts │ ├── isWin.ts │ ├── isNum.ts │ ├── isDef.ts │ ├── isTrue.ts │ ├── isContainCn.ts │ ├── isObject.ts │ ├── isSymbol.ts │ ├── isSocketUrl.ts │ ├── isBlob.ts │ ├── isNameEn.ts │ ├── isRelative.ts │ ├── isActive.ts │ ├── isComment.ts │ ├── isElement.ts │ ├── isReg.ts │ ├── isCalc.ts │ ├── isHex.ts │ ├── isNameCn.ts │ ├── isNil.ts │ ├── isTrainNumber.ts │ ├── isEmail.ts │ ├── isIPv4.ts │ ├── isPercent.ts │ ├── isPostCode.ts │ ├── isUrl.ts │ ├── isNm.ts │ ├── isVue.ts │ ├── isWeakSet.ts │ ├── isRem.ts │ ├── isVh.ts │ ├── isVw.ts │ ├── isDivElement.ts │ ├── isWeakMap.ts │ ├── isSet.ts │ ├── isMap.ts │ ├── isPlainObject.ts │ ├── isStyleElement.ts │ ├── isIFrameElement.ts │ ├── isScriptElement.ts │ ├── isShadowRoot.ts │ ├── isEsModule.ts │ ├── isMobile.ts │ ├── isPromise.ts │ ├── isProxyDocument.ts │ ├── isVersion.ts │ ├── isImageElement.ts │ ├── isNode.ts │ ├── isAbsolute.ts │ ├── isVar.ts │ ├── isPx.ts │ ├── isFileType.ts │ ├── isSupportCamera.ts │ ├── isLeapYear.ts │ ├── isEmpty.ts │ ├── isKeyOf.ts │ ├── isBase64.ts │ ├── isIdCard.ts │ ├── isSoldierId.ts │ ├── isSupportWebp.ts │ ├── isFile.ts │ ├── isBottom.ts │ ├── isSameDay.ts │ └── isVideo.ts ├── webComponent │ └── index.ts ├── storage │ ├── index.ts │ ├── jsLocal.ts │ └── jsSession.ts ├── html │ ├── index.ts │ ├── escapeHtml.ts │ └── unescapeHtml.ts ├── log │ ├── index.ts │ ├── log.ts │ └── globalErrorCapture.ts ├── script │ ├── index.ts │ ├── addStyleScoped.ts │ ├── addLink.ts │ └── addScript.ts ├── css │ ├── hasClassName.ts │ ├── getCssVar.ts │ ├── index.ts │ ├── setStyle.ts │ ├── addStyleRules.ts │ ├── setCssVar.ts │ ├── addClass.ts │ ├── removeClass.ts │ ├── removeStyle.ts │ └── getClasses.ts ├── message │ ├── index.ts │ └── createEventBus.ts ├── scroll │ ├── index.ts │ ├── getScrollPosition.ts │ ├── getScrollProgress.ts │ ├── scrollToTop.ts │ └── scrollToView.ts ├── num │ ├── index.ts │ ├── isEven.ts │ └── multiply.ts ├── js │ ├── noop.ts │ ├── noopFalse.ts │ ├── sleep.ts │ ├── executeStr.ts │ ├── parallel.ts │ ├── fnToUrl.ts │ ├── streamToUrl.ts │ ├── curry.ts │ ├── handleImageError.ts │ ├── retryAsync.ts │ ├── chainFns.ts │ ├── useSwitch.ts │ ├── singleModel.ts │ ├── reduceAsync.ts │ ├── timeout.ts │ ├── copy.ts │ ├── listenStack.ts │ ├── index.ts │ ├── useServiceWorker.ts │ ├── nextTick.ts │ ├── useReader.ts │ └── promiseFinally.ts ├── monitor │ ├── index.ts │ ├── timeCost.ts │ └── calFps.ts ├── utils │ ├── assign.ts │ ├── index.ts │ ├── hasOwn.ts │ ├── unmount.ts │ └── beforeUnmount.ts ├── random │ ├── index.ts │ ├── randomHexColor.ts │ ├── randomRgba.ts │ ├── randomRange.ts │ ├── randomArray.ts │ ├── randomStr.ts │ └── randomDate.ts ├── vite │ ├── index.ts │ └── vitePluginExport.ts ├── animate │ ├── cubicIn.ts │ ├── quartIn.ts │ ├── quadIn.ts │ ├── quadOut.ts │ ├── quintIn.ts │ ├── quintOut.ts │ ├── sineOut.ts │ ├── circIn.ts │ ├── circOut.ts │ ├── expoIn.ts │ ├── expoOut.ts │ ├── quartOut.ts │ ├── cubicOut.ts │ ├── sineInOut.ts │ ├── backIn.ts │ ├── backOut.ts │ ├── cubicInOut.ts │ ├── elasticIn.ts │ ├── elasticOut.ts │ ├── quartInOut.ts │ ├── bounceIn.ts │ ├── sineIn.ts │ ├── quadInOut.ts │ ├── circInOut.ts │ ├── quintInOut.ts │ ├── backInOut.ts │ ├── expoInOut.ts │ ├── bounceInOut.ts │ ├── elasticInOut.ts │ ├── bounceOut.ts │ └── index.ts ├── event │ ├── createTextNode.ts │ ├── createFragment.ts │ ├── download.ts │ ├── useRange.ts │ ├── useResizeObserver.ts │ ├── useMouse.ts │ ├── useTimeout.ts │ ├── removeElement.ts │ ├── useWindowScroll.ts │ ├── useElementBounding.ts │ ├── createElement.ts │ ├── useFocus.ts │ └── useClick.ts ├── node │ ├── isInstallPkg.ts │ ├── fileCopy.ts │ ├── withTaskName.ts │ ├── isExist.ts │ ├── isWritable.ts │ ├── transformArgv.ts │ ├── index.ts │ ├── isRust.ts │ ├── hasPkg.ts │ ├── isGo.ts │ ├── isPkg.ts │ ├── getExportBundle.ts │ └── writeFile.ts ├── to │ ├── blobToUrl.ts │ ├── toArray.ts │ ├── index.ts │ ├── fileToBlob.ts │ ├── toAbsolutePath.ts │ ├── base64ToBlob.ts │ ├── fileToArrayBuffer.ts │ ├── toSlice.ts │ ├── base64ToFile.ts │ ├── rgbToHex.ts │ ├── toNumber.ts │ └── hexToRgb.ts ├── worker │ ├── fileSpliceWorker.ts │ └── useNodeWorkerThread.ts ├── date │ ├── index.ts │ ├── getDaysOfMonth.ts │ ├── getFirstDay.ts │ ├── compareDateTime.ts │ ├── compareTime.ts │ └── getDifferenceDays.ts ├── array │ ├── filterEmpty.ts │ ├── index.ts │ ├── chunk.ts │ ├── getAverage.ts │ ├── forEachBack.ts │ ├── toggleItem.ts │ ├── forEach.ts │ ├── mapBack.ts │ └── removeItem.ts ├── canvas │ ├── index.ts │ ├── Canvas.ts │ ├── Point.ts │ ├── Line.ts │ ├── getImageData.ts │ └── Square.ts ├── object │ ├── index.ts │ ├── clearUndefined.ts │ ├── objectToMap.ts │ └── mapToObject.ts ├── screen │ ├── index.ts │ ├── useShare.ts │ ├── fullScreen.ts │ ├── exitFullscreen.ts │ └── picInPic.ts ├── perf │ ├── once.ts │ ├── index.ts │ ├── throttle.ts │ ├── debounce.ts │ └── prefetch.ts └── string │ ├── escapeRegExp.ts │ ├── ensureSuffix.ts │ ├── ensurePrefix.ts │ ├── index.ts │ ├── splice.ts │ ├── parseTime.ts │ ├── spaceFormat.ts │ ├── hyphenate.ts │ ├── hash.ts │ ├── getUrlParam.ts │ ├── camelize.ts │ ├── pwdLevel.ts │ └── trim.ts ├── playground ├── .npmrc ├── src │ ├── types.ts │ ├── composables │ │ ├── index.ts │ │ └── dark.ts │ ├── pages │ │ ├── [...all].vue │ │ ├── hi │ │ │ └── [name].vue │ │ └── README.md │ ├── utils │ │ └── index.ts │ ├── styles │ │ └── main.css │ ├── App.vue │ ├── main.ts │ └── components │ │ └── Footer.vue ├── public │ ├── aini.mp3 │ └── favicon.svg ├── .gitignore ├── shims.d.ts ├── netlify.toml ├── components.d.ts ├── manifest.json ├── tsconfig.json └── server │ └── index.js ├── pnpm-workspace.yaml ├── .github-contributors └── Simon-He95_lazy-js-utils.json ├── assets ├── kv.png ├── zfb.jpg └── wechat.jpg ├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ └── config.yml ├── .npmrc ├── netlify.toml ├── .editorconfig ├── .gitattributes ├── .vscode ├── extensions.json └── settings.json ├── .gitignore ├── vitest.config.ts ├── test ├── canvas │ ├── DotTextCanvas.test.ts │ ├── getImageData.test.ts │ ├── sliderValidation.test.ts │ ├── CreateSignatureCanvas.test.ts │ ├── DotImageCanvas.test.ts │ ├── Canvas.test.ts │ └── removeRoundSpace.test.ts ├── __snapshots__ │ └── basic.test.ts.snap ├── flex │ └── waterfall.test.ts ├── event │ ├── useHead.test.ts │ ├── download.test.ts │ ├── findElement.test.ts │ ├── collisionDetection.test.ts │ ├── dragEvent.test.ts │ ├── createElement.test.ts │ ├── useFocus.test.ts │ ├── useMouse.test.ts │ ├── useHover.test.ts │ ├── createFragment.test.ts │ ├── useKeyBoard.test.ts │ ├── useWindowScroll.test.ts │ ├── useLongPress.test.ts │ ├── useResizeObserver.test.ts │ ├── useIntersectionObserver.test.ts │ ├── useMutationObserver.test.ts │ ├── useClick.test.ts │ ├── useTimeout.test.ts │ ├── useEventListener.test.ts │ ├── useInterval.test.ts │ ├── insertElement.test.ts │ ├── removeElement.test.ts │ └── useElementBounding.test.ts ├── array │ ├── getAverage.test.ts │ ├── chunk.test.ts │ ├── countBy.test.ts │ ├── diff.test.ts │ ├── filterEmpty.test.ts │ ├── forEach.test.ts │ └── sort.test.ts ├── domain │ ├── formateDate.test.ts │ └── crossVideoElement.test.ts ├── date │ ├── formateDate.test.ts │ └── getDateList.test.ts ├── compress │ ├── compressImage.test.ts │ └── compressCss.test.ts ├── css │ ├── setCssVar.test.ts │ ├── hasClassName.test.ts │ ├── setStyle.test.ts │ ├── useNamespace.test.ts │ ├── getClasses.test.ts │ ├── getCssVar.test.ts │ └── removeStyle.test.ts └── html │ ├── unescapeHtml.test.ts │ ├── escapeHtml.test.ts │ └── getStyles.test.ts ├── types.d.ts ├── .prettierignore ├── .prettierrc.json ├── jsr.json ├── tsconfig.json ├── tsdown.config.ts └── scripts ├── list_missing_en.js └── list_missing_en.cjs /src/asserts/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /playground/.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - playground 3 | -------------------------------------------------------------------------------- /src/flex/index.ts: -------------------------------------------------------------------------------- 1 | export * from './waterfall' 2 | -------------------------------------------------------------------------------- /src/iframe/index.ts: -------------------------------------------------------------------------------- 1 | export * from './postMessage' 2 | -------------------------------------------------------------------------------- /playground/src/types.ts: -------------------------------------------------------------------------------- 1 | const xx: Pr = {} 2 | -------------------------------------------------------------------------------- /src/https/index.ts: -------------------------------------------------------------------------------- 1 | export * from './httpsRedirect' 2 | -------------------------------------------------------------------------------- /src/qrcode/index.ts: -------------------------------------------------------------------------------- 1 | export * from './generateQRCode' 2 | -------------------------------------------------------------------------------- /src/theme/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useSystemColor' 2 | -------------------------------------------------------------------------------- /playground/src/composables/index.ts: -------------------------------------------------------------------------------- 1 | export * from './dark' 2 | -------------------------------------------------------------------------------- /.github-contributors/Simon-He95_lazy-js-utils.json: -------------------------------------------------------------------------------- 1 | ["KiligFei"] 2 | -------------------------------------------------------------------------------- /assets/kv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simon-He95/lazy-js-utils/HEAD/assets/kv.png -------------------------------------------------------------------------------- /assets/zfb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simon-He95/lazy-js-utils/HEAD/assets/zfb.jpg -------------------------------------------------------------------------------- /playground/src/pages/[...all].vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/compress/index.ts: -------------------------------------------------------------------------------- 1 | export * from './compressCss' 2 | export * from './compressImage' 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: Simon-He95 2 | custom: ['https://github.com/Simon-He95/sponsor'] 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | # shamefully-hoist=true 2 | strict-peer-dependencies=false 3 | shell-emulator=true 4 | -------------------------------------------------------------------------------- /assets/wechat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simon-He95/lazy-js-utils/HEAD/assets/wechat.jpg -------------------------------------------------------------------------------- /src/parse/index.ts: -------------------------------------------------------------------------------- 1 | export * from './parse' 2 | export * from './parseCss' 3 | export * from './parseUrl' 4 | -------------------------------------------------------------------------------- /playground/public/aini.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simon-He95/lazy-js-utils/HEAD/playground/public/aini.mp3 -------------------------------------------------------------------------------- /playground/src/composables/dark.ts: -------------------------------------------------------------------------------- 1 | export const isDark = useDark() 2 | export const toggleDark = useToggle(isDark) 3 | -------------------------------------------------------------------------------- /src/request/index.ts: -------------------------------------------------------------------------------- 1 | export * from './VFetch' 2 | export * from './asyncPool' 3 | export * from './sendBeacon' 4 | -------------------------------------------------------------------------------- /src/speak/index.ts: -------------------------------------------------------------------------------- 1 | export * from './speech' 2 | export * from './speechToText' 3 | export * from './analyzeUserVoice' 4 | -------------------------------------------------------------------------------- /src/is/isArray.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否是数组 3 | * @description EN: Alias for Array.isArray. 4 | */ 5 | export const isArray = Array.isArray 6 | -------------------------------------------------------------------------------- /src/webComponent/index.ts: -------------------------------------------------------------------------------- 1 | export * from './render' 2 | export * from './crossImageElement' 3 | export * from './crossVideoElement' 4 | -------------------------------------------------------------------------------- /playground/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export function a() {} 2 | 3 | export function b() {} 4 | 5 | function c() {} 6 | 7 | export default c 8 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = "playground/dist" 3 | command = "pnpm run play:build" 4 | 5 | [build.environment] 6 | NODE_VERSION = "16" 7 | -------------------------------------------------------------------------------- /src/storage/index.ts: -------------------------------------------------------------------------------- 1 | export * from './dbStorage' 2 | export * from './jsCookie' 3 | export * from './jsLocal' 4 | export * from './jsSession' 5 | -------------------------------------------------------------------------------- /src/html/index.ts: -------------------------------------------------------------------------------- 1 | export * from './escapeHtml' 2 | export * from './htmlTransform' 3 | export * from './unescapeHtml' 4 | export * from './getStyles' 5 | -------------------------------------------------------------------------------- /src/log/index.ts: -------------------------------------------------------------------------------- 1 | export * from './debugWarn' 2 | export * from './globalErrorCapture' 3 | export * from './interceptError' 4 | export * from './log' 5 | -------------------------------------------------------------------------------- /src/script/index.ts: -------------------------------------------------------------------------------- 1 | export * from './addLink' 2 | export * from './addScript' 3 | export * from './addStyle' 4 | export * from './addStyleScoped' 5 | -------------------------------------------------------------------------------- /playground/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vite-ssg-dist 3 | .vite-ssg-temp 4 | *.local 5 | dist 6 | dist-ssr 7 | node_modules 8 | .idea/ 9 | *.log 10 | -------------------------------------------------------------------------------- /src/css/hasClassName.ts: -------------------------------------------------------------------------------- 1 | export function hasClassName(element: Element, className: string): boolean { 2 | return element.classList.contains(className) 3 | } 4 | -------------------------------------------------------------------------------- /playground/src/styles/main.css: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | #app { 4 | margin: 0; 5 | padding: 0; 6 | } 7 | 8 | html.dark { 9 | background: #121212; 10 | } 11 | -------------------------------------------------------------------------------- /src/message/index.ts: -------------------------------------------------------------------------------- 1 | export * from './createEventBus' 2 | export * from './useStorageListen' 3 | export * from './useSocket' 4 | export * from './createChannel' 5 | -------------------------------------------------------------------------------- /src/scroll/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getScrollPosition' 2 | export * from './scrollToTop' 3 | export * from './scrollToView' 4 | export * from './getScrollProgress' 5 | -------------------------------------------------------------------------------- /src/num/index.ts: -------------------------------------------------------------------------------- 1 | export * from './calNum' 2 | export * from './formateNum' 3 | export * from './uppercaseNum' 4 | export * from './multiply' 5 | export * from './isEven' 6 | -------------------------------------------------------------------------------- /src/js/noop.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 空函数 3 | * @description EN: A no-op helper that performs no action; useful as a default callback placeholder. 4 | */ 5 | export function noop() {} 6 | -------------------------------------------------------------------------------- /src/is/isOnline.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否在线 3 | */ 4 | /** 5 | * 判断当前浏览器是否在线 6 | * @description EN: Mirrors `navigator.onLine`. 7 | */ 8 | export const isOnline = navigator.onLine 9 | -------------------------------------------------------------------------------- /src/monitor/index.ts: -------------------------------------------------------------------------------- 1 | export * from './calFps' 2 | export * from './getDevice' 3 | export * from './monitorPef' 4 | export * from './timeCost' 5 | export * from './getLocation' 6 | -------------------------------------------------------------------------------- /playground/shims.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import type { DefineComponent } from 'vue' 3 | 4 | const component: DefineComponent<{}, {}, any> 5 | export default component 6 | } 7 | -------------------------------------------------------------------------------- /src/script/addStyleScoped.ts: -------------------------------------------------------------------------------- 1 | export function addStyleScoped(s: string, scoped: string) { 2 | return s.replace(/([.#]*\w+)[\s.>+\w]*\{/g, (v, k) => 3 | v.replace(k, `[${scoped}] ${k}`)) 4 | } 5 | -------------------------------------------------------------------------------- /src/utils/assign.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 兼容的 Object.assign 别名 3 | * @description EN: Alias for Object.assign, provided for convenience and consistent imports. 4 | */ 5 | export const assign = Object.assign 6 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './common' 2 | export * from './hasOwn' 3 | export * from './mount' 4 | export * from './unmount' 5 | export * from './assign' 6 | export * from './beforeUnmount' 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | *.ts linguist-detectable=false 3 | *.css linguist-detectable=false 4 | *.scss linguist-detectable=false 5 | *.js linguist-detectable=true 6 | *.vue linguist-detectable=true 7 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "antfu.vite", 4 | "antfu.iconify", 5 | "antfu.unocss", 6 | "johnsoncodehk.volar", 7 | "dbaeumer.vscode-eslint" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /src/js/noopFalse.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 始终返回 false 的函数 3 | * @description EN: Returns a constant false value, handy for event handlers that must cancel default behaviour. 4 | */ 5 | export const noopFalse = () => false 6 | -------------------------------------------------------------------------------- /src/random/index.ts: -------------------------------------------------------------------------------- 1 | export * from './randomDate' 2 | export * from './randomHexColor' 3 | export * from './randomRange' 4 | export * from './randomRgba' 5 | export * from './randomArray' 6 | export * from './uuid' 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Discord Chat 4 | url: https://discord.gg/vkzdkjeRCW 5 | about: Ask questions and discuss with other Vite users in real time. 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | node_modules 3 | *.log 4 | idea/ 5 | *.local 6 | .DS_Store 7 | dist 8 | .cache 9 | .idea 10 | .history 11 | logs 12 | &-debug.log 13 | *-error.log 14 | .eslintcache 15 | tmp 16 | 17 | -------------------------------------------------------------------------------- /src/vite/index.ts: -------------------------------------------------------------------------------- 1 | export * from './vitePluginCopyHtml' 2 | export * from './vitePluginExport' 3 | // export * from './vitePluginTransformVdeep' 4 | // export * from './vitePluginExportType' 5 | // export * from './vitePluginCssVar' 6 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | test: { 5 | environment: 'jsdom', // 使用 jsdom 环境来模拟浏览器 DOM 6 | }, 7 | assetsInclude: ['**/*.lrc'], // 让 Vite 将 .lrc 文件视为资源 8 | }) 9 | -------------------------------------------------------------------------------- /src/asserts/assertNotExists.ts: -------------------------------------------------------------------------------- 1 | export function assertNotExists( 2 | val: T | null | undefined, 3 | message = 'val exists', 4 | ): asserts val is null | undefined { 5 | if (val !== null && val !== undefined) 6 | throw new Error(message) 7 | } 8 | -------------------------------------------------------------------------------- /test/canvas/DotTextCanvas.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | 3 | describe.skip('dotTextCanvas test', () => { 4 | it('test', () => { 5 | // DotTextCanvas('hi', 10, 'red', 10) 6 | expect(true).toBe(true) 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /test/__snapshots__/basic.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`test htmlTransform > htmlTransform test 1`] = `"hi

hello

你好"`; 4 | -------------------------------------------------------------------------------- /types.d.ts: -------------------------------------------------------------------------------- 1 | // 声明 .lrc 文件的模块类型 2 | declare module '*.lrc' { 3 | const content: string 4 | export default content 5 | } 6 | 7 | // 声明 .lrc?raw 的模块类型 8 | declare module '*.lrc?raw' { 9 | const content: string 10 | export default content 11 | } 12 | -------------------------------------------------------------------------------- /src/is/isBrowser.ts: -------------------------------------------------------------------------------- 1 | import { isDef } from './isDef' 2 | 3 | /** 4 | * 判断当前环境是否具有浏览器全局(window) 5 | * @description EN: True in environments where `globalThis.window` is defined (typical browsers). 6 | */ 7 | export const isBrowser = isDef(globalThis?.window) 8 | -------------------------------------------------------------------------------- /src/is/isDate.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否是日期格式 3 | * @description EN: Check whether a value is a Date instance. 4 | * @param d - candidate value 5 | * @returns boolean 6 | */ 7 | export function isDate(d: any): d is Date { 8 | return d instanceof Date 9 | } 10 | -------------------------------------------------------------------------------- /src/is/isFalse.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否是false 3 | * @description EN: Check whether a value is the boolean false. 4 | * @param v - candidate value 5 | * @returns boolean 6 | */ 7 | export function isFalse(v: any): v is false { 8 | return v === false 9 | } 10 | -------------------------------------------------------------------------------- /src/is/isFn.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否是函数 3 | * @description EN: Check whether a value is a function. 4 | * @param o - candidate value 5 | * @returns boolean 6 | */ 7 | export function isFn(o: any): o is Function { 8 | return typeof o === 'function' 9 | } 10 | -------------------------------------------------------------------------------- /src/is/isNaN.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否为 NaN 3 | * @description EN: Wraps Number.isNaN for a consistent helper. 4 | * @param o Candidate value. 5 | * @returns {boolean} 6 | */ 7 | export function isNaN(o: any): boolean { 8 | return Number.isNaN(o) 9 | } 10 | -------------------------------------------------------------------------------- /src/num/isEven.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断偶数 3 | * @param { number } n 数字 4 | * @returns 5 | * @description EN: Return true if `n` is an even integer (coerced to number). 6 | */ 7 | export function isEven(n: number | string) { 8 | return +n % 2 === 0 9 | } 10 | -------------------------------------------------------------------------------- /src/animate/cubicIn.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 立方进入缓动曲线 3 | * @description EN: Cubic ease-in that accelerates following a cubic curve. 4 | * @param { number } t 归一化时间进度,范围 0-1 5 | * @returns { number } 6 | */ 7 | export function cubicIn(t: number) { 8 | return t * t * t 9 | } 10 | -------------------------------------------------------------------------------- /src/is/isStr.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否为字符串 3 | * @description EN: Check whether a value has type 'string'. 4 | * @param {any} o Candidate value. 5 | * @returns {o is string} 6 | */ 7 | export function isStr(o: any): o is string { 8 | return typeof o === 'string' 9 | } 10 | -------------------------------------------------------------------------------- /src/event/createTextNode.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Create a DOM Text node from a string. 3 | * 4 | * @param {string} text Text content. 5 | * @returns {Text} A DOM Text node. 6 | */ 7 | export function createTextNode(text: string) { 8 | return document.createTextNode(text) 9 | } 10 | -------------------------------------------------------------------------------- /src/animate/quartIn.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 四次进入缓动曲线 3 | * @description EN: Quartic ease-in that ramps up rapidly using a fourth-power curve. 4 | * @param { number } t 归一化时间进度,范围 0-1 5 | * @returns { number } 6 | */ 7 | export function quartIn(t: number) { 8 | return t ** 4.0 9 | } 10 | -------------------------------------------------------------------------------- /src/is/isBool.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否是boolean类型 3 | * @description EN: Check whether a value is a boolean. 4 | * @param value - candidate value 5 | * @returns boolean 6 | */ 7 | export function isBool(value: any): value is boolean { 8 | return typeof value === 'boolean' 9 | } 10 | -------------------------------------------------------------------------------- /src/node/isInstallPkg.ts: -------------------------------------------------------------------------------- 1 | import { jsShell } from './jsShell' 2 | 3 | export async function isInstallPkg(pkg: string) { 4 | const { status } = await jsShell(`if ! command -v ${pkg} &> /dev/null; then 5 | exit 1 6 | else 7 | exit 0 8 | fi`) 9 | return status === 0 10 | } 11 | -------------------------------------------------------------------------------- /src/random/randomHexColor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 随机hex颜色 3 | * @returns 4 | * @description EN: Generate a random 6-digit hex color string like `#a1b2c3`. 5 | */ 6 | export function randomHexColor(): string { 7 | return `#${(Math.random() * 0xFFFFF * 1000000).toString(16).slice(0, 6)}` 8 | } 9 | -------------------------------------------------------------------------------- /src/to/blobToUrl.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Create an object URL for a Blob. 3 | * 4 | * @param blob - Blob to create a URL for 5 | * @returns A blob URL that can be used as a src/href 6 | */ 7 | export function blobToUrl(blob: Blob) { 8 | return window.URL.createObjectURL(blob) 9 | } 10 | -------------------------------------------------------------------------------- /test/flex/waterfall.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { waterfall } from '../../src/flex' 3 | 4 | describe.skip('waterfall test', () => { 5 | it('test', () => { 6 | waterfall([], 'body', 200) 7 | expect(true).toBe(true) 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /src/animate/quadIn.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 二次进入缓动曲线 3 | * @description EN: Quadratic ease-in that accelerates proportionally to the square of progress. 4 | * @param { number } t 归一化时间进度,范围 0-1 5 | * @returns { number } 6 | */ 7 | export function quadIn(t: number) { 8 | return t * t 9 | } 10 | -------------------------------------------------------------------------------- /src/js/sleep.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 等待函数 3 | * @param { number } ms 延迟秒数 4 | * @returns 5 | * @description EN: Pause execution for `ms` milliseconds and resolve a Promise. 6 | */ 7 | export function sleep(ms: number) { 8 | return new Promise(resolve => setTimeout(resolve, ms)) 9 | } 10 | -------------------------------------------------------------------------------- /src/worker/fileSpliceWorker.ts: -------------------------------------------------------------------------------- 1 | import { createChunk } from '../perf/createChunk' 2 | 3 | onmessage = async (e) => { 4 | const { file, chunkSize, startIndex, endIndex } = e.data 5 | for (let i = startIndex; i < endIndex; i++) 6 | createChunk(file, i, chunkSize).then(postMessage) 7 | } 8 | -------------------------------------------------------------------------------- /playground/src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/animate/quadOut.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 二次离开缓动曲线 3 | * @description EN: Quadratic ease-out that decelerates with a simple parabolic curve. 4 | * @param { number } t 归一化时间进度,范围 0-1 5 | * @returns { number } 6 | */ 7 | export function quadOut(t: number) { 8 | return -t * (t - 2.0) 9 | } 10 | -------------------------------------------------------------------------------- /src/is/isNull.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否为 null 3 | * @description EN: Check if a value is strictly `null`. 4 | * @param {any} o Candidate value. 5 | * @returns {o is null} True when the value is exactly null. 6 | */ 7 | export function isNull(o: any): o is null { 8 | return o === null 9 | } 10 | -------------------------------------------------------------------------------- /src/animate/quintIn.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 五次进入缓动曲线 3 | * @description EN: Quintic ease-in that accelerates aggressively using a fifth-power curve. 4 | * @param { number } t 归一化时间进度,范围 0-1 5 | * @returns { number } 6 | */ 7 | export function quintIn(t: number) { 8 | return t * t * t * t * t 9 | } 10 | -------------------------------------------------------------------------------- /src/is/isUndef.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否为 undefined 3 | * @description EN: Returns true when the value is exactly `undefined`. 4 | * @param o Candidate value. 5 | * @returns {o is undefined} 6 | */ 7 | export function isUndef(o: any): o is undefined { 8 | return typeof o === 'undefined' 9 | } 10 | -------------------------------------------------------------------------------- /src/is/isWin.ts: -------------------------------------------------------------------------------- 1 | import process from 'node:process' 2 | 3 | /** 4 | * 判断是否为 Windows 平台 5 | * @description EN: Returns true when running on Windows (platform 'win32'). 6 | * @returns {boolean} 7 | */ 8 | export function isWin(): boolean { 9 | return process.platform === 'win32' 10 | } 11 | -------------------------------------------------------------------------------- /test/canvas/getImageData.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { getImageData } from '../../src/canvas' 3 | 4 | describe('getImageData test', () => { 5 | it('test', () => { 6 | getImageData('../../assets/kv.png') 7 | expect(true).toBe(true) 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /playground/netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = "dist" 3 | command = "npx pnpm i --store=node_modules/.pnpm-store && npx pnpm run build" 4 | 5 | [build.environment] 6 | NPM_FLAGS = "--version" 7 | NODE_VERSION = "16" 8 | 9 | [[redirects]] 10 | from = "/*" 11 | to = "/index.html" 12 | status = 200 13 | -------------------------------------------------------------------------------- /src/animate/quintOut.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 五次离开缓动曲线 3 | * @description EN: Quintic ease-out that decelerates sharply with a fifth-power falloff. 4 | * @param { number } t 归一化时间进度,范围 0-1 5 | * @returns { number } 6 | */ 7 | export function quintOut(t: number) { 8 | return --t * t * t * t * t + 1 9 | } 10 | -------------------------------------------------------------------------------- /src/animate/sineOut.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 正弦离开缓动曲线 3 | * @description EN: Sine ease-out that eases smoothly to the end along a sine wave. 4 | * @param { number } t 归一化时间进度,范围 0-1 5 | * @returns { number } 6 | */ 7 | export function sineOut(t: number) { 8 | return Math.sin((t * Math.PI) / 2) 9 | } 10 | -------------------------------------------------------------------------------- /test/event/useHead.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { useHead } from '../../src/event' 3 | 4 | describe('useHead test', () => { 5 | it('test', () => { 6 | useHead({ 7 | title: 'myhead', 8 | }) 9 | 10 | expect(true).toBe(true) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /src/animate/circIn.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 圆形进入缓动曲线 3 | * @description EN: Circular ease-in that accelerates as if moving along a quarter circle. 4 | * @param { number } t 归一化时间进度,范围 0-1 5 | * @returns { number } 6 | */ 7 | export function circIn(t: number) { 8 | return 1.0 - Math.sqrt(1.0 - t * t) 9 | } 10 | -------------------------------------------------------------------------------- /src/animate/circOut.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 圆形离开缓动曲线 3 | * @description EN: Circular ease-out that decelerates smoothly toward the end of the motion. 4 | * @param { number } t 归一化时间进度,范围 0-1 5 | * @returns { number } 6 | */ 7 | export function circOut(t: number) { 8 | return Math.sqrt((2.0 - t) * t) 9 | } 10 | -------------------------------------------------------------------------------- /src/animate/expoIn.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 指数进入缓动曲线 3 | * @description EN: Exponential ease-in that starts slowly then grows rapidly. 4 | * @param { number } t 归一化时间进度,范围 0-1 5 | * @returns { number } 6 | */ 7 | export function expoIn(t: number) { 8 | return t === 0.0 ? t : 2.0 ** (10.0 * (t - 1.0)) 9 | } 10 | -------------------------------------------------------------------------------- /src/animate/expoOut.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 指数离开缓动曲线 3 | * @description EN: Exponential ease-out that decays rapidly toward the end. 4 | * @param { number } t 归一化时间进度,范围 0-1 5 | * @returns { number } 6 | */ 7 | export function expoOut(t: number) { 8 | return t === 1.0 ? t : 1.0 - 2.0 ** (-10.0 * t) 9 | } 10 | -------------------------------------------------------------------------------- /src/animate/quartOut.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 四次离开缓动曲线 3 | * @description EN: Quartic ease-out that slows down using a fourth-power falloff. 4 | * @param { number } t 归一化时间进度,范围 0-1 5 | * @returns { number } 6 | */ 7 | export function quartOut(t: number) { 8 | return (t - 1.0) ** 3.0 * (1.0 - t) + 1.0 9 | } 10 | -------------------------------------------------------------------------------- /test/event/download.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { download } from '../../src/event' 3 | 4 | describe.skip('download test', () => { 5 | it('test', () => { 6 | download('xxx.jpg') 7 | expect(true).toMatchInlineSnapshot('') 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /test/event/findElement.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { findElement } from '../../src/event' 3 | 4 | describe('findElement test', () => { 5 | it('test', () => { 6 | const div = findElement('div') 7 | expect(div).toMatchInlineSnapshot('null') 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /src/animate/cubicOut.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 立方离开缓动曲线 3 | * @description EN: Cubic ease-out that decelerates following a cubic falloff. 4 | * @param { number } t 归一化时间进度,范围 0-1 5 | * @returns { number } 6 | */ 7 | export function cubicOut(t: number) { 8 | const f = t - 1.0 9 | return f * f * f + 1.0 10 | } 11 | -------------------------------------------------------------------------------- /src/is/isNum.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否是number类型 3 | */ 4 | /** 5 | * 判断是否为数字 6 | * @description EN: Check whether a value has type 'number'. 7 | * @param {any} o Candidate value. 8 | * @returns {o is number} 9 | */ 10 | export function isNum(o: any): o is number { 11 | return typeof o === 'number' 12 | } 13 | -------------------------------------------------------------------------------- /test/array/getAverage.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { getAverage } from '../../src/array' 3 | 4 | describe('getAverage test', () => { 5 | it('test', () => { 6 | const numbers = [2, 3] 7 | expect(getAverage(numbers)).toMatchInlineSnapshot('"2.50"') 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /test/event/collisionDetection.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { collisionDetection } from '../../src/event' 3 | 4 | describe('collisionDetection test', () => { 5 | it('test', () => { 6 | expect(collisionDetection('.div1', '.div2')).toMatchInlineSnapshot('false') 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /test/event/dragEvent.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { dragEvent } from '../../src/event' 3 | 4 | describe.skip('dragEvent test', () => { 5 | it('test', () => { 6 | dragEvent('.drag') 7 | expect(true).toMatchInlineSnapshot('') 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /src/is/isDef.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断元素不是undefined 3 | * @description EN: Determine whether a value is defined (not undefined). 4 | * @param v - candidate value 5 | * @returns boolean 6 | */ 7 | export function isDef(v: T): v is T extends undefined ? never : T { 8 | return typeof v !== 'undefined' 9 | } 10 | -------------------------------------------------------------------------------- /src/is/isTrue.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否是true 3 | /** 4 | * \u5224\u65ad\u662f\u5426\u662ftrue 5 | * @description EN: Check whether a value is the boolean literal true. 6 | * @param v - candidate value 7 | * @returns v is true 8 | */ 9 | export function isTrue(v: any): v is true { 10 | return v === true 11 | } 12 | -------------------------------------------------------------------------------- /test/domain/formateDate.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { crossImageElement } from '../../src/webComponent' 3 | 4 | describe('crossImageElement test', () => { 5 | it('test', () => { 6 | crossImageElement() 7 | expect(true).toMatchInlineSnapshot('true') 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /test/event/createElement.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { createElement } from '../../src/event' 3 | 4 | describe('createElement test', () => { 5 | it('test', () => { 6 | const div = createElement('div') 7 | expect(div).toMatchInlineSnapshot('
') 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /test/event/useFocus.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { useFocus } from '../../src/event' 3 | 4 | describe('useFocus test', () => { 5 | it('test', () => { 6 | const p = document.createElement('input') 7 | useFocus(p) 8 | 9 | expect(true).toBe(true) 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /test/event/useMouse.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { useMouse } from '../../src/event' 3 | 4 | describe('useMouse test', () => { 5 | it('test', () => { 6 | useMouse((e) => { 7 | console.log(e) 8 | }, 100) 9 | 10 | expect(true).toEqual(true) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /src/animate/sineInOut.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 正弦往返缓动曲线 3 | * @description EN: Sine ease-in-out that follows a half sine wave for smooth start and end. 4 | * @param { number } t 归一化时间进度,范围 0-1 5 | * @returns { number } 6 | */ 7 | export function sineInOut(t: number) { 8 | return -0.5 * (Math.cos(Math.PI * t) - 1) 9 | } 10 | -------------------------------------------------------------------------------- /src/https/httpsRedirect.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * http重定向到https 3 | * @description EN: If the current page is loaded over HTTP, redirect to the HTTPS version of the same URL. 4 | */ 5 | export function httpsRedirect() { 6 | if (location.protocol !== 'https:') 7 | location.replace(`https://${location.href.split('//')[1]}`) 8 | } 9 | -------------------------------------------------------------------------------- /src/is/isContainCn.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否包含中文 3 | * @description EN: Returns true if the input string contains any CJK Unified Ideographs (Chinese characters). 4 | * @param s - input string 5 | * @returns boolean 6 | */ 7 | export function isContainCn(s: string): boolean { 8 | return /[\u4E00-\u9FA5]/.test(s) 9 | } 10 | -------------------------------------------------------------------------------- /src/is/isObject.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否为对象类型 3 | * @description EN: Returns true for values with typeof 'object' (note: arrays/null are objects too). 4 | * @param {any} value Candidate value. 5 | * @returns {boolean} 6 | */ 7 | export function isObject(value: any): boolean { 8 | return typeof value === 'object' 9 | } 10 | -------------------------------------------------------------------------------- /src/is/isSymbol.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否是symbol类型 3 | */ 4 | /** 5 | * 判断是否为 symbol 6 | * @description EN: Check whether a value has type 'symbol'. 7 | * @param {any} o Candidate value. 8 | * @returns {o is symbol} 9 | */ 10 | export function isSymbol(o: any): o is symbol { 11 | return typeof o === 'symbol' 12 | } 13 | -------------------------------------------------------------------------------- /test/domain/crossVideoElement.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { crossVideoElement } from '../../src/webComponent' 3 | 4 | describe('crossVideoElement test', () => { 5 | it('test', () => { 6 | crossVideoElement() 7 | expect(true).toMatchInlineSnapshot('true') 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | packages/*/CHANGELOG.md 2 | playground-temp/ 3 | dist/ 4 | temp/ 5 | LICENSE.md 6 | pnpm-lock.yaml 7 | pnpm-workspace.yaml 8 | playground/tsconfig-json-load-error/has-error/tsconfig.json 9 | playground/html/invalid.html 10 | playground/html/valid.html 11 | playground/worker/classic-worker.js 12 | .history 13 | -------------------------------------------------------------------------------- /src/is/isSocketUrl.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否为 WebSocket URL 3 | * @description EN: Returns true for URLs that start with ws:// or wss://. 4 | * @param {string} url URL string. 5 | * @returns {boolean} 6 | */ 7 | export function isSocketUrl(url: string) { 8 | return url.startsWith('ws://') || url.startsWith('wss://') 9 | } 10 | -------------------------------------------------------------------------------- /test/canvas/sliderValidation.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { sliderValidation } from '../../src/canvas' 3 | 4 | describe.skip('sliderValidation test', () => { 5 | it('test', () => { 6 | sliderValidation('../../assets/kv.png', 'body') 7 | expect(true).toBe(true) 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /test/date/formateDate.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { formateDate } from '../../src/date' 3 | 4 | describe('formateDate test', () => { 5 | it('test', () => { 6 | const d = formateDate(new Date(), 'yyyy-mm-dd') 7 | expect(d).toMatchInlineSnapshot(`"2025-24-06"`) 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /test/event/useHover.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { useHover } from '../../src/event' 3 | 4 | describe('useHover test', () => { 5 | it('test', () => { 6 | useHover('div', () => { 7 | console.log('isHovered') 8 | }) 9 | 10 | expect(true).toBe(true) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /src/animate/backIn.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 回拉进入缓动曲线 3 | * @description EN: Back-in easing that briefly reverses direction before accelerating forward. 4 | * @param { number } t 归一化时间进度,范围 0-1 5 | * @returns { number } 6 | */ 7 | export function backIn(t: number) { 8 | const s = 1.70158 9 | return t * t * ((s + 1) * t - s) 10 | } 11 | -------------------------------------------------------------------------------- /src/event/createFragment.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Create and return a DocumentFragment. Useful as a lightweight container 3 | * for building up DOM nodes before inserting them into the live document. 4 | * 5 | * @returns DocumentFragment 6 | */ 7 | export function createFragment() { 8 | return document.createDocumentFragment() 9 | } 10 | -------------------------------------------------------------------------------- /src/is/isBlob.ts: -------------------------------------------------------------------------------- 1 | import { _toString } from '../utils/common' 2 | 3 | /** 4 | * 判断是否是blob 5 | * @description EN: Check whether a value is a Blob object. 6 | * @param o - candidate value 7 | * @returns boolean 8 | */ 9 | export function isBlob(o: any): o is Blob { 10 | return _toString.call(o) === '[object Blob]' 11 | } 12 | -------------------------------------------------------------------------------- /test/event/createFragment.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { createFragment } from '../../src/event' 3 | 4 | describe('createFragment test', () => { 5 | it('test', () => { 6 | const div = createFragment() 7 | expect(div).toMatchInlineSnapshot('') 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /src/animate/backOut.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 回拉离开缓动曲线 3 | * @description EN: Back-out easing that overshoots the target before settling at the end. 4 | * @param { number } t 归一化时间进度,范围 0-1 5 | * @returns { number } 6 | */ 7 | export function backOut(t: number) { 8 | const s = 1.70158 9 | return --t * t * ((s + 1) * t + s) + 1 10 | } 11 | -------------------------------------------------------------------------------- /src/is/isNameEn.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断输入是否为英文姓名样式 3 | * @description EN: Simple heuristic to test for English name-like strings (letters and spaces, 2-22 chars). 4 | * @param {string} s Candidate string. 5 | * @returns {boolean} 6 | */ 7 | export function isNameEn(s: string) { 8 | return /(^[a-z][a-z\s]{0,20}[a-z]$)/i.test(s) 9 | } 10 | -------------------------------------------------------------------------------- /src/is/isRelative.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否是相对路径 3 | */ 4 | /** 5 | * 判断路径是否为相对路径 6 | * @description EN: Returns true for strings starting with './' or '../'. 7 | * @param {string} str Path string. 8 | * @returns {boolean} 9 | */ 10 | export function isRelative(str: string): boolean { 11 | return /^(\.\.\/|\.\/)/.test(str) 12 | } 13 | -------------------------------------------------------------------------------- /src/worker/useNodeWorkerThread.ts: -------------------------------------------------------------------------------- 1 | import type { NodeWorkerPayload } from '../types' 2 | import { useProcressNodeWorker } from '../node/useNodeWorker' 3 | import { jsShell } from '../node/jsShell' 4 | 5 | useProcressNodeWorker(({ params, ...args }: NodeWorkerPayload) => 6 | jsShell(`${params}`, Object.assign({ stdio: 'pipe' }, args)), 7 | ) 8 | -------------------------------------------------------------------------------- /test/canvas/CreateSignatureCanvas.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | 3 | // import { CreateSignatureCanvas } from '../../src/canvas' 4 | 5 | describe.skip('createSignatureCanvas test', () => { 6 | it('test', () => { 7 | // new CreateSignatureCanvas() 8 | expect(true).toEqual(true) 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /test/canvas/DotImageCanvas.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | 3 | // import { DotImageCanvas } from '../../src/canvas' 4 | 5 | describe.skip('dotImageCanvas test', () => { 6 | it('test', () => { 7 | // new DotImageCanvas('../../assets/kv.png', 'red', 10) 8 | expect(true).toBe(true) 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /src/asserts/assertExists.ts: -------------------------------------------------------------------------------- 1 | export function assertExists( 2 | val: T | null | undefined, 3 | message: string | Error = 'val does not exist', 4 | ): asserts val is T { 5 | if (val === null || val === undefined) { 6 | if (message instanceof Error) 7 | throw message 8 | 9 | throw new Error(message) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/date/index.ts: -------------------------------------------------------------------------------- 1 | export * from './formateDate' 2 | export * from './getDateList' 3 | export * from './getDaysOfMonth' 4 | export * from './createCalendar' 5 | export * from './compareDate' 6 | export * from './compareTime' 7 | export * from './compareDateTime' 8 | export * from './getFirstDay' 9 | export * from './getDifferenceDays' 10 | -------------------------------------------------------------------------------- /src/is/isActive.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断当前页面是否处于活跃状态 3 | * @returns 4 | */ 5 | /** 6 | * 判断当前页面是否处于活动(未被隐藏)状态 7 | * @description EN: Returns true when the document is currently visible/active 8 | * (i.e. `document.hidden` is falsy). 9 | * @returns {boolean} 10 | */ 11 | export function isActive() { 12 | return !document.hidden 13 | } 14 | -------------------------------------------------------------------------------- /src/is/isComment.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否为 HTML 注释 3 | * @description EN: Test whether a string looks like an HTML comment (). 4 | * Matches across newlines as well. 5 | * @param {string} s Input string. 6 | * @returns {boolean} 7 | */ 8 | export function isComment(s: string) { 9 | return //.test(s) 10 | } 11 | -------------------------------------------------------------------------------- /src/is/isElement.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断元素是HTMLElement 3 | * @description EN: Check whether a value is an HTMLElement. 4 | * @param element - candidate value 5 | * @returns boolean 6 | */ 7 | export function isElement(element: any): element is HTMLElement { 8 | return typeof element === 'object' && element instanceof HTMLElement 9 | } 10 | -------------------------------------------------------------------------------- /src/is/isReg.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否是正则 3 | * @description EN: Check whether a value is a RegExp instance. 4 | * @param o Candidate value. 5 | * @returns {o is RegExp} True when the value is a regular expression. 6 | */ 7 | export function isReg(o: any): o is RegExp { 8 | return typeof o === 'object' && o.constructor === RegExp 9 | } 10 | -------------------------------------------------------------------------------- /src/js/executeStr.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 执行字符串表达式 3 | * @description EN: Evaluate the provided string as JavaScript expression and return its value. Use with caution. 4 | * @param { string } str 需要执行的表达式字符串 5 | * @returns { any } 6 | */ 7 | export function executeStr(str: string): any { 8 | return new Function(`return (${str})`)() 9 | } 10 | -------------------------------------------------------------------------------- /test/event/useKeyBoard.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { useKeyBoard } from '../../src/event' 3 | 4 | describe('useKeyBoard test', () => { 5 | it('test', () => { 6 | useKeyBoard('ctrl+c', () => { 7 | console.log('press ctrl+c') 8 | }) 9 | expect(true).toEqual(true) 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /src/animate/cubicInOut.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 立方往返缓动曲线 3 | * @description EN: Cubic ease-in-out that accelerates and decelerates with a cubic curve. 4 | * @param { number } t 归一化时间进度,范围 0-1 5 | * @returns { number } 6 | */ 7 | export function cubicInOut(t: number) { 8 | return t < 0.5 ? 4.0 * t * t * t : 0.5 * (2.0 * t - 2.0) ** 3.0 + 1.0 9 | } 10 | -------------------------------------------------------------------------------- /src/animate/elasticIn.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 弹性进入缓动曲线 3 | * @description EN: Elastic ease-in that overshoots with oscillations before moving forward. 4 | * @param { number } t 归一化时间进度,范围 0-1 5 | * @returns { number } 6 | */ 7 | export function elasticIn(t: number) { 8 | return Math.sin((13.0 * t * Math.PI) / 2) * 2.0 ** (10.0 * (t - 1.0)) 9 | } 10 | -------------------------------------------------------------------------------- /src/css/getCssVar.ts: -------------------------------------------------------------------------------- 1 | import type { MaybeElement } from '../types' 2 | import { mount } from '../utils/mount' 3 | 4 | export function getCssVar( 5 | element: MaybeElement, 6 | style: string, 7 | callback: (css: string) => void, 8 | ) { 9 | return mount(element, el => 10 | callback?.(getComputedStyle(el).getPropertyValue(style))) 11 | } 12 | -------------------------------------------------------------------------------- /src/css/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getClasses' 2 | export * from './getCssVar' 3 | export * from './hasClassName' 4 | export * from './setCssVar' 5 | export * from './useNamespace' 6 | export * from './setStyle' 7 | export * from './removeStyle' 8 | export * from './addStyleRules' 9 | export * from './addClass' 10 | export * from './removeClass' 11 | -------------------------------------------------------------------------------- /src/is/isCalc.ts: -------------------------------------------------------------------------------- 1 | import { isStr } from './isStr' 2 | 3 | /** 4 | * 判断字符串是calc() 5 | * @description EN: Check whether a value is a CSS calc() string. 6 | * @param value - candidate value 7 | * @returns boolean 8 | */ 9 | export function isCalc(value: unknown): value is string { 10 | return isStr(value) && value.startsWith('calc(') 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/hasOwn.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断对象是否拥有指定的自有属性 3 | * @description EN: Check whether the given object has an own property named `key`. 4 | * @param { object } obj 需要检查的对象 5 | * @param { string } key 属性名 6 | * @returns { boolean } 7 | */ 8 | export function hasOwn(obj: object, key: string): boolean { 9 | return Reflect.has(obj, key) 10 | } 11 | -------------------------------------------------------------------------------- /test/compress/compressImage.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { compressImage } from '../../src/compress' 3 | 4 | describe.skip('compressImage test', () => { 5 | it('test', () => { 6 | const image = compressImage('../../assets/kv.png') 7 | expect(image).toMatchInlineSnapshot('Promise {}') 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /test/event/useWindowScroll.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { useWindowScroll } from '../../src/event' 3 | 4 | describe('useWindowScroll test', () => { 5 | it('test', () => { 6 | useWindowScroll((left, top) => { 7 | console.log(left, top) 8 | }) 9 | expect(true).toEqual(true) 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /src/array/filterEmpty.ts: -------------------------------------------------------------------------------- 1 | /** 2 | /** 3 | * 过滤数组中的空值(falsey 值) 4 | * @description EN: Remove empty/falsey values from an array (filters with Boolean). 5 | * @param {T[]} array Input array. 6 | * @returns {T[]} Filtered array with truthy values only. 7 | */ 8 | export function filterEmpty(array: T[]) { 9 | return array.filter(Boolean) 10 | } 11 | -------------------------------------------------------------------------------- /src/is/isHex.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否是十六进制颜色值 3 | * @description EN: Check whether a string is a hex color (e.g. "#fff", "#ffffff"). 4 | * @param {string} hex The input string to test. 5 | * @returns {boolean} True if the input matches a hex color pattern. 6 | */ 7 | export function isHex(hex: string) { 8 | return /^#[0-9A-F]{2,}$/i.test(hex) 9 | } 10 | -------------------------------------------------------------------------------- /src/is/isNameCn.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否为中文姓名(含·) 3 | * @description EN: Test whether a string is a Chinese personal name (2-16 CJK chars or middle dot). 4 | * @param {string} s Candidate name string. 5 | * @returns {boolean} 6 | */ 7 | export const isNameCn = (s: string) => /^[\u4E00-\u9FA5·]{2,16}$/.test(s) 8 | 9 | // console.log(isNameCn('你好11')) 10 | -------------------------------------------------------------------------------- /src/is/isNil.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否为 null 或 undefined 3 | * @description EN: Returns true when the value is strictly null or undefined. 4 | * @param {any} value Candidate value. 5 | * @returns {value is null | undefined} 6 | */ 7 | export function isNil(value: any): value is null | undefined { 8 | return value === null || value === undefined 9 | } 10 | -------------------------------------------------------------------------------- /src/is/isTrainNumber.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否是火车号 3 | */ 4 | /** 5 | * 判断是否为火车车次(简单校验) 6 | * @description EN: Heuristic check for train numbers using common prefixes and 1-4 digits. 7 | * @param {string} s Candidate train number string. 8 | * @returns {boolean} 9 | */ 10 | export const isTrainNumber = (s: string) => /^[GCDZTSPKXLY1-9]\d{1,4}$/.test(s) 11 | -------------------------------------------------------------------------------- /src/animate/elasticOut.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 弹性离开缓动曲线 3 | * @description EN: Elastic ease-out that releases with oscillations while settling at the end. 4 | * @param { number } t 归一化时间进度,范围 0-1 5 | * @returns { number } 6 | */ 7 | export function elasticOut(t: number) { 8 | return Math.sin((-13.0 * (t + 1.0) * Math.PI) / 2) * 2.0 ** (-10.0 * t) + 1.0 9 | } 10 | -------------------------------------------------------------------------------- /src/animate/quartInOut.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 四次往返缓动曲线 3 | * @description EN: Quartic ease-in-out that uses a fourth-power curve for sharp acceleration and deceleration. 4 | * @param { number } t 归一化时间进度,范围 0-1 5 | * @returns { number } 6 | */ 7 | export function quartInOut(t: number) { 8 | return t < 0.5 ? +8.0 * t ** 4.0 : -8.0 * (t - 1.0) ** 4.0 + 1.0 9 | } 10 | -------------------------------------------------------------------------------- /src/canvas/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Canvas' 2 | export * from './CreateSignatureCanvas' 3 | export * from './DotImageCanvas' 4 | export * from './DotTextCanvas' 5 | export * from './getImageData' 6 | export * from './removeRoundSpace' 7 | export * from './sliderValidation' 8 | export * from './Line' 9 | export * from './Point' 10 | export * from './Square' 11 | -------------------------------------------------------------------------------- /src/is/isEmail.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断邮箱地址 3 | * @description EN: Validate whether the input looks like an email address. 4 | * @param s - email string or number 5 | * @returns boolean 6 | */ 7 | export function isEmail(s: string | number): boolean { 8 | return /^[\w.-]+@[a-z0-9-]+(\.[a-z0-9-]+)*\.[a-z0-9]{2,6}$/i.test( 9 | s.toString(), 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /src/is/isIPv4.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否为 IPv4 地址 3 | * @description EN: Test whether a string is a valid IPv4 address. 4 | * @param {string} ip Candidate IP string. 5 | * @returns {boolean} 6 | */ 7 | export function isIPv4(ip: string) { 8 | return /^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)$/.test( 9 | ip, 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /src/is/isPercent.ts: -------------------------------------------------------------------------------- 1 | import { isStr } from './isStr' 2 | /** 3 | * 判断是否为百分比字符串 4 | * @description EN: Returns true for strings ending with '%' (e.g. '50%'). 5 | * @param {unknown} value Candidate value. 6 | * @returns {boolean} 7 | */ 8 | export function isPercent(value: unknown): value is string { 9 | return isStr(value) && value.endsWith('%') 10 | } 11 | -------------------------------------------------------------------------------- /src/is/isPostCode.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否是邮编 3 | */ 4 | /** 5 | * 判断是否为邮政编码 6 | * @description EN: Simplified check for a 6-digit postal code (China-style). 7 | * @param {string|number} s Candidate value. 8 | * @returns {boolean} 9 | */ 10 | export function isPostCode(s: string | number): boolean { 11 | return /^[1-9]\d{5}$/.test(s.toString()) 12 | } 13 | -------------------------------------------------------------------------------- /src/is/isUrl.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否是 URL 3 | * @description EN: Simple check whether a string looks like an HTTP/HTTPS URL. 4 | * @param {string} url The candidate URL string. 5 | * @returns {boolean} True if the string begins with "http://" or "https://". 6 | */ 7 | export function isUrl(url: string): boolean { 8 | return /^https?:\/\/.*/.test(url) 9 | } 10 | -------------------------------------------------------------------------------- /src/object/index.ts: -------------------------------------------------------------------------------- 1 | export * from './deepMerge' 2 | export * from './generateKeyObject' 3 | export * from './mapTransform' 4 | export * from './stringify' 5 | export * from './transformKey' 6 | export * from './traverse' 7 | export * from './deepClone' 8 | export * from './deepCompare' 9 | export * from './objectToMap' 10 | export * from './mapToObject' 11 | -------------------------------------------------------------------------------- /src/animate/bounceIn.ts: -------------------------------------------------------------------------------- 1 | import { bounceOut } from './bounceOut' 2 | 3 | /** 4 | * 弹跳进入缓动曲线 5 | * @description EN: Bounce-in easing that simulates a bouncing object accelerating toward the start. 6 | * @param { number } t 归一化时间进度,范围 0-1 7 | * @returns { number } 8 | */ 9 | export function bounceIn(t: number) { 10 | return 1.0 - bounceOut(1.0 - t) 11 | } 12 | -------------------------------------------------------------------------------- /test/css/setCssVar.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { setCssVar } from '../../src/css' 3 | 4 | describe('setCssVar test', () => { 5 | it('test', () => { 6 | const el = document.createElement('div') 7 | setCssVar(el, { '--main-bg': 'red' }) 8 | expect(el.style[0]).toMatchInlineSnapshot('"--main-bg"') 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /test/event/useLongPress.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { useLongPress } from '../../src/event' 3 | 4 | describe('useLongPress test', () => { 5 | it('test', () => { 6 | useLongPress('div', 1000, () => { 7 | console.log('you press div more than 1000ms') 8 | }) 9 | expect(true).toEqual(true) 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /test/canvas/Canvas.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { Canvas } from '../../src/canvas' 3 | 4 | describe.skip('canvas test', () => { 5 | it('test', () => { 6 | const { canvas } = new Canvas(200, 300) 7 | const { width, height } = canvas 8 | expect(width).toEqual(200) 9 | expect(height).toEqual(300) 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /test/event/useResizeObserver.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { useResizeObserver } from '../../src/event' 3 | 4 | describe('useResizeObserver test', () => { 5 | it('test', () => { 6 | useResizeObserver((width, height) => { 7 | console.log(width, height) 8 | }) 9 | 10 | expect(true).toEqual(true) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /playground/components.d.ts: -------------------------------------------------------------------------------- 1 | // generated by unplugin-vue-components 2 | // We suggest you to commit this file into source control 3 | // Read more: https://github.com/vuejs/vue-next/pull/3399 4 | 5 | declare module 'vue' { 6 | export interface GlobalComponents { 7 | Footer: typeof import('./src/components/Footer.vue')['default'] 8 | } 9 | } 10 | 11 | export {} 12 | -------------------------------------------------------------------------------- /src/animate/sineIn.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 正弦进入缓动曲线 3 | * @description EN: Sine ease-in that starts gently following a quarter sine wave. 4 | * @param { number } t 归一化时间进度,范围 0-1 5 | * @returns { number } 6 | */ 7 | export function sineIn(t: number) { 8 | const v = Math.cos(t * Math.PI * 0.5) 9 | if (Math.abs(v) < 1e-14) 10 | return 1 11 | else return 1 - v 12 | } 13 | -------------------------------------------------------------------------------- /src/canvas/Canvas.ts: -------------------------------------------------------------------------------- 1 | export class Canvas { 2 | canvas = document.createElement('canvas') 3 | ctx = this.canvas.getContext('2d')! 4 | constructor(width?: number, height?: number) { 5 | if (width) 6 | this.canvas.width = width * devicePixelRatio 7 | if (height) 8 | this.canvas.height = height * devicePixelRatio 9 | return this 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/is/isNm.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否是nodemodule依赖 3 | */ 4 | /** 5 | * 判断是否为 node module 风格的导入路径(不以 './'、'../' 或 '/' 开头) 6 | * @description EN: Heuristic to detect bare module specifiers (e.g. 'vue', 'lodash'). 7 | * @param {string} src Import path string. 8 | * @returns {boolean} 9 | */ 10 | export function isNm(src: string) { 11 | return /^[^./\s]/.test(src) 12 | } 13 | -------------------------------------------------------------------------------- /src/is/isVue.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断文件是不是.vue 3 | * @param { string } src 文件路径 4 | * @returns 5 | */ 6 | /** 7 | * 判断文件路径是否是 Vue 单文件组件(.vue) 8 | * @description EN: Simple check for filenames ending with '.vue'. 9 | * @param {string} src Path or filename. 10 | * @returns {boolean} 11 | */ 12 | export function isVue(src: string) { 13 | return src.endsWith('.vue') 14 | } 15 | -------------------------------------------------------------------------------- /src/is/isWeakSet.ts: -------------------------------------------------------------------------------- 1 | import { _toString } from '../utils/common' 2 | 3 | /** 4 | * 判断是否为 WeakSet 5 | * @description EN: Check whether a value is a WeakSet instance. 6 | * @param {any} o Candidate value. 7 | * @returns {o is WeakSet} 8 | */ 9 | export function isWeakSet(o: any): o is WeakSet { 10 | return _toString.call(o) === '[object WeakSet]' 11 | } 12 | -------------------------------------------------------------------------------- /src/js/parallel.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 并行执行 3 | * @param { any[] } tasks 数组 4 | * @param { (...args: any[]) => any } fn 函数 5 | * @returns 6 | * @description EN: Execute `fn` over `tasks` in parallel and return a Promise resolved with all results. 7 | */ 8 | export function parallel(tasks: any[], fn: (...args: any[]) => any) { 9 | return Promise.all(tasks.map(fn)) 10 | } 11 | -------------------------------------------------------------------------------- /src/is/isRem.ts: -------------------------------------------------------------------------------- 1 | import { isStr } from './isStr' 2 | 3 | /** 4 | * 判断是否为 rem 单位字符串 5 | * @description EN: Returns true for strings ending with 'rem' (e.g. '1.2rem'). 6 | * @param {unknown} value Candidate value. 7 | * @returns {value is string} 8 | */ 9 | export function isRem(value: unknown): value is string { 10 | return isStr(value) && value.endsWith('rem') 11 | } 12 | -------------------------------------------------------------------------------- /src/is/isVh.ts: -------------------------------------------------------------------------------- 1 | import { isStr } from './isStr' 2 | 3 | /** 4 | * 判断是否为 vh 单位字符串 5 | * @description EN: Returns true for strings ending with 'vh' (viewport height unit). 6 | * @param {unknown} value Candidate value. 7 | * @returns {value is string} 8 | */ 9 | export function isVh(value: unknown): value is string { 10 | return isStr(value) && value.endsWith('vh') 11 | } 12 | -------------------------------------------------------------------------------- /src/is/isVw.ts: -------------------------------------------------------------------------------- 1 | import { isStr } from './isStr' 2 | 3 | /** 4 | * 判断是否为 vw 单位字符串 5 | * @description EN: Returns true for strings ending with 'vw' (viewport width unit). 6 | * @param {unknown} value Candidate value. 7 | * @returns {value is string} 8 | */ 9 | export function isVw(value: unknown): value is string { 10 | return isStr(value) && value.endsWith('vw') 11 | } 12 | -------------------------------------------------------------------------------- /test/event/useIntersectionObserver.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { useIntersectionObserver } from '../../src/event' 3 | 4 | describe('useIntersectionObserver test', () => { 5 | it('test', () => { 6 | useIntersectionObserver('div', (res) => { 7 | console.log(res) 8 | }) 9 | 10 | expect(true).toBe(true) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /test/event/useMutationObserver.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { useMutationObserver } from '../../src/event' 3 | 4 | describe('useMutationObserver test', () => { 5 | it('test', () => { 6 | useMutationObserver('div', (mutations) => { 7 | console.log(mutations) 8 | }) 9 | 10 | expect(true).toEqual(true) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /src/animate/quadInOut.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 二次往返缓动曲线 3 | * @description EN: Quadratic ease-in-out that blends acceleration and deceleration symmetrically. 4 | * @param { number } t 归一化时间进度,范围 0-1 5 | * @returns { number } 6 | */ 7 | export function quadInOut(t: number) { 8 | t /= 0.5 9 | if (t < 1) 10 | return 0.5 * t * t 11 | t-- 12 | return -0.5 * (t * (t - 2) - 1) 13 | } 14 | -------------------------------------------------------------------------------- /src/is/isDivElement.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断目标是否为 HTMLDivElement 3 | * @description EN: Narrow type guard to detect
elements. 4 | * @param {unknown} target Candidate value. 5 | * @returns {target is HTMLDivElement} 6 | */ 7 | export function isDivElement(target: unknown): target is HTMLDivElement { 8 | return (target as HTMLDivElement)?.tagName?.toUpperCase() === 'DIV' 9 | } 10 | -------------------------------------------------------------------------------- /src/is/isWeakMap.ts: -------------------------------------------------------------------------------- 1 | import { _toString } from '../utils/common' 2 | 3 | /** 4 | * 判断是否为 WeakMap 5 | * @description EN: Check whether a value is a WeakMap instance. 6 | * @param {any} o Candidate value. 7 | * @returns {o is WeakMap} 8 | */ 9 | export function isWeakMap(o: any): o is WeakMap { 10 | return _toString.call(o) === '[object WeakMap]' 11 | } 12 | -------------------------------------------------------------------------------- /src/random/randomRgba.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 随机rgba颜色 3 | * @param { number } [opacity] 透明度 默认 1 4 | * @returns 5 | * @description EN: Generate a random `rgba(r,g,b,a)` color string with optional opacity. 6 | */ 7 | export function randomRgba(opacity = 1) { 8 | return `rgba(${Math.random() * 255}, ${Math.random() * 255}, ${ 9 | Math.random() * 255 10 | }, ${opacity})` 11 | } 12 | -------------------------------------------------------------------------------- /test/css/hasClassName.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { hasClassName } from '../../src/css' 3 | 4 | describe('hasClassName test', () => { 5 | it('test', () => { 6 | const el = document.createElement('div') 7 | el.setAttribute('class', 'my-class') 8 | expect(hasClassName(el, 'my-class')).toMatchInlineSnapshot('true') 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /test/css/setStyle.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { setStyle } from '../../src/css' 3 | 4 | describe('setStyle test', () => { 5 | it('test', () => { 6 | const el = document.createElement('div') 7 | setStyle(el, { 8 | background: 'red', 9 | }) 10 | expect(el.style.background).toMatchInlineSnapshot('"red"') 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /src/is/isSet.ts: -------------------------------------------------------------------------------- 1 | import { _toString } from '../utils/common' 2 | 3 | /** 4 | * 判断是否为 Set 5 | * @description EN: Check whether a value is a Set instance. 6 | * @param {any} o Candidate value. 7 | * @returns {o is Set} True when the internal [[Class]] is 'Set'. 8 | */ 9 | export function isSet(o: any): o is Set { 10 | return _toString.call(o) === '[object Set]' 11 | } 12 | -------------------------------------------------------------------------------- /src/array/index.ts: -------------------------------------------------------------------------------- 1 | export * from './diff' 2 | export * from './forEach' 3 | export * from './getAverage' 4 | export * from './quickFilter' 5 | export * from './quickFind' 6 | export * from './sort' 7 | export * from './sortByOrder' 8 | export * from './uniqueArray' 9 | export * from './chunk' 10 | export * from './countBy' 11 | export * from './flatten' 12 | export * from './filterEmpty' 13 | -------------------------------------------------------------------------------- /src/is/isMap.ts: -------------------------------------------------------------------------------- 1 | import { _toString } from '../utils/common' 2 | 3 | /** 4 | * 判断是否是 Map 5 | * @description EN: Check whether a value is a Map instance. 6 | * @param {any} o Candidate value. 7 | * @returns {o is Map} True if the internal [[Class]] is 'Map'. 8 | */ 9 | export function isMap(o: any): o is Map { 10 | return _toString.call(o) === '[object Map]' 11 | } 12 | -------------------------------------------------------------------------------- /src/is/isPlainObject.ts: -------------------------------------------------------------------------------- 1 | import { _toString } from '../utils/common' 2 | 3 | /** 4 | * Check whether value is a plain object (i.e. {}). 5 | * 6 | * @param {any} o Candidate value. 7 | * @returns {o is Record} True when `o` is a plain object. 8 | */ 9 | export function isPlainObject(o: any): o is Record { 10 | return _toString.call(o) === '[object Object]' 11 | } 12 | -------------------------------------------------------------------------------- /src/is/isStyleElement.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断目标是否为 HTMLStyleElement 3 | * @description EN: Narrow type guard for 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/animate/circInOut.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 圆形往返缓动曲线 3 | * @description EN: Circular ease-in-out that starts and ends with circular acceleration and deceleration. 4 | * @param { number } t 归一化时间进度,范围 0-1 5 | * @returns { number } 6 | */ 7 | export function circInOut(t: number) { 8 | if ((t *= 2) < 1) 9 | return -0.5 * (Math.sqrt(1 - t * t) - 1) 10 | return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1) 11 | } 12 | -------------------------------------------------------------------------------- /src/animate/quintInOut.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 五次往返缓动曲线 3 | * @description EN: Quintic ease-in-out that combines strong acceleration and deceleration using a fifth-power curve. 4 | * @param { number } t 归一化时间进度,范围 0-1 5 | * @returns { number } 6 | */ 7 | export function quintInOut(t: number) { 8 | if ((t *= 2) < 1) 9 | return 0.5 * t * t * t * t * t 10 | return 0.5 * ((t -= 2) * t * t * t * t + 2) 11 | } 12 | -------------------------------------------------------------------------------- /src/is/isEsModule.ts: -------------------------------------------------------------------------------- 1 | import type { IsESModule } from '../types' 2 | 3 | /** 4 | * 判断对象是ESModule 5 | * @description EN: Detect if an object is an ES module (common __esModule or Symbol.toStringTag). 6 | * @param obj - candidate object 7 | * @returns boolean 8 | */ 9 | export function isESModule(obj: any): obj is IsESModule { 10 | return obj.__esModule || obj[Symbol.toStringTag] === 'Module' 11 | } 12 | -------------------------------------------------------------------------------- /src/is/isMobile.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否是手机号 3 | * @description EN: Check whether a string looks like a mobile phone number (China-focused pattern). 4 | * @param s - phone number string 5 | * @returns boolean 6 | */ 7 | export function isMobile(s: string): boolean { 8 | return /^((13\d)|(14[5-9])|(15([0-35-9]))|(16[67])|(17[1-8])|(18\d)|(19[1|3])|(19[5|6])|(19[8|9]))\d{8}$/.test( 9 | s, 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /src/is/isPromise.ts: -------------------------------------------------------------------------------- 1 | import { _toString } from '../utils/common' 2 | 3 | /** 4 | * 判断是否是promise 5 | */ 6 | /** 7 | * 判断是否为 Promise 8 | * @description EN: Check whether a value is a Promise instance. 9 | * @param {any} o Candidate value. 10 | * @returns {o is Promise} 11 | */ 12 | export function isPromise(o: any): o is Promise { 13 | return _toString.call(o) === '[object Promise]' 14 | } 15 | -------------------------------------------------------------------------------- /src/is/isProxyDocument.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断目标是否为 ProxyDocument(特定实现的代理文档对象) 3 | * @description EN: Type guard that tests for an object whose [[Class]] is 'ProxyDocument'. 4 | * @param {unknown} target Candidate value. 5 | * @returns {target is Document} 6 | */ 7 | export function isProxyDocument(target: unknown): target is Document { 8 | return toString.call(target) === '[object ProxyDocument]' 9 | } 10 | -------------------------------------------------------------------------------- /src/scroll/getScrollPosition.ts: -------------------------------------------------------------------------------- 1 | import type { Position } from '../types' 2 | 3 | /** 4 | * 获取滚动条位置 5 | * @param el 默认window 6 | * @returns Position 7 | * @description EN: Return the current scroll position {x,y} for the given window-like object. 8 | */ 9 | export function getScrollPosition(el: Window = window): Position { 10 | return { 11 | x: el.scrollX, 12 | y: el.scrollY, 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/css/useNamespace.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { useNamespace } from '../../src/css' 3 | 4 | describe('useNamespace test', () => { 5 | it('test', () => { 6 | const namespace = useNamespace('vi') 7 | const select = namespace('select') 8 | const css = select.b('primary') 9 | expect(css).toMatchInlineSnapshot('"vi-select-primary"') 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /test/event/useTimeout.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { useTimeout } from '../../src/event' 3 | 4 | describe('useTimeout test', () => { 5 | it('test', () => { 6 | let count = 0 7 | const stop = useTimeout(() => { 8 | count++ 9 | }, 1000) 10 | setTimeout(() => { 11 | stop() 12 | expect(count).toEqual(1) 13 | }, 1001) 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /src/js/fnToUrl.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 将函数转化为URL 3 | * @description EN: Convert a function to a blob URL so it can be used as a 4 | * Worker script or loaded dynamically. 5 | * @param { Function } fn 函数 6 | * @returns {string} blob URL 7 | */ 8 | export function fnToUrl(fn: Function) { 9 | const blob = new Blob([`(${fn.toString()})()`], { type: 'text/javascript' }) 10 | return URL.createObjectURL(blob) 11 | } 12 | -------------------------------------------------------------------------------- /src/js/streamToUrl.ts: -------------------------------------------------------------------------------- 1 | import { toBase64 } from '../to/toBase64' 2 | 3 | /** 4 | * 将流文件转为base64 5 | * @param { ArrayBuffer } stream 流 6 | * @returns 7 | * @description EN: Convert an ArrayBuffer (binary stream) into a data URL (base64) using a Blob and helper. 8 | */ 9 | export async function streamToUrl(stream: ArrayBuffer) { 10 | return await toBase64(new Blob([stream], { type: 'image/*' })) 11 | } 12 | -------------------------------------------------------------------------------- /test/css/getClasses.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { getClasses } from '../../src/css' 3 | 4 | describe('getClasses test', () => { 5 | it('test', () => { 6 | const str = ` 7 |
8 | ` 9 | getClasses(str, (_class) => { 10 | expect(_class).toMatchInlineSnapshot('"my-class"') 11 | return _class 12 | }) 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /src/is/isVersion.ts: -------------------------------------------------------------------------------- 1 | const NUM = /^\d+$/ 2 | 3 | /** 4 | * 判断是否为版本号 5 | * @description EN: Check whether a version string consists of dot-separated numeric parts. 6 | * @param {string} version Version string, e.g. '1.2.3'. 7 | * @returns {boolean} 8 | */ 9 | export function isVersion(version: string) { 10 | return version.split('.').every(v => NUM.test(v)) 11 | } 12 | 13 | // console.log(isVersion('0.0.1')); 14 | -------------------------------------------------------------------------------- /test/compress/compressCss.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { compressCss } from '../../src/compress' 3 | 4 | describe('canvas test', () => { 5 | it('test', () => { 6 | const css = ` 7 | div { 8 | background: red; 9 | } 10 | ` 11 | const compress = compressCss(css) 12 | expect(compress).toMatchInlineSnapshot('"div{background:red;}"') 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /src/is/isImageElement.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断目标是否为 HTMLImageElement 3 | * @description EN: Narrow type check to determine whether the target is an element. 4 | * @param {unknown} target Candidate value. 5 | * @returns {target is HTMLImageElement} 6 | */ 7 | export function isImageElement(target: unknown): target is HTMLImageElement { 8 | return (target as HTMLImageElement)?.tagName?.toUpperCase() === 'IMG' 9 | } 10 | -------------------------------------------------------------------------------- /src/random/randomRange.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 获取随机范围值 3 | * @param min 最小值 默认 MIN_SAFE_INTEGER 4 | * @param max 最大值 默认 MAX_VALUE 5 | * @returns 6 | * @description EN: Return a random integer between min and max inclusive. 7 | */ 8 | export function randomRange( 9 | min: number = Number.MIN_SAFE_INTEGER, 10 | max: number = Number.MAX_VALUE, 11 | ) { 12 | return Math.floor(Math.random() * (max - min + 1)) + min 13 | } 14 | -------------------------------------------------------------------------------- /src/screen/index.ts: -------------------------------------------------------------------------------- 1 | export * from './exitFullscreen' 2 | export * from './fullScreen' 3 | export * from './picInPic' 4 | export * from './shareScreen' 5 | export * from './useCamera' 6 | export * from './useShare' 7 | export * from './useVideo' 8 | export * from './useRecorder' 9 | export * from './useVideoSubtitle' 10 | export * from './useAudio' 11 | export * from './useFrequency' 12 | export * from './getVideoFrame' 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": ["bumpp", "pnpm", "Vitesse", "vitest"], 3 | "prettier.enable": false, 4 | "typescript.tsdk": "node_modules/typescript/lib", 5 | "editor.codeActionsOnSave": { 6 | "source.fixAll.eslint": "explicit" 7 | }, 8 | "files.associations": { 9 | "*.css": "postcss" 10 | }, 11 | "unocss.root": ["playground"], 12 | "editor.inlineSuggest.showToolbar": "always" 13 | } 14 | -------------------------------------------------------------------------------- /src/is/isNode.ts: -------------------------------------------------------------------------------- 1 | import { isNum } from './isNum' 2 | 3 | /** 4 | * 判断目标是否为 DOM Node 5 | * @description EN: Returns true when the target is a DOM Node or looks like one (has numeric nodeType). 6 | * @param {unknown} target Candidate value. 7 | * @returns {target is Node} 8 | */ 9 | export function isNode(target: unknown): target is Node { 10 | return target instanceof Node || isNum((target as Node)?.nodeType) 11 | } 12 | -------------------------------------------------------------------------------- /test/array/chunk.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { chunk } from '../../src/array' 3 | 4 | describe('chunk test', () => { 5 | it('test', () => { 6 | expect(chunk([1, 3, 5, 7], 2)).toMatchInlineSnapshot(` 7 | [ 8 | [ 9 | 1, 10 | 3, 11 | ], 12 | [ 13 | 5, 14 | 7, 15 | ], 16 | ] 17 | `) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /test/event/useEventListener.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { useEventListener } from '../../src/event' 3 | 4 | describe('useEventListener test', () => { 5 | it('test', () => { 6 | const p = document.createElement('div') 7 | // useEventListener 8 | useEventListener(p, 'click', () => { 9 | console.log('click') 10 | }) 11 | expect(true).toBe(true) 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /src/animate/backInOut.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 回拉往返缓动曲线 3 | * @description EN: Back-in-out easing that overshoots when starting and ending the motion. 4 | * @param { number } t 归一化时间进度,范围 0-1 5 | * @returns { number } 6 | */ 7 | export function backInOut(t: number) { 8 | const s = 1.70158 * 1.525 9 | if ((t *= 2) < 1) 10 | return 0.5 * (t * t * ((s + 1) * t - s)) 11 | return 0.5 * ((t -= 2) * t * ((s + 1) * t + s) + 2) 12 | } 13 | -------------------------------------------------------------------------------- /src/js/curry.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 柯里化 3 | * @description EN: Create a curried version of the provided function. 4 | * @param { Function } f 函数 5 | * @returns 函数 6 | */ 7 | export function curry(f: Function) { 8 | const g = (...args: any[]) => { 9 | if (args.length >= f.length) 10 | return f(...args) 11 | 12 | return (...more: any[]) => { 13 | return g(...args, ...more) 14 | } 15 | } 16 | return g 17 | } 18 | -------------------------------------------------------------------------------- /src/node/fileCopy.ts: -------------------------------------------------------------------------------- 1 | import { jsShell } from './jsShell' 2 | 3 | /** 4 | * 将文件拷贝到另一个目录 5 | * @param urls 需要被拷贝的文件路径 6 | * @param destination 目录 7 | * @returns IShellMessage 8 | * @description EN: Copy given files to a destination directory via a shell command wrapper. 9 | */ 10 | export function fileCopy(urls: string[], destination: string) { 11 | return jsShell(`cp -r {${urls.join(',')}} ${destination}`, 'pipe') 12 | } 13 | -------------------------------------------------------------------------------- /src/animate/expoInOut.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 指数往返缓动曲线 3 | * @description EN: Exponential ease-in-out that uses steep growth and decay for a dramatic motion. 4 | * @param { number } t 归一化时间进度,范围 0-1 5 | * @returns { number } 6 | */ 7 | export function expoInOut(t: number) { 8 | return t === 0.0 || t === 1.0 9 | ? t 10 | : t < 0.5 11 | ? +0.5 * 2.0 ** (20.0 * t - 10.0) 12 | : -0.5 * 2.0 ** (10.0 - t * 20.0) + 1.0 13 | } 14 | -------------------------------------------------------------------------------- /src/css/setStyle.ts: -------------------------------------------------------------------------------- 1 | import { stringify } from '../object/stringify' 2 | import { mount } from '../utils/mount' 3 | 4 | export function setStyle( 5 | el: HTMLElement | string, 6 | styleObj: Record, 7 | ) { 8 | const styles = stringify(styleObj, { 9 | sep: ';', 10 | eq: ':', 11 | hyp: true, 12 | }) 13 | return mount(el, (el) => { 14 | el.style.cssText = el.style.cssText + styles 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /src/node/withTaskName.ts: -------------------------------------------------------------------------------- 1 | // gulpfile -> withTaskName('clean', async () => run('rm -rf dist')), 2 | /** 3 | * 处理gulp任务 4 | * @param name 任务名 5 | * @param fn 任务函数 6 | * @returns 7 | * @description EN: Attach a displayName property to a function so tools like Gulp can show the task name. 8 | */ 9 | export function withTaskName(name: string, fn: T) { 10 | return Object.assign(fn, { displayName: name }) 11 | } 12 | -------------------------------------------------------------------------------- /src/object/clearUndefined.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 删除对象中值为undefined的key 3 | * @param obj 对象 4 | * @returns 5 | * @description EN: Remove keys whose values are `undefined` from an object (mutates and returns the same object). 6 | */ 7 | export function clearUndefined>(obj: T): T { 8 | Object.keys(obj).forEach((key: string) => 9 | obj[key] === undefined ? delete obj[key] : {}, 10 | ) 11 | return obj 12 | } 13 | -------------------------------------------------------------------------------- /src/perf/once.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 只执行一次函数 3 | * @param { Function } fn 函数 4 | * @returns { Function } 函数 5 | * @description EN: Wrap `fn` so it only runs once; subsequent calls are ignored. 6 | */ 7 | export function once(fn: Function): Function { 8 | let called = false 9 | return function (this: any, ...args: any[]) { 10 | if (!called) { 11 | called = true 12 | return fn.apply(this, args) 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /playground/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "lang": "en-us", 3 | "name": "Temperature converter app", 4 | "short_name": "Temperature converter", 5 | "description": "A basic temperature converter application that can convert to and from Celsius, Kelvin, and Fahrenheit", 6 | "start_url": "./", 7 | "background_color": "#2f3d58", 8 | "theme_color": "#2f3d58", 9 | "orientation": "any", 10 | "display": "standalone", 11 | "icons": [] 12 | } 13 | -------------------------------------------------------------------------------- /src/css/addStyleRules.ts: -------------------------------------------------------------------------------- 1 | import { mount } from '../utils' 2 | 3 | /** 4 | * 5 | * @param { string } rule '#blanc { color: white }' 6 | * @param { number } [index] cssRules 中的位置 7 | * @description EN: Insert a CSS rule string into the first stylesheet at an optional index. 8 | */ 9 | export function addRules(rule: string, index?: number) { 10 | mount(() => { 11 | document.styleSheets[0].insertRule(rule, index) 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /src/to/toArray.ts: -------------------------------------------------------------------------------- 1 | import { isArray } from './../is/isArray' 2 | 3 | /** 4 | * Ensure value is an array. If input is already an array it is returned as-is; 5 | * otherwise the value is wrapped in a single-element array. 6 | * 7 | * @param array - Value or array of values. 8 | * @returns An array containing the original value(s). 9 | */ 10 | export function toArray(array: T) { 11 | return isArray(array) ? array : [array] 12 | } 13 | -------------------------------------------------------------------------------- /src/animate/bounceInOut.ts: -------------------------------------------------------------------------------- 1 | import { bounceOut } from './bounceOut' 2 | 3 | /** 4 | * 弹跳往返缓动曲线 5 | * @description EN: Bounce-in-out easing that bounces when starting and when finishing the motion. 6 | * @param { number } t 归一化时间进度,范围 0-1 7 | * @returns { number } 8 | */ 9 | export function bounceInOut(t: number) { 10 | return t < 0.5 11 | ? 0.5 * (1.0 - bounceOut(1.0 - t * 2.0)) 12 | : 0.5 * bounceOut(t * 2.0 - 1.0) + 0.5 13 | } 14 | -------------------------------------------------------------------------------- /src/css/setCssVar.ts: -------------------------------------------------------------------------------- 1 | import type { MaybeElement } from '../types' 2 | import { mount } from '../utils/mount' 3 | 4 | export function setCssVar( 5 | element: MaybeElement, 6 | styleObj: Record, 7 | ) { 8 | return mount( 9 | element, 10 | el => 11 | styleObj 12 | && Object.keys(styleObj).forEach(key => 13 | (el as HTMLElement).style.setProperty(key, styleObj[key]), 14 | ), 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /src/is/isAbsolute.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否是绝对路径 3 | * @description EN: Return true when the provided path looks like an absolute path. Matches Unix absolute paths, Windows drive letters, or leading slashes/backslashes. 4 | * @param {string} url Path or URL string to test. 5 | * @returns {boolean} True when the path appears absolute. 6 | */ 7 | export function isAbsolute(url: string): boolean { 8 | return /^\/|^\\|^[a-z]:[/\\]/i.test(url) 9 | } 10 | -------------------------------------------------------------------------------- /src/is/isVar.ts: -------------------------------------------------------------------------------- 1 | import { isStr } from './isStr' 2 | 3 | /** 4 | * 判断是否是 css 变量表达式 (var(...)) 5 | * @description EN: Check whether a string is a CSS variable usage like "var(--x)". 6 | * @param {unknown} value Candidate value. 7 | * @returns {value is string} True when the value is a string starting with 'var('. 8 | */ 9 | export function isVar(value: unknown): value is string { 10 | return isStr(value) && value.startsWith('var(') 11 | } 12 | -------------------------------------------------------------------------------- /src/html/escapeHtml.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 转义html 3 | * @description EN: Escape HTML special characters in a string to their entity equivalents. 4 | * @param { string } s 字符串 5 | * @returns 6 | */ 7 | export function escapeHtml(s: string): string { 8 | return s.replace( 9 | /[&<>'"]/g, 10 | (tag: string) => 11 | ({ '&': '&', '<': '<', '>': '>', '\'': ''', '"': '"' }[ 12 | tag 13 | ] || tag), 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /src/is/isPx.ts: -------------------------------------------------------------------------------- 1 | import { isNum } from './isNum' 2 | import { isStr } from './isStr' 3 | 4 | /** 5 | * 判断是否为 px 单位或数字 6 | * @description EN: Returns true for numbers or strings ending with 'px' (e.g. '12px'). 7 | * @param {unknown} value Candidate value. 8 | * @returns {value is string | number} 9 | */ 10 | export function isPx(value: unknown): value is string | number { 11 | return (isStr(value) && value.endsWith('px')) || isNum(value) 12 | } 13 | -------------------------------------------------------------------------------- /test/css/getCssVar.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { getCssVar } from '../../src/css' 3 | 4 | describe('getCssVar test', () => { 5 | it('test', () => { 6 | const el = document.createElement('div') 7 | el.setAttribute('style', '--main-bg: red;') 8 | 9 | getCssVar(el, '--main-bg', (_class) => { 10 | expect(_class).toMatchInlineSnapshot(`"red"`) 11 | return _class 12 | }) 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /test/event/useInterval.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { useInterval } from '../../src/event' 3 | 4 | describe('useInterval test', () => { 5 | it('test', () => { 6 | let count = 0 7 | const stop = useInterval(() => { 8 | count++ 9 | }, 1000) 10 | expect(count).toEqual(0) 11 | 12 | setTimeout(() => { 13 | stop() 14 | expect(count).toEqual(1) 15 | }, 1000) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /src/css/addClass.ts: -------------------------------------------------------------------------------- 1 | import { mount } from '../utils' 2 | import type { MaybeElement } from './../types' 3 | 4 | /** 5 | * dom上添加class 6 | * @description EN: Add a CSS class to the provided element(s). 7 | * @param { MaybeElement } selector 元素 8 | * @param { string } className class类 9 | * @returns 10 | */ 11 | export function addClass(selector: MaybeElement, className: string) { 12 | return mount(selector, el => el.classList.add(className)) 13 | } 14 | -------------------------------------------------------------------------------- /src/is/isFileType.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断文件类型 3 | * @description EN: Test if a filename/path has a given extension. 4 | * @param { string } file 文件路径 5 | * @param { string } appendix 文件类型 6 | * @returns boolean 7 | */ 8 | export function isFileType(file: string, appendix: string): boolean { 9 | const reg = new RegExp(`\\.${appendix}(\\?[^.]+)?$`) 10 | return reg.test(file) 11 | } 12 | 13 | // const isCss = isFileType('./useEventListener.css', 'css') // true 14 | -------------------------------------------------------------------------------- /src/node/isExist.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import { toAbsolutePath } from '../to/toAbsolutePath' 3 | 4 | /** 5 | * 判断文件是否存在 6 | * @param url 7 | * @returns 8 | * @description EN: Return true if the given path exists on disk, false otherwise. 9 | */ 10 | export function isExist(url: string): boolean { 11 | try { 12 | fs.accessSync(toAbsolutePath(url)) 13 | return true 14 | } 15 | catch (error) { 16 | return false 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/perf/index.ts: -------------------------------------------------------------------------------- 1 | export * from './debounce' 2 | export * from './fileSplice' 3 | export * from './getLru' 4 | export * from './lazyLoad' 5 | export * from './memorizeFn' 6 | export * from './preload' 7 | export * from './prefetch' 8 | export * from './throttle' 9 | export * from './useRaf' 10 | export * from './useRic' 11 | export * from './once' 12 | export * from './createChunk' 13 | export * from './createHybridMap' 14 | export * from './createHybridSet' 15 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "tabWidth": 2, 4 | "singleQuote": true, 5 | "printWidth": 80, 6 | "trailingComma": "all", 7 | "overrides": [ 8 | { 9 | "files": ["*.json5"], 10 | "options": { 11 | "singleQuote": false, 12 | "quoteProps": "preserve" 13 | } 14 | }, 15 | { 16 | "files": ["*.yml"], 17 | "options": { 18 | "singleQuote": false 19 | } 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /src/date/getDaysOfMonth.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the number of days in a given month. 3 | * 4 | * @param {number} currentMonth - The month for which to get the number of days (0-based, where 0 = January, 11 = December). 5 | * @returns {number} The number of days in the specified month. 6 | */ 7 | export function getDaysOfMonth(currentMonth: number) { 8 | const d = new Date() 9 | d.setMonth(currentMonth + 1) 10 | d.setDate(0) 11 | return d.getDate() 12 | } 13 | -------------------------------------------------------------------------------- /src/node/isWritable.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | 3 | /** 4 | * 同步地测试用户对 path 指定的文件或目录的权限 5 | * @param { string } filename 文件或目录路径 6 | * @returns 7 | * @description EN: Return true if the given file or directory is writable by the current user. 8 | */ 9 | export function isWritable(filename: string): boolean { 10 | try { 11 | fs.accessSync(filename, fs.constants.W_OK) 12 | return true 13 | } 14 | catch { 15 | return false 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/utils/unmount.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 浏览器卸载时 3 | * @description EN: Register a callback to run when the window unloads. 4 | * @param { (ev: Event) => void } callback 回调 5 | * @returns { (ev: Event) => void } 6 | */ 7 | export function unmount(callback: (ev: Event) => void) { 8 | const fn = window.onunload || function () {} 9 | window.onunload = function (ev: Event) { 10 | callback?.(ev) 11 | fn.call(this as any, ev) 12 | } 13 | return callback 14 | } 15 | -------------------------------------------------------------------------------- /test/html/unescapeHtml.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { unescapeHtml } from '../../src/html' 3 | 4 | describe('unescapeHtml test', () => { 5 | it('test', () => { 6 | const template 7 | = '<div style="background:red;">hello, world</div>' 8 | 9 | expect(unescapeHtml(template)).toMatchInlineSnapshot( 10 | `"
hello, world
"`, 11 | ) 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /src/css/removeClass.ts: -------------------------------------------------------------------------------- 1 | import { mount } from '../utils' 2 | import type { MaybeElement } from './../types' 3 | 4 | /** 5 | * dom上删除class 6 | * @description EN: Remove a CSS class from the provided element(s). 7 | * @param { MaybeElement } selector 元素 8 | * @param { string } className class类 9 | * @returns 10 | */ 11 | export function removeClass(selector: MaybeElement, className: string) { 12 | return mount(selector, el => el.classList.remove(className)) 13 | } 14 | -------------------------------------------------------------------------------- /src/html/unescapeHtml.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 反转义html 3 | * @description EN: Convert HTML entities back into their corresponding characters. 4 | * @param { string } s 字符串 5 | * @returns 6 | */ 7 | export function unescapeHtml(s: string): string { 8 | return s.replace( 9 | /&|<|>|'|"/g, 10 | (tag: string) => 11 | ({ '&': '&', '<': '<', '>': '>', ''': '\'', '"': '"' }[ 12 | tag 13 | ] || tag), 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /src/string/escapeRegExp.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Escape special characters in a string so it can be used safely inside a 3 | * regular expression pattern. 4 | * 5 | * @param str - input string 6 | * @returns escaped string safe for use in RegExp constructors 7 | * @description EN: Escape special characters so the string can be used inside a RegExp pattern. 8 | */ 9 | export function escapeRegExp(str: string) { 10 | return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') 11 | } 12 | -------------------------------------------------------------------------------- /test/event/insertElement.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { insertElement } from '../../src/event' 3 | 4 | describe('insertElement test', () => { 5 | it('test', () => { 6 | const p = document.createElement('div') 7 | const div = document.createElement('div') 8 | insertElement(p, div) 9 | expect(p.childNodes).toMatchInlineSnapshot(` 10 | NodeList [ 11 |
, 12 | ] 13 | `) 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /playground/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import { createRouter, createWebHistory } from 'vue-router' 3 | import routes from 'virtual:generated-pages' 4 | import App from './App.vue' 5 | import '@unocss/reset/tailwind.css' 6 | import './styles/main.css' 7 | import 'uno.css' 8 | 9 | const app = createApp(App) 10 | const router = createRouter({ 11 | history: createWebHistory(import.meta.env.BASE_URL), 12 | routes, 13 | }) 14 | app.use(router) 15 | app.mount('#app') 16 | -------------------------------------------------------------------------------- /src/is/isSupportCamera.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断当前环境是否支持摄像头采集(getUserMedia) 3 | * @description EN: Heuristic detection for getUserMedia support across legacy vendor prefixes. 4 | * @returns {boolean} 5 | */ 6 | export function isSupportCamera(): boolean { 7 | return !!( 8 | navigator 9 | && (navigator.getUserMedia 10 | || (navigator as any).webkitGetUserMedia 11 | || (navigator as any).mozGetUserMedia 12 | || (navigator as any).msGetUserMedia) 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /src/monitor/timeCost.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 检测函数执行耗时 3 | * @description EN: Measure and log the execution time (seconds) of the provided synchronous function. 4 | * @param { Function } fn 需要测量的函数 5 | * @returns { number } 函数执行耗时(秒) 6 | */ 7 | export function timeCost(fn: Function): number { 8 | const start = new Date().getTime() 9 | fn() 10 | const end = new Date().getTime() 11 | const time = (end - start) / 1000 12 | console.log(`timeCost: ${time}s`) 13 | return time 14 | } 15 | -------------------------------------------------------------------------------- /src/asserts/assertEquals.ts: -------------------------------------------------------------------------------- 1 | import { isEqual } from '../is/isEqual' 2 | 3 | type Allowed = 4 | | unknown 5 | | void 6 | | null 7 | | undefined 8 | | boolean 9 | | number 10 | | string 11 | | unknown[] 12 | | object 13 | 14 | export function assertEquals( 15 | val: T, 16 | expected: U, 17 | message = 'val is not same as expected', 18 | ): asserts val is U { 19 | if (!isEqual(val, expected)) 20 | throw new Error(message) 21 | } 22 | -------------------------------------------------------------------------------- /src/string/ensureSuffix.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Ensure the given string ends with the provided suffix. If it already 3 | * ends with the suffix, the original string is returned; otherwise the 4 | * suffix is appended. 5 | * 6 | * @param suffix - suffix to ensure 7 | * @param str - input string 8 | * @returns string that ends with suffix 9 | */ 10 | export function ensureSuffix(suffix: string, str: string) { 11 | if (str.endsWith(suffix)) 12 | return str 13 | return str + suffix 14 | } 15 | -------------------------------------------------------------------------------- /src/event/download.ts: -------------------------------------------------------------------------------- 1 | import { createElement } from './createElement' 2 | 3 | /** 4 | * Trigger a download by creating a temporary anchor and clicking it. 5 | * 6 | * @param href - URL to download 7 | * @param download - suggested filename 8 | */ 9 | export function download(href: string, download = '') { 10 | try { 11 | createElement('a', { 12 | href, 13 | download, 14 | }).click() 15 | } 16 | catch (error: any) { 17 | throw new Error(error) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/is/isLeapYear.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Determine whether a year is a leap year. 3 | * 4 | * @param {number} year Year number. 5 | * @returns {boolean} 6 | */ 7 | /** 8 | * 判断是否为闰年 9 | * @description EN: Returns true for leap years (divisible by 400 or divisible by 4 and not by 100). 10 | * @param {number} year Year number. 11 | * @returns {boolean} 12 | */ 13 | export function isLeapYear(year: number): boolean { 14 | return year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0) 15 | } 16 | -------------------------------------------------------------------------------- /src/node/transformArgv.ts: -------------------------------------------------------------------------------- 1 | import process from 'node:process' 2 | 3 | /** 4 | * 5 | * @returns 处理argv --flag如果未设置值默认为true 6 | * @description EN: Parse process.argv into a key/value object where `--flag` without `=` yields true. 7 | */ 8 | export function transformArgv() { 9 | return process?.argv?.slice(2).reduce((result, arg) => { 10 | const [key, value] = arg.split('=') 11 | result[key.slice(2)] = value || true 12 | return result 13 | }, {} as Record) 14 | } 15 | -------------------------------------------------------------------------------- /test/html/escapeHtml.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { escapeHtml } from '../../src/html' 3 | 4 | describe('escapeHtml test', () => { 5 | it('test', () => { 6 | const template = ` 7 |
hello, world
8 | ` 9 | 10 | expect(escapeHtml(template)).toMatchInlineSnapshot(` 11 | " 12 | <div style="background:red;">hello, world</div> 13 | " 14 | `) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /src/string/ensurePrefix.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Ensure the given string starts with the provided prefix. If it already 3 | * starts with the prefix, the original string is returned; otherwise the 4 | * prefix is prepended. 5 | * 6 | * @param prefix - prefix to ensure 7 | * @param str - input string 8 | * @returns string that starts with prefix 9 | */ 10 | export function ensurePrefix(prefix: string, str: string) { 11 | if (!str.startsWith(prefix)) 12 | return prefix + str 13 | return str 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/beforeUnmount.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 浏览器关闭前 3 | * @description EN: Register a callback to run before the window unloads (beforeunload). 4 | * @param { (ev: Event) => void } callback 回调 5 | * @returns { (ev: Event) => void } 6 | */ 7 | export function beforeUnmount(callback: (ev: Event) => void) { 8 | const fn = window.onbeforeunload || function () {} 9 | window.onbeforeunload = function (ev: Event) { 10 | callback?.(ev) 11 | fn.call(this as any, ev) 12 | } 13 | return callback 14 | } 15 | -------------------------------------------------------------------------------- /src/date/getFirstDay.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 获取当前周的周一日期 3 | * @description EN: Return the ISO date string for Monday of the current week (YYYY-MM-DD). 4 | * @returns 5 | */ 6 | export function getFirstDay() { 7 | const today = new Date() 8 | const day = today.getDay() // 获取今天是周几 9 | const diff = day === 0 ? 6 : day - 1 // 计算与周一相差天数 10 | const monday = new Date(today) // 克隆一次日期对象 11 | monday.setDate(monday.getDate() - diff) // 设置为周一日期 12 | return monday.toISOString().match(/(^\d+-\d+-\d+)/)![1] 13 | } 14 | -------------------------------------------------------------------------------- /src/is/isEmpty.ts: -------------------------------------------------------------------------------- 1 | import { isArray } from './isArray' 2 | /** 3 | * 判断是否为空:undefined、null、空字符串 或 空数组 4 | * @description EN: Returns true for undefined, null, empty string, or empty arrays. 5 | * @param {unknown} val Candidate value to test. 6 | * @returns {boolean} True when the value is considered empty. 7 | */ 8 | export function isEmpty(val: unknown) { 9 | return ( 10 | val === undefined 11 | || val === null 12 | || val === '' 13 | || (isArray(val) && !val.length) 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /src/is/isKeyOf.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 属性是否存在于对象 3 | * @param { object } obj 对象 4 | * @param { string } k 属性 5 | * @returns 6 | */ 7 | /** 8 | * 判断属性是否存在于对象上 9 | * @description EN: Narrowing helper that checks whether a given key exists on an object (using the `in` operator). 10 | * @param {object} obj Target object. 11 | * @param {string|symbol} k Candidate key. 12 | * @returns {k is keyof T} 13 | */ 14 | export function isKeyOf(obj: T, k: keyof any): k is keyof T { 15 | return k in obj 16 | } 17 | -------------------------------------------------------------------------------- /src/string/index.ts: -------------------------------------------------------------------------------- 1 | export * from './camelize' 2 | export * from './getUrlParam' 3 | export * from './hyphenate' 4 | export * from './pwdLevel' 5 | export * from './trim' 6 | export * from './useJSONParse' 7 | export * from './spaceFormat' 8 | export * from './parseLrc' 9 | export * from './parseTime' 10 | export * from './hash' 11 | export * from './transformWithBack' 12 | export * from './escapeRegExp' 13 | export * from './compareVersion' 14 | export * from './findMatchEnd' 15 | export * from './findMatchStart' 16 | -------------------------------------------------------------------------------- /src/to/index.ts: -------------------------------------------------------------------------------- 1 | export * from './arrayToExcel' 2 | export * from './arrayToTree' 3 | export * from './base64ToBlob' 4 | export * from './base64ToFile' 5 | export * from './toAbsolutePath' 6 | export * from './toSlice' 7 | export * from './toBase64' 8 | export * from './toObject' 9 | export * from './toArray' 10 | export * from './treeToArray' 11 | export * from './fileToBlob' 12 | export * from './fileToArrayBuffer' 13 | export * from './blobToUrl' 14 | export * from './rgbToHex' 15 | export * from './hexToRgb' 16 | -------------------------------------------------------------------------------- /src/array/chunk.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 将数组拆分为指定大小的块 3 | * @description EN: Split an array into chunks of the given size. 4 | * @param {T[]} arr Input array to split. 5 | * @param {number} [size] Maximum size of each chunk. 6 | * @returns {T[][]} Array of chunked arrays. 7 | */ 8 | export function chunk(arr: T[], size = 1): T[][] { 9 | if (size < 1) 10 | return [] 11 | const result: T[][] = [] 12 | for (let i = 0; i < arr.length; i += size) result.push(arr.slice(i, i + size)) 13 | 14 | return result 15 | } 16 | -------------------------------------------------------------------------------- /src/is/isBase64.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Check whether a string is a base64 data URL. 3 | * 4 | * @description EN: Validate whether a string is a data URL that contains base64-encoded content. 5 | * @param {string} base64 Candidate string. 6 | * @returns {boolean} True when the string looks like a base64 data URL. 7 | */ 8 | export function isBase64(base64: string) { 9 | return /^\s*data:(?:[a-z]+\/[a-z0-9-+.]+(?:;[a-z-]+=[a-z0-9-]+)?)?(?:;base64)?,([\w!$&',()*+;=\-.~:@/?%\s]*?)\s*/i.test( 10 | base64, 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /test/css/removeStyle.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { removeStyle } from '../../src/css' 3 | 4 | describe('removeStyle test', () => { 5 | it('test', () => { 6 | const el = document.createElement('div') 7 | // el.setAttribute('style', 'background:red;') 8 | el.style.background = 'red' 9 | expect(el.style.background).toMatchInlineSnapshot('"red"') 10 | removeStyle(el, 'background') 11 | expect(el.style.background).toMatchInlineSnapshot('""') 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /src/is/isIdCard.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否是身份证 3 | * @param s 4 | * @returns 5 | */ 6 | /** 7 | * 判断是否为身份证号(中国身份证格式的简单校验) 8 | * @description EN: Heuristic check for Chinese ID card numbers (15 or 18 digits with possible 'X'). 9 | * @param {string} s Candidate ID string. 10 | * @returns {boolean} 11 | */ 12 | export function isIdCard(s: string): boolean { 13 | return /^[1-9]\d{7}((0\d)|(1[0-2]))(([0|12]\d)|3[01])\d{3}$|^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|12]\d)|3[01])\d{3}([0-9X])$/.test( 14 | s, 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /playground/src/pages/hi/[name].vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 21 | -------------------------------------------------------------------------------- /src/is/isSoldierId.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否是军官证 3 | * @description EN: Heuristic check for a soldier/officer ID string. This function uses a simple regex and may not cover all real-world formats; it is intended as a lightweight validator rather than authoritative verification. 4 | * @param {string} s Candidate ID string. 5 | * @returns {boolean} True when the string matches the expected pattern. 6 | */ 7 | export function isSoldierId(s: string) { 8 | return /^[\u4E00-\u9FA5](\u5B57\u7B2C)([0-9a-z]{4,8})(\u53F7?)$/i.test(s) 9 | } 10 | -------------------------------------------------------------------------------- /src/is/isSupportWebp.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断当前浏览器是否支持 WebP 3 | * @description EN: Returns true when the current browser can encode a WebP 4 | * data URL from a canvas (a common heuristic for WebP support). 5 | * @returns {boolean} 6 | */ 7 | export function isSupportWebp(): boolean { 8 | try { 9 | return ( 10 | document 11 | .createElement('canvas') 12 | .toDataURL('image/webp') 13 | .indexOf('data:image/webp') === 0 14 | ) 15 | } 16 | catch (error) { 17 | return false 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/node/index.ts: -------------------------------------------------------------------------------- 1 | export * from './fileCopy' 2 | export * from './getExportBundle' 3 | export * from './getPkg' 4 | export * from './getPkgTool' 5 | export * from './jsShell' 6 | export * from './transformArgv' 7 | export * from './useNodeWorker' 8 | export * from './withTaskName' 9 | export * from './writeFile' 10 | export * from './hasPkg' 11 | export * from './isInstallPkg' 12 | export * from './isExist' 13 | export * from './isGo' 14 | export * from './isRust' 15 | export * from './isWritable' 16 | export * from './isPkg' 17 | -------------------------------------------------------------------------------- /src/string/splice.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Insert newValue into str at the given index (string splice). 3 | * 4 | * @param str - original string 5 | * @param index - insertion index 6 | * @param newValue - string to insert 7 | * @returns new string with newValue inserted 8 | * @description EN: Insert a substring into a string at the specified index and return the new string. 9 | */ 10 | export function splice(str: string, index: number, newValue: string) { 11 | return `${str.slice(0, index)}${newValue}${str.slice(index)}` 12 | } 13 | -------------------------------------------------------------------------------- /src/to/fileToBlob.ts: -------------------------------------------------------------------------------- 1 | import { fileToArrayBuffer } from './fileToArrayBuffer' 2 | 3 | /** 4 | * Convert a File to a Blob (preserves provided MIME type). 5 | * 6 | * @param file - File to convert 7 | * @param type - Optional MIME type for the resulting Blob 8 | * @returns A Blob built from the file's ArrayBuffer 9 | */ 10 | export async function fileToBlob(file: File, type: string = file.type) { 11 | const buffer = await fileToArrayBuffer(file) 12 | const blob = new Blob([buffer], { type }) 13 | return blob 14 | } 15 | -------------------------------------------------------------------------------- /playground/src/pages/README.md: -------------------------------------------------------------------------------- 1 | ## File-based Routing 2 | 3 | Routes will be auto-generated for Vue files in this dir with the same file structure. 4 | Check out [`vite-plugin-pages`](https://github.com/hannoeru/vite-plugin-pages) for more details. 5 | 6 | ### Path Aliasing 7 | 8 | `~/` is aliased to `./src/` folder. 9 | 10 | For example, instead of having 11 | 12 | ```ts 13 | import { isDark } from '../../../../composables' 14 | ``` 15 | 16 | now, you can use 17 | 18 | ```ts 19 | import { isDark } from '~/composables' 20 | ``` 21 | -------------------------------------------------------------------------------- /src/node/isRust.ts: -------------------------------------------------------------------------------- 1 | import process from 'node:process' 2 | import path from 'node:path' 3 | import { jsShell } from './jsShell' 4 | 5 | /** 6 | * 判断是否是rust环境 7 | * @description EN: Detect whether the current project is a Rust project by checking for Cargo.toml. 8 | */ 9 | export async function isRust(rootPath = process.cwd()) { 10 | const url = path.resolve(rootPath, 'Cargo.toml') 11 | const { result } = await jsShell( 12 | `test -f "${url}" && echo "0"|| echo "1"`, 13 | 'pipe', 14 | ) 15 | return result === '0' 16 | } 17 | -------------------------------------------------------------------------------- /src/screen/useShare.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 分享内容 3 | * @param options files?: File[]; 4 | text?: string; 5 | title?: string; 6 | url?: string; 7 | * @returns 8 | */ 9 | export function useShare(options: ShareData = {}) { 10 | if (!navigator || !navigator.canShare) 11 | throw new Error('当前环境不支持分享') 12 | // @description EN: Use the Web Share API to share files/text/url if supported by the user agent. 13 | if (!options.files || navigator.canShare({ files: options.files })) 14 | return navigator.share(options) 15 | } 16 | -------------------------------------------------------------------------------- /src/string/parseTime.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Parse a time string (HH:MM:SS or MM:SS) into total seconds. 3 | * 4 | * Example: '01:02:03' => 1 * 3600 + 2 * 60 + 3 5 | * 6 | * @param timeStr - time string in H:M:S or M:S form 7 | * @returns number of seconds represented by the string 8 | */ 9 | export function parseTime(timeStr: string) { 10 | const parts = timeStr.split(':') 11 | const len = parts.length - 1 12 | return parts.reduce((pre, cur, i) => pre + +cur * 60 ** (len - i), 0) 13 | } 14 | 15 | // console.log(parseTime('01:02:03')) 16 | -------------------------------------------------------------------------------- /src/to/toAbsolutePath.ts: -------------------------------------------------------------------------------- 1 | import process from 'node:process' 2 | import path from 'node:path' 3 | import { isAbsolute } from '../is/isAbsolute' 4 | 5 | /** 6 | * 将相对路径转换为基于当前工作目录的绝对路径 7 | * @param {string} url 要转换的路径 8 | * @returns {string} 绝对路径 9 | * @description EN: Convert a relative filesystem path to an absolute path using process.cwd(); returns the original path if already absolute. 10 | */ 11 | export function toAbsolutePath(url: string): string { 12 | return isAbsolute(url) ? url : path.resolve(process.cwd(), url) 13 | } 14 | -------------------------------------------------------------------------------- /src/node/hasPkg.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | import { jsShell } from './jsShell' 3 | 4 | /** 5 | * 判断是否存在package.json 6 | * @param { string } rootPath 绝对路径 7 | * @returns boolean 8 | * @description EN: Check whether a package.json exists at the given root path. 9 | */ 10 | export async function hasPkg(rootPath: string) { 11 | const url = path.resolve(rootPath, 'package.json') 12 | const { result } = await jsShell( 13 | `test -f "${url}" && echo "0"|| echo "1"`, 14 | 'pipe', 15 | ) 16 | return result === '0' 17 | } 18 | -------------------------------------------------------------------------------- /src/array/getAverage.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 计算数字数组的平均值并格式化为指定小数位 3 | * @description EN: Compute the average of a number array and return it formatted to a fixed number of decimal places. 4 | * @param {number[]} array Input numbers. 5 | * @param {number} [fraction] Number of decimal places to keep. 6 | * @returns {string} Formatted average as string (from toFixed). 7 | */ 8 | export function getAverage(array: number[], fraction = 2): string { 9 | return (array.reduce((pre, cur) => pre + cur, 0) / array.length).toFixed( 10 | fraction, 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /src/string/spaceFormat.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Normalize repeated whitespace in a string by replacing runs of whitespace 3 | * characters with a single replacer string (default is a single space). 4 | * 5 | * @param str - input string 6 | * @param replacer - replacement for runs of whitespace (default: ' ') 7 | * @returns normalized string 8 | * @description EN: Replace consecutive whitespace characters with a single replacer string. 9 | */ 10 | export function spaceFormat(str: string, replacer = ' ') { 11 | return str.replace(/\s+/g, replacer) 12 | } 13 | -------------------------------------------------------------------------------- /src/to/base64ToBlob.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Convert a base64 data URL to a `Blob`. 3 | * 4 | * @param {string} s Base64 data URL (e.g. 'data:image/png;base64,...'). 5 | * @returns {Blob} A Blob containing the decoded bytes. 6 | */ 7 | export function base64ToBlob(s: string): Blob { 8 | const arr = s.split(',') 9 | const mime = arr[0]?.match(/:(.*?);/)?.[1] 10 | const bstr = atob(arr[1]) 11 | let n = bstr.length 12 | const u8arr = new Uint8Array(n) 13 | while (n--) u8arr[n] = bstr.charCodeAt(n) 14 | return new Blob([u8arr], { type: mime }) 15 | } 16 | -------------------------------------------------------------------------------- /src/animate/elasticInOut.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 弹性往返缓动曲线 3 | * @description EN: Elastic ease-in-out that oscillates at both the start and end of the motion. 4 | * @param { number } t 归一化时间进度,范围 0-1 5 | * @returns { number } 6 | */ 7 | export function elasticInOut(t: number) { 8 | return t < 0.5 9 | ? 0.5 10 | * Math.sin(((+13.0 * Math.PI) / 2) * 2.0 * t) 11 | * 2.0 ** (10.0 * (2.0 * t - 1.0)) 12 | : 0.5 13 | * Math.sin(((-13.0 * Math.PI) / 2) * (2.0 * t - 1.0 + 1.0)) 14 | * 2.0 ** (-10.0 * (2.0 * t - 1.0)) 15 | + 1.0 16 | } 17 | -------------------------------------------------------------------------------- /src/event/useRange.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Get the bounding client rect for a caret/cursor position at the start of a node. 3 | * Useful for positioning popups next to inline nodes. If the container has offsets 4 | * the caller may need to subtract them. 5 | * 6 | * @param target - Node to measure 7 | * @returns DOMRect representing the range bounding box 8 | */ 9 | export function useRange(target: Node) { 10 | const range = document.createRange() 11 | range.setStart(target, 0) 12 | range.setEnd(target, 0) 13 | return range.getBoundingClientRect() 14 | } 15 | -------------------------------------------------------------------------------- /src/random/randomArray.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 随机打乱数组 3 | * @description EN: Shuffle an array in-place using the Fisher–Yates algorithm. 4 | * @param array any[] 5 | * @returns array 6 | */ 7 | export function randomArray(array: any[]) { 8 | // 从最后一个元素开始,依次将当前元素和之前的某个随机位置上的元素交换 9 | for (let i = array.length - 1; i > 0; i--) { 10 | const j = Math.floor(Math.random() * (i + 1)) // 随机生成一个介于 0 和 i 之间的整数 11 | ;[array[i], array[j]] = [array[j], array[i]] // 交换当前元素和随机位置上的元素 12 | } 13 | return array 14 | } 15 | 16 | // console.log(randomArray([1, 2, 3, 4, 5])) 17 | -------------------------------------------------------------------------------- /src/script/addLink.ts: -------------------------------------------------------------------------------- 1 | import { createElement } from '../event/createElement' 2 | 3 | /** 4 | * Add a tag to document head and return a remover. 5 | * 6 | * @param {string} href Stylesheet URL. 7 | * @returns {() => void} Function that removes the link element. 8 | */ 9 | export function addLink(href: string): () => void { 10 | const l = createElement('link', { 11 | rel: 'stylesheet', 12 | href, 13 | type: 'text/css', 14 | }) 15 | document.head.appendChild(l) 16 | return () => document.head.removeChild(l) 17 | } 18 | -------------------------------------------------------------------------------- /src/string/hyphenate.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 将大驼峰转为xx-xx 3 | * @param { string } s 字符串 4 | * @returns string 5 | */ 6 | /** 7 | * Convert a PascalCase or camelCase string to kebab-case (hyphen-separated). 8 | * 9 | * Example: "MyVariableName" -> "my-variable-name" 10 | * 11 | * @param s - input string in camelCase or PascalCase 12 | * @returns the hyphenated (kebab-case) string 13 | * @description EN: Convert camelCase or PascalCase to kebab-case. 14 | */ 15 | export function hyphenate(s: string): string { 16 | return s.replace(/([A-Z])/g, '-$1').toLowerCase() 17 | } 18 | -------------------------------------------------------------------------------- /jsr.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@simon/utils", 3 | "version": "0.0.0", 4 | "exports": "./src/index.ts", 5 | "license": "MIT", 6 | "funding": "https://github.com/sponsors/Simon-He95", 7 | "homepage": "https://github.com/Simon-He95/vitesse-jsr#readme", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/Simon-He95/vitesse-jsr.git" 11 | }, 12 | "bugs": "https://github.com/Simon-He95/vitesse-jsr/issues", 13 | "exclude": [ 14 | "tsup.config.ts", 15 | "eslint.config.js", 16 | "pnpm-lock.yaml", 17 | "tests/**" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /test/event/removeElement.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { insertElement, removeElement } from '../../src/event' 3 | 4 | describe('removeElement test', () => { 5 | it('test', () => { 6 | const p = document.createElement('div') 7 | const div = document.createElement('div') 8 | insertElement(p, div) 9 | expect(p.childNodes).toMatchInlineSnapshot(` 10 | NodeList [ 11 |
, 12 | ] 13 | `) 14 | removeElement(div) 15 | expect(p.childNodes).toMatchInlineSnapshot(`NodeList []`) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "jsx": "preserve", 5 | "lib": ["esnext", "DOM"], 6 | "module": "esnext", 7 | "moduleResolution": "node", 8 | "resolveJsonModule": true, 9 | "typeRoots": ["./node_modules/@types", "./types.d.ts"], 10 | "strict": true, 11 | "strictNullChecks": true, 12 | "esModuleInterop": true, 13 | "skipDefaultLibCheck": true, 14 | "skipLibCheck": true 15 | }, 16 | "include": ["src/**/*", "test/**/*", "types.d.ts"], 17 | "exclude": ["dist", "playground", "cypress"] 18 | } 19 | -------------------------------------------------------------------------------- /src/node/isGo.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | import process from 'node:process' 3 | import { jsShell } from '../node/jsShell' 4 | 5 | /** 6 | * 判断是否是在go环境 7 | * @returns 8 | * @description EN: Detect whether the current project is a Go project by checking for go files or go.mod. 9 | */ 10 | export async function isGo(rootPath = process.cwd()) { 11 | const url = path.resolve(rootPath, 'go.mod') 12 | const { result } = await jsShell( 13 | `(test -f "main.go" || test -f "${url}") && echo "0"|| echo "1"`, 14 | 'pipe', 15 | ) 16 | return result === '0' 17 | } 18 | -------------------------------------------------------------------------------- /src/object/objectToMap.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Convert an object to a Map. Keys that look like JSON objects ("{...}") are 3 | * parsed back to objects using JSON.parse. 4 | * 5 | * @param {Record} obj Input object. 6 | * @returns {Map} Map representation. 7 | */ 8 | export function objectToMap(obj: Record) { 9 | return Object.keys(obj).reduce((result, key) => { 10 | const value = obj[key] 11 | if (/\{.*\}/.test(key)) 12 | result.set(JSON.parse(key), value) 13 | else result.set(key, value) 14 | return result 15 | }, new Map()) 16 | } 17 | -------------------------------------------------------------------------------- /src/iframe/postMessage.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Forward a postMessage to an iframe's contentWindow. 3 | * 4 | * @param {HTMLIFrameElement} el Target iframe element. 5 | * @param {any} data Data to postMessage. 6 | * @param {string} targetOrigin Target origin string (e.g. '*'). 7 | * @param {Transferable[]} [transfer] Optional transfer list. 8 | * @returns {void} 9 | */ 10 | export function postMessage( 11 | el: HTMLIFrameElement, 12 | data: any, 13 | targetOrigin: string, 14 | transfer?: Transferable[], 15 | ) { 16 | return el.contentWindow!.postMessage(data, targetOrigin, transfer) 17 | } 18 | -------------------------------------------------------------------------------- /src/is/isFile.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import { _toString } from '../utils/common' 3 | import { isStr } from './isStr' 4 | 5 | /** 6 | * 判断是否是文件 7 | * @description EN: Check whether the input is a File (or file path on Node.js). 8 | * @param o - Blob, File or string path 9 | * @returns boolean 10 | */ 11 | export function isFile(o: Blob | string): o is File { 12 | if (isStr(o)) { 13 | try { 14 | return fs.statSync(o as string).isFile() 15 | } 16 | catch (error) { 17 | return false 18 | } 19 | } 20 | return _toString.call(o) === '[object File]' 21 | } 22 | -------------------------------------------------------------------------------- /src/perf/throttle.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 截流函数 3 | * @param { Function } fn 要执行的函数 4 | * @param { number } stop 限流时间窗口(毫秒) 5 | * @returns { Function } 返回被节流包装后的函数 6 | * @description EN: Create a throttled version of `fn` that ensures `fn` runs at most once every `stop` milliseconds. 7 | */ 8 | export function throttle(fn: Function, stop: number) { 9 | let start = 0 10 | return function (this: any, ...args: any[]) { 11 | const end = Date.now() 12 | if (end - start >= stop) { 13 | const result = fn.call(this, ...args) 14 | start = end 15 | return result 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tsdown.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsdown' 2 | 3 | export default defineConfig({ 4 | target: 'node14', 5 | format: ['cjs', 'esm'], 6 | // splitting: true, 7 | // bundle: true, 8 | sourcemap: false, 9 | clean: true, 10 | dts: true, 11 | entry: [ 12 | 'src/index.ts', 13 | 'src/node/index.ts', 14 | 'src/worker/*.ts', 15 | 'src/webComponent/index.ts', 16 | 'src/vite/index.ts', 17 | 'src/types/index.ts', 18 | ], 19 | define: { 20 | __DEV__: 'false', 21 | }, 22 | external: ['spark-md5', 'qrcode', 'htmlparser2'], 23 | platform: 'node', 24 | }) 25 | -------------------------------------------------------------------------------- /src/js/handleImageError.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 图片加载失败时轮换备用图 3 | * @description EN: Return an error handler that swaps an image's `src` with fallback URLs each time a load failure occurs. 4 | * @param { string[] } errorImageSrc 备用图片地址列表 5 | * @returns { (event: Event) => void } 6 | */ 7 | export function handleImageError(errorImageSrc: string[]) { 8 | let index = 0 9 | const max = errorImageSrc.length - 1 10 | return (event: Event) => { 11 | if (index > max) 12 | return 13 | const target = event.target as HTMLImageElement 14 | target.src = errorImageSrc[index] 15 | index++ 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/js/retryAsync.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 异步重试 3 | * @description EN: Retry an asynchronous function up to `retries` times before rethrowing the last error. 4 | * @param { () => Promise } fn 待执行的异步函数 5 | * @param { number } retries 最大重试次数 6 | * @returns { Promise } 7 | */ 8 | export async function retryAsync( 9 | fn: () => Promise, 10 | retries: number, 11 | ): Promise { 12 | try { 13 | return await fn() 14 | } 15 | catch (error: any) { 16 | if (retries > 0) { 17 | return retryAsync(fn, retries - 1) 18 | } 19 | else { 20 | throw error 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/vite/vitePluginExport.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 将一些vite不能处理的文件导出 3 | * @param { string } config 后缀文件 4 | * @returns vitePlugin 5 | */ 6 | /** 7 | * @description EN: Simple Vite plugin that converts a matched file into a default export of its content string. 8 | */ 9 | export function vitePluginExport(config: string) { 10 | return { 11 | name: 'vite-plugin-export', 12 | transform(src: any, id: string) { 13 | if (id.endsWith(config)) { 14 | return { 15 | code: `export default ${JSON.stringify(src)}`, 16 | map: null, 17 | } 18 | } 19 | }, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/js/chainFns.ts: -------------------------------------------------------------------------------- 1 | import { noop } from './noop' 2 | 3 | /** 4 | * 链式调用 5 | * @description EN: Compose multiple functions into one that calls them in sequence, returning the last non-undefined result. 6 | * @param { Function[] } fns 函数数组 7 | * @returns 8 | */ 9 | export function chainFns(...fns: Function[]): Function { 10 | if (fns.length === 0) 11 | return noop 12 | if (fns.length === 1) 13 | return fns[0] 14 | 15 | return function (this: any, ...args: any[]) { 16 | let result 17 | for (const fn of fns) result = fn.apply(this, args) || result 18 | 19 | return result 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/string/hash.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Fast string hash that returns a small alphanumeric token for the input. 3 | * This is non-cryptographic and intended for short, human-friendly keys. 4 | * 5 | * @param str - input string to hash 6 | * @returns 6-character base36 hash string 7 | */ 8 | export function hash(str: string) { 9 | let i 10 | let l 11 | let hval = 0x811C9DC5 12 | 13 | for (i = 0, l = str.length; i < l; i++) { 14 | hval ^= str.charCodeAt(i) 15 | hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24) 16 | } 17 | return `00000${(hval >>> 0).toString(36)}`.slice(-6) 18 | } 19 | -------------------------------------------------------------------------------- /test/array/countBy.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { countBy } from '../../src/array' 3 | 4 | describe('countBy test', () => { 5 | it('test', () => { 6 | const array = [ 7 | { user: '1', active: true }, 8 | { user: '2', active: false }, 9 | { user: '3', active: true }, 10 | { user: '4', active: true }, 11 | { user: '5', active: true }, 12 | ] 13 | const left = countBy(array, item => item.active) 14 | expect(left).toMatchInlineSnapshot(` 15 | { 16 | "false": 1, 17 | "true": 4, 18 | } 19 | `) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /playground/src/components/Footer.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 22 | -------------------------------------------------------------------------------- /src/array/forEachBack.ts: -------------------------------------------------------------------------------- 1 | import { isArray } from '../is/isArray' 2 | 3 | /** 4 | * 反向遍历数组 5 | * @description EN: Iterate over an array from end to start, invoking the callback for each element. 6 | * @param {T[]} arr Input array. 7 | * @param {(item: T, i: number) => void} callback Callback called with value and index. 8 | * @returns {void} 9 | */ 10 | export function forEachBack( 11 | arr: T[], 12 | callback: (item: T, i: number) => void, 13 | ): T[] { 14 | if (!isArray(arr)) 15 | return arr 16 | const len = arr.length - 1 17 | for (let i = len; i >= 0; i--) callback(arr[i], i) 18 | return arr 19 | } 20 | -------------------------------------------------------------------------------- /test/event/useElementBounding.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { useElementBounding } from '../../src/event' 3 | 4 | describe('useElementBounding test', () => { 5 | it('test', () => { 6 | const p = document.createElement('div') 7 | useElementBounding(p, (res) => { 8 | expect(res).toMatchInlineSnapshot(` 9 | { 10 | "bottom": 0, 11 | "height": 0, 12 | "left": 0, 13 | "right": 0, 14 | "top": 0, 15 | "width": 0, 16 | "x": 0, 17 | "y": 0, 18 | } 19 | `) 20 | }) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /src/js/useSwitch.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 轮询返回数组中的值 3 | * @description EN: Cycle through the provided options on each call, returning to the first value after reaching the end. 4 | * @param { any[] } options 需要轮换返回的值列表 5 | * @returns { () => any } 6 | */ 7 | export function useSwitch(options: any[]) { 8 | let index = 0 9 | const max = options.length - 1 10 | return () => { 11 | const result = options[index] 12 | index++ 13 | if (index > max) 14 | index = 0 15 | return result 16 | } 17 | } 18 | 19 | // const fn = useSwitch([true, false]) 20 | // console.log(fn()) // true 21 | // console.log(fn()) // false 22 | -------------------------------------------------------------------------------- /src/screen/fullScreen.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 全屏模式 3 | * @returns 4 | * @description EN: Request the browser to enter fullscreen using common vendor-prefixed APIs; returns an Error if not supported. 5 | */ 6 | export function fullScreen() { 7 | try { 8 | const el: any = document.documentElement 9 | const rfs 10 | = el.requestFullScreen 11 | || el.webkitRequestFullScreen 12 | || el.mozRequestFullScreen 13 | || el.msRequestFullScreen 14 | if (rfs) 15 | rfs.call(el) 16 | else return new Error('浏览器不支持全屏') 17 | } 18 | catch (error: any) { 19 | throw new Error(error) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/canvas/removeRoundSpace.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { removeRoundSpace } from '../../src/canvas' 3 | 4 | describe('removeRoundSpace test', () => { 5 | it('test', () => { 6 | const arrays = [ 7 | [0, 1, 0], 8 | [0, 1, 0], 9 | [0, 1, 0], 10 | ] 11 | expect(removeRoundSpace(arrays)).toMatchInlineSnapshot(` 12 | [ 13 | [ 14 | 1, 15 | 0, 16 | ], 17 | [ 18 | 1, 19 | 0, 20 | ], 21 | [ 22 | 1, 23 | 0, 24 | ], 25 | ] 26 | `) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /test/date/getDateList.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { getDateList } from '../../src/date' 3 | 4 | describe('getDateList test', () => { 5 | it('test', () => { 6 | const d = getDateList('1995-02-10', 10) 7 | expect(d).toMatchInlineSnapshot(` 8 | [ 9 | "1995-02-10", 10 | "1995-02-11", 11 | "1995-02-12", 12 | "1995-02-13", 13 | "1995-02-14", 14 | "1995-02-15", 15 | "1995-02-16", 16 | "1995-02-17", 17 | "1995-02-18", 18 | "1995-02-19", 19 | "1995-02-20", 20 | ] 21 | `) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /src/compress/compressCss.ts: -------------------------------------------------------------------------------- 1 | import { trim } from '../string/trim' 2 | 3 | export function compressCss(s: string): string { 4 | return ([...s.matchAll(/([^{]+)\{([^}]+)\}/g)] as unknown as string[]).reduce( 5 | (result, [_, selector, style]) => 6 | (result += `${trim(selector) 7 | .replace(/\s+/g, ' ') 8 | .replace(/\s*,\s*/g, ',')}{${style.replace(/\s*/g, '')}}`), 9 | '', 10 | ) 11 | } 12 | 13 | /** 14 | * @description EN: Minify CSS rules by trimming selectors and removing redundant whitespace in declarations. 15 | * @param s - CSS string to compress 16 | * @returns compressed CSS string 17 | */ 18 | -------------------------------------------------------------------------------- /src/num/multiply.ts: -------------------------------------------------------------------------------- 1 | import { isNum } from '../is' 2 | 3 | /** 4 | * 乘倍 5 | * @param { number | string } i 6 | * @param { number } multiple 倍数 7 | * @returns 8 | * @description EN: Multiply numeric inputs or repeat strings `multiple` times. If `i` is numeric, returns numeric product; otherwise repeats string. 9 | */ 10 | export function multiply(i: number | string, multiple: number) { 11 | const times = Math.trunc(Number(multiple) || 0) 12 | // 如果是数值字符串也转换为数字相乘 13 | if (isNum(i)) 14 | return Number(i) * times 15 | // 字符串重复,保证非负整数次数 16 | if (times <= 0) 17 | return '' 18 | return String(i).repeat(times) 19 | } 20 | -------------------------------------------------------------------------------- /src/to/fileToArrayBuffer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Read a File/Blob as an ArrayBuffer and return a Uint8Array. 3 | * 4 | * @param {File} file File or Blob to read. 5 | * @returns {Promise} Resolves with file bytes. 6 | */ 7 | export function fileToArrayBuffer(file: File): Promise { 8 | return new Promise((resolve, reject) => { 9 | const reader = new FileReader() 10 | 11 | reader.onload = function () { 12 | const arrayBuffer = new Uint8Array(reader.result as any) 13 | resolve(arrayBuffer) 14 | } 15 | 16 | reader.onerror = reject 17 | 18 | reader.readAsArrayBuffer(file) 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /src/string/getUrlParam.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Parse query parameters from a URL or a raw query string into an object. 3 | * 4 | * @param s - full URL or query string (if omitted, uses window.location.search) 5 | * @returns an object mapping keys to values, or undefined if no query present 6 | */ 7 | export function getUrlParam(s?: string): Record | undefined { 8 | s = (s || window.location.search).split('?')[1] 9 | if (!s) 10 | return 11 | return s.split('&').reduce((pre, cur) => { 12 | const [key, value] = cur.split('=') 13 | pre[key] = value 14 | return pre 15 | }, {} as Record) 16 | } 17 | -------------------------------------------------------------------------------- /test/html/getStyles.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { getStyles } from '../../src/html' 3 | 4 | describe('getStyles test', () => { 5 | it('test', () => { 6 | const template = ` 7 |
hello, world
8 | ` 9 | const style = getStyles(template, (style) => { 10 | expect(style).toMatchInlineSnapshot('"background:red;"') 11 | return `${style}color:white;` 12 | }) 13 | 14 | expect(style).toMatchInlineSnapshot(` 15 | " 16 |
hello, world
17 | " 18 | `) 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /src/js/singleModel.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 单例模式 3 | * @description EN: Create a singleton proxy for a class so constructing it always returns the same instance. 4 | */ 5 | export function singleModel(className: any) { 6 | let ins: any 7 | return new Proxy(className, { 8 | construct(Target, args) { 9 | if (!ins) 10 | ins = new Target(...args) 11 | return ins 12 | }, 13 | }) 14 | } 15 | 16 | // class Video { 17 | // constructor() { 18 | // console.log('video created'); 19 | // } 20 | // } 21 | // const vi = singleModel(Video) 22 | // const v1 = new vi() 23 | // const v2 = new vi() 24 | // console.log(v1 === v2); 25 | -------------------------------------------------------------------------------- /src/monitor/calFps.ts: -------------------------------------------------------------------------------- 1 | import { useRaf } from '../perf' 2 | 3 | /** 4 | * 计算并输出当前帧率 5 | * @description EN: Track frames with `requestAnimationFrame` and log the averaged FPS once per second. 6 | * @returns { () => void } 取消帧率监听的停止函数 7 | */ 8 | export function calFps(): () => void { 9 | let lastTime: number 10 | let frame = 0 11 | return useRaf((timestamp) => { 12 | frame++ 13 | if (!lastTime) 14 | lastTime = timestamp 15 | if (timestamp - lastTime > 1000) { 16 | console.log('FPS:', Math.round(frame * 1000) / (timestamp - lastTime)) 17 | frame = 0 18 | lastTime = timestamp 19 | } 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "incremental": false, 4 | "target": "es2016", 5 | "lib": ["DOM", "ESNext"], 6 | "baseUrl": ".", 7 | "module": "ESNext", 8 | "moduleResolution": "node", 9 | "paths": { 10 | "~/*": ["src/*"] 11 | }, 12 | "resolveJsonModule": true, 13 | "types": ["vite/client", "vite-plugin-pages/client"], 14 | "strict": true, 15 | "strictNullChecks": true, 16 | "noUnusedLocals": true, 17 | "esModuleInterop": true, 18 | "forceConsistentCasingInFileNames": true, 19 | "skipLibCheck": true 20 | }, 21 | "exclude": ["dist", "node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /src/canvas/Point.ts: -------------------------------------------------------------------------------- 1 | export class Point { 2 | r: number 3 | ctx: CanvasRenderingContext2D 4 | color: string 5 | constructor(ctx: CanvasRenderingContext2D, r = 1, color = '#fff') { 6 | this.r = r * devicePixelRatio 7 | this.ctx = ctx 8 | this.color = color 9 | } 10 | 11 | draw(x: number, y: number, color?: string, r?: number) { 12 | this.ctx.beginPath() 13 | this.ctx.arc( 14 | x * devicePixelRatio, 15 | y * devicePixelRatio, 16 | (r ?? this.r) * devicePixelRatio, 17 | 0, 18 | 2 * Math.PI, 19 | ) 20 | this.ctx.fillStyle = color ?? this.color 21 | this.ctx.fill() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/random/randomStr.ts: -------------------------------------------------------------------------------- 1 | const urlAlphabet 2 | = 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict' 3 | /** 4 | * 随机字符串 5 | * @description EN: Generate a random string of given length from a character 6 | * set. Useful for IDs and non-cryptographic tokens. 7 | * @param { number } size 长度 默认 16 8 | * @param { string } dict Character set to draw from (default alphabet above). 9 | * @returns {string} 10 | */ 11 | export function randomStr(size = 16, dict = urlAlphabet): string { 12 | let id = '' 13 | let i = size 14 | const len = dict.length 15 | while (i--) id += dict[(Math.random() * len) | 0] 16 | return id 17 | } 18 | -------------------------------------------------------------------------------- /src/screen/exitFullscreen.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 退出全屏模式 3 | * @returns 4 | * @description EN: Exit fullscreen mode by attempting common vendor-prefixed cancel methods; returns an Error when unsupported. 5 | */ 6 | export function exitFullscreen() { 7 | try { 8 | const el: any = (parent as any).documentElement 9 | const cfs 10 | = el?.cancelFullScreen 11 | || el?.webkitCancelFullScreen 12 | || el?.mozCancelFullScreen 13 | || el?.exitFullScreen 14 | if (cfs) 15 | cfs.call(el) 16 | else return new Error('切换失败,可尝试Esc退出') 17 | } 18 | catch (error: any) { 19 | throw new Error(error) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/is/isBottom.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Determine whether the document is scrolled to the bottom (within `distance`). 3 | * @description EN: Returns true when the document scroll position is at or near the bottom. 4 | * @param {number} [distance] Additional threshold in pixels. 5 | * @returns {boolean} 6 | */ 7 | export function isBottom(distance = 0): boolean { 8 | try { 9 | return ( 10 | document.documentElement.clientHeight + window.scrollY + distance 11 | >= (document.documentElement.scrollHeight 12 | || document.documentElement.clientHeight) 13 | ) 14 | } 15 | catch (error: any) { 16 | throw new Error(error) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/event/useResizeObserver.ts: -------------------------------------------------------------------------------- 1 | import { useEventListener } from './useEventListener' 2 | 3 | /** 4 | * Call the callback when the window is resized, returning the viewport width and height. 5 | * 6 | * @param callback - Receives (width, height) 7 | * @returns A stop function for the resize listener 8 | */ 9 | export function useResizeObserver( 10 | callback: (width: number, height: number) => void, 11 | ) { 12 | return useEventListener(window, 'resize', () => 13 | callback?.( 14 | document.documentElement.clientWidth || document.body.clientWidth, 15 | document.documentElement.clientHeight || document.body.clientHeight, 16 | )) 17 | } 18 | -------------------------------------------------------------------------------- /src/canvas/Line.ts: -------------------------------------------------------------------------------- 1 | export class Line { 2 | ctx: CanvasRenderingContext2D 3 | color: string 4 | isFill: boolean 5 | constructor(ctx: CanvasRenderingContext2D, color = '#fff', isFill = false) { 6 | this.ctx = ctx 7 | this.color = color 8 | this.isFill = isFill 9 | } 10 | 11 | draw(points: number[][], color?: string) { 12 | this.ctx.beginPath() 13 | points.forEach(([x, y]) => this.ctx.lineTo(x, y)) 14 | if (this.isFill) { 15 | this.ctx.fillStyle = color ?? this.color 16 | this.ctx.fill() 17 | } 18 | else { 19 | this.ctx.strokeStyle = color ?? this.color 20 | this.ctx.stroke() 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/node/isPkg.ts: -------------------------------------------------------------------------------- 1 | import process from 'node:process' 2 | import path from 'node:path' 3 | import { jsShell } from './jsShell' 4 | 5 | /** 6 | * 判断路径下是否有package.jsons 7 | * @param { string } rootPath 默认 process.cwd() 8 | * @description EN: Determine whether a package.json exists in the given path (defaults to process.cwd()). 9 | */ 10 | export async function isPkg(rootPath: string = process.cwd()) { 11 | const url = path.resolve( 12 | rootPath.replace(/package.json$/, ''), 13 | 'package.json', 14 | ) 15 | const { result } = await jsShell( 16 | `test -f "${url}" && echo "0"|| echo "1"`, 17 | 'pipe', 18 | ) 19 | return result === '0' 20 | } 21 | -------------------------------------------------------------------------------- /src/css/removeStyle.ts: -------------------------------------------------------------------------------- 1 | import { isArray } from '../is/isArray' 2 | import { mount } from '../utils/mount' 3 | 4 | export function removeStyle( 5 | el: HTMLElement | string, 6 | styles: string[] | string, 7 | ) { 8 | const removeStyles: string[] = isArray(styles) ? styles : [styles] 9 | return mount(el, (el) => { 10 | const css = el.style.cssText 11 | el.style.cssText = removeStyles 12 | .reduce( 13 | (result: string, style: string) => result.replace(getReg(style), ''), 14 | css, 15 | ) 16 | .trim() 17 | }) 18 | } 19 | 20 | function getReg(style: string) { 21 | return new RegExp(`${style}: [\\w()!\\-,.\\s0-9]+;`) 22 | } 23 | -------------------------------------------------------------------------------- /src/is/isSameDay.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否是同一天 3 | * @param { Date } dateLeft 日期一 4 | * @param { Date } dateRight 日期二 5 | */ 6 | /** 7 | * 判断两个日期是否为同一天 8 | * @description EN: Compares year, month and date to determine whether two Date 9 | * objects represent the same calendar day. 10 | * @param {Date} dateLeft First date. 11 | * @param {Date} dateRight Second date. 12 | * @returns {boolean} 13 | */ 14 | export function isSameDay(dateLeft: Date, dateRight: Date): boolean { 15 | return ( 16 | dateLeft.getFullYear() === dateRight.getFullYear() 17 | && dateLeft.getMonth() === dateRight.getMonth() 18 | && dateLeft.getDate() === dateRight.getDate() 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /src/event/useMouse.ts: -------------------------------------------------------------------------------- 1 | import { useEventListener } from './useEventListener' 2 | 3 | /** 4 | * Throttled mousemove listener. The callback will be called at most once per `delay` ms. 5 | * 6 | * @param callback - MouseEvent handler 7 | * @param delay - Minimum ms between invocations 8 | * @returns A stop function to remove the listener 9 | */ 10 | export function useMouse( 11 | callback: (e: MouseEvent) => void, 12 | delay = 0, 13 | ): () => void { 14 | let timeStart = Date.now() 15 | return useEventListener(window, 'mousemove', (e) => { 16 | if (Date.now() - timeStart >= delay) { 17 | timeStart = Date.now() 18 | callback(e) 19 | } 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /src/js/reduceAsync.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 异步 reduce 3 | * @description EN: Perform an asynchronous reduction over an array, awaiting each reducer invocation before proceeding. 4 | * @param { K[] } arr 待处理的数组 5 | * @param { (acc: T, item: K, index: number, arr: K[]) => Promise | T } reducer 异步或同步累加器 6 | * @param { T } initialValue 初始值 7 | * @returns { Promise } 8 | */ 9 | export async function reduceAsync( 10 | arr: K[], 11 | reducer: (acc: T, item: K, index: number, arr: K[]) => any, 12 | initialValue: T, 13 | ) { 14 | let acc = initialValue 15 | for (let i = 0; i < arr.length; i++) { 16 | acc = await reducer(acc, arr[i], i, arr) 17 | } 18 | return acc 19 | } 20 | -------------------------------------------------------------------------------- /src/animate/bounceOut.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 弹跳离开缓动曲线 3 | * @description EN: Bounce-out easing that mimics an object dropping and bouncing to rest. 4 | * @param { number } t 归一化时间进度,范围 0-1 5 | * @returns { number } 6 | */ 7 | export function bounceOut(t: number) { 8 | const a = 4.0 / 11.0 9 | const b = 8.0 / 11.0 10 | const c = 9.0 / 10.0 11 | 12 | const ca = 4356.0 / 361.0 13 | const cb = 35442.0 / 1805.0 14 | const cc = 16061.0 / 1805.0 15 | 16 | const t2 = t * t 17 | 18 | return t < a 19 | ? 7.5625 * t2 20 | : t < b 21 | ? 9.075 * t2 - 9.9 * t + 3.4 22 | : t < c 23 | ? ca * t2 - cb * t + cc 24 | : 10.8 * t * t - 20.52 * t + 10.72 25 | } 26 | -------------------------------------------------------------------------------- /src/event/useTimeout.ts: -------------------------------------------------------------------------------- 1 | import { isFn } from '../is/isFn' 2 | import type { UseTimeoutReturn } from '../types' 3 | 4 | /** 5 | * Run a function once after a delay and return a stop function. 6 | * If fn is not a function, undefined is returned. 7 | * 8 | * @param fn - Function to run after the delay 9 | * @param duration - Delay in milliseconds (default 0) 10 | * @returns A function that clears the timeout, or undefined if fn is not a function 11 | */ 12 | export function useTimeout(fn: T, duration = 0): UseTimeoutReturn { 13 | if (!isFn(fn)) 14 | return undefined as any 15 | const timer = setTimeout(fn, duration) 16 | return (() => clearTimeout(timer)) as any 17 | } 18 | -------------------------------------------------------------------------------- /src/perf/debounce.ts: -------------------------------------------------------------------------------- 1 | import { isNull } from '../is/isNull' 2 | 3 | /** 4 | * 防抖函数 5 | * @param { Function } fn 函数 6 | * @param { number } time 时间(毫秒) 7 | * @returns { Function } 返回被防抖包装的函数 8 | * @description EN: Return a debounced version of `fn` that delays invocation until `time` milliseconds have passed without a new call. 9 | */ 10 | export function debounce(fn: Function, time: number) { 11 | let timer: any = null 12 | return function (this: any, e?: any) { 13 | if (!isNull(timer)) 14 | clearTimeout(timer) 15 | timer = setTimeout(() => { 16 | const result = fn.call(this, e) 17 | timer = null 18 | return result 19 | }, time) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/array/diff.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { diff } from '../../src/array' 3 | 4 | describe('diff test', () => { 5 | it('test same', () => { 6 | const arr1 = ['1', '2'] 7 | const arr2 = ['2', '3'] 8 | expect( 9 | diff(arr1, arr2, { 10 | compare: 'same', 11 | result: 'value', 12 | }), 13 | ).toEqual(['2']) 14 | }) 15 | it('test different', () => { 16 | const arr1 = ['1', '2'] 17 | const arr2 = ['2', '3'] 18 | expect( 19 | diff(arr1, arr2, { 20 | compare: 'different', 21 | result: 'value', 22 | }), 23 | ).toMatchInlineSnapshot([['1', '3']]) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /src/event/removeElement.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Remove an element from the DOM. 3 | * 4 | * Accepts an HTMLElement (or ChildNode/DocumentFragment) or a selector string. 5 | * If a selector is provided it will be resolved via `findElement` (caller 6 | * should resolve before calling if passing a selector). 7 | * 8 | * @param el - Element or selector to remove 9 | * @returns the parent HTMLElement that contained the removed node, or null 10 | */ 11 | export function removeElement( 12 | el: HTMLElement | ChildNode | DocumentFragment | string, 13 | ): HTMLElement | null { 14 | const p = (el as HTMLElement).parentElement 15 | if (p) 16 | p.removeChild(el as HTMLElement) 17 | return p 18 | } 19 | -------------------------------------------------------------------------------- /src/theme/useSystemColor.ts: -------------------------------------------------------------------------------- 1 | import { useEventListener } from '../event' 2 | 3 | type Color = 'light' | 'dark' 4 | 5 | /** 6 | * 监听系统颜色模式 7 | * @description EN: Observe the OS color-scheme preference and invoke the callback with `light` or `dark` whenever it changes. 8 | * @param { (color: 'light' | 'dark') => void } callback 接收系统配色的回调 9 | * @returns { () => void } 解绑监听的函数 10 | */ 11 | export function useSystemColor(callback: (color: Color) => void): () => void { 12 | const match = matchMedia('(prefers-color-scheme: dark)') 13 | callback(match.matches ? 'dark' : 'light') 14 | return useEventListener(match, 'change', () => 15 | callback(match.matches ? 'dark' : 'light')) 16 | } 17 | -------------------------------------------------------------------------------- /src/object/mapToObject.ts: -------------------------------------------------------------------------------- 1 | import { isObject } from '../is' 2 | 3 | /** 4 | * Convert a Map to a plain object. Non-string keys are JSON.stringified. 5 | * 6 | * @param {Map} map Input Map. 7 | * @returns {Record} Plain object representation. 8 | */ 9 | export function mapToObject(map: Map) { 10 | return Array.from(map).reduce((result, [key, value]) => { 11 | if (isObject(key)) 12 | key = JSON.stringify(key) 13 | 14 | result[key] = value 15 | return result 16 | }, {} as Record) 17 | } 18 | 19 | // const map = new Map() 20 | // map.set(1, '123') 21 | // map.set({ name: 'ss' }, '2') 22 | 23 | // console.log(mapToObject(map)) 24 | -------------------------------------------------------------------------------- /src/string/camelize.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Convert a kebab-case string (hyphen-separated) to camelCase or PascalCase. 3 | * 4 | * Examples: 5 | * - camelize('my-variable') => 'myVariable' 6 | * - camelize('my-variable', true) => 'MyVariable' 7 | * 8 | * @param s - input string in kebab-case 9 | * @param pascalCase - if true, return PascalCase (first letter capitalized) 10 | * @returns the camelized string 11 | */ 12 | export function camelize(s: string, pascalCase?: boolean): string { 13 | const camelized = s.replace(/-(\w)/g, (_all, letter) => letter.toUpperCase()) 14 | 15 | if (pascalCase) { 16 | return camelized.charAt(0).toUpperCase() + camelized.slice(1) 17 | } 18 | 19 | return camelized 20 | } 21 | -------------------------------------------------------------------------------- /src/array/toggleItem.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 在数组中切换元素:如果存在则移除,否则添加 3 | * @description EN: Toggle an item inside the provided array. If `index` is given, it overrides searching by value. 4 | * @param {T[]} arr The array to operate on. 5 | * @param {T} item The item to toggle. 6 | * @param {number} [index] Optional index at which to toggle the item instead of searching by value. 7 | * @returns {T[]} The mutated array after toggling. 8 | */ 9 | export function toggleItem(arr: T[], item: T, index?: number): T[] { 10 | if (!arr) 11 | return [] 12 | 13 | const idx = index === undefined ? arr.indexOf(item) : index 14 | if (idx === -1) 15 | arr.push(item) 16 | else arr.splice(idx, 1) 17 | 18 | return arr 19 | } 20 | -------------------------------------------------------------------------------- /src/js/timeout.ts: -------------------------------------------------------------------------------- 1 | import { promiseFinally } from './promiseFinally' 2 | 3 | /** 4 | * 超时函数 5 | * @param { Function } fn 函数 6 | * @param { number } ms 时间 7 | * @param { string } msg 错误消息 8 | * @returns 9 | * @description EN: Run `fn` (which may be async) but reject with an Error(msg) if it doesn't finish within `ms` milliseconds. 10 | */ 11 | export const timeout = function timeout(fn: Function, ms: number, msg: string) { 12 | let timerId: NodeJS.Timeout 13 | const warpPromise = promiseFinally(fn, () => clearTimeout(timerId)) 14 | const timerPromise = new Promise((resolve, reject) => { 15 | timerId = setTimeout(() => reject(new Error(msg)), ms) 16 | }) 17 | return Promise.race([warpPromise, timerPromise]) 18 | } 19 | -------------------------------------------------------------------------------- /src/event/useWindowScroll.ts: -------------------------------------------------------------------------------- 1 | import { useEventListener } from './useEventListener' 2 | 3 | /** 4 | * Listen to document scroll and call callback with current scrollLeft/scrollTop. 5 | * 6 | * @param callback - Receives (left, top) 7 | * @returns A stop function for the scroll listener 8 | */ 9 | export function useWindowScroll( 10 | callback: (left: number, top: number) => void, 11 | ): () => void { 12 | return useEventListener(document, 'scroll', () => 13 | callback?.( 14 | document.documentElement.scrollLeft 15 | || window.pageXOffset 16 | || document.body.scrollLeft, 17 | document.documentElement.scrollTop 18 | || window.pageYOffset 19 | || document.body.scrollTop, 20 | )) 21 | } 22 | -------------------------------------------------------------------------------- /src/js/copy.ts: -------------------------------------------------------------------------------- 1 | import { createElement } from '../event/createElement' 2 | 3 | /** 4 | * 获取电脑粘贴板的内容 5 | * @description EN: Copy the provided string to the system clipboard using a temporary textarea and execCommand. 6 | * @param { string } s 内容 7 | * @returns 8 | */ 9 | export function copy(s: string): boolean { 10 | try { 11 | const textarea = createElement( 12 | 'textarea', 13 | { 14 | readonly: 'readonly', 15 | }, 16 | s, 17 | ) 18 | document.body.appendChild(textarea) 19 | textarea.select() 20 | const res = document.execCommand('copy') 21 | document.body.removeChild(textarea) 22 | return res 23 | } 24 | catch (error: any) { 25 | throw new Error(error) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/to/toSlice.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Fast slice implementation that copies elements from `start` to the end into 3 | * a new array. Designed to be faster than `Array.prototype.slice` in tight 4 | * loops for array-like structures. 5 | * 6 | * @param list - Array-like or string 7 | * @param start - Start index (defaults to 0) 8 | * @returns A new array containing the sliced values 9 | */ 10 | export function toSlice(list: any, start?: number): Array { 11 | start = start || 0 12 | let i = list.length - start 13 | const ret: Array = new Array(i) 14 | while (i--) ret[i] = list[i + start] 15 | 16 | return ret 17 | } 18 | 19 | // const arr = [1, 3, 5, 7, 9] 20 | // const data = toArray(arr, 2) // [5,7,9] 21 | // console.log(data) 22 | -------------------------------------------------------------------------------- /playground/server/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | 3 | const app = express() 4 | app.all('*', (req, res, next) => { 5 | // 设置允许跨域的域名,*代表允许任意域名跨域 6 | res.header('Access-Control-Allow-Origin', '*') 7 | // 允许的header类型 8 | res.header('Access-Control-Allow-Headers', 'content-type') 9 | // 跨域允许的请求方式 10 | res.header('Access-Control-Allow-Methods', 'DELETE,PUT,POST,GET,OPTIONS') 11 | if (req.method.toLowerCase() === 'options') 12 | res.send(200) // 让options尝试请求快速结束 13 | else next() 14 | }) 15 | 16 | app.get('/test', (req, res) => { 17 | res.json({ 18 | code: 300, 19 | body: { 20 | name: 'simon', 21 | }, 22 | }) 23 | }) 24 | 25 | app.listen(5001, () => { 26 | // console.log('服务器启动5001') 27 | }) 28 | -------------------------------------------------------------------------------- /src/script/addScript.ts: -------------------------------------------------------------------------------- 1 | import { createElement } from '../event/createElement' 2 | 3 | /** 4 | * Add a 15 | 16 | // 20 | 21 | // 30 | 31 | // ` 32 | // getClasses(str, (classes, block, i) => { 33 | // return classes + 'nihao' 34 | // }) 35 | -------------------------------------------------------------------------------- /src/event/useClick.ts: -------------------------------------------------------------------------------- 1 | import type { MaybeElement } from '../types' 2 | import { mount } from '../utils/mount' 3 | import { useEventListener } from './useEventListener' 4 | 5 | /** 6 | * Attach a click listener to a target (element or selector). 7 | * The listener will be mounted when the element becomes available and can be stopped. 8 | * 9 | * @param target - A DOM element, selector string, or Document to attach the listener to 10 | * @param callback - MouseEvent handler 11 | * @returns A function that stops the listener. If called before the element exists, it will cancel mounting. 12 | */ 13 | export function useClick( 14 | target: MaybeElement | Document, 15 | callback: (e: MouseEvent) => void, 16 | ) { 17 | let stop: () => void 18 | let stopped = false 19 | mount(target, (target) => { 20 | stop = useEventListener(target as Element, 'click', callback) 21 | if (stopped) 22 | stop?.() 23 | }) 24 | return () => { 25 | if (!stop) 26 | return (stopped = true) 27 | stop?.() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/log/globalErrorCapture.ts: -------------------------------------------------------------------------------- 1 | import { useEventListener } from '../event/useEventListener' 2 | import { log } from './log' 3 | 4 | /** 5 | * 全局错误捕获 6 | * @description EN: Capture global window `error` events and log structured error information. 7 | * @returns { () => void } 注销监听器的清理函数 8 | */ 9 | export function globalErrorCapture() { 10 | return useEventListener(window, 'error', (err) => { 11 | const { colno, lineno, error, filename, message } = err 12 | if (message.toLowerCase().includes('script error')) 13 | return 14 | const msg = [ 15 | `Message: ${message}\n`, 16 | `URL: ${filename}:${lineno}:${colno}\n`, 17 | `Line: ${lineno}\n`, 18 | `Column: ${colno}\n`, 19 | `Error object: ${JSON.stringify(error)}`, 20 | ].join('-') 21 | log(msg, { 22 | style: { 23 | color: '#337ecc', 24 | padding: '2px 10px', 25 | fontSize: 14, 26 | fontWeight: 'bold', 27 | }, 28 | type: 'error', 29 | }) 30 | return false 31 | }) 32 | } 33 | --------------------------------------------------------------------------------