├── .editorconfig ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── renovate.json └── workflows │ ├── export-size.yml │ ├── publish.yml │ └── tests.yml ├── .gitignore ├── .nvmrc ├── .prettierignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── codecov.yml ├── package-lock.json ├── package.json ├── packages ├── demo │ ├── eslint.config.js │ ├── meta.config.js │ ├── package.json │ ├── src │ │ ├── css │ │ │ └── styles.scss │ │ ├── js │ │ │ ├── app.ts │ │ │ ├── components │ │ │ │ ├── AnimateScrollTest.js │ │ │ │ ├── AnimateTest.js │ │ │ │ ├── AnimateTestMultiple.js │ │ │ │ ├── BreakPointManagerDemo │ │ │ │ │ ├── BreakpointManagerDemoBase.js │ │ │ │ │ ├── BreakpointManagerDemoDesktop.js │ │ │ │ │ ├── BreakpointManagerDemoMobile.js │ │ │ │ │ ├── BreakpointManagerDemoTablet.js │ │ │ │ │ └── index.js │ │ │ │ ├── BreakpointObserverDemo.js │ │ │ │ ├── Cursor.js │ │ │ │ ├── Lazyload.js │ │ │ │ ├── MediaQueryDemo.js │ │ │ │ ├── Parallax.js │ │ │ │ ├── ParentNativeEvent │ │ │ │ │ ├── Child.js │ │ │ │ │ └── index.js │ │ │ │ ├── PointerProps.ts │ │ │ │ ├── ResponsiveOptions.js │ │ │ │ ├── ScrollToDemo.js │ │ │ │ ├── ScrolledInViewOffset.js │ │ │ │ ├── Skew.js │ │ │ │ └── Slider.js │ │ │ └── utils │ │ │ │ └── isObjectFreed.js │ │ ├── package.json │ │ └── templates │ │ │ ├── components │ │ │ ├── animate-test.twig │ │ │ ├── deep-nested-children.twig │ │ │ ├── lots-of-children.twig │ │ │ └── parallax.twig │ │ │ ├── layouts │ │ │ └── base.twig │ │ │ └── pages │ │ │ ├── animate-test.twig │ │ │ ├── child-native-event.twig │ │ │ ├── deep-nested-children.twig │ │ │ ├── drag.twig │ │ │ ├── index.twig │ │ │ ├── pointer-props.twig │ │ │ ├── responsive-options.twig │ │ │ ├── scrolled-in-view-nan.twig │ │ │ └── scrolled-in-view-offset.twig │ └── tailwind.config.js ├── docs │ ├── .vitepress │ │ ├── config.ts │ │ └── theme │ │ │ ├── components │ │ │ ├── Loader.vue │ │ │ └── PreviewIframe.vue │ │ │ ├── composables │ │ │ ├── useAllLinks.ts │ │ │ └── useObserver.ts │ │ │ ├── custom.scss │ │ │ ├── index.js │ │ │ └── styles.scss │ ├── api │ │ ├── configuration.md │ │ ├── decorators │ │ │ ├── index.md │ │ │ ├── withBreakpointManager.md │ │ │ ├── withBreakpointObserver.md │ │ │ ├── withDrag.md │ │ │ ├── withExtraConfig.md │ │ │ ├── withFreezedOptions.md │ │ │ ├── withIntersectionObserver.md │ │ │ ├── withMountOnMediaQuery.md │ │ │ ├── withMountWhenInView.md │ │ │ ├── withMountWhenPrefersMotion.md │ │ │ ├── withMutation.md │ │ │ ├── withRelativePointer.md │ │ │ ├── withResponsiveOptions.md │ │ │ ├── withScrolledInView.md │ │ │ └── withScrolledInView │ │ │ │ └── demo.md │ │ ├── helpers │ │ │ ├── createApp.md │ │ │ ├── getClosestParent.md │ │ │ ├── getDirectChildren.md │ │ │ ├── getInstanceFromElement.md │ │ │ ├── getInstances.md │ │ │ ├── importOnInteraction.md │ │ │ ├── importOnMediaQuery.md │ │ │ ├── importWhenIdle.md │ │ │ ├── importWhenPrefersMotion.md │ │ │ ├── importWhenVisible.md │ │ │ ├── index.md │ │ │ └── isDirectChild.md │ │ ├── html │ │ │ ├── data-component.md │ │ │ ├── data-option.md │ │ │ ├── data-ref.md │ │ │ └── index.md │ │ ├── index.md │ │ ├── instance-events.md │ │ ├── instance-methods.md │ │ ├── instance-properties.md │ │ ├── instantiation.md │ │ ├── methods-hooks-events.md │ │ ├── methods-hooks-lifecycle.md │ │ ├── methods-hooks-services.md │ │ ├── services │ │ │ ├── index.md │ │ │ ├── useDrag.md │ │ │ ├── useKey.md │ │ │ ├── useLoad.md │ │ │ ├── useMutation.md │ │ │ ├── usePointer.md │ │ │ ├── useRaf.md │ │ │ ├── useResize.md │ │ │ └── useScroll.md │ │ └── static-methods.md │ ├── assets │ │ ├── events-diagram.png │ │ ├── lifecycle-hooks-dark.svg │ │ ├── lifecycle-hooks.svg │ │ ├── services-hooks-dark.svg │ │ └── services-hooks.svg │ ├── guide │ │ ├── going-further │ │ │ ├── lazy-imports.md │ │ │ ├── registering-new-services.md │ │ │ ├── typing-components.md │ │ │ └── using-decorators.md │ │ ├── index.md │ │ ├── introduction │ │ │ ├── lifecycle-hooks.md │ │ │ ├── managing-components.md │ │ │ ├── managing-options.md │ │ │ ├── managing-refs.md │ │ │ ├── using-services.md │ │ │ └── working-with-events.md │ │ ├── migration │ │ │ ├── v1-to-v2.md │ │ │ └── v2-to-v3.md │ │ └── recipes │ │ │ ├── counter-component │ │ │ ├── Counter.html │ │ │ ├── Counter.js │ │ │ └── index.md │ │ │ ├── scroll-linked-animation │ │ │ ├── ScrollLinkedAnimation.html │ │ │ ├── ScrollLinkedAnimation.js │ │ │ └── index.md │ │ │ └── teleport-refs │ │ │ ├── Modal.html │ │ │ ├── Modal.js │ │ │ └── index.md │ ├── index.md │ ├── package.json │ ├── postcss.config.cjs │ ├── public │ │ └── logo.png │ ├── tailwind.config.cjs │ ├── utils │ │ ├── Queue.md │ │ ├── SmartQueue.md │ │ ├── cache.md │ │ ├── collision │ │ │ ├── boundingRectToCircle.md │ │ │ ├── collideCircleCircle.md │ │ │ ├── collideCircleRect.md │ │ │ ├── collidePointCircle.md │ │ │ ├── collidePointRect.md │ │ │ ├── collideRectRect.md │ │ │ └── index.md │ │ ├── createElement.md │ │ ├── css │ │ │ ├── addClass.md │ │ │ ├── addStyle.md │ │ │ ├── animate.md │ │ │ ├── getOffsetSizes.md │ │ │ ├── matrix.md │ │ │ ├── removeClass.md │ │ │ ├── removeStyle.md │ │ │ ├── toggleClass.md │ │ │ ├── transform.md │ │ │ └── transition.md │ │ ├── debounce.md │ │ ├── domScheduler.md │ │ ├── history │ │ │ ├── historyPush.md │ │ │ ├── historyReplace.md │ │ │ ├── index.md │ │ │ └── objectToURLSearchParams.md │ │ ├── index.md │ │ ├── is │ │ │ ├── index.md │ │ │ ├── isArray.md │ │ │ ├── isBoolean.md │ │ │ ├── isDefined.md │ │ │ ├── isDev.md │ │ │ ├── isEmpty.md │ │ │ ├── isEmptyString.md │ │ │ ├── isFunction.md │ │ │ ├── isNull.md │ │ │ ├── isNumber.md │ │ │ ├── isObject.md │ │ │ └── isString.md │ │ ├── keyCodes.md │ │ ├── loadElement.md │ │ ├── loadIframe.md │ │ ├── loadImage.md │ │ ├── loadLink.md │ │ ├── loadScript.md │ │ ├── math │ │ │ ├── clamp.md │ │ │ ├── clamp01.md │ │ │ ├── createEaseInOut.md │ │ │ ├── createEaseOut.md │ │ │ ├── createRange.md │ │ │ ├── damp.md │ │ │ ├── ease.md │ │ │ ├── inertiaFinalValue.md │ │ │ ├── lerp.md │ │ │ ├── map.md │ │ │ ├── mean.md │ │ │ └── round.md │ │ ├── memo.md │ │ ├── memoize.md │ │ ├── nextFrame.md │ │ ├── nextMicrotask.md │ │ ├── nextTick.md │ │ ├── randomInt.md │ │ ├── randomItem.md │ │ ├── scrollTo.md │ │ ├── string │ │ │ ├── camelCase.md │ │ │ ├── dashCase.md │ │ │ ├── endsWith.md │ │ │ ├── index.md │ │ │ ├── lowerCase.md │ │ │ ├── pascalCase.md │ │ │ ├── snakeCase.md │ │ │ ├── startsWith.md │ │ │ ├── upperCase.md │ │ │ ├── withLeadingCharacters.md │ │ │ ├── withLeadingSlash.md │ │ │ ├── withTrailingCharacters.md │ │ │ ├── withTrailingSlash.md │ │ │ ├── withoutLeadingCharacters.md │ │ │ ├── withoutLeadingCharactersRecursive.md │ │ │ ├── withoutLeadingSlash.md │ │ │ ├── withoutTrailingCharacters.md │ │ │ ├── withoutTrailingCharactersRecursive.md │ │ │ └── withoutTrailingSlash.md │ │ ├── throttle.md │ │ ├── trapFocus.md │ │ ├── tween.md │ │ └── useScheduler.md │ └── vite.config.js ├── global.d.ts ├── js-toolkit │ ├── Base │ │ ├── Base.ts │ │ ├── features.ts │ │ ├── index.ts │ │ ├── managers │ │ │ ├── AbstractManager.ts │ │ │ ├── ChildrenManager.ts │ │ │ ├── EventsManager.ts │ │ │ ├── OptionsManager.ts │ │ │ ├── RefsManager.ts │ │ │ ├── ResponsiveOptionsManager.ts │ │ │ ├── ServicesManager.ts │ │ │ └── index.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── decorators │ │ ├── index.ts │ │ ├── withBreakpointManager.ts │ │ ├── withBreakpointObserver.ts │ │ ├── withDrag.ts │ │ ├── withExtraConfig.ts │ │ ├── withFreezedOptions.ts │ │ ├── withIntersectionObserver.ts │ │ ├── withMountOnMediaQuery.ts │ │ ├── withMountWhenInView.ts │ │ ├── withMountWhenPrefersMotion.ts │ │ ├── withMutation.ts │ │ ├── withName.ts │ │ ├── withRelativePointer.ts │ │ ├── withResponsiveOptions.ts │ │ └── withScrolledInView │ │ │ ├── index.ts │ │ │ ├── types.ts │ │ │ ├── utils.ts │ │ │ └── withScrolledInView.ts │ ├── helpers │ │ ├── createApp.ts │ │ ├── getClosestParent.ts │ │ ├── getDirectChildren.ts │ │ ├── getInstanceFromElement.ts │ │ ├── importOnInteraction.ts │ │ ├── importOnMediaQuery.ts │ │ ├── importWhenIdle.ts │ │ ├── importWhenPrefersMotion.ts │ │ ├── importWhenVisible.ts │ │ ├── index.ts │ │ └── utils.ts │ ├── index.ts │ ├── package.json │ ├── services │ │ ├── AbstractService.ts │ │ ├── DragService.ts │ │ ├── KeyService.ts │ │ ├── LoadService.ts │ │ ├── MutationService.ts │ │ ├── PointerService.ts │ │ ├── RafService.ts │ │ ├── ResizeService.ts │ │ ├── ScrollService.ts │ │ ├── index.ts │ │ └── utils.ts │ └── utils │ │ ├── Queue.ts │ │ ├── SmartQueue.ts │ │ ├── cache.ts │ │ ├── collide │ │ ├── boundingRectToCircle.ts │ │ ├── collideCircleCircle.ts │ │ ├── collideCircleRect.ts │ │ ├── collidePointCircle.ts │ │ ├── collidePointRect.ts │ │ ├── collideRectRect.ts │ │ └── index.ts │ │ ├── css │ │ ├── animate.ts │ │ ├── classes.ts │ │ ├── getOffsetSizes.ts │ │ ├── index.ts │ │ ├── matrix.ts │ │ ├── styles.ts │ │ ├── transform.ts │ │ ├── transition.ts │ │ └── utils.ts │ │ ├── debounce.ts │ │ ├── dom │ │ ├── ancestors.ts │ │ ├── createElement.ts │ │ └── index.ts │ │ ├── getComponentResolver.ts │ │ ├── has.ts │ │ ├── history.ts │ │ ├── index.ts │ │ ├── is.ts │ │ ├── keyCodes.ts │ │ ├── loadElement.ts │ │ ├── math │ │ ├── clamp.ts │ │ ├── clamp01.ts │ │ ├── createEases.ts │ │ ├── createRange.ts │ │ ├── damp.ts │ │ ├── ease.ts │ │ ├── index.ts │ │ ├── inertiaFinalValue.ts │ │ ├── lerp.ts │ │ ├── map.ts │ │ ├── mean.ts │ │ └── round.ts │ │ ├── memo.ts │ │ ├── memoize.ts │ │ ├── nextFrame.ts │ │ ├── nextMicrotask.ts │ │ ├── nextTick.ts │ │ ├── noop.ts │ │ ├── object │ │ └── getAllProperties.ts │ │ ├── random.ts │ │ ├── scheduler.ts │ │ ├── scrollTo.ts │ │ ├── string │ │ ├── changeCase.ts │ │ ├── endsWith.ts │ │ ├── index.ts │ │ ├── startsWith.ts │ │ ├── withLeadingCharacters.ts │ │ ├── withLeadingSlash.ts │ │ ├── withTrailingCharacters.ts │ │ ├── withTrailingSlash.ts │ │ ├── withoutLeadingCharacters.ts │ │ ├── withoutLeadingCharactersRecursive.ts │ │ ├── withoutLeadingSlash.ts │ │ ├── withoutTrailingCharacters.ts │ │ ├── withoutTrailingCharactersRecursive.ts │ │ └── withoutTrailingSlash.ts │ │ ├── throttle.ts │ │ ├── trapFocus.ts │ │ ├── tween.ts │ │ └── wait.ts └── tests │ ├── Base │ ├── Base.spec.ts │ ├── __snapshots__ │ │ └── Base.spec.ts.snap │ ├── features.spec.ts │ ├── index.spec.ts │ ├── managers │ │ ├── ChildrenManager.spec.ts │ │ ├── EventsManager.spec.ts │ │ ├── OptionsManager.spec.ts │ │ ├── RefsManager.spec.ts │ │ ├── ResponsiveOptionsManager.spec.ts │ │ └── ServicesManager.spec.ts │ └── utils.spec.ts │ ├── __utils__ │ ├── event.ts │ ├── faketimers.ts │ ├── h.ts │ ├── happydom.ts │ ├── index.ts │ ├── matchMedia.ts │ ├── mockFeatures.ts │ ├── mockIntersectionObserver.ts │ ├── mockLoad.ts │ ├── mockRequestIdleCallback.ts │ ├── resizeWindow.ts │ └── scroll.ts │ ├── decorators │ ├── index.spec.ts │ ├── withBreakpointManager.spec.ts │ ├── withBreakpointObserver.spec.ts │ ├── withDrag.spec.ts │ ├── withExtraConfig.spec.ts │ ├── withFreezedOptions.spec.ts │ ├── withIntersectionObserver.spec.ts │ ├── withMountOnMediaQuery.spec.ts │ ├── withMountWhenInView.spec.ts │ ├── withMountWhenPrefersMotion.spec.ts │ ├── withMutation.spec.ts │ ├── withRelativePointer.spec.ts │ ├── withResponsiveOptions.spec.ts │ └── withScrolledInView │ │ ├── utils.spec.ts │ │ └── withScrolledInView.spec.ts │ ├── helpers │ ├── createApp.spec.ts │ ├── getClosestParent.spec.ts │ ├── getDirectChildren.spec.ts │ ├── getInstanceFromElement.spec.ts │ ├── importOnInteraction.spec.ts │ ├── importOnMediaQuery.spec.ts │ ├── importWhenIdle.spec.ts │ ├── importWhenPrefersMotion.spec.ts │ ├── importWhenVisible.spec.ts │ └── index.spec.ts │ ├── index.spec.ts │ ├── package.json │ ├── services │ ├── AbstractService.spec.ts │ ├── DragService.spec.ts │ ├── KeyService.spec.ts │ ├── LoadService.spec.ts │ ├── MutationService.spec.ts │ ├── PointerService.spec.ts │ ├── RafService.spec.ts │ ├── ResizeService.spec.ts │ ├── ScrollService.spec.ts │ └── index.spec.ts │ ├── utils │ ├── Queue.spec.ts │ ├── SmartQueue.spec.ts │ ├── cache.spec.ts │ ├── collide │ │ ├── boundingRectToCircle.spec.ts │ │ ├── collideCircleCircle.spec.ts │ │ ├── collideCircleRect.spec.ts │ │ ├── collidePointCircle.spec.ts │ │ ├── collidePointRect.spec.ts │ │ └── collideRectRect.spec.ts │ ├── css │ │ ├── animate.spec.ts │ │ ├── classes.spec.ts │ │ ├── getOffsetSizes.spec.ts │ │ ├── matrix.spec.ts │ │ ├── styles.spec.ts │ │ ├── transform.spec.ts │ │ ├── transition.spec.ts │ │ └── utils.spec.ts │ ├── debounce.spec.ts │ ├── dom │ │ └── createElement.spec.ts │ ├── history.server.spec.ts │ ├── history.spec.ts │ ├── index.spec.ts │ ├── is.spec.ts │ ├── isDefined.spec.ts │ ├── isFunction.spec.ts │ ├── loadElement.spec.ts │ ├── math │ │ ├── __snapshots__ │ │ │ └── ease.spec.ts.snap │ │ ├── clamp.spec.ts │ │ ├── clamp01.spec.ts │ │ ├── createRange.spec.ts │ │ ├── damp.spec.ts │ │ ├── ease.spec.ts │ │ ├── inertiaFinalValue.spec.ts │ │ ├── lerp.spec.ts │ │ ├── map.spec.ts │ │ ├── mean.spec.ts │ │ └── round.spec.ts │ ├── memoize.spec.ts │ ├── nextFrame.spec.ts │ ├── nextTick.spec.ts │ ├── random.spec.ts │ ├── scrollTo.spec.ts │ ├── string │ │ ├── changeCase.spec.ts │ │ ├── endsWith.spec.ts │ │ ├── index.spec.ts │ │ └── startsWith.spec.ts │ ├── throttle.spec.ts │ └── trapFocus.spec.ts │ └── vitest.config.ts ├── prettier.config.js ├── scripts ├── add-utils-export.js └── build.js ├── tsconfig.build.json ├── tsconfig.json └── tsconfig.lint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | indent_style = space 9 | indent_size = 2 10 | end_of_line = lf 11 | charset = utf-8 12 | trim_trailing_whitespace = true 13 | insert_final_newline = true 14 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "github>studiometa/renovate" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.github/workflows/export-size.yml: -------------------------------------------------------------------------------- 1 | name: export-size 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | - develop 8 | 9 | jobs: 10 | export-size: 11 | runs-on: macos-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | fetch-depth: '0' 16 | - uses: titouanmathis/export-size-action@main 17 | with: 18 | github_token: ${{ secrets.GITHUB_TOKEN }} 19 | build_script: npm run build-for-export-size 20 | paths: dist 21 | node-version: 22 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS files 2 | .DS_Store 3 | 4 | # NPM files 5 | node_modules/ 6 | yarn.lock 7 | .cache/ 8 | packages/**/package-lock.json 9 | .eslintcache 10 | .stylelintcache 11 | 12 | # Log files 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | 17 | # Editor directories and files 18 | .idea 19 | .vscode 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | *.sublime-* 26 | 27 | # Build files 28 | dist/ 29 | demo/dist/ 30 | /packages/docs/.vitepress/cache 31 | coverage/ 32 | /full.d.ts 33 | /index.d.ts 34 | /lcov.info 35 | /tsconfig.lint.tsbuildinfo 36 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 22 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | packages/docs/guide/recipes/counter-component/Counter.html 3 | packages/docs/guide/recipes/scroll-linked-animation/ScrollLinkedAnimation.html 4 | packages/docs/.vitepress/cache 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Studio Meta (https://www.studiometa.fr) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | informational: true 6 | patch: 7 | default: 8 | informational: true 9 | -------------------------------------------------------------------------------- /packages/demo/eslint.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig, js, ts, prettier, globals } from '@studiometa/eslint-config'; 2 | 3 | export default defineConfig(js, ts, prettier, { 4 | files: ['src/js/**/*'], 5 | languageOptions: { 6 | globals: globals.browser, 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /packages/demo/meta.config.js: -------------------------------------------------------------------------------- 1 | import { resolve } from 'node:path'; 2 | import { defineConfig } from '@studiometa/webpack-config'; 3 | import { prototyping } from '@studiometa/webpack-config-preset-prototyping'; 4 | 5 | export default defineConfig({ 6 | presets: [prototyping({ ts: true })], 7 | webpack(config) { 8 | config.resolve.alias = { 9 | ...config.resolve.alias, 10 | '@studiometa/js-toolkit': resolve('../js-toolkit'), 11 | }; 12 | 13 | config.cache = { 14 | ...config.cache, 15 | buildDependencies: { 16 | config: [import.meta.filename], 17 | toolkit: [resolve('../js-toolkit')], 18 | }, 19 | }; 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /packages/demo/src/css/styles.scss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | 3 | @tailwind components; 4 | 5 | @tailwind utilities; 6 | 7 | main { 8 | height: 5000px; 9 | } 10 | 11 | .fade-in-from { 12 | opacity: 0; 13 | } 14 | 15 | .fade-in-active { 16 | transition: opacity 1s ease-out; 17 | } 18 | 19 | .accordion-item__container[aria-hidden='true'] { 20 | display: none; 21 | } 22 | 23 | [data-breakpoint]::after, 24 | [data-breakpoint]::before { 25 | z-index: -999; 26 | position: absolute; 27 | pointer-events: none; 28 | opacity: 0; 29 | } 30 | 31 | [data-breakpoint]::after { 32 | content: 's,m,l'; 33 | } 34 | 35 | [data-breakpoint]::before { 36 | content: 's'; 37 | } 38 | 39 | @media (min-width: 768px) { 40 | [data-breakpoint]::before { 41 | content: 'm'; 42 | } 43 | } 44 | 45 | @media (min-width: 1280px) { 46 | [data-breakpoint]::before { 47 | content: 'l'; 48 | } 49 | } 50 | 51 | .cursor-grab { 52 | cursor: grab; 53 | } 54 | 55 | .cursor-grabbing { 56 | cursor: grabbing; 57 | } 58 | -------------------------------------------------------------------------------- /packages/demo/src/js/components/AnimateScrollTest.js: -------------------------------------------------------------------------------- 1 | import { withScrolledInView } from '@studiometa/js-toolkit'; 2 | import AnimateTest from './AnimateTest.js'; 3 | 4 | /** 5 | * 6 | */ 7 | export default class AnimateScrollTest extends withScrolledInView(AnimateTest) { 8 | static config = { 9 | name: 'AnimateScrollTest', 10 | }; 11 | 12 | /** 13 | * 14 | */ 15 | scrolledInView(props) { 16 | this.animate.progress(props.dampedProgress.y); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/demo/src/js/components/AnimateTestMultiple.js: -------------------------------------------------------------------------------- 1 | import { animate, ease } from '@studiometa/js-toolkit/utils'; 2 | import AnimateTest from './AnimateTest.js'; 3 | 4 | /** 5 | * AnimateTest class. 6 | */ 7 | export default class AnimateTestMultiple extends AnimateTest { 8 | /** 9 | * Config. 10 | */ 11 | static config = { 12 | ...AnimateTest.config, 13 | name: 'AnimateTestMultiple', 14 | refs: ['targets[]', 'start', 'pause', 'play', 'finish', 'progress'], 15 | options: { 16 | stagger: Number, 17 | }, 18 | }; 19 | 20 | /** 21 | * 22 | */ 23 | mounted() { 24 | const steps = this.$options.steps.map((step) => { 25 | if (typeof step.easing === 'string') { 26 | step.easing = ease[step.easing]; 27 | } 28 | 29 | return step; 30 | }); 31 | 32 | const options = { 33 | smooth: this.$options.smooth, 34 | stagger: this.$options.stagger, 35 | duration: this.$options.duration, 36 | easing: ease[this.$options.easing], 37 | }; 38 | 39 | console.log(options); 40 | 41 | this.animate = animate(this.$refs.targets, steps, { 42 | ...options, 43 | onProgress: (progress) => { 44 | this.$refs.progress.value = progress; 45 | }, 46 | }); 47 | this.animate.progress(this.$refs.progress.valueAsNumber); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/demo/src/js/components/BreakPointManagerDemo/BreakpointManagerDemoBase.js: -------------------------------------------------------------------------------- 1 | import { Base } from '@studiometa/js-toolkit'; 2 | 3 | /** 4 | * 5 | */ 6 | export default class BreakpointManagerDemoBase extends Base { 7 | static config = { 8 | log: false, 9 | refs: ['content'], 10 | }; 11 | 12 | /** 13 | * 14 | */ 15 | mounted() { 16 | this.$log('mounted'); 17 | this.$refs.content.innerHTML = this.$options.name; 18 | } 19 | 20 | /** 21 | * 22 | */ 23 | destroyed() { 24 | this.$log('destroyed'); 25 | } 26 | 27 | /** 28 | * 29 | */ 30 | onClick(event) { 31 | this.$log('click', event); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/demo/src/js/components/BreakPointManagerDemo/BreakpointManagerDemoDesktop.js: -------------------------------------------------------------------------------- 1 | import BreakpointManagerDemoBase from './BreakpointManagerDemoBase.js'; 2 | 3 | /** 4 | * 5 | */ 6 | export default class BreakpointManagerDemoDesktop extends BreakpointManagerDemoBase { 7 | static config = { 8 | ...BreakpointManagerDemoBase.config, 9 | name: 'BreakpointManagerDemoDesktop', 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /packages/demo/src/js/components/BreakPointManagerDemo/BreakpointManagerDemoMobile.js: -------------------------------------------------------------------------------- 1 | import BreakpointManagerDemoBase from './BreakpointManagerDemoBase.js'; 2 | 3 | /** 4 | * 5 | */ 6 | export default class BreakpointManagerDemoMobile extends BreakpointManagerDemoBase { 7 | static config = { 8 | ...BreakpointManagerDemoBase.config, 9 | name: 'BreakpointManagerDemoMobile', 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /packages/demo/src/js/components/BreakPointManagerDemo/BreakpointManagerDemoTablet.js: -------------------------------------------------------------------------------- 1 | import BreakpointManagerDemoBase from './BreakpointManagerDemoBase.js'; 2 | 3 | /** 4 | * 5 | */ 6 | export default class BreakpointManagerDemoTablet extends BreakpointManagerDemoBase { 7 | static config = { 8 | ...BreakpointManagerDemoBase.config, 9 | name: 'BreakpointManagerDemoTablet', 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /packages/demo/src/js/components/BreakPointManagerDemo/index.js: -------------------------------------------------------------------------------- 1 | import { Base, withBreakpointManager } from '@studiometa/js-toolkit'; 2 | import BreakpointManagerDemoMobile from './BreakpointManagerDemoMobile.js'; 3 | import BreakpointManagerDemoTablet from './BreakpointManagerDemoTablet.js'; 4 | import BreakpointManagerDemoDesktop from './BreakpointManagerDemoDesktop.js'; 5 | 6 | /** 7 | * 8 | */ 9 | export default class BreakpointManagerDemo extends withBreakpointManager(Base, [ 10 | ['s', BreakpointManagerDemoMobile], 11 | ['m', BreakpointManagerDemoTablet], 12 | ['l', BreakpointManagerDemoDesktop], 13 | ]) { 14 | static config = { 15 | name: 'BreakpointManagerDemo', 16 | log: true, 17 | refs: ['content'], 18 | }; 19 | 20 | /** 21 | * 22 | */ 23 | mounted() { 24 | this.$log('mounted'); 25 | } 26 | 27 | /** 28 | * 29 | */ 30 | resized({ breakpoint }) { 31 | this.$log('breakpoint:', breakpoint); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/demo/src/js/components/BreakpointObserverDemo.js: -------------------------------------------------------------------------------- 1 | import { Base, withBreakpointObserver } from '@studiometa/js-toolkit'; 2 | 3 | /** 4 | * 5 | */ 6 | export default class BreakpointObserverDemo extends withBreakpointObserver(Base) { 7 | static config = { 8 | name: 'BreakpointObserverDemo', 9 | log: true, 10 | refs: ['content'], 11 | }; 12 | 13 | /** 14 | * 15 | */ 16 | mounted() { 17 | this.$log('mounted'); 18 | this.status = 'mounted'; 19 | } 20 | 21 | /** 22 | * 23 | */ 24 | destroyed() { 25 | this.$log('destroyed'); 26 | this.status = 'destroyed'; 27 | } 28 | 29 | /** 30 | * 31 | */ 32 | onClick(event) { 33 | this.$log('click', event); 34 | } 35 | 36 | /** 37 | * 38 | */ 39 | set status(value) { 40 | let content = `${this.$options.name}
`; 41 | 42 | const { activeBreakpoints, inactiveBreakpoints } = this.$options; 43 | 44 | if (activeBreakpoints) { 45 | content += `activeBreakpoints: ${activeBreakpoints}
`; 46 | } else if (inactiveBreakpoints) { 47 | content += `inactiveBreakpoints: ${inactiveBreakpoints}
`; 48 | } 49 | 50 | content += `status: ${value}`; 51 | 52 | this.$refs.content.innerHTML = content; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/demo/src/js/components/Cursor.js: -------------------------------------------------------------------------------- 1 | import { Base } from '@studiometa/js-toolkit'; 2 | 3 | /** 4 | * 5 | */ 6 | export default class Cursor extends Base { 7 | static config = { 8 | name: 'Cursor', 9 | refs: ['inner'], 10 | }; 11 | 12 | /** 13 | * 14 | */ 15 | moved({ x, y, delta, isDown }) { 16 | let transform = `translate3d(${x}px, ${y}px, 0)`; 17 | 18 | if (isDown) { 19 | transform += ' scale(0.75)'; 20 | } 21 | 22 | this.$el.style.transform = transform; 23 | this.$refs.inner.style.transform = `translate3d(${delta.x * -1}px, ${delta.y * -1}px, 0)`; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/demo/src/js/components/MediaQueryDemo.js: -------------------------------------------------------------------------------- 1 | import { Base, withMountOnMediaQuery } from '@studiometa/js-toolkit'; 2 | 3 | /** 4 | * 5 | */ 6 | export default class MediaQueryDemo extends withMountOnMediaQuery( 7 | Base, 8 | 'not (prefers-reduced-motion)', 9 | ) { 10 | static config = { 11 | name: 'MediaQueryDemo', 12 | log: true, 13 | }; 14 | 15 | /** 16 | * 17 | */ 18 | mounted() { 19 | this.$log('Mounted! The user accepts motion.'); 20 | } 21 | 22 | /** 23 | * 24 | */ 25 | destroyed() { 26 | this.$log('Destroyed! The user prefers reduced motion.'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/demo/src/js/components/Parallax.js: -------------------------------------------------------------------------------- 1 | import { Base, withScrolledInView, withFreezedOptions } from '@studiometa/js-toolkit'; 2 | import { map, transform } from '@studiometa/js-toolkit/utils'; 3 | 4 | /** 5 | * 6 | */ 7 | export default class Parallax extends withScrolledInView(withFreezedOptions(Base)) { 8 | static config = { 9 | name: 'Parallax', 10 | refs: ['target'], 11 | options: { 12 | speed: Number, 13 | }, 14 | }; 15 | 16 | /** 17 | * 18 | */ 19 | scrolledInView(props) { 20 | const { target } = this.$refs; 21 | const y = map(props.dampedProgress.y, 0, 1, 100, -100) * this.$options.speed; 22 | const scale = map(props.dampedProgress.x, 0, 1, 0.5, 2); 23 | return () => transform(target, { y, scale }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/demo/src/js/components/ParentNativeEvent/Child.js: -------------------------------------------------------------------------------- 1 | import { Base } from '@studiometa/js-toolkit'; 2 | 3 | /** 4 | * Child class. 5 | */ 6 | export default class Child extends Base { 7 | /** 8 | * Config. 9 | */ 10 | static config = { 11 | name: 'Child', 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /packages/demo/src/js/components/ParentNativeEvent/index.js: -------------------------------------------------------------------------------- 1 | import { Base } from '@studiometa/js-toolkit'; 2 | import Child from './Child.js'; 3 | 4 | /** 5 | * ParentNativeEvent class. 6 | */ 7 | export default class ParentNativeEvent extends Base { 8 | /** 9 | * Config. 10 | */ 11 | static config = { 12 | name: 'ParentNativeEvent', 13 | log: true, 14 | components: { 15 | Child, 16 | }, 17 | }; 18 | 19 | /** 20 | * 21 | */ 22 | onChildClick(...args) { 23 | this.$log(this.$id, 'onChildClick', ...args); 24 | } 25 | 26 | /** 27 | * 28 | */ 29 | onChildDede(...args) { 30 | this.$log(this.$id, 'onChildDede', ...args); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/demo/src/js/components/PointerProps.ts: -------------------------------------------------------------------------------- 1 | import { Base, withRelativePointer } from '@studiometa/js-toolkit'; 2 | import type { BaseConfig, BaseProps, PointerServiceProps } from '@studiometa/js-toolkit'; 3 | import { transform, clamp01 } from '@studiometa/js-toolkit/utils'; 4 | 5 | export interface PointerPropsProps extends BaseProps { 6 | $refs: { 7 | props: HTMLElement; 8 | }; 9 | } 10 | 11 | /** 12 | * PointerProps class. 13 | */ 14 | export default class PointerProps extends withRelativePointer(Base) { 15 | /** 16 | * Config. 17 | */ 18 | static config: BaseConfig = { 19 | name: 'PointerProps', 20 | refs: ['props', 'scaler'], 21 | }; 22 | 23 | movedrelative(props: PointerServiceProps) { 24 | this.$refs.props.textContent = JSON.stringify(props, null, 2); 25 | transform(this.$refs.scaler, { 26 | scaleX: clamp01(props.progress.x) + 0.5, 27 | scaleY: clamp01(props.progress.y) + 0.5, 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/demo/src/js/components/ResponsiveOptions.js: -------------------------------------------------------------------------------- 1 | import { Base, withResponsiveOptions } from '@studiometa/js-toolkit'; 2 | 3 | /** 4 | * ResponsiveOptions class. 5 | */ 6 | export default class ResponsiveOptions extends withResponsiveOptions(Base) { 7 | /** 8 | * Config. 9 | */ 10 | static config = { 11 | name: 'ResponsiveOptions', 12 | options: { 13 | fooBar: { 14 | type: String, 15 | responsive: true, 16 | }, 17 | }, 18 | }; 19 | 20 | /** 21 | * Mounted hook. 22 | * @returns {void} 23 | */ 24 | mounted() { 25 | this.print(); 26 | } 27 | 28 | /** 29 | * Resized hook. 30 | * @returns {void} 31 | */ 32 | resized() { 33 | this.print(); 34 | } 35 | 36 | /** 37 | * Print data to the DOM. 38 | * @returns {void} 39 | */ 40 | print() { 41 | this.$el.innerHTML = `${this.$services.get('resized').breakpoint}
${JSON.stringify( 42 | this.$options, 43 | null, 44 | 2, 45 | )}`; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/demo/src/js/components/ScrollToDemo.js: -------------------------------------------------------------------------------- 1 | import { Base } from '@studiometa/js-toolkit'; 2 | import { scrollTo } from '@studiometa/js-toolkit/utils'; 3 | 4 | /** 5 | * 6 | */ 7 | export default class ScrollToDemo extends Base { 8 | static config = { 9 | name: 'ScrollToDemo', 10 | log: true, 11 | refs: ['text', 'btn'], 12 | }; 13 | 14 | /** 15 | * 16 | */ 17 | mounted() { 18 | this.$log('mounted'); 19 | } 20 | 21 | /** 22 | * 23 | */ 24 | async onBtnClick() { 25 | this.$log('start scroll'); 26 | await scrollTo(this.$refs.text, { duration: 2, easing: [1, 0, 0, 1] }); 27 | this.$log('end scroll'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/demo/src/js/components/ScrolledInViewOffset.js: -------------------------------------------------------------------------------- 1 | import { Base, withScrolledInView } from '@studiometa/js-toolkit'; 2 | 3 | /** 4 | * ScrolledInViewOffset class. 5 | */ 6 | export default class ScrolledInViewOffset extends withScrolledInView(Base) { 7 | /** 8 | * Config. 9 | */ 10 | static config = { 11 | name: 'ScrolledInViewOffset', 12 | refs: ['progress'], 13 | }; 14 | 15 | /** 16 | * Scrolled in view. 17 | * @param {import('@studiometa/js-toolkit').ScrolledInViewProps} props 18 | * @returns {void} 19 | */ 20 | scrolledInView(props) { 21 | const { x, y } = props.progress; 22 | this.$refs.progress.textContent = `{ x: ${x.toFixed(3)}, y: ${y.toFixed(3)} }`; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/demo/src/js/components/Skew.js: -------------------------------------------------------------------------------- 1 | import { Base, withMountWhenInView, useScroll, useRaf } from '@studiometa/js-toolkit'; 2 | import { damp, clamp, transform } from '@studiometa/js-toolkit/utils'; 3 | 4 | const scroll = useScroll(); 5 | const raf = useRaf(); 6 | 7 | let skewY = 0; 8 | let dampedSkewY = 0; 9 | 10 | scroll.add('Skew.js', (props) => { 11 | skewY = clamp(props.delta.y * -0.005, -0.1, 0.1) * 40; 12 | 13 | if (!raf.has('Skew.js')) { 14 | raf.add('Skew.js', () => { 15 | dampedSkewY = damp(skewY, dampedSkewY, 0.1); 16 | 17 | if (dampedSkewY === skewY) { 18 | raf.remove('Skew.js'); 19 | } 20 | }); 21 | } 22 | }); 23 | 24 | /** 25 | * 26 | */ 27 | export default class Skew extends withMountWhenInView(Base) { 28 | static config = { 29 | name: 'Skew', 30 | }; 31 | 32 | /** 33 | * 34 | */ 35 | scrolled(props) { 36 | if (props.changed.y && !this.$services.has('ticked')) { 37 | this.$services.enable('ticked'); 38 | } 39 | } 40 | 41 | /** 42 | * 43 | */ 44 | ticked() { 45 | if (dampedSkewY === skewY) { 46 | this.$services.disable('ticked'); 47 | } 48 | 49 | const { $el } = this; 50 | return () => transform($el, { skewY: dampedSkewY }); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/demo/src/js/components/Slider.js: -------------------------------------------------------------------------------- 1 | import { Base } from '@studiometa/js-toolkit'; 2 | 3 | /** 4 | * 5 | */ 6 | export default class Slider extends Base { 7 | static config = { 8 | name: 'Slider', 9 | }; 10 | 11 | // onChange({ previous, index, direction }) {} 12 | } 13 | -------------------------------------------------------------------------------- /packages/demo/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module" 3 | } 4 | -------------------------------------------------------------------------------- /packages/demo/src/templates/components/parallax.twig: -------------------------------------------------------------------------------- 1 | {# 2 | /** 3 | * @param {string|number} speed 4 | * The speed of the parallax effect. 5 | * @param {boolean} debug 6 | * Wether to enable the debug or not. 7 | */ 8 | #} 9 | 10 |
17 |
18 |
19 | -------------------------------------------------------------------------------- /packages/demo/src/templates/layouts/base.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @studiometa/js-toolkit demo 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | {% block main %} 13 | Main. 14 | {% endblock %} 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /packages/demo/src/templates/pages/child-native-event.twig: -------------------------------------------------------------------------------- 1 | {% extends '@layouts/base.twig' %} 2 | {% block main %} 3 |
4 | 5 | 6 |
7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /packages/demo/src/templates/pages/deep-nested-children.twig: -------------------------------------------------------------------------------- 1 | {% extends '@layouts/base.twig' %} 2 | 3 | {% block main %} 4 | {% include '@components/deep-nested-children.twig' %} 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /packages/demo/src/templates/pages/drag.twig: -------------------------------------------------------------------------------- 1 | {% extends '@layouts/base.twig' %} 2 | 3 | {% block main %} 4 |
5 |
6 |
7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /packages/demo/src/templates/pages/pointer-props.twig: -------------------------------------------------------------------------------- 1 | {% extends '@layouts/base.twig' %} 2 | 3 | {% block main %} 4 |
5 |

 6 |     
8 |
9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /packages/demo/src/templates/pages/responsive-options.twig: -------------------------------------------------------------------------------- 1 | {% extends '@layouts/base.twig' %} 2 | {% block main %} 3 |
 7 |     ...
 8 |   
9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /packages/demo/src/templates/pages/scrolled-in-view-nan.twig: -------------------------------------------------------------------------------- 1 | {% extends '@layouts/base.twig' %} 2 | 3 | {% block main %} 4 |
7 |
0
8 |
9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /packages/demo/tailwind.config.js: -------------------------------------------------------------------------------- 1 | import { config } from '@studiometa/tailwind-config'; 2 | 3 | export default { 4 | presets: [config], 5 | content: ['src/templates/**/*.twig', 'src/**/*.js'], 6 | }; 7 | -------------------------------------------------------------------------------- /packages/docs/.vitepress/theme/components/Loader.vue: -------------------------------------------------------------------------------- 1 | 32 | -------------------------------------------------------------------------------- /packages/docs/.vitepress/theme/composables/useObserver.ts: -------------------------------------------------------------------------------- 1 | let callbacks:Array = []; 2 | let observer; 3 | 4 | function mainCallback(mutations, obs) { 5 | callbacks.forEach((callback) => { 6 | callback(mutations, observer); 7 | }); 8 | } 9 | 10 | export default function useObserver(callback: MutationCallback) { 11 | if (typeof window === 'undefined') { 12 | return; 13 | } 14 | 15 | if (!observer) { 16 | observer = new MutationObserver(mainCallback); 17 | } 18 | 19 | callbacks.push(callback); 20 | 21 | function cleanup() { 22 | callbacks = callbacks.filter((cb) => cb !== callback); 23 | } 24 | 25 | function observe(element, options) { 26 | observer.observe(element, options); 27 | } 28 | 29 | function disconnect() { 30 | observer.disconnect(); 31 | } 32 | 33 | return { 34 | observe, 35 | cleanup, 36 | disconnect, 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /packages/docs/.vitepress/theme/custom.scss: -------------------------------------------------------------------------------- 1 | @use '@shikijs/vitepress-twoslash/style.css'; 2 | @use 'tailwindcss/utilities'; 3 | 4 | .VPHomeHero .name, 5 | .VPHomeHero .text { 6 | max-width: none !important; 7 | } 8 | -------------------------------------------------------------------------------- /packages/docs/.vitepress/theme/index.js: -------------------------------------------------------------------------------- 1 | import DefaultTheme from 'vitepress/theme'; 2 | import TwoslashFloatingVue from '@shikijs/vitepress-twoslash/client'; 3 | import PreviewIframe from './components/PreviewIframe.vue'; 4 | import './custom.scss'; 5 | 6 | /** @type {import('vitepress').Theme} */ 7 | export default { 8 | extends: DefaultTheme, 9 | enhanceApp({ app }) { 10 | app.component('PreviewIframe', PreviewIframe); 11 | app.use(TwoslashFloatingVue); 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /packages/docs/.vitepress/theme/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss/base'; 2 | @import 'tailwindcss/components'; 3 | @import 'tailwindcss/utilities'; 4 | -------------------------------------------------------------------------------- /packages/docs/api/decorators/index.md: -------------------------------------------------------------------------------- 1 | # Decorators 2 | 3 | Decorators are used to add extra functionalities to a Base class. The following ones are available: 4 | 5 | - [withBreakpointManager](./withBreakpointManager.html) 6 | - [withBreakpointObserver](./withBreakpointObserver.html) 7 | - [withDrag](./withDrag.html) 8 | - [withExtraConfig](./withExtraConfig.html) 9 | - [withFreezedOptions](./withFreezedOptions.html) 10 | - [withIntersectionObserver](./withIntersectionObserver.html) 11 | - [withMountWhenInView](./withMountWhenInView.html) 12 | - [withMountOnMediaQuery](./withMountOnMediaQuery.html) 13 | - [withMountWhenPrefersMotion](./withMountWhenPrefersMotion.html) 14 | -------------------------------------------------------------------------------- /packages/docs/api/decorators/withFreezedOptions.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # withFreezedOptions 6 | 7 | Use this decorator to transfrom the `$options` property to be read only. This can help improve performance when accessing options in the [`ticked` service method](/api/methods-hooks-services.html#ticked). 8 | 9 | ## Usage 10 | 11 | ```js twoslash 12 | import { withFreezedOptions } from '@studiometa/js-toolkit'; 13 | import { Slider } from '@studiometa/ui'; 14 | 15 | export default class SliderWithFreezedOptions extends withFreezedOptions(Slider) {} 16 | ``` 17 | 18 | ### Parameters 19 | 20 | - `BaseClass` (`Base`): The class to use as parent. 21 | 22 | ### Return value 23 | 24 | - `Base`: A child class of the given class with the `$options` property freezed. 25 | -------------------------------------------------------------------------------- /packages/docs/api/decorators/withMountWhenPrefersMotion.md: -------------------------------------------------------------------------------- 1 | # withMountWhenPrefersMotion 2 | 3 | Use this decorator to create a component which will mount and destroy itself based on the `(prefers-reduced-motion)` media query. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { Base, withMountWhenPrefersMotion } from '@studiometa/js-toolkit'; 9 | import Component from './Component.js'; 10 | 11 | export default withMountWhenPrefersMotion(Component); 12 | ``` 13 | 14 | ### Parameters 15 | 16 | - `Base` (`BaseConstructor`): The `Base` class or a class extending it. 17 | 18 | ### Return value 19 | 20 | - `BaseConstructor`: A child class of the given class which will be mounted when the media query matches. 21 | 22 | ## API 23 | 24 | This decorator does not expose a specific API. 25 | 26 | ## Examples 27 | 28 | ### Simple usage 29 | 30 | ```js {1,3,10,14} twoslash 31 | import { Base, withMountWhenPrefersMotion } from '@studiometa/js-toolkit'; 32 | 33 | export default class Component extends withMountWhenPrefersMotion(Base) { 34 | static config = { 35 | name: 'Component', 36 | log: true, 37 | }; 38 | 39 | mounted() { 40 | this.$log('Accepts motion!'); 41 | } 42 | 43 | destroyed() { 44 | this.$log('User enabled reduced motion after mount.'); 45 | } 46 | } 47 | ``` 48 | -------------------------------------------------------------------------------- /packages/docs/api/helpers/getClosestParent.md: -------------------------------------------------------------------------------- 1 | # getClosestParent 2 | 3 | Use the `getClosestParent` function to get the closest instance of a given parent component. 4 | 5 | ## Usage 6 | 7 | ```js {1,9,15} twoslash 8 | import { Base, getClosestParent } from '@studiometa/js-toolkit'; 9 | import Child from './Child.js'; 10 | 11 | class Parent extends Base { 12 | static config = { 13 | name: 'Parent', 14 | components: { 15 | Child, 16 | Parent, // Useful for recursive components only 17 | }, 18 | }; 19 | 20 | onChildClick(index, event) { 21 | const childInstance = this.$children.Child[index]; 22 | const parent = getClosestParent(childInstance, Parent); 23 | 24 | if (parent === this) { 25 | event.preventDefault(); 26 | } 27 | } 28 | } 29 | ``` 30 | 31 | **Parameters** 32 | 33 | - `childInstance` (`Base`): the instance from which to look up for the parent 34 | - `parentConstructor` (`typeof Base`): the constructor of the parent component 35 | 36 | **Return value** 37 | 38 | - `Base | null`: the instance of the closes parent, `null` if no parent matching the constructor was found 39 | -------------------------------------------------------------------------------- /packages/docs/api/helpers/getInstanceFromElement.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # getInstanceFromElement 6 | 7 | Use the `getInstanceFromElement` function to get a class instance attached to a DOM element. 8 | 9 | ## Usage 10 | 11 | ```js {1,4,6} twoslash 12 | import { getInstanceFromElement } from '@studiometa/js-toolkit'; 13 | import Component from './Component.js'; 14 | 15 | const componentInstance = getInstanceFromElement(document.body, Component); 16 | ``` 17 | 18 | **Parameters** 19 | 20 | - `element` (`HTMLElement`): the target element 21 | - `BaseConstructor` (`BaseConstructor`): the class (constructor) of the component to look for 22 | 23 | **Return value** 24 | 25 | - `null | InstanceType`: `null` if the element has no instance of the given type attached, an instance of the given type otherwise 26 | -------------------------------------------------------------------------------- /packages/docs/api/helpers/getInstances.md: -------------------------------------------------------------------------------- 1 | # getInstances 2 | 3 | Use the `getInstances` function to retrieve all mounted instances of every components. You can get instances for a specific component by providing its constructor as first parameter of the function. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { Base, getInstances } from '@studiometa/js-toolkit'; 9 | import Component from './Component.js'; 10 | 11 | // Get all mounted instances of all components 12 | const instances = getInstances(); // Set 13 | 14 | // Get all mounted instances of the `Component` component 15 | getInstances(Component); // Set 16 | ``` 17 | 18 | **Parameters** 19 | 20 | - `ctor` (`undefined | typeof Base`): the class from which the instances should be retrieved 21 | 22 | **Return value** 23 | 24 | - `Set`: all the instances created with the given class 25 | -------------------------------------------------------------------------------- /packages/docs/api/helpers/importOnMediaQuery.md: -------------------------------------------------------------------------------- 1 | # importOnMediaQuery 2 | 3 | Use this function to import components according to a specified [media query](https://developer.mozilla.org/en-US/docs/Web/CSS/@media#media_features). 4 | 5 | ## Usage 6 | 7 | ```js {6} twoslash 8 | import { importOnMediaQuery } from '@studiometa/js-toolkit'; 9 | 10 | // Import Component.js if the device is in portrait. 11 | importOnMediaQuery(() => import('./components/Component.js'), '(orientation: portrait)'); 12 | ``` 13 | 14 | **Parameters** 15 | 16 | - `importFn` (`() => Promise`): the function to import components 17 | - `media` (`string`): a [media query](https://developer.mozilla.org/en-US/docs/Web/CSS/@media#media_features) 18 | 19 | **Returns** 20 | 21 | - `Promise`: a promise resolving to the the component's class 22 | 23 | ## Example 24 | 25 | ```js {1,7-8} twoslash 26 | import { Base, importOnMediaQuery } from '@studiometa/js-toolkit'; 27 | 28 | class App extends Base { 29 | static config = { 30 | name: 'App', 31 | components: { 32 | Component: () => 33 | importOnMediaQuery(() => import('./components/Component.js'), '(orientation: portrait)'), 34 | }, 35 | }; 36 | } 37 | ``` 38 | -------------------------------------------------------------------------------- /packages/docs/api/helpers/importWhenIdle.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # importWhenIdle 6 | 7 | Use this function to import components when the [`requestIdleCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback) function is called. 8 | 9 | ## Usage 10 | 11 | ```js twoslash 12 | import { importWhenIdle } from '@studiometa/js-toolkit'; 13 | 14 | importWhenIdle(() => import('./components/Component.js'), { timeout: 1000 }); 15 | ``` 16 | 17 | **Parameters** 18 | 19 | - `importFn` (`() => Promise`): the function to import components 20 | - `options` (`{ timeout?: number }`): the time to wait in milliseconds before forcing the import to be made, defaults to `1` 21 | 22 | **Returns** 23 | 24 | - `Promise`: a promise resolving to the the component's class 25 | 26 | ## Example 27 | 28 | ```js {1,7} twoslash 29 | import { Base, importWhenIdle } from '@studiometa/js-toolkit'; 30 | 31 | class App extends Base { 32 | static config = { 33 | name: 'App', 34 | components: { 35 | Component: () => importWhenIdle(() => import('./components/Component.js')), 36 | }, 37 | }; 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /packages/docs/api/helpers/importWhenPrefersMotion.md: -------------------------------------------------------------------------------- 1 | # importWhenPrefersMotion 2 | 3 | Use this function to import components when the user accepts motion. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { importWhenPrefersMotion } from '@studiometa/js-toolkit'; 9 | 10 | // Import Component.js if the user accepts motion. 11 | importWhenPrefersMotion(() => import('./components/Component.js')); 12 | ``` 13 | 14 | **Parameters** 15 | 16 | - `importFn` (`() => Promise`): the function to import components 17 | 18 | **Returns** 19 | 20 | - `Promise`: a promise resolving to the the component's class 21 | 22 | ## Example 23 | 24 | ```js {1,7} twoslash 25 | import { Base, importWhenPrefersMotion } from '@studiometa/js-toolkit'; 26 | 27 | class App extends Base { 28 | static config = { 29 | name: 'App', 30 | components: { 31 | Component: () => importWhenPrefersMotion(() => import('./components/Component.js')), 32 | }, 33 | }; 34 | } 35 | ``` 36 | -------------------------------------------------------------------------------- /packages/docs/api/helpers/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: true 3 | --- 4 | 5 | # Helpers 6 | 7 | The following helper functions can be used to achieve advanced setup of your application. 8 | 9 | ## App helper 10 | 11 | The following helpers can help you bootstrap the root class of your application: 12 | 13 | - [createApp](./createApp.html) 14 | 15 | ## Instance helpers 16 | 17 | Some helpers can be used to work with instances. 18 | 19 | - [getClosestParent](./getClosestParent.html) 20 | - [getDirectChildren](./getDirectChildren.html) 21 | - [getInstanceFromElement](./getInstanceFromElement.html) 22 | - [getInstances](./getInstances.html) 23 | - [isDirectChild](./isDirectChild.html) 24 | 25 | ## Lazy import helpers 26 | 27 | Some components might not need to be imported and instantiated immediately on page load. The following functions will help you define custom scenarios for when to import these components. 28 | 29 | - [importOnInteraction](./importOnInteraction.html) 30 | - [importWhenIdle](./importWhenIdle.html) 31 | - [importWhenVisible](./importWhenVisible.html) 32 | - [importOnMediaQuery](./importOnMediaQuery.html) 33 | - [importWhenPrefersMotion](./importWhenPrefersMotion.html) 34 | -------------------------------------------------------------------------------- /packages/docs/api/helpers/isDirectChild.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # isDirectChild 6 | 7 | Use the `isDirectChild` function to test if a child component instance is a direct descendant of the given parent instance. This function is helpful when working with nested components which declare themselves as children. 8 | 9 | ## Usage 10 | 11 | ```js {1,9,14} twoslash 12 | import { Base, isDirectChild } from '@studiometa/js-toolkit'; 13 | import Child from './Child.js'; 14 | 15 | class Parent extends Base { 16 | static config = { 17 | name: 'Parent', 18 | components: { 19 | Child, 20 | Parent, // Useful for recursive components only 21 | }, 22 | }; 23 | 24 | onChildClick({ target, event }) { 25 | if (isDirectChild(this, 'Parent', 'Child', target)) { 26 | event.preventDefault(); 27 | } 28 | } 29 | } 30 | ``` 31 | 32 | **Parameters** 33 | 34 | - `parentInstance` (`Base`): the parent instance. 35 | - `parentName` (`string`): the name of the recursive parent as specified in the `config.components` object. 36 | - `childName` (`string`): the name of the child component as specified in the `config.components` object. 37 | - `childInstance` (`Base`): the child instance. 38 | 39 | **Return value** 40 | 41 | - `boolean`: `true` if the given child instance is a direct descendant of the given parent instance. 42 | -------------------------------------------------------------------------------- /packages/docs/api/html/data-option.md: -------------------------------------------------------------------------------- 1 | # data-option-<​name> 2 | 3 | Use `data-option-` attributes to configure the options of components from your HTML. 4 | 5 | :::tip READ MORE 6 | 7 | - [Configuration: `config.options`](/api/configuration.html#config-options) 8 | - [Guide: managing options](/guide/introduction/managing-options.html) 9 | 10 | ::: 11 | 12 | ## data-option-no-<​name> 13 | 14 | When using a boolean option, you can negate its value by prefixing its name with `no-`. 15 | 16 | ```js twoslash 17 | import { Base } from '@studiometa/js-toolkit'; 18 | 19 | /** 20 | * @extends {Base<{ $options: { open: boolean } }>} 21 | */ 22 | class Component extends Base { 23 | static config = { 24 | name: 'Component', 25 | options: { 26 | open: { 27 | type: Boolean, 28 | default: true, 29 | }, 30 | }, 31 | }; 32 | 33 | mounted() { 34 | console.log(this.$options.open); // false 35 | } 36 | } 37 | ``` 38 | 39 | ```html 40 |
41 | ``` 42 | -------------------------------------------------------------------------------- /packages/docs/api/html/data-ref.md: -------------------------------------------------------------------------------- 1 | # data-ref 2 | 3 | Use `data-ref` attributes to define elements inside the scope of a component which should be accessed in JavaScript with the `$refs` instance property. 4 | 5 | :::tip READ MORE 6 | 7 | - [Configuration: `config.refs`](/api/configuration.html#config-refs) 8 | - [Guide: managing refs](/guide/introduction/managing-refs.html) 9 | 10 | ::: 11 | -------------------------------------------------------------------------------- /packages/docs/api/index.md: -------------------------------------------------------------------------------- 1 | # Base class 2 | 3 | Create components with ease by extending the `Base` class. 4 | 5 | ```js twoslash 6 | import { Base } from '@studiometa/js-toolkit'; 7 | 8 | class Component extends Base { 9 | static config = { 10 | name: 'Component', 11 | }; 12 | } 13 | ``` 14 | 15 | Discover how to configure and use the `Base` class in the following sections: 16 | 17 | - [Configuration](/api/configuration.html) 18 | - [Lifecycle hooks](/api/methods-hooks-lifecycle.html) 19 | - [Services hooks](/api/methods-hooks-services.html) 20 | - [Events hooks](/api/methods-hooks-events.html) 21 | - [Instantiation](/api/instantiation.html) 22 | - [Instance properties](/api/instance-properties.html) 23 | - [Instance methods](/api/instance-methods.html) 24 | - [Intance events](/api/instance-events.html) 25 | - [Static methods](/api/static-methods.html) 26 | -------------------------------------------------------------------------------- /packages/docs/api/services/index.md: -------------------------------------------------------------------------------- 1 | # Services 2 | 3 | Services will help you implement common tasks by abstracting their tedious parts. A service will always have the following signature: 4 | 5 | ```ts 6 | interface ServiceProps { 7 | [key:string]: any; 8 | } 9 | 10 | interface Service { 11 | add: (key:string, fn: (props:ServiceProps) => void) => void; 12 | remove: (key:string) => void; 13 | has: (key:string) => boolean; 14 | props: () => ServiceProps; 15 | } 16 | 17 | useService(...args?):Service; 18 | ``` 19 | 20 | The following services are available: 21 | 22 | - [useDrag](./useDrag.html) to implement drag features 23 | - [useKey](./useKey.html) to implement keyboard features 24 | - [useLoad](./useLoad.html) to implement window load features 25 | - [usePointer](./usePointer.html) to implement mouse/touch features 26 | - [useRaf](./useRaf.html) to implement `requestAnimationFrame` features 27 | - [useResize](./useResize.html) to implement window resize features 28 | - [useScroll](./useScroll.html) to implement scroll features 29 | -------------------------------------------------------------------------------- /packages/docs/api/services/useLoad.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # Load service 6 | 7 | The load service will help you manage actions that should run on `window` load event. 8 | 9 | ## Usage 10 | 11 | ```js twoslash 12 | import { useLoad } from '@studiometa/js-toolkit'; 13 | 14 | const { add, remove } = useLoad(); 15 | 16 | // Add a callback 17 | add('custom-id', () => { 18 | console.log('loaded!'); 19 | }); 20 | 21 | // Remove the callback 22 | remove('custom-id'); 23 | ``` 24 | 25 | ## Props 26 | 27 | This service does not have any props. 28 | -------------------------------------------------------------------------------- /packages/docs/api/services/useRaf.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # RAF service 6 | 7 | The RAF (short for `requestAnimationFrame`) service will help you manage your render loops. 8 | 9 | ## Usage 10 | 11 | ```js twoslash 12 | import { useRaf } from '@studiometa/js-toolkit'; 13 | 14 | const { add, remove, props } = useRaf(); 15 | 16 | add('custom-id', (props) => { 17 | console.log(props.time); // latest `performance.now()` 18 | 19 | // Read the DOM and compute values... 20 | 21 | return () => { 22 | // Update the DOM... 23 | }; 24 | }); 25 | 26 | // Get the latest prop object 27 | console.log(props()); 28 | 29 | // Remove the callback 30 | remove('custom-id'); 31 | ``` 32 | 33 | ## Props 34 | 35 | ### `time` 36 | 37 | - Type: `Number` 38 | 39 | The time elapsed since the [time origin](https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp#The_time_origin). 40 | 41 | ::: tip 42 | Read the [`performance.now()` documentation](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now) to find out more on the time origin. 43 | ::: 44 | -------------------------------------------------------------------------------- /packages/docs/api/static-methods.md: -------------------------------------------------------------------------------- 1 | # Static methods 2 | 3 | ## `$register(nameOrSelector?: string)` 4 | 5 | Use the `$register` method to instantiate a class on each element matching the given component's name or CSS selector. This methods uses the [child component resolution](#components) to find components. 6 | 7 | **Parameters** 8 | 9 | - `nameOrSelector` (`string`): the name of the component or a CSS selector 10 | 11 | **Return value** 12 | 13 | - `Base[]`: an array of instances of the component that triggered the method 14 | 15 | ```js twoslash 16 | import { Base } from '@studiometa/js-toolkit'; 17 | 18 | class Component extends Base { 19 | static config = { 20 | name: 'Component', 21 | }; 22 | } 23 | 24 | // Will mount the Component class on `[data-component="Component"]` elements 25 | Component.$register(); 26 | 27 | // Will mount the Component class on `[data-component="Foo"]` elements 28 | Component.$register('Foo'); 29 | 30 | // Will look the component class on `.my-component` elements 31 | Component.$register('.my-component'); 32 | ``` 33 | -------------------------------------------------------------------------------- /packages/docs/assets/events-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/studiometa/js-toolkit/b9e34e1581ba0366f388cc844f3c617a68c9a108/packages/docs/assets/events-diagram.png -------------------------------------------------------------------------------- /packages/docs/guide/going-further/lazy-imports.md: -------------------------------------------------------------------------------- 1 | # Lazy imports 2 | -------------------------------------------------------------------------------- /packages/docs/guide/going-further/registering-new-services.md: -------------------------------------------------------------------------------- 1 | # Registering new services 2 | -------------------------------------------------------------------------------- /packages/docs/guide/going-further/using-decorators.md: -------------------------------------------------------------------------------- 1 | # Using decorators 2 | 3 | Decorators can enhance the `Base` class capabilities, or any component extending it. 4 | 5 | :::tip 6 | Find all the natively available decorators and their API details in the [Decorators](/api/decorators/) section of the API Reference. 7 | ::: 8 | -------------------------------------------------------------------------------- /packages/docs/guide/recipes/counter-component/Counter.html: -------------------------------------------------------------------------------- 1 |
2 | 7 |
10 | 0 11 |
12 | 17 |
18 | -------------------------------------------------------------------------------- /packages/docs/guide/recipes/counter-component/Counter.js: -------------------------------------------------------------------------------- 1 | import { Base } from '@studiometa/js-toolkit'; 2 | 3 | export default class Counter extends Base { 4 | static config = { 5 | name: 'Counter', 6 | refs: ['add', 'remove', 'counter'], 7 | }; 8 | 9 | get count() { 10 | return Number(this.$refs.counter.innerHTML); 11 | } 12 | 13 | set count(value) { 14 | this.$refs.counter.innerHTML = value; 15 | } 16 | 17 | onAddClick() { 18 | this.count += 1; 19 | } 20 | 21 | onRemoveClick() { 22 | this.count -= 1; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/docs/guide/recipes/counter-component/index.md: -------------------------------------------------------------------------------- 1 | # Counter component 2 | 3 | Find below the JavaScript and HTML implementation of a counter component. 4 | 5 | 30 | 31 |
32 | 33 | ::: code-group 34 | 35 | <<< ./Counter.js 36 | 37 | <<< ./Counter.html 38 | 39 | ```js [app.js] 40 | import { Base, createApp } from '@studiometa/js-toolkit'; 41 | import Counter from './Counter.js'; 42 | 43 | class App extends Base { 44 | static config = { 45 | name: 'App', 46 | components: { 47 | Counter, 48 | }, 49 | }; 50 | } 51 | 52 | export default createApp(App); 53 | ``` 54 | 55 | ::: 56 | -------------------------------------------------------------------------------- /packages/docs/guide/recipes/teleport-refs/Modal.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
Content.
4 |
5 | -------------------------------------------------------------------------------- /packages/docs/guide/recipes/teleport-refs/index.md: -------------------------------------------------------------------------------- 1 | # Teleport refs 2 | 3 | If a component teleports refs outside of its scope, these refs will not be found anymore and will not be accessible by using `this.$refs.`. 4 | 5 | We can make sure that the moved refs are still accessible by saving the original ones before teleporting them and overwriting the `$refs` getter in the component. 6 | 7 | ### Modal component example 8 | 9 | 19 | 20 | ::: code-group 21 | 22 | <<< ./Modal.js 23 | 24 | <<< ./Modal.html 25 | 26 | ::: 27 | -------------------------------------------------------------------------------- /packages/docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | sidebar: false 4 | title: A data-attributes driven micro-framework 5 | description: The JS Toolkit by Studio Meta is a JavaScript data-attributes driven micro-framework shipped with plenty of useful utility functions to boost your project. 6 | hero: 7 | name: '@studiometa/js-toolkit' 8 | text: A data-attributes driven micro framework 9 | tagline: Write JavaScript components as classes and bind them to the DOM with data-attributes 10 | actions: 11 | - theme: brand 12 | text: Get Started 13 | link: /guide/ 14 | - theme: alt 15 | text: View on GitHub 16 | link: https://github.com/studiometa/js-toolkit 17 | features: 18 | - title: A micro-framework 19 | icon: 🔧 20 | details: An abstract class to help you write small and efficient JavaScript classes as well as orchestrate them. 21 | - title: Useful services 22 | icon: ⚡ 23 | details: Services will help you implement common tasks by abstracting their tedious parts. 24 | - title: Plenty of utilities 25 | icon: 📦 26 | details: Functions to help you manipulate the DOM, use math calculations, use the history API and more. 27 | --- 28 | -------------------------------------------------------------------------------- /packages/docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@studiometa/js-toolkit-docs", 3 | "version": "3.0.4", 4 | "type": "module", 5 | "private": true, 6 | "scripts": { 7 | "dev": "vitepress dev .", 8 | "build": "vitepress build .", 9 | "preview": "vitepress preview ." 10 | }, 11 | "devDependencies": { 12 | "@shikijs/vitepress-twoslash": "3.4.2", 13 | "@studiometa/tailwind-config": "2.1.0", 14 | "tailwindcss": "3.4.17", 15 | "vitepress": "1.6.3", 16 | "vitepress-plugin-llms": "1.1.4" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/docs/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/docs/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/studiometa/js-toolkit/b9e34e1581ba0366f388cc844f3c617a68c9a108/packages/docs/public/logo.png -------------------------------------------------------------------------------- /packages/docs/utils/Queue.md: -------------------------------------------------------------------------------- 1 | # Queue 2 | 3 | Create a class instance to dispatch functions in a queue. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { Queue } from '@studiometa/js-toolkit/utils'; 9 | 10 | const queue = new Queue(); 11 | queue.add(() => console.log('1')); 12 | queue.add(() => console.log('2')); 13 | ``` 14 | 15 | ### Parameters 16 | 17 | - `concurrency` (`Number`): the number of tasks to execute at the same time, defaults to `10` 18 | - `waiter` (`(cb: (...args:unknown[]) => unknown) => unknown`): a scheduler function for the next batch execution, defaults to an immediately invoked function `(cb) => cb()` 19 | -------------------------------------------------------------------------------- /packages/docs/utils/SmartQueue.md: -------------------------------------------------------------------------------- 1 | # SmartQueue 2 | 3 | Manage tasks in a smart queue. Tasks will be automatically batch-called inside a single event loop lasting no more than 50ms (duration of what is considered a long task). 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { SmartQueue } from '@studiometa/js-toolkit/utils'; 9 | 10 | const queue = new SmartQueue(); 11 | queue.add(() => console.log('1')); 12 | queue.add(() => console.log('2')); 13 | ``` 14 | 15 | ### Parameters 16 | 17 | This class does not accept any parameter. 18 | -------------------------------------------------------------------------------- /packages/docs/utils/cache.md: -------------------------------------------------------------------------------- 1 | # cache 2 | 3 | Cache the result of a function with a single key or a list of keys in nested maps. 4 | 5 | ::: warning 6 | The `cache` function is global, meaning that keys are share across all `cache` calls in different files for the same runtime. Make sure to use a local context variable as the first key to avoid sharing cache between unrelated contexts. 7 | ::: 8 | 9 | ## Usage 10 | 11 | ```js twoslash 12 | import { cache } from '@studiometa/js-toolkit/utils'; 13 | 14 | const keys = [document.body, Symbol('key')]; 15 | const callback = () => performance.now(); 16 | 17 | console.log(cache(keys, callback)); // 100 18 | console.log(cache(keys, callback) === cache(keys, callback)); // true 19 | 20 | setTimeout(() => { 21 | console.log(cache(keys, callback)); // 100 22 | }, 100); 23 | 24 | console.log(cache('key', callback)); // 200 25 | console.log(cache('key', callback) === cache('key', callback)); // true 26 | 27 | setTimeout(() => { 28 | console.log(cache('key', callback)); // 200 29 | }, 100); 30 | ``` 31 | 32 | ### Parameters 33 | 34 | - `keys` (`any | Array`): a list of keys to be used to cache the result of the callback 35 | - `callback` (`() => any`): the callback executed to retrieve the value to cache 36 | 37 | ### Return value 38 | 39 | The value returned by the `callback` function given as parameter. 40 | -------------------------------------------------------------------------------- /packages/docs/utils/collision/boundingRectToCircle.md: -------------------------------------------------------------------------------- 1 | # boundingRectToCircle 2 | 3 | Convert an object describing a rectangle to an object describing a circle. 4 | 5 | ::: tip 6 | To enable it for non-square elements, use the second parameter to force the calculation. 7 | ::: 8 | 9 | ## Usage 10 | 11 | ```js twoslash 12 | import { boundingRectToCircle } from '@studiometa/js-toolkit/utils'; 13 | 14 | const clientRect1 = { x: 0, y: 0, width: 100, height: 100 }; 15 | boundingRectToCircle(clientRect1); // { x: 50, y: 50, radius: 50 } 16 | 17 | const clientRect2 = element.getBoundingClientRect(); 18 | boundingRectToCircle(clientRect2); 19 | ``` 20 | 21 | ### Parameters 22 | 23 | - `rect` (`{ x: number, y: number, width: number, height: number }`): the dimension of the rectangle to transform 24 | - `force` (`boolean`) : force the function to work with non-square input, defaults to `false` 25 | 26 | ### Return value 27 | 28 | - `{ x: number, y: number, radius: number }`: an object describing a circle 29 | -------------------------------------------------------------------------------- /packages/docs/utils/collision/collideCircleCircle.md: -------------------------------------------------------------------------------- 1 | # collideCircleCircle 2 | 3 | Test if two circles collide with one another. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { collideCircleCircle } from '@studiometa/js-toolkit/utils'; 9 | 10 | const circle1 = { 11 | x: 40, 12 | y: 40, 13 | radius: 40, 14 | }; 15 | 16 | const circle2 = { 17 | x: 100, 18 | y: 100, 19 | radius: 60, 20 | }; 21 | 22 | collideCircleCircle(circle1, circle2); // true 23 | ``` 24 | 25 | ### Parameters 26 | 27 | - `circle1` (`{ x: number, y: number, radius: number }`): the first circle dimensions and position 28 | - `circle2` (`{ x: number, y: number, radius: number }`): the second circle dimensions and position 29 | 30 | ### Return value 31 | 32 | - `boolean`: wether the circles are colliding or not 33 | -------------------------------------------------------------------------------- /packages/docs/utils/collision/collideCircleRect.md: -------------------------------------------------------------------------------- 1 | # collideCircleRect 2 | 3 | Test if a circle collides with a rectangle. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { collideCircleRect } from '@studiometa/js-toolkit/utils'; 9 | 10 | const circle = { 11 | x: 40, 12 | y: 40, 13 | radius: 40, 14 | }; 15 | 16 | const rect = { 17 | x: 40, 18 | y: 40, 19 | width: 60, 20 | height: 60, 21 | }; 22 | 23 | collideCircleRect(circle, rect); // true 24 | ``` 25 | 26 | ### Parameters 27 | 28 | - `circle` (`{ x: number, y: number, radius: number }`): the circle dimensions and position 29 | - `rect` (`{ x: number, y: number, width: number, height: number }`): the rectangle dimensions and position 30 | 31 | ### Return value 32 | 33 | - `boolean`: wether the circle and rectangle are colliding 34 | -------------------------------------------------------------------------------- /packages/docs/utils/collision/collidePointCircle.md: -------------------------------------------------------------------------------- 1 | # collidePointCircle 2 | 3 | Test if a point is inside a circle. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { collidePointCircle } from '@studiometa/js-toolkit/utils'; 9 | 10 | const point = { 11 | x: 25, 12 | y: 25, 13 | }; 14 | 15 | const circle = { 16 | x: 40, 17 | y: 40, 18 | radius: 40, 19 | }; 20 | 21 | collidePointCircle(point, circle); // true 22 | ``` 23 | 24 | ### Parameters 25 | 26 | - `point` (`{ x: number, y: number }`): the point position 27 | - `circle` (`{ x: number, y: number, radius: number }`): the circle dimensions and position 28 | 29 | ### Return value 30 | 31 | - `boolean`: wether the point and circle are colliding or not 32 | -------------------------------------------------------------------------------- /packages/docs/utils/collision/collidePointRect.md: -------------------------------------------------------------------------------- 1 | # collidePointRect 2 | 3 | Test if a point collides with a rectangle. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { collidePointRect } from '@studiometa/js-toolkit/utils'; 9 | 10 | const point = { 11 | x: 0, 12 | y: 0, 13 | }; 14 | 15 | const rect = { 16 | x: 0, 17 | y: 0, 18 | width: 20, 19 | height: 20, 20 | }; 21 | 22 | collidePointRect(point, rect); // true 23 | ``` 24 | 25 | ### Parameters 26 | 27 | - `point` (`{ x: number, y: number }`): the point's position 28 | - `rect` (`{ x: number, y: number, width: number, height: number }`): the rectangle's dimensions and position 29 | 30 | ### Return value 31 | 32 | - `boolean`: wether the point and rectangle are colliding or not 33 | -------------------------------------------------------------------------------- /packages/docs/utils/collision/collideRectRect.md: -------------------------------------------------------------------------------- 1 | # collideRectRect 2 | 3 | Test if a rectangle collides with another rectangle. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { collideRectRect } from '@studiometa/js-toolkit/utils'; 9 | 10 | const rect1 = { 11 | x: 0, 12 | y: 0, 13 | width: 20, 14 | height: 20, 15 | }; 16 | 17 | const rect2 = { 18 | x: 0, 19 | y: 0, 20 | width: 20, 21 | height: 20, 22 | }; 23 | 24 | collideRectRect(rect1, rect2); // true 25 | ``` 26 | 27 | ### Parameters 28 | 29 | - `rect1` (`{ x: number, y: number, width: number, height: number }`): the first rectangle's dimensions and position 30 | - `rect2` (`{ x: number, y: number, width: number, height: number }`): the second rectangle's dimensions and position 31 | 32 | ### Return value 33 | 34 | - `boolean`: wether the rectangles are colliding or not 35 | -------------------------------------------------------------------------------- /packages/docs/utils/collision/index.md: -------------------------------------------------------------------------------- 1 | # Collision 2 | 3 | Collision utils can be used to test if two DOM elements are overlapping. 4 | 5 | - [boundingRectToCircle](./boundingRectToCircle.html) 6 | - [collideCircleCircle](./collideCircleCircle.html) 7 | - [collideCircleRect](./collideCircleRect.html) 8 | - [collidePointCircle](./collidePointCircle.html) 9 | - [collidePointRect](./collidePointRect.html) 10 | - [collideRectRect](./collideRectRect.html) 11 | 12 | Inspired by http://www.jeffreythompson.org/collision-detection/. 13 | -------------------------------------------------------------------------------- /packages/docs/utils/createElement.md: -------------------------------------------------------------------------------- 1 | # createElement 2 | 3 | A function for creating HTML elements. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { createElement } from '@studiometa/js-toolkit/utils'; 9 | 10 | const div = createElement('div', 'hello world'); 11 | const main = createElement('main', { id: 'main' }, div); 12 | 13 | console.log(main.outerHTML); //
hello world
14 | 15 | // camelCase attribute keys are converted to dash-case 16 | createElement('div', { dataOption: 'foo' }); //
17 | 18 | // if the second parameter is a string, a Node or and array, it is treated as chidlren 19 | createElement('div', 'lorem ipsum'); //
lorem ipsum
20 | ``` 21 | 22 | ### Parameters 23 | 24 | - `tag` (`string`): the name of the element to create, defaults to `div` 25 | - `attributes` (`Record`): attributes for the created element, camelCase keys are transformed to dash-case 26 | - `children` (`string | Node | Array`): child or children to append to the element 27 | 28 | ### Return value 29 | 30 | An element with the given attributes and children. 31 | -------------------------------------------------------------------------------- /packages/docs/utils/css/addClass.md: -------------------------------------------------------------------------------- 1 | # addClass 2 | 3 | This method will add one or more classes to an HTML element. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { addClass } from '@studiometa/js-toolkit/utils'; 9 | 10 | addClass(document.body, 'is-locked'); 11 | addClass([document.body, document.documentElement], 'is-locked'); 12 | addClass(document.querySelectorAll('.should-be-locked'), 'is-locked'); 13 | addClass(document.body, ['is-locked', 'has-pointer']); 14 | ``` 15 | 16 | ### Parameters 17 | 18 | - `element` (`Element | Element[] | NodeListOf`): the target HTML element 19 | - `classes` (`string | string[]`): CSS classes to add 20 | 21 | ### Return value 22 | 23 | This function does not return anything. 24 | -------------------------------------------------------------------------------- /packages/docs/utils/css/addStyle.md: -------------------------------------------------------------------------------- 1 | # addStyle 2 | 3 | This method will add styles to an HTML element. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { addStyle } from '@studiometa/js-toolkit/utils'; 9 | 10 | addStyle(document.body, { display: 'block' }); 11 | addStyle([document.body, document.documentElement], { display: 'block' }); 12 | addStyle(document.querySelectorAll('div'), { display: 'block' }); 13 | ``` 14 | 15 | ### Parameters 16 | 17 | - `element` (`HTMLElement | HTMLElement[] | NodeListOf`): the target HTML element 18 | - `styles` (`Partial`): CSS styles to add 19 | 20 | ### Return value 21 | 22 | This function does not return anything. 23 | -------------------------------------------------------------------------------- /packages/docs/utils/css/getOffsetSizes.md: -------------------------------------------------------------------------------- 1 | # getOffsetSizes 2 | 3 | This function will return a `DOMRect` like object representing the position of the given element without its CSS transforms. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { getOffsetSizes } from '@studiometa/js-toolkit/utils'; 9 | 10 | const sizes = getOffsetSizes(document.body); 11 | ``` 12 | 13 | ### Parameters 14 | 15 | - `element` (`HTMLElement`): the scale on the x axis 16 | 17 | ### Return value 18 | 19 | An object with the same properties as a `DOMRect`: 20 | 21 | ```ts 22 | { 23 | x: number; 24 | y: number; 25 | width: number; 26 | height: number; 27 | top: number; 28 | right number; 29 | bottom: number; 30 | left: number; 31 | } 32 | ``` 33 | -------------------------------------------------------------------------------- /packages/docs/utils/css/matrix.md: -------------------------------------------------------------------------------- 1 | # matrix 2 | 3 | This method will format a matrix CSS transform function. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { matrix } from '@studiometa/js-toolkit/utils'; 9 | 10 | document.body.style.transform = matrix({ scaleX: 0.5, scaleY: 0.5 }); 11 | // matrix(0.5, 0, 0, 0.5, 0, 0) 12 | ``` 13 | 14 | ### Parameters 15 | 16 | - `options.scaleX?` (`number`): the scale on the x axis 17 | - `options.scaleY?` (`number`): the scale on the y axis 18 | - `options.skewX?` (`number`): the skew on the x axis 19 | - `options.skewY?` (`number`): the skew on the y axis 20 | - `options.translateX?` (`number`): the translate on the x axis 21 | - `options.translateY?` (`number`): the translate on the y axis 22 | 23 | ### Return value 24 | 25 | - `String`: a formatted CSS matrix transform 26 | -------------------------------------------------------------------------------- /packages/docs/utils/css/removeClass.md: -------------------------------------------------------------------------------- 1 | # removeClass 2 | 3 | This method will remove one or more classes to an HTML element. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { removeClass } from '@studiometa/js-toolkit/utils'; 9 | 10 | removeClass(document.body, 'is-locked'); 11 | addClass([document.body, document.documentElement], 'is-locked'); 12 | removeClass(document.querySelectorAll('.is-locked'), 'is-locked'); 13 | removeClass(document.body, ['is-locked', 'has-pointer']); 14 | ``` 15 | 16 | ### Parameters 17 | 18 | - `element` (`Element | Element | NodeListOf`): the target HTML element 19 | - `classes` (`string | string[]`): CSS classes to remove 20 | 21 | ### Return value 22 | 23 | This function does not return anything. 24 | -------------------------------------------------------------------------------- /packages/docs/utils/css/removeStyle.md: -------------------------------------------------------------------------------- 1 | # removeStyle 2 | 3 | This method will remove styles from an HTML element. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { removeStyle } from '@studiometa/js-toolkit/utils'; 9 | 10 | removeStyle(document.body, { display: 'block' }); 11 | addStyle([document.body, document.documentElement], { display: 'block' }); 12 | addStyle(document.querySelectorAll('div'), { display: 'block' }); 13 | ``` 14 | 15 | ### Parameters 16 | 17 | - `element` (`HTMLElement | HTMLElement[] | NodeListOf`): the target HTML element 18 | - `styles` (`Partial`): CSS styles to remove 19 | 20 | ### Return value 21 | 22 | This function does not return anything. 23 | -------------------------------------------------------------------------------- /packages/docs/utils/css/toggleClass.md: -------------------------------------------------------------------------------- 1 | # toggleClass 2 | 3 | This method will toggle one or more classes to an HTML element. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { toggleClass } from '@studiometa/js-toolkit/utils'; 9 | 10 | toggleClass(document.body, 'is-locked'); 11 | toggleClass(document.body, 'is-locked', true); 12 | toggleClass([document.body, document.documentElement], 'is-locked', true); 13 | toggleClass(document.querySelectorAll('.should-be-locked'), 'is-locked', true); 14 | toggleClass(document.body, ['is-locked', 'has-pointer']); 15 | toggleClass(document.body, ['is-locked', 'has-pointer'], true); 16 | ``` 17 | 18 | ### Parameters 19 | 20 | - `element` (`Element | Element | NodeListOf`): the target HTML element 21 | - `classes` (`string | string[]`): CSS classes to remove 22 | - `force` (`boolean`): turns the toggle into a one way-only operation, remove the classes when `false`, add the classes when `true` 23 | 24 | ### Return value 25 | 26 | This function does not return anything. 27 | -------------------------------------------------------------------------------- /packages/docs/utils/css/transform.md: -------------------------------------------------------------------------------- 1 | # transform 2 | 3 | Use this function to apply transforms to an element. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { transform } from '@studiometa/js-toolkit/utils'; 9 | 10 | transform(document.body, { 11 | x: 100, 12 | scale: 0.5, 13 | }); 14 | 15 | transform([document.body, document.documentElement], { 16 | x: 100, 17 | scale: 0.5, 18 | }); 19 | 20 | transform(document.querySelectorAll('div'), { 21 | x: 100, 22 | scale: 0.5, 23 | }); 24 | ``` 25 | 26 | ### Parameters 27 | 28 | - `element` (`HTMLElement | HTMLElement[] | NodeListOf`): the target HTML element 29 | - `props` ([`TransformProps`](#types)): an object describing the transformations to apply 30 | 31 | ### Return value 32 | 33 | A `string` representing the transform property value. 34 | 35 | ### Types 36 | 37 | ```ts 38 | interface TransformProps { 39 | x?: number; 40 | y?: number; 41 | z?: number; 42 | rotate?: number; 43 | rotateX?: number; 44 | rotateY?: number; 45 | rotateZ?: number; 46 | scale?: number; 47 | scaleX?: number; 48 | scaleY?: number; 49 | scaleZ?: number; 50 | skew?: number; 51 | skewX?: number; 52 | skewY?: number; 53 | } 54 | ``` 55 | -------------------------------------------------------------------------------- /packages/docs/utils/debounce.md: -------------------------------------------------------------------------------- 1 | # debounce 2 | 3 | Execute a function until it stops being called. 4 | 5 | ::: tip 6 | You can read [Debounce vs throttle](https://redd.one/blog/debounce-vs-throttle) if the difference between the `debounce` and `throttle` methods is not clear for you. 7 | ::: 8 | 9 | ## Usage 10 | 11 | ```js twoslash 12 | import { debounce } from '@studiometa/js-toolkit/utils'; 13 | 14 | const debouncedFn = debounce(() => { 15 | console.log('Hello 👋'); 16 | }, 500); 17 | 18 | debouncedFn(); // Hello 👋 19 | ``` 20 | 21 | ### Parameters 22 | 23 | - `fn` (`Function`): the function to execute 24 | - `delay` (`Number`): the delay in milliseconds 25 | 26 | ### Return value 27 | 28 | A new function which will execute the given function only when it is not called in the given delay. 29 | -------------------------------------------------------------------------------- /packages/docs/utils/domScheduler.md: -------------------------------------------------------------------------------- 1 | # domScheduler 2 | 3 | A scheduler created with the [`useScheduler` function](./useScheduler.md) and made to schedule reads and writes from the DOM to avoid layout thrashing. Find out more by reading this [web.dev article](https://web.dev/avoid-large-complex-layouts-and-layout-thrashing). 4 | 5 | This scheduler has 3 stages: `read`, `write` and `afterWrite`. 6 | 7 | ## Usage 8 | 9 | ```js twoslash 10 | import { domScheduler } from '@studiometa/js-toolkit/utils'; 11 | 12 | domScheduler.read(() => { 13 | const size = document.body.offsetWidth; 14 | 15 | domScheduler.write(() => { 16 | document.body.style.transform = `translateX(${size * 0.1}px)`; 17 | }); 18 | 19 | domScheduler.afterWrite(() => { 20 | console.log('transform has been applied!'); 21 | }); 22 | }); 23 | ``` 24 | -------------------------------------------------------------------------------- /packages/docs/utils/history/index.md: -------------------------------------------------------------------------------- 1 | # History 2 | 3 | Helpers to interact with the [History API](http://developer.mozilla.org/en-US/docs/Web/API/History_API). The two `historyPush` and `historyReplace` functions allow you to replace parts of the current URL with the History API by merging the given options with the current URL, avoiding the need for complex merging strategies. 4 | 5 | - [historyPush](./historyPush.html) 6 | - [historyReplace](./historyReplace.html) 7 | - [objectToURLSearchParams](./objectToURLSearchParams.html) 8 | -------------------------------------------------------------------------------- /packages/docs/utils/history/objectToURLSearchParams.md: -------------------------------------------------------------------------------- 1 | # objectToURLSearchParams 2 | 3 | Transform an object into an URLSearchParams instance to match with PHP implementation of the `$_GET` variable. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { objectToURLSearchParams } from '@studiometa/js-toolkit/utils'; 9 | 10 | const params = { 11 | str: 'str', 12 | num: 1, 13 | bool1: true, 14 | bool2: false, 15 | null: null, 16 | undefined: undefined, 17 | arr: [1, 2], 18 | obj: { 19 | one: 1, 20 | two: 2, 21 | }, 22 | }; 23 | 24 | objectToURLSearchParams(params).toString(); 25 | // str=str&num=1&bool1=true&bool2=false&arr[0]=1&arr[1]=2&obj[one]=1&obj[two]=2 26 | ``` 27 | 28 | ### Parameters 29 | 30 | - `obj` (`Record`): the object to convert 31 | - `initialSearch` (`URLSearchParams`): the initial `URLSearchParams` instance to use, defaults to `window.location.search` 32 | 33 | ### Return value 34 | 35 | - `URLSearchParams`: an instanc of URLSearchParams with the object's values as entries 36 | -------------------------------------------------------------------------------- /packages/docs/utils/index.md: -------------------------------------------------------------------------------- 1 | # Utils 2 | 3 | Find below functions that can help you in your projects. 4 | 5 | 11 | 12 | 23 | -------------------------------------------------------------------------------- /packages/docs/utils/is/index.md: -------------------------------------------------------------------------------- 1 | # Value utils 2 | 3 | Quickly test values. 4 | 5 | - [isDev](./isDev.html) 6 | - [isFunction](./isFunction.html) 7 | - [isDefined](./isDefined.html) 8 | - [isString](./isString.html) 9 | - [isObject](./isObject.html) 10 | - [isNumber](./isNumber.html) 11 | - [isBoolean](./isBoolean.html) 12 | - [isArray](./isArray.html) 13 | -------------------------------------------------------------------------------- /packages/docs/utils/is/isArray.md: -------------------------------------------------------------------------------- 1 | # isArray 2 | 3 | Test if a value is an array. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { isArray } from '@studiometa/js-toolkit/utils'; 9 | 10 | isArray([]); // true 11 | isArray(1); // false 12 | ``` 13 | 14 | ### Parameters 15 | 16 | - `value` (`any`): the value to test 17 | 18 | ### Return value 19 | 20 | Returns `true` if the given value is an array, `false` otherwise. 21 | -------------------------------------------------------------------------------- /packages/docs/utils/is/isBoolean.md: -------------------------------------------------------------------------------- 1 | # isBoolean 2 | 3 | Test if a value is a boolean. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { isBoolean } from '@studiometa/js-toolkit/utils'; 9 | 10 | isBoolean(true); // true 11 | isBoolean('foo'); // false 12 | ``` 13 | 14 | ### Parameters 15 | 16 | - `value` (`any`): the value to test 17 | 18 | ### Return value 19 | 20 | Returns `true` if the given value is a boolean, `false` otherwise. 21 | -------------------------------------------------------------------------------- /packages/docs/utils/is/isDefined.md: -------------------------------------------------------------------------------- 1 | # isDefined 2 | 3 | Test if a value is defined. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { isDefined } from '@studiometa/js-toolkit/utils'; 9 | 10 | isDefined('foo'); // true 11 | isDefined(123); // true 12 | isDefined(null); // true 13 | isDefined(); // false 14 | ``` 15 | 16 | ### Parameters 17 | 18 | - `value` (`any`): the value to test 19 | 20 | ### Return value 21 | 22 | Returns `true` if the given value is not `undefined`, `false` otherwise. 23 | -------------------------------------------------------------------------------- /packages/docs/utils/is/isDev.md: -------------------------------------------------------------------------------- 1 | # isDev 2 | 3 | A constant which is `true` in dev mode (if a global `__DEV__` variable is set and truthy). 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { isDev } from '@studiometa/js-toolkit/utils'; 9 | 10 | if (isDev) { 11 | console.log('We are in a development environment.'); 12 | } 13 | ``` 14 | -------------------------------------------------------------------------------- /packages/docs/utils/is/isEmpty.md: -------------------------------------------------------------------------------- 1 | # isEmpty 2 | 3 | Test if a value is empty. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { isEmpty } from '@studiometa/js-toolkit/utils'; 9 | 10 | isEmpty(); // true 11 | isEmpty(null); // true 12 | isEmpty(''); // true 13 | isEmpty([]); // true 14 | isEmpty({}); // true 15 | 16 | isEmpty(0); // false 17 | isEmpty(1); // false 18 | isEmpty([1, 2]); // false 19 | isEmpty({ foo: 'foo' }); // false 20 | 21 | class Foo {} 22 | isEmpty(new Foo()); // false 23 | ``` 24 | 25 | ### Parameters 26 | 27 | - `value` (`any`): the value to test 28 | 29 | ### Return value 30 | 31 | Returns `true` if the given value is considered empty, `false` otherwise. 32 | -------------------------------------------------------------------------------- /packages/docs/utils/is/isEmptyString.md: -------------------------------------------------------------------------------- 1 | # isEmptyString 2 | 3 | Test if a value is an empty string. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { isEmptyString } from '@studiometa/js-toolkit/utils'; 9 | 10 | isEmptyString(''); // true 11 | isEmptyString('foo'); // false 12 | isEmptyString(); // false 13 | isEmptyString([]); // false 14 | ``` 15 | 16 | ### Parameters 17 | 18 | - `value` (`any`): the value to test 19 | 20 | ### Return value 21 | 22 | Returns `true` if the given value is an empty string, `false` otherwise. 23 | -------------------------------------------------------------------------------- /packages/docs/utils/is/isFunction.md: -------------------------------------------------------------------------------- 1 | # isFunction 2 | 3 | Test if a value is a function. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { isFunction } from '@studiometa/js-toolkit/utils'; 9 | 10 | isFunction(() => 'foo'); // true 11 | isFunction('foo'); // false 12 | ``` 13 | 14 | ### Parameters 15 | 16 | - `value` (`any`): the value to test 17 | 18 | ### Return value 19 | 20 | Returns `true` if the given value is a function, `false` otherwise. 21 | -------------------------------------------------------------------------------- /packages/docs/utils/is/isNull.md: -------------------------------------------------------------------------------- 1 | # isNull 2 | 3 | Test if a value is `null`. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { isNull } from '@studiometa/js-toolkit/utils'; 9 | 10 | isNull(null); // true 11 | isNull(); // false 12 | isNull(''); // false 13 | ``` 14 | 15 | ### Parameters 16 | 17 | - `value` (`any`): the value to test 18 | 19 | ### Return value 20 | 21 | Returns `true` if the given value is `null`, `false` otherwise. 22 | -------------------------------------------------------------------------------- /packages/docs/utils/is/isNumber.md: -------------------------------------------------------------------------------- 1 | # isNumber 2 | 3 | Test if a value is a number. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { isNumber } from '@studiometa/js-toolkit/utils'; 9 | 10 | isNumber(10); // true 11 | isNumber('foo'); // false 12 | ``` 13 | 14 | ### Parameters 15 | 16 | - `value` (`any`): the value to test 17 | 18 | ### Return value 19 | 20 | Returns `true` if the given value is a number, `false` otherwise. 21 | -------------------------------------------------------------------------------- /packages/docs/utils/is/isObject.md: -------------------------------------------------------------------------------- 1 | # isObject 2 | 3 | Test if a value is an object. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { isObject } from '@studiometa/js-toolkit/utils'; 9 | 10 | isObject({}); // true 11 | isObject('foo'); // false 12 | ``` 13 | 14 | ### Parameters 15 | 16 | - `value` (`any`): the value to test 17 | 18 | ### Return value 19 | 20 | Returns `true` if the given value is an object, `false` otherwise. 21 | -------------------------------------------------------------------------------- /packages/docs/utils/is/isString.md: -------------------------------------------------------------------------------- 1 | # isString 2 | 3 | Test if a value is a string. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { isString } from '@studiometa/js-toolkit/utils'; 9 | 10 | isString('foo'); // true 11 | isString(123); // false 12 | ``` 13 | 14 | ### Parameters 15 | 16 | - `value` (`any`): the value to test 17 | 18 | ### Return value 19 | 20 | Returns `true` if the given value is a string, `false` otherwise. 21 | -------------------------------------------------------------------------------- /packages/docs/utils/keyCodes.md: -------------------------------------------------------------------------------- 1 | # keyCodes 2 | 3 | Map of keyboard keycodes and their human readable name. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { keyCodes } from '@studiometa/js-toolkit/utils'; 9 | 10 | document.addEventListener('keydown', (e) => { 11 | if (e.keyCode === keyCodes.ESC) { 12 | // do something when the user presses the escape key... 13 | } 14 | }); 15 | ``` 16 | -------------------------------------------------------------------------------- /packages/docs/utils/math/clamp.md: -------------------------------------------------------------------------------- 1 | # clamp 2 | 3 | Clamp a value in a given range. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { clamp } from '@studiometa/js-toolkit/utils'; 9 | 10 | clamp(5, 0, 10); // 5 11 | clamp(15, 0, 10); // 10 12 | clamp(-5, 0, 10); // 0 13 | ``` 14 | 15 | ### Parameters 16 | 17 | - `value` (`number`): The value to clamp. 18 | - `min` (`number`): The range minimum value. 19 | - `max` (`number`): The rande maximum value. 20 | 21 | ### Return value 22 | 23 | - `number`: The clamped value. 24 | -------------------------------------------------------------------------------- /packages/docs/utils/math/clamp01.md: -------------------------------------------------------------------------------- 1 | # clamp01 2 | 3 | Clamp a value in the 0–1 range. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { clamp01 } from '@studiometa/js-toolkit/utils'; 9 | 10 | clamp01(0.5); // 0.5 11 | clamp01(1.5); // 1 12 | ``` 13 | 14 | ### Parameters 15 | 16 | - `value` (`number`): The value to clamp between `0` and `1`. 17 | 18 | ### Return value 19 | 20 | - `number`: The clamped between `0` and `1` value. 21 | -------------------------------------------------------------------------------- /packages/docs/utils/math/createEaseInOut.md: -------------------------------------------------------------------------------- 1 | # createEaseInOut 2 | 3 | Generate an in-out easing function given an ease-in function. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { easeInCirc, createEaseInOut } from '@studiometa/js-toolkit/utils'; 9 | 10 | const easeIntOutCirc = createEaseInOut(easeInCirc); 11 | ``` 12 | 13 | ### Parameters 14 | 15 | - `easeIn` (`(progress: number) => number`): an easing function to reverse 16 | 17 | ### Return value 18 | 19 | - `(progress: number) => number`: the reversed easing function 20 | -------------------------------------------------------------------------------- /packages/docs/utils/math/createEaseOut.md: -------------------------------------------------------------------------------- 1 | # createEaseOut 2 | 3 | Generate a reversed easing function from a given easing function. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { easeInCirc, createEaseOut } from '@studiometa/js-toolkit/utils'; 9 | 10 | const easeOutCirc = createEaseOut(easeInCirc); 11 | ``` 12 | 13 | ### Parameters 14 | 15 | - `easeIn` (`(progress: number) => number`): an easing function to reverse 16 | 17 | ### Return value 18 | 19 | - `(progress: number) => number`: the reversed easing function 20 | -------------------------------------------------------------------------------- /packages/docs/utils/math/createRange.md: -------------------------------------------------------------------------------- 1 | # createRange 2 | 3 | Create an array of number between a given range and incremental step. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { createRange } from '@studiometa/js-toolkit/utils'; 9 | 10 | createRange(0, 10, 2); // [0, 2, 4, 6, 8, 10] 11 | ``` 12 | 13 | ### Parameters 14 | 15 | - `min` (`number`): the minimum value for the range 16 | - `max` (`number`): the maximum value for the rance 17 | - `step` (`number`): the value to increment each number 18 | 19 | ### Return value 20 | 21 | - `number[]`: an array of numbers 22 | -------------------------------------------------------------------------------- /packages/docs/utils/math/damp.md: -------------------------------------------------------------------------------- 1 | # damp 2 | 3 | Get the next damped value for a given target and factor. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { damp } from '@studiometa/js-toolkit/utils'; 9 | 10 | const targetValue = 10; 11 | const factor = 0.5; 12 | const precision = 0.1; 13 | let currentValue = 5; 14 | 15 | currentValue = damp(targetValue, currentValue, factor, precision); // 7.5 16 | currentValue = damp(targetValue, currentValue, factor, precision); // 8.75 17 | currentValue = damp(targetValue, currentValue, factor, precision); // 9.375 18 | currentValue = damp(targetValue, currentValue, factor, precision); // 9.6875 19 | currentValue = damp(targetValue, currentValue, factor, precision); // 9.84375 20 | currentValue = damp(targetValue, currentValue, factor, precision); // 9.921875 21 | currentValue = damp(targetValue, currentValue, factor, precision); // 10 22 | ``` 23 | 24 | ### Parameters 25 | 26 | - `targetValue` (`number`): The final value. 27 | - `currentValue` (`number`): The current value. 28 | - `factor` (`number`): The factor used to reach the target value, defaults to `0.5`. 29 | - `precision` (`number`): The factor used to reach the target value, defaults to `0.01`. 30 | 31 | ### Return value 32 | 33 | - `number`: The next damped value 34 | -------------------------------------------------------------------------------- /packages/docs/utils/math/inertiaFinalValue.md: -------------------------------------------------------------------------------- 1 | # inertiaFinalValue 2 | 3 | Calculate the final value for an inertia effect given inital value and velocity. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { inertiaFinalValue } from '@studiometa/js-toolkit/utils'; 9 | 10 | inertiaFinalValue(0, 10); 11 | inertiaFinalValue(0, 10, 0.5); 12 | ``` 13 | 14 | ### Parameters 15 | 16 | - `initialValue` (`number`): The initial value. 17 | - `initialDelta` (`number`): The velocity. 18 | - `dampFactor?` (`number`): The speed to reach the target value, defaults to `0.85`. 19 | 20 | ### Return value 21 | 22 | - `number`: the final value of the inertia logic 23 | -------------------------------------------------------------------------------- /packages/docs/utils/math/lerp.md: -------------------------------------------------------------------------------- 1 | # lerp 2 | 3 | Interpolate a ratio between a given interval. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { lerp } from '@studiometa/js-toolkit/utils'; 9 | 10 | lerp(0, 10, 0.5); // 5 11 | ``` 12 | 13 | ### Parameters 14 | 15 | - `min` (`number`): The interval minimum value. 16 | - `max` (`number`): The interval maximum value. 17 | - `ratio` (`number`): The ratio to get. 18 | 19 | ### Return value 20 | 21 | - `number`: The lerped value 22 | -------------------------------------------------------------------------------- /packages/docs/utils/math/map.md: -------------------------------------------------------------------------------- 1 | # map 2 | 3 | Map a value from one interval to another. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { map } from '@studiometa/js-toolkit/utils'; 9 | 10 | map(5, 0, 10, 0, 20); // 10 11 | ``` 12 | 13 | ### Parameters 14 | 15 | - `value` (`number`): The value to map. 16 | - `inputMin` (`number`): The input's minimum value. 17 | - `inputMax` (`number`): The input's maximum value. 18 | - `outputMin` (`number`): The output's minimum value. 19 | - `outputMax` (`number`): The output's maximum value. 20 | 21 | ### Return value 22 | 23 | - `number`: The input value mapped to the output range. 24 | -------------------------------------------------------------------------------- /packages/docs/utils/math/mean.md: -------------------------------------------------------------------------------- 1 | # mean 2 | 3 | Get the mean value from an array of numbers. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { mean } from '@studiometa/js-toolkit/utils'; 9 | 10 | mean([5, 0, 10]); // 5 11 | mean([20, 0, 10]); // 10 12 | mean([-10, 0, 10]); // 0 13 | ``` 14 | 15 | ### Parameters 16 | 17 | - `numbers` (`number[]`): The original values. 18 | 19 | ### Return value 20 | 21 | - `number`: The mean number. 22 | -------------------------------------------------------------------------------- /packages/docs/utils/math/round.md: -------------------------------------------------------------------------------- 1 | # round 2 | 3 | Round a number to the given decimals. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { round } from '@studiometa/js-toolkit/utils'; 9 | 10 | round(10.5); // 10 11 | round(10.546, 2); // 10.55 12 | ``` 13 | 14 | ### Parameters 15 | 16 | - `value` (`number`): The value to round. 17 | - `decimals?` (`number`): The number of decimals to round to, defaults to `0`. 18 | 19 | ### Return value 20 | 21 | - `number`: The rounded number. 22 | -------------------------------------------------------------------------------- /packages/docs/utils/memo.md: -------------------------------------------------------------------------------- 1 | # memo 2 | 3 | Like the [`memoize` function](./memoize.html), but simpler and smaller. 4 | 5 | The cache key will be generated by joining all the args together and the cache will never expire. 6 | 7 | ## Usage 8 | 9 | ```js twoslash 10 | import { memo } from '@studiometa/js-toolkit/utils'; 11 | 12 | function heavyFunction(a, b) { 13 | return a.replace(/foo/g, b); 14 | } 15 | 16 | const memoHeavyFunction = memo(heavyFunction); 17 | 18 | memoHeavyFunction('foo', 'bar'); // bar 19 | ``` 20 | 21 | ### Parameters 22 | 23 | - `fn` (`Function`): the function to cache 24 | 25 | ### Return value 26 | 27 | This function returns a new function which will cache the first function results. 28 | -------------------------------------------------------------------------------- /packages/docs/utils/memoize.md: -------------------------------------------------------------------------------- 1 | # memoize 2 | 3 | Memorize the output of a function in the memory cache. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { memoize } from '@studiometa/js-toolkit/utils'; 9 | 10 | function heavyFunction(param) { 11 | return param ** param; 12 | } 13 | 14 | const memoizedHeavyFunction = memoize(heavyFunction); 15 | 16 | memoizedHeavyFunction(2); // 4 17 | ``` 18 | 19 | ### Parameters 20 | 21 | - `fn` (`Function`): the function to memorize 22 | - `options` (`{ maxAge?: number, cacheKey?: (...args:any) => string, cache?: Map }`): options on on how to memorize the output of the function 23 | 24 | ### Return value 25 | 26 | This function returns a new function which will cache the first function results. 27 | -------------------------------------------------------------------------------- /packages/docs/utils/nextFrame.md: -------------------------------------------------------------------------------- 1 | # nextFrame 2 | 3 | Execute a given function in the next window frame. This function is a promisified version of the [`requestAnimationFrame` function](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame). 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { nextFrame } from '@studiometa/js-toolkit/utils'; 9 | 10 | // Callback API 11 | nextFrame(() => { 12 | console.log('I will be executed in the next frame!'); 13 | }); 14 | 15 | // Promise API 16 | await nextFrame(); 17 | console.log('I will be executed in the next frame!'); 18 | ``` 19 | 20 | ### Parameters 21 | 22 | - `callback` (`(time:DOMHighResTimeStamp) => unknown`): the function to execute 23 | 24 | [Source](https://github.com/studiometa/js-toolkit/blob/master/src/utils/nextFrame.js) 25 | 26 | ### Return value 27 | 28 | This function returns a `Promise` resolving on the next frame with the return value of the provided callback. 29 | -------------------------------------------------------------------------------- /packages/docs/utils/nextMicrotask.md: -------------------------------------------------------------------------------- 1 | # nextMicrotask 2 | 3 | Execute a given function in the next microtask. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { nextMicrotask } from '@studiometa/js-toolkit/utils'; 9 | 10 | // Callback APIs 11 | nextMicrotask(() => { 12 | console.log('I will be executed in the next microtask!'); 13 | }); 14 | 15 | // Promise API 16 | await nextMicrotask(); 17 | console.log('I will be executed in the next microtask!'); 18 | ``` 19 | 20 | ### Parameters 21 | 22 | - `fn` (`() => T`): the function to execute 23 | 24 | ### Return value 25 | 26 | This function returns a `Promise` resolving on the next microtask. 27 | -------------------------------------------------------------------------------- /packages/docs/utils/nextTick.md: -------------------------------------------------------------------------------- 1 | # nextTick 2 | 3 | Execute a given function in the next window tick. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { nextTick } from '@studiometa/js-toolkit/utils'; 9 | 10 | // Callback API 11 | nextTick(() => { 12 | console.log('I will be executed in the next tick!'); 13 | }); 14 | 15 | // Promise API 16 | await nextTick(); 17 | console.log('I will be executed in the next tick!'); 18 | ``` 19 | 20 | ### Parameters 21 | 22 | - `fn` (`Function`): the function to execute 23 | 24 | ### Return value 25 | 26 | This function returns a `Promise` resolving on the next tick. 27 | -------------------------------------------------------------------------------- /packages/docs/utils/randomInt.md: -------------------------------------------------------------------------------- 1 | # randomInt 2 | 3 | Get a random integer between bounds. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { randomInt } from '@studiometa/js-toolkit/utils'; 9 | 10 | randomInt(10); // an integer between 0 and 10 11 | randomInt(20, 10); // an integer between 10 and 20 12 | ``` 13 | 14 | ### Parameters 15 | 16 | - `a` (`number`): lower bound 17 | - `b` (`number`): upper bound (default: `0`) 18 | 19 | ### Return value 20 | 21 | This function returns a random `number` between the `a` and `b` values. 22 | -------------------------------------------------------------------------------- /packages/docs/utils/randomItem.md: -------------------------------------------------------------------------------- 1 | # randomItem 2 | 3 | Get a random item of an array or a random character of a string. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { randomItem } from '@studiometa/js-toolkit/utils'; 9 | 10 | const items = ['a', 'b', 'c', 'd', 'e', 'f']; 11 | randomItem(items); // one of the value in `items` 12 | 13 | const string = 'abcdef'; 14 | randomItem(string); // one of the letter in `string` 15 | ``` 16 | 17 | ### Parameters 18 | 19 | - `items` (`array|string`): array or string 20 | 21 | ### Return value 22 | 23 | This function returns a random item of an array, a random character of a string or `undefined` if the array or string is empty. 24 | -------------------------------------------------------------------------------- /packages/docs/utils/string/camelCase.md: -------------------------------------------------------------------------------- 1 | # camelCase 2 | 3 | Transform a string to `camelCase`. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { camelCase } from '@studiometa/js-toolkit/utils'; 9 | 10 | camelCase('Some String Content'); // someStringContent 11 | ``` 12 | 13 | ### Params 14 | 15 | - `string` (`string`): The string to transform. 16 | 17 | ### Return value 18 | 19 | - `string`: The transformed string. 20 | -------------------------------------------------------------------------------- /packages/docs/utils/string/dashCase.md: -------------------------------------------------------------------------------- 1 | # dashCase 2 | 3 | Transform a string to `dash-case`. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { dashCase } from '@studiometa/js-toolkit/utils'; 9 | 10 | dashCase('Some String Content'); // some-string-content 11 | ``` 12 | 13 | ### Params 14 | 15 | - `string` (`string`): The string to transform. 16 | 17 | ### Return value 18 | 19 | - `string`: The transformed string. 20 | -------------------------------------------------------------------------------- /packages/docs/utils/string/endsWith.md: -------------------------------------------------------------------------------- 1 | # endsWith 2 | 3 | A [more performant](https://jsbench.me/1hlkqqd0ff/1) way than `String.prototype.endsWith` to test if a string ends with another string. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { endsWith } from '@studiometa/js-toolkit/utils'; 9 | 10 | endsWith('string', 'ing'); // true 11 | endsWith('string', 'no'); // false 12 | ``` 13 | 14 | ### Params 15 | 16 | - `string` (`string`): The string to test. 17 | - `search` (`string`): The string to search for at the end. 18 | 19 | ### Return value 20 | 21 | - `boolean`: Wether the string ends with search or not. 22 | -------------------------------------------------------------------------------- /packages/docs/utils/string/index.md: -------------------------------------------------------------------------------- 1 | # String utils 2 | 3 | ## Leading and trailing characters 4 | 5 | The leading and trailing string utilities will ensure that characters from the start or end of a string are present by adding or removing them. 6 | 7 | - [withLeadingCharacters](./withLeadingCharacters.html) 8 | - [withLeadingSlash](./withLeadingSlash.html) 9 | - [withoutLeadingCharacters](./withoutLeadingCharacters.html) 10 | - [withoutLeadingCharactersRecursive](./withoutLeadingCharactersRecursive.html) 11 | - [withoutLeadingSlash](./withoutLeadingSlash.html) 12 | - [withoutTrailingCharacters](./withoutTrailingCharacters.html) 13 | - [withoutTrailingCharactersRecursive](./withoutTrailingCharactersRecursive.html) 14 | - [withoutTrailingSlash](./withoutTrailingSlash.html) 15 | - [withTrailingCharacters](./withTrailingCharacters.html) 16 | - [withTrailingSlash](./withTrailingSlash.html) 17 | -------------------------------------------------------------------------------- /packages/docs/utils/string/lowerCase.md: -------------------------------------------------------------------------------- 1 | # lowerCase 2 | 3 | Transform a string to `lowercase`. This function is an alias of `String.prototype.toLowercase()`. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { lowerCase } from '@studiometa/js-toolkit/utils'; 9 | 10 | lowerCase('Some String Content'); // some string content 11 | ``` 12 | 13 | ### Params 14 | 15 | - `string` (`string`): The string to transform. 16 | 17 | ### Return value 18 | 19 | - `string`: The transformed string. 20 | -------------------------------------------------------------------------------- /packages/docs/utils/string/pascalCase.md: -------------------------------------------------------------------------------- 1 | # pascalCase 2 | 3 | Transform a string to `PascalCase`. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { pascalCase } from '@studiometa/js-toolkit/utils'; 9 | 10 | pascalCase('Some String Content'); // SomeStringContent 11 | ``` 12 | 13 | ### Params 14 | 15 | - `string` (`string`): The string to transform. 16 | 17 | ### Return value 18 | 19 | - `string`: The transformed string. 20 | -------------------------------------------------------------------------------- /packages/docs/utils/string/snakeCase.md: -------------------------------------------------------------------------------- 1 | # snakeCase 2 | 3 | Transform a string to `snake_case`. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { snakeCase } from '@studiometa/js-toolkit/utils'; 9 | 10 | snakeCase('Some String Content'); // some_string_content 11 | ``` 12 | 13 | ### Params 14 | 15 | - `string` (`string`): The string to transform. 16 | 17 | ### Return value 18 | 19 | - `string`: The transformed string. 20 | -------------------------------------------------------------------------------- /packages/docs/utils/string/startsWith.md: -------------------------------------------------------------------------------- 1 | # startsWith 2 | 3 | A [more performant](https://jsbench.me/1hlkqqd0ff/1) way than `String.prototype.startsWith` to test if a string starts with another string. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { startsWith } from '@studiometa/js-toolkit/utils'; 9 | 10 | startsWith('string', 'str'); // true 11 | startsWith('string', 'no'); // false 12 | ``` 13 | 14 | ### Params 15 | 16 | - `string` (`string`): The string to test. 17 | - `search` (`string`): The string to search for at the begining. 18 | 19 | ### Return value 20 | 21 | - `boolean`: Wether the string starts with search or not. 22 | -------------------------------------------------------------------------------- /packages/docs/utils/string/upperCase.md: -------------------------------------------------------------------------------- 1 | # upperCase 2 | 3 | Transform a string to `UPPERCASE`. This function is an alias of `String.prototype.toUppercase()`. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { upperCase } from '@studiometa/js-toolkit/utils'; 9 | 10 | upperCase('Some String Content'); // SOME STRING CONTENT 11 | ``` 12 | 13 | ### Params 14 | 15 | - `string` (`string`): The string to transform. 16 | 17 | ### Return value 18 | 19 | - `string`: The transformed string. 20 | -------------------------------------------------------------------------------- /packages/docs/utils/string/withLeadingCharacters.md: -------------------------------------------------------------------------------- 1 | # withLeadingCharacters 2 | 3 | ## Usage 4 | 5 | ```js twoslash 6 | import { withLeadingCharacters } from '@studiometa/js-toolkit/utils'; 7 | 8 | withLeadingCharacters('string', '__'); // "__string" 9 | withLeadingCharacters('__string', '__'); // "__string" 10 | ``` 11 | 12 | ### Params 13 | 14 | - `string` (`string`): The string to modify. 15 | - `characters` (`string`): The characters to add to the start of the string. 16 | 17 | ### Return value 18 | 19 | - `string`: The modified string. 20 | -------------------------------------------------------------------------------- /packages/docs/utils/string/withLeadingSlash.md: -------------------------------------------------------------------------------- 1 | # withLeadingSlash 2 | 3 | ## Usage 4 | 5 | ```js twoslash 6 | import { withLeadingSlash } from '@studiometa/js-toolkit/utils'; 7 | 8 | withLeadingSlash('string'); // "/string" 9 | withLeadingSlash('/string'); // "/string" 10 | ``` 11 | 12 | ### Parameters 13 | 14 | - `string` (`string`): The string to modify. 15 | 16 | ### Return value 17 | 18 | - `string`: The modified string. 19 | -------------------------------------------------------------------------------- /packages/docs/utils/string/withTrailingCharacters.md: -------------------------------------------------------------------------------- 1 | # withTrailingCharacters 2 | 3 | ## Usage 4 | 5 | ```js twoslash 6 | import { withTrailingCharacters } from '@studiometa/js-toolkit/utils'; 7 | 8 | withTrailingCharacters('string', '__'); // "string__" 9 | withTrailingCharacters('string__', '__'); // "string__" 10 | ``` 11 | 12 | ### Parameters 13 | 14 | - `string` (`string`): The string to modify. 15 | - `characters` (`string`): The characters to add to the end of the string. 16 | 17 | ### Return value 18 | 19 | - `string`: The modified string. 20 | -------------------------------------------------------------------------------- /packages/docs/utils/string/withTrailingSlash.md: -------------------------------------------------------------------------------- 1 | # withTrailingSlash 2 | 3 | ## Usage 4 | 5 | ```js twoslash 6 | import { withTrailingSlash } from '@studiometa/js-toolkit/utils'; 7 | 8 | withTrailingSlash('string'); // "string/" 9 | withTrailingSlash('string/'); // "string/" 10 | ``` 11 | 12 | ### Parameters 13 | 14 | - `string` (`string`): The string to modify. 15 | 16 | ### Return value 17 | 18 | - `string`: The modified string. 19 | -------------------------------------------------------------------------------- /packages/docs/utils/string/withoutLeadingCharacters.md: -------------------------------------------------------------------------------- 1 | # withoutLeadingCharacters 2 | 3 | ## Usage 4 | 5 | ```js twoslash 6 | import { withoutLeadingCharacters } from '@studiometa/js-toolkit/utils'; 7 | 8 | withoutLeadingCharacters('__string', '__'); // "string" 9 | withoutLeadingCharacters('string', '__'); // "string" 10 | ``` 11 | 12 | ### Parameters 13 | 14 | - `string` (`string`): The string to modify. 15 | - `characters` (`string`): The characters to remove from the start of the string. 16 | 17 | ### Return value 18 | 19 | - `string`: The modified string. 20 | -------------------------------------------------------------------------------- /packages/docs/utils/string/withoutLeadingCharactersRecursive.md: -------------------------------------------------------------------------------- 1 | # withoutLeadingCharactersRecursive 2 | 3 | ## Usage 4 | 5 | ```js twoslash 6 | import { withoutLeadingCharactersRecursive } from '@studiometa/js-toolkit/utils'; 7 | 8 | withoutLeadingCharactersRecursive('///string', '/'); // "string" 9 | withoutLeadingCharactersRecursive('____string', '__'); // "string" 10 | withoutLeadingCharactersRecursive('string', '__'); // "string" 11 | ``` 12 | 13 | ### Parameters 14 | 15 | - `string` (`string`): The string to modify. 16 | - `characters` (`string`): The characters to recursively remove from the start of the string. 17 | 18 | ### Return value 19 | 20 | - `string`: The modified string. 21 | -------------------------------------------------------------------------------- /packages/docs/utils/string/withoutLeadingSlash.md: -------------------------------------------------------------------------------- 1 | # withoutLeadingSlash 2 | 3 | ## Usage 4 | 5 | ```js twoslash 6 | import { withoutLeadingSlash } from '@studiometa/js-toolkit/utils'; 7 | 8 | withoutLeadingSlash('/string'); // "string" 9 | withoutLeadingSlash('string'); // "string" 10 | ``` 11 | 12 | ### Parameters 13 | 14 | - `string` (`string`): The string to modify. 15 | 16 | ### Return value 17 | 18 | - `string`: The modified string. 19 | -------------------------------------------------------------------------------- /packages/docs/utils/string/withoutTrailingCharacters.md: -------------------------------------------------------------------------------- 1 | # withoutTrailingCharacters 2 | 3 | ## Usage 4 | 5 | ```js twoslash 6 | import { withoutTrailingCharacters } from '@studiometa/js-toolkit/utils'; 7 | 8 | withoutTrailingCharacters('string__', '__'); // "string" 9 | withoutTrailingCharacters('string', '__'); // "string" 10 | ``` 11 | 12 | ### Parameters 13 | 14 | - `string` (`string`): The string to modify. 15 | - `characters` (`string`): The characters to remove from the end of the string. 16 | 17 | ### Return value 18 | 19 | - `string`: The modified string. 20 | -------------------------------------------------------------------------------- /packages/docs/utils/string/withoutTrailingCharactersRecursive.md: -------------------------------------------------------------------------------- 1 | # withoutTrailingCharactersRecursive 2 | 3 | ## Usage 4 | 5 | ```js twoslash 6 | import { withoutTrailingCharactersRecursive } from '@studiometa/js-toolkit/utils'; 7 | 8 | withoutLeadingCharactersRecursive('string///', '/'); // "string" 9 | withoutTrailingCharactersRecursive('string____', '__'); // "string" 10 | withoutTrailingCharactersRecursive('string', '__'); // "string" 11 | ``` 12 | 13 | ### Parameters 14 | 15 | - `string` (`string`): The string to modify. 16 | - `characters` (`string`): The characters to recursively remove from the end of the string. 17 | 18 | ### Return value 19 | 20 | - `string`: The modified string. 21 | -------------------------------------------------------------------------------- /packages/docs/utils/string/withoutTrailingSlash.md: -------------------------------------------------------------------------------- 1 | # withoutTrailingSlash 2 | 3 | ## Usage 4 | 5 | ```js twoslash 6 | import { withoutTrailingSlash } from '@studiometa/js-toolkit/utils'; 7 | 8 | withoutTrailingSlash('string/'); // "string" 9 | withoutTrailingSlash('string'); // "string" 10 | ``` 11 | 12 | ### Parameters 13 | 14 | - `string` (`string`): The string to modify. 15 | 16 | ### Return value 17 | 18 | - `string`: The modified string. 19 | -------------------------------------------------------------------------------- /packages/docs/utils/throttle.md: -------------------------------------------------------------------------------- 1 | # throttle 2 | 3 | Limit the execution of a function one time for the given delay in milliseconds. 4 | 5 | ## Usage 6 | 7 | ```js twoslash 8 | import { throttle } from '@studiometa/js-toolkit/utils'; 9 | 10 | const throttledFn = throttle(() => { 11 | console.log('Hello 👋'); 12 | }, 500); 13 | 14 | throttledFn(); // Hello 👋 15 | ``` 16 | 17 | ### Parameters 18 | 19 | - `fn` (`Function`): the function to execute 20 | - `delay` (`Number`): the delay in milliseconds 21 | 22 | ### Return value 23 | 24 | A new function which will execute the given function at the given limited rate. 25 | -------------------------------------------------------------------------------- /packages/docs/utils/trapFocus.md: -------------------------------------------------------------------------------- 1 | # trapFocus 2 | 3 | Trap the tab navigation inside a given element. 4 | 5 | ::: tip 6 | To understand the "tab trap" usage, read [Using JavaScript to trap focus in an element](https://hiddedevries.nl/en/blog/2017-01-29-using-javascript-to-trap-focus-in-an-element). 7 | ::: 8 | 9 | ## Usage 10 | 11 | ```js twoslash 12 | import { trapFocus, untrapFocus } from '@studiometa/js-toolkit/utils'; 13 | 14 | // Limit the tab navigation to focusable children of the document's body 15 | document.addEventListener('keyup', (event) => { 16 | trapFocus(document.body, event); 17 | }); 18 | 19 | // Resume the trap and refocus the previously focused element 20 | untrapFocus(); 21 | ``` 22 | 23 | ## Parameters 24 | 25 | **trapFocus** 26 | 27 | - `element` (`HTMLElement`): the element in which to trap the focus 28 | - `event` (`KeyboardEvent`): the `keyup` event object 29 | 30 | **untrapFocus** 31 | 32 | The `untrapFocus` function does not need any argument. 33 | 34 | ## Return value 35 | 36 | Both functions return nothing. 37 | -------------------------------------------------------------------------------- /packages/docs/utils/useScheduler.md: -------------------------------------------------------------------------------- 1 | # useScheduler 2 | 3 | Use this function to generate a scheduler in order to group the execution of some tasks in the specified steps. 4 | 5 | This can be useful to avoid layout trashing when working with the DOM, find out more by reading this [web.dev article](https://web.dev/avoid-large-complex-layouts-and-layout-thrashing). 6 | 7 | ## Usage 8 | 9 | ```js 10 | import { useScheduler } from '@studiometa/js-toolkit/utils'; 11 | 12 | const { before, after } = useScheduler(['before', 'after']); 13 | 14 | after(() => console.log('after')); 15 | before(() => console.log('before')); 16 | before(() => console.log('before')); 17 | after(() => console.log('after')); 18 | 19 | // Will output: 20 | // before 21 | // before 22 | // after 23 | // after 24 | ``` 25 | 26 | ### Parameters 27 | 28 | - `steps` (`string[]`): the list of steps the generated scheduler should have 29 | 30 | ### Return value 31 | 32 | This function returns a scheduler object with methods to add functions to execute on each given step. 33 | 34 | ```ts 35 | function useScheduler(steps:T[]):Record void) => void> 36 | ``` 37 | -------------------------------------------------------------------------------- /packages/global.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | // eslint-disable-next-line vars-on-top, no-var 3 | var __DEV__: boolean; 4 | interface Window { 5 | __DEV__: typeof __DEV__; 6 | ResizeObserver?: (callback: () => void) => void; 7 | } 8 | 9 | interface CSSStyleDeclaration { 10 | scrollMarginTop: string; 11 | } 12 | } 13 | 14 | export {}; 15 | -------------------------------------------------------------------------------- /packages/js-toolkit/Base/features.ts: -------------------------------------------------------------------------------- 1 | export type Features = { 2 | blocking: boolean; 3 | breakpoints: Record; 4 | prefix: string; 5 | attributes: { 6 | component: string; 7 | option: string; 8 | ref: string; 9 | }; 10 | }; 11 | 12 | interface FeaturesMap extends Map { 13 | get(key: T): Features[T]; 14 | set(key: T, value: Features[T]): this; 15 | } 16 | 17 | export const features = new Map([ 18 | ['blocking', false], 19 | [ 20 | 'breakpoints', 21 | { 22 | xxs: '0rem', 23 | xs: '30rem', // 480px 24 | s: '48rem', // 768px 25 | m: '64rem', // 1024px 26 | l: '80rem', // 1280px 27 | xl: '90rem', // 1440px 28 | xxl: '120rem', // 1920px 29 | xxxl: '160rem', // 2560px 30 | }, 31 | ], 32 | ['prefix', 'tk'], 33 | [ 34 | 'attributes', 35 | { 36 | component: 'data-component', 37 | option: 'data-option', 38 | ref: 'data-ref', 39 | }, 40 | ], 41 | ]) as FeaturesMap; 42 | -------------------------------------------------------------------------------- /packages/js-toolkit/Base/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Base.js'; 2 | export { getInstances } from './utils.js'; 3 | -------------------------------------------------------------------------------- /packages/js-toolkit/Base/managers/AbstractManager.ts: -------------------------------------------------------------------------------- 1 | import type { Base } from '../index.js'; 2 | 3 | /** 4 | * AbstractManager class. 5 | */ 6 | export class AbstractManager { 7 | /** 8 | * Base instance. 9 | */ 10 | __base: Base; 11 | 12 | /** 13 | * Get the base instance root element. 14 | */ 15 | get __element() { 16 | return this.__base.$el; 17 | } 18 | 19 | /** 20 | * Get the base instance config. 21 | */ 22 | get __config() { 23 | return this.__base.__config; 24 | } 25 | 26 | /** 27 | * Get the events manager. 28 | */ 29 | get __eventsManager() { 30 | return this.__base.__events; 31 | } 32 | 33 | __props: T = {} as any; 34 | 35 | get props() { 36 | return this.__props; 37 | } 38 | 39 | /** 40 | * Class constructor. 41 | */ 42 | constructor(base: Base) { 43 | this.__base = base; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/js-toolkit/Base/managers/index.ts: -------------------------------------------------------------------------------- 1 | export { AbstractManager } from './AbstractManager.js'; 2 | export { ChildrenManager } from './ChildrenManager.js'; 3 | export { EventsManager } from './EventsManager.js'; 4 | export { OptionsManager } from './OptionsManager.js'; 5 | export { RefsManager } from './RefsManager.js'; 6 | export { ResponsiveOptionsManager } from './ResponsiveOptionsManager.js'; 7 | export { ServicesManager } from './ServicesManager.js'; 8 | -------------------------------------------------------------------------------- /packages/js-toolkit/decorators/index.ts: -------------------------------------------------------------------------------- 1 | export * from './withBreakpointManager.js'; 2 | export * from './withBreakpointObserver.js'; 3 | export * from './withDrag.js'; 4 | export * from './withExtraConfig.js'; 5 | export * from './withFreezedOptions.js'; 6 | export * from './withIntersectionObserver.js'; 7 | export * from './withMountOnMediaQuery.js'; 8 | export * from './withMountWhenInView.js'; 9 | export * from './withMountWhenPrefersMotion.js'; 10 | export * from './withMutation.js'; 11 | export * from './withName.js'; 12 | export * from './withRelativePointer.js'; 13 | export * from './withResponsiveOptions.js'; 14 | export * from './withScrolledInView/index.js'; 15 | -------------------------------------------------------------------------------- /packages/js-toolkit/decorators/withExtraConfig.ts: -------------------------------------------------------------------------------- 1 | import merge from 'deepmerge'; 2 | import type { Options as DeepmergeOptions } from 'deepmerge'; 3 | import type { BaseDecorator, BaseInterface } from '../Base/types.js'; 4 | import type { Base, BaseProps, BaseConfig } from '../Base/index.js'; 5 | 6 | /** 7 | * Extends the configuration of an existing class. 8 | */ 9 | export function withExtraConfig( 10 | BaseClass: typeof Base, 11 | config: Partial, 12 | options: DeepmergeOptions = {}, 13 | ): BaseDecorator { 14 | const newConfig = merge(BaseClass.config, config, options); 15 | 16 | if (newConfig.name === BaseClass.config.name) { 17 | newConfig.name = `${BaseClass.config.name}WithExtraConfig`; 18 | } 19 | 20 | /** 21 | * Class. 22 | */ 23 | class WithExtraConfig extends BaseClass { 24 | /** 25 | * Config. 26 | */ 27 | static config: BaseConfig = newConfig; 28 | } 29 | 30 | // @ts-ignore 31 | return WithExtraConfig; 32 | } 33 | -------------------------------------------------------------------------------- /packages/js-toolkit/decorators/withFreezedOptions.ts: -------------------------------------------------------------------------------- 1 | import type { BaseDecorator, BaseInterface } from '../Base/types.js'; 2 | import type { Base, BaseProps, BaseOptions } from '../Base/index.js'; 3 | 4 | export interface WithFreezedOptionsInterface extends BaseInterface { 5 | readonly $options: Readonly; 6 | } 7 | 8 | /** 9 | * Freeze the `$options` property to improve performance. 10 | */ 11 | export function withFreezedOptions( 12 | BaseClass: typeof Base, 13 | ): BaseDecorator { 14 | /** 15 | * Class. 16 | */ 17 | class WithFreezedOptions extends BaseClass { 18 | /** 19 | * Hold freezed options 20 | * 21 | * @private 22 | */ 23 | __freezedOptions; 24 | 25 | /** 26 | * Lazyly freeze the `$options` property. 27 | */ 28 | get $options() { 29 | if (!this.__freezedOptions) { 30 | Object.defineProperty(this, '__freezedOptions', { 31 | value: Object.freeze({ ...super.$options }), 32 | enumerable: false, 33 | configurable: true, 34 | }); 35 | } 36 | 37 | return this.__freezedOptions; 38 | } 39 | } 40 | 41 | // @ts-ignore 42 | return WithFreezedOptions; 43 | } 44 | -------------------------------------------------------------------------------- /packages/js-toolkit/decorators/withMountWhenPrefersMotion.ts: -------------------------------------------------------------------------------- 1 | import { withMountOnMediaQuery } from './withMountOnMediaQuery.js'; 2 | import type { Base } from '../Base/index.js'; 3 | import { BaseDecorator, BaseInterface } from '../Base/types.js'; 4 | 5 | export function withMountWhenPrefersMotion( 6 | BaseClass: typeof Base, 7 | ): BaseDecorator { 8 | return withMountOnMediaQuery(BaseClass, 'not (prefers-reduced-motion)'); 9 | } 10 | -------------------------------------------------------------------------------- /packages/js-toolkit/decorators/withName.ts: -------------------------------------------------------------------------------- 1 | import type { BaseDecorator, BaseInterface } from '../Base/types.js'; 2 | import type { Base, BaseProps, BaseConfig } from '../Base/index.js'; 3 | 4 | /** 5 | * Override the name of the given component. 6 | */ 7 | export function withName( 8 | BaseClass: typeof Base, 9 | name: string, 10 | ): BaseDecorator { 11 | // @ts-ignore 12 | return class extends BaseClass { 13 | static config: BaseConfig = { 14 | ...BaseClass.config, 15 | name, 16 | }; 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /packages/js-toolkit/decorators/withScrolledInView/index.ts: -------------------------------------------------------------------------------- 1 | export * from './withScrolledInView.js'; 2 | export * from './types.js'; 3 | -------------------------------------------------------------------------------- /packages/js-toolkit/decorators/withScrolledInView/types.ts: -------------------------------------------------------------------------------- 1 | export type OffsetValue = 'start' | 'center' | 'end' | string | number; 2 | 3 | export type NormalizedOffset = [[OffsetValue, OffsetValue], [OffsetValue, OffsetValue]]; 4 | -------------------------------------------------------------------------------- /packages/js-toolkit/helpers/getClosestParent.ts: -------------------------------------------------------------------------------- 1 | import type { Base, BaseConstructor } from '../Base/index.js'; 2 | import { getInstanceFromElement } from './getInstanceFromElement.js'; 3 | import { getAncestorWhere } from '../utils/index.js'; 4 | 5 | /** 6 | * Get the closest parent of a component. 7 | */ 8 | export function getClosestParent( 9 | childInstance: Base, 10 | ParentConstructor: T, 11 | ) { 12 | const parentEl = getAncestorWhere( 13 | childInstance.$el, 14 | (element) => element && getInstanceFromElement(element, ParentConstructor) !== null, 15 | ); 16 | 17 | return parentEl ? getInstanceFromElement(parentEl, ParentConstructor) : null; 18 | } 19 | -------------------------------------------------------------------------------- /packages/js-toolkit/helpers/getInstanceFromElement.ts: -------------------------------------------------------------------------------- 1 | import type { BaseConstructor, BaseEl } from '../Base/index.js'; 2 | 3 | /** 4 | * Get a component instance from a DOM element. 5 | */ 6 | export function getInstanceFromElement( 7 | element: BaseEl, 8 | Constructor: T, 9 | ): InstanceType | null { 10 | if (!element || !element.__base__ || !element.__base__.has(Constructor)) { 11 | return null; 12 | } 13 | 14 | return element.__base__.get(Constructor) as InstanceType; 15 | } 16 | -------------------------------------------------------------------------------- /packages/js-toolkit/helpers/importWhenIdle.ts: -------------------------------------------------------------------------------- 1 | import type { BaseConstructor } from '../Base/index.js'; 2 | import { getComponentResolver } from '../utils/index.js'; 3 | 4 | type ImportWhenIdleOptions = { 5 | timeout?: number; 6 | }; 7 | 8 | /** 9 | * Import a component when user is idle. 10 | * 11 | * @template {BaseConstructor} T 12 | * @param {() => Promise} fn 13 | * The import function. 14 | * @param {ImportWhenIdleOptions} [options] 15 | * The time to wait before triggering the callback if never idle. 16 | * @returns {Promise} 17 | */ 18 | export function importWhenIdle( 19 | fn: () => Promise, 20 | { timeout = 1 }: ImportWhenIdleOptions = {}, 21 | ): Promise { 22 | const resolver = getComponentResolver(fn); 23 | 24 | return new Promise((resolve) => { 25 | if (!('requestIdleCallback' in window)) { 26 | setTimeout(() => { 27 | resolver(resolve); 28 | }, timeout); 29 | } else { 30 | window.requestIdleCallback( 31 | () => { 32 | setTimeout(() => { 33 | resolver(resolve); 34 | }, 0); 35 | }, 36 | { timeout }, 37 | ); 38 | } 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /packages/js-toolkit/helpers/importWhenPrefersMotion.ts: -------------------------------------------------------------------------------- 1 | import type { BaseConstructor } from '../Base/index.js'; 2 | import { importOnMediaQuery } from './importOnMediaQuery.js'; 3 | 4 | /** 5 | * Import a component if user does not reduce motion. 6 | * 7 | * @template {BaseConstructor} T 8 | * @param {() => Promise} fn 9 | * @returns {Promise} 10 | */ 11 | export function importWhenPrefersMotion( 12 | fn: () => Promise, 13 | ): Promise { 14 | return importOnMediaQuery(fn, 'not (prefers-reduced-motion)'); 15 | } 16 | -------------------------------------------------------------------------------- /packages/js-toolkit/helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './createApp.js'; 2 | export * from './getClosestParent.js'; 3 | export * from './getDirectChildren.js'; 4 | export * from './getInstanceFromElement.js'; 5 | export * from './importOnInteraction.js'; 6 | export * from './importOnMediaQuery.js'; 7 | export * from './importWhenIdle.js'; 8 | export * from './importWhenPrefersMotion.js'; 9 | export * from './importWhenVisible.js'; 10 | -------------------------------------------------------------------------------- /packages/js-toolkit/helpers/utils.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | import { getComponentElements } from '../Base/utils.js'; 3 | import { isString, isArray } from '../utils/index.js'; 4 | 5 | /** 6 | * Get the target elements for the lazy import helper functions. 7 | * 8 | * @param {string|HTMLElement|HTMLElement[]} nameOrSelectorOrElement 9 | * The original selector or element, or list of elements. 10 | * @param {HTMLElement} [context] 11 | * The optional context to use to query for elements. 12 | * @returns {HTMLElement[]} A normalized list of elements. 13 | */ 14 | export function getTargetElements( 15 | nameOrSelectorOrElement: string | HTMLElement | HTMLElement[], 16 | context: HTMLElement, 17 | ): HTMLElement[] { 18 | if (isString(nameOrSelectorOrElement)) { 19 | return getComponentElements(nameOrSelectorOrElement, context); 20 | } 21 | 22 | if (!isArray(nameOrSelectorOrElement)) { 23 | return [nameOrSelectorOrElement]; 24 | } 25 | 26 | return nameOrSelectorOrElement; 27 | } 28 | -------------------------------------------------------------------------------- /packages/js-toolkit/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Base/index.js'; 2 | export * from './Base/types.js'; 3 | export * from './decorators/index.js'; 4 | export * from './helpers/index.js'; 5 | export * from './services/index.js'; 6 | -------------------------------------------------------------------------------- /packages/js-toolkit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@studiometa/js-toolkit", 3 | "version": "3.0.4", 4 | "description": "A set of useful little bits of JavaScript to boost your project! 🚀", 5 | "publishConfig": { 6 | "access": "public" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/studiometa/js-toolkit.git" 11 | }, 12 | "author": "Studio Meta (https://www.studiometa.fr)", 13 | "license": "MIT", 14 | "bugs": { 15 | "url": "https://github.com/studiometa/js-toolkit/issues" 16 | }, 17 | "homepage": "https://github.com/studiometa/js-toolkit#readme", 18 | "type": "module", 19 | "sideEffects": false, 20 | "main": "./index.ts", 21 | "module": "./index.ts", 22 | "types": "./index.d.ts", 23 | "exports": { 24 | ".": { 25 | "import": "./index.ts", 26 | "types": "./index.d.ts", 27 | "default": "./index.ts" 28 | }, 29 | "./utils": { 30 | "import": "./utils/index.ts", 31 | "types": "./utils/index.d.ts", 32 | "default": "./utils/index.js" 33 | } 34 | }, 35 | "keywords": [ 36 | "js-toolkit", 37 | "component", 38 | "studiometa", 39 | "toolkit" 40 | ], 41 | "dependencies": { 42 | "@motionone/easing": "^10.18.0", 43 | "deepmerge": "^4.3.1" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/js-toolkit/services/LoadService.ts: -------------------------------------------------------------------------------- 1 | import type { ServiceInterface, ServiceConfig } from './AbstractService.js'; 2 | import { AbstractService } from './AbstractService.js'; 3 | 4 | export interface LoadServiceProps { 5 | time: DOMHighResTimeStamp; 6 | } 7 | 8 | export type LoadServiceInterface = ServiceInterface; 9 | 10 | export class LoadService extends AbstractService { 11 | static config: ServiceConfig = [[() => window, [['load']]]]; 12 | 13 | props: LoadServiceProps = { 14 | time: performance.now(), 15 | }; 16 | 17 | handleEvent() { 18 | this.props.time = window.performance.now(); 19 | this.trigger(this.props); 20 | } 21 | } 22 | 23 | /** 24 | * Use the load service. 25 | */ 26 | export function useLoad(): LoadServiceInterface { 27 | return LoadService.getInstance(); 28 | } 29 | -------------------------------------------------------------------------------- /packages/js-toolkit/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './AbstractService.js'; 2 | export * from './DragService.js'; 3 | export * from './KeyService.js'; 4 | export * from './LoadService.js'; 5 | export * from './MutationService.js'; 6 | export * from './PointerService.js'; 7 | export * from './RafService.js'; 8 | export * from './ResizeService.js'; 9 | export * from './ScrollService.js'; 10 | -------------------------------------------------------------------------------- /packages/js-toolkit/services/utils.ts: -------------------------------------------------------------------------------- 1 | export const ONCE_CAPTURE_EVENT_OPTIONS = { once: true, capture: true }; 2 | export const PASSIVE_CAPTURE_EVENT_OPTIONS = { passive: true, capture: true }; 3 | export const PASSIVE_EVENT_OPTIONS = { passive: true }; 4 | export const CAPTURE_EVENT_OPTIONS = { capture: true }; 5 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/cache.ts: -------------------------------------------------------------------------------- 1 | import { isArray } from './is.js'; 2 | 3 | const map = new Map(); 4 | 5 | /** 6 | * Cache the result of a callback in map instances. 7 | */ 8 | export function cache(keys: any | any[], callback: () => T): T { 9 | const normalizedKeys = isArray(keys) ? keys : [keys]; 10 | let value = map; 11 | let index = 1; 12 | 13 | for (const key of normalizedKeys) { 14 | if (!value.has(key)) { 15 | const newValue = index === keys.length ? callback() : new Map(); 16 | value.set(key, newValue); 17 | } 18 | 19 | value = value.get(key); 20 | index += 1; 21 | } 22 | 23 | return value as T; 24 | } 25 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/collide/boundingRectToCircle.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {Object} Circle 3 | * @property {number} x Circle's x position 4 | * @property {number} y Circle's y position 5 | * @property {number} radius Circle's radius 6 | */ 7 | 8 | /** 9 | * Convert clientRect to a formatted circle object 10 | * 11 | * @param {Partial} domRect DOMRect of a square DOMElement 12 | * @param {boolean} force Force usage of non-square DOMElements 13 | * @returns {Circle} Circle object that can be used in collides functions 14 | */ 15 | export function boundingRectToCircle({ x, y, width, height }, force = false) { 16 | if (width !== height && !force) { 17 | throw new Error('Initial DOMElement is not a square. Please use the force mode.'); 18 | } 19 | return { 20 | x: x + width / 2, 21 | y: y + height / 2, 22 | radius: (width + height) / 4, 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/collide/collideCircleCircle.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {Object} Circle 3 | * @property {number} x Circle's x position 4 | * @property {number} y Circle's y position 5 | * @property {number} radius Circle's radius 6 | */ 7 | 8 | /** 9 | * Test if a circle collides with another. 10 | * Inspired by http://www.jeffreythompson.org/collision-detection/circle-circle.php 11 | * 12 | * @param {Circle} circle1 Circle 1 13 | * @param {Circle} circle2 Circle 2 14 | * @returns {boolean} Are the sides of one circle touching the other ? 15 | */ 16 | export function collideCircleCircle(circle1, circle2) { 17 | // get distance between the circle's centers 18 | // use the Pythagorean Theorem to compute the distance 19 | const distX = circle1.x - circle2.x; 20 | const distY = circle1.y - circle2.y; 21 | const distance = Math.sqrt(distX * distX + distY * distY); 22 | 23 | // if the distance is less than the sum of the circle's 24 | // radii, the circles are touching! 25 | if (distance <= circle1.radius + circle2.radius) { 26 | return true; 27 | } 28 | return false; 29 | } 30 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/collide/collidePointCircle.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {Object} Point 3 | * @property {number} x Point's x position 4 | * @property {number} y Point's y position 5 | */ 6 | 7 | /** 8 | * @typedef {Object} Circle 9 | * @property {number} x Circle's x position 10 | * @property {number} y Circle's y position 11 | * @property {number} radius Circle's radius 12 | */ 13 | 14 | /** 15 | * Test if a point is inside a circle. 16 | * Inspired by http://www.jeffreythompson.org/collision-detection/point-circle.php 17 | * 18 | * @param {Point} point Point 19 | * @param {Circle} circle Circle 20 | * @returns {boolean} Is the point inside the circle's bounds ? 21 | */ 22 | export function collidePointCircle(point, circle) { 23 | // get distance between the point and circle's center 24 | // using the Pythagorean Theorem 25 | const distX = point.x - circle.x; 26 | const distY = point.y - circle.y; 27 | const distance = Math.sqrt(distX * distX + distY * distY); 28 | 29 | // if the distance is less than the circle's 30 | // radius the point is inside! 31 | return distance <= circle.radius; 32 | } 33 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/collide/collidePointRect.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {Object} Point 3 | * @property {number} x Point's x position 4 | * @property {number} y Point's y position 5 | */ 6 | 7 | /** 8 | * @typedef {Object} Rect 9 | * @property {number} x Rectangle's x position 10 | * @property {number} y Rectangle's y position 11 | * @property {number} width Rectangle's width 12 | * @property {number} height Rectangle's height 13 | */ 14 | 15 | /** 16 | * Test if a point collides with a rectangle. 17 | * Inspired by http://www.jeffreythompson.org/collision-detection/point-rect.php 18 | * 19 | * @param {Point} point Point 20 | * @param {Rect} rect Rectangle 21 | * @returns {boolean} Is the point inside the rectangle's bounds ? 22 | */ 23 | export function collidePointRect(point, rect) { 24 | return ( 25 | point.x >= rect.x && // right of the left edge AND 26 | point.x <= rect.x + rect.width && // left of the right edge AND 27 | point.y >= rect.y && // below the top AND 28 | point.y <= rect.y + rect.height // above the bottom 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/collide/collideRectRect.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {Object} Rect 3 | * @property {number} x Rectangle's x position 4 | * @property {number} y Rectangle's y position 5 | * @property {number} width Rectangle's width 6 | * @property {number} height Rectangle's height 7 | */ 8 | 9 | /** 10 | * Test if a rectangle collides with another rectangle. 11 | * Inspired by http://www.jeffreythompson.org/collision-detection/rect-rect.php 12 | * 13 | * @param {Rect} rect1 Rectangle 1 14 | * @param {Rect} rect2 Rectangle 2 15 | * @returns {boolean} Are the sides of one rectangle touching the other ? 16 | */ 17 | export function collideRectRect(rect1, rect2) { 18 | return ( 19 | rect1.x + rect1.width >= rect2.x && // rect1 right edge past rect2 left AND 20 | rect1.x <= rect2.x + rect2.width && // rect1 left edge past rect2 right AND 21 | rect1.y + rect1.height >= rect2.y && // rect1 top edge past rect2 bottom AND 22 | rect1.y <= rect2.y + rect2.height // rect1 bottom edge past rect2 top 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/collide/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Inspired by http://www.jeffreythompson.org/collision-detection/ 3 | */ 4 | export { boundingRectToCircle } from './boundingRectToCircle.js'; 5 | export { collideCircleCircle } from './collideCircleCircle.js'; 6 | export { collideCircleRect } from './collideCircleRect.js'; 7 | export { collidePointCircle } from './collidePointCircle.js'; 8 | export { collidePointRect } from './collidePointRect.js'; 9 | export { collideRectRect } from './collideRectRect.js'; 10 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/css/getOffsetSizes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Get a `DOMRect` like `Object` for an element, without its transforms. 3 | */ 4 | export function getOffsetSizes(element: HTMLElement) { 5 | let parent = element; 6 | let x = -window.scrollX; 7 | let y = -window.scrollY; 8 | 9 | while (parent) { 10 | x += parent.offsetLeft; 11 | y += parent.offsetTop; 12 | // @ts-ignore 13 | parent = parent.offsetParent; 14 | } 15 | 16 | const width = element.offsetWidth; 17 | const height = element.offsetHeight; 18 | 19 | return { 20 | x, 21 | y, 22 | width, 23 | height, 24 | top: y, 25 | right: width + x, 26 | bottom: height + y, 27 | left: x, 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/css/index.ts: -------------------------------------------------------------------------------- 1 | export * from './animate.js'; 2 | export { add as addClass, remove as removeClass, toggle as toggleClass } from './classes.js'; 3 | export { add as addStyle, remove as removeStyle } from './styles.js'; 4 | export { getOffsetSizes } from './getOffsetSizes.js'; 5 | export { matrix } from './matrix.js'; 6 | export { transform } from './transform.js'; 7 | export { transition } from './transition.js'; 8 | export type { TransformProps } from './transform.js'; 9 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/css/matrix.ts: -------------------------------------------------------------------------------- 1 | type MatrixTransform = { 2 | scaleX?: number; 3 | scaleY?: number; 4 | skewX?: number; 5 | skewY?: number; 6 | translateX?: number; 7 | translateY?: number; 8 | }; 9 | 10 | /** 11 | * Format a CSS transform matrix with the given values. 12 | * 13 | * @param {Object} transform 14 | * @param {number} [transform.scaleX=1] The scale on the x axis. 15 | * @param {number} [transform.scaleY=1] The scale on the y axis. 16 | * @param {number} [transform.skewX=0] The skew on the x axis. 17 | * @param {number} [transform.skewY=0] The skew on the y axis. 18 | * @param {number} [transform.translateX=0] The translate on the x axis. 19 | * @param {number} [transform.translateY=0] The translate on the y axis. 20 | * @returns {string} A formatted CSS matrix transform. 21 | * @example 22 | * ```js 23 | * matrix({ scaleX: 0.5, scaleY: 0.5 }); 24 | * // matrix(0.5, 0, 0, 0.5, 0, 0) 25 | * ``` 26 | */ 27 | export function matrix(transform?: MatrixTransform): string { 28 | // eslint-disable-next-line no-param-reassign 29 | transform = transform || {}; 30 | return `matrix(${transform.scaleX ?? 1}, ${transform.skewY ?? 0}, ${transform.skewX ?? 0}, ${ 31 | transform.scaleY ?? 1 32 | }, ${transform.translateX ?? 0}, ${transform.translateY ?? 0})`; 33 | } 34 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/css/styles.ts: -------------------------------------------------------------------------------- 1 | import { isObject } from '../is.js'; 2 | // eslint-disable-next-line import/extensions 3 | import { eachElements } from './utils.js'; 4 | 5 | /** 6 | * Manage a list of style properties on an element. 7 | */ 8 | function setStyles( 9 | elementOrElements: HTMLElement | HTMLElement[] | NodeListOf, 10 | styles: Partial, 11 | method: 'add' | 'remove' = 'add', 12 | ) { 13 | if (!elementOrElements || !styles || !isObject(styles)) { 14 | return; 15 | } 16 | 17 | eachElements(elementOrElements, (el) => { 18 | for (const [prop, value] of Object.entries(styles)) { 19 | el.style[prop] = method === 'add' ? value : ''; 20 | } 21 | }); 22 | } 23 | 24 | /** 25 | * Add styles to an element. 26 | */ 27 | export function add( 28 | elementOrElements: HTMLElement | HTMLElement[] | NodeListOf, 29 | styles: Partial, 30 | ) { 31 | setStyles(elementOrElements, styles); 32 | } 33 | 34 | /** 35 | * Remove class names from an element. 36 | */ 37 | export function remove( 38 | elementOrElements: HTMLElement | HTMLElement[] | NodeListOf, 39 | styles: Partial, 40 | ) { 41 | setStyles(elementOrElements, styles, 'remove'); 42 | } 43 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/css/utils.ts: -------------------------------------------------------------------------------- 1 | export function eachElements( 2 | elementOrElements: S | S[] | NodeListOf, 3 | callback: (element: S, index: number, elements: S[]) => T, 4 | ): T[] { 5 | if (elementOrElements instanceof Node) { 6 | return [callback(elementOrElements, 0, [elementOrElements])]; 7 | } 8 | 9 | return Array.from(elementOrElements).map(callback); 10 | } 11 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/debounce.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns a function, that, as long as it continues to be invoked, 3 | * will not be triggered. The function will be called after it stops 4 | * being called for N milliseconds. 5 | * 6 | * @param {(...args:unknown[]) => void} fn The function to call. 7 | * @param {number} [delay=300] The delay in ms to wait before calling the function. 8 | * @returns {(...args:unknown[]) => void} The debounced function. 9 | */ 10 | export function debounce( 11 | fn: (...args: unknown[]) => void, 12 | delay = 300, 13 | ): (...args: unknown[]) => void { 14 | let timeout; 15 | return function debounced(...args) { 16 | clearTimeout(timeout); 17 | timeout = setTimeout(() => { 18 | fn(...args); 19 | }, delay); 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/dom/ancestors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Get an ancestor matching the given condition. 3 | */ 4 | export function getAncestorWhere(element: HTMLElement, condition: (el: HTMLElement) => boolean) { 5 | if (!element) { 6 | return null; 7 | } 8 | 9 | const ancestor = element.parentElement; 10 | 11 | return condition(ancestor) ? ancestor : getAncestorWhere(ancestor, condition); 12 | } 13 | 14 | /** 15 | * Get an ancestor matching the given condition, stop when the until function is truthy. 16 | */ 17 | export function getAncestorWhereUntil( 18 | element: HTMLElement, 19 | condition: (el: HTMLElement) => boolean, 20 | until: (el: HTMLElement) => boolean, 21 | ): HTMLElement | null { 22 | return getAncestorWhere(element, (el) => condition(el) || until(el)); 23 | } 24 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/dom/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ancestors.js'; 2 | export * from './createElement.js'; 3 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/getComponentResolver.ts: -------------------------------------------------------------------------------- 1 | import { isFunction } from './is.js'; 2 | 3 | /** 4 | * Default component resolver 5 | * 6 | * @param {Function} fn 7 | * @returns {Function} 8 | */ 9 | export function getComponentResolver(fn) { 10 | return (resolve, cb?: () => unknown) => { 11 | fn().then((module) => { 12 | const ResolvedClass = module.default ?? module; 13 | resolve(ResolvedClass); 14 | 15 | if (isFunction(cb)) { 16 | cb(); 17 | } 18 | }); 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/has.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Test if the current context as a global `window` object available. 3 | */ 4 | export function hasWindow() { 5 | return typeof window !== 'undefined'; 6 | } 7 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { debounce } from './debounce.js'; 2 | export * from './trapFocus.js'; 3 | export { keyCodes } from './keyCodes.js'; 4 | export { memoize } from './memoize.js'; 5 | export { nextFrame } from './nextFrame.js'; 6 | export { nextTick } from './nextTick.js'; 7 | export { nextMicrotask } from './nextMicrotask.js'; 8 | export { throttle } from './throttle.js'; 9 | export { scrollTo } from './scrollTo.js'; 10 | export { getComponentResolver } from './getComponentResolver.js'; 11 | export * from './is.js'; 12 | export * from './has.js'; 13 | export * from './css/index.js'; 14 | export { 15 | objectToURLSearchParams, 16 | push as historyPush, 17 | replace as historyReplace, 18 | } from './history.js'; 19 | export * from './collide/index.js'; 20 | export * from './math/index.js'; 21 | export * from './string/index.js'; 22 | export * from './scheduler.js'; 23 | export * from './noop.js'; 24 | export { tween } from './tween.js'; 25 | export { Queue } from './Queue.js'; 26 | export { SmartQueue } from './SmartQueue.js'; 27 | export * from './dom/index.js'; 28 | export * from './wait.js'; 29 | export * from './random.js'; 30 | export * from './memo.js'; 31 | export * from './loadElement.js'; 32 | export * from './cache.js'; 33 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/keyCodes.ts: -------------------------------------------------------------------------------- 1 | export const keyCodes = { 2 | ENTER: 13, 3 | SPACE: 32, 4 | TAB: 9, 5 | ESC: 27, 6 | LEFT: 37, 7 | UP: 38, 8 | RIGHT: 39, 9 | DOWN: 40, 10 | } as const; 11 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/math/clamp.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Clamp a value in a given range. 3 | * 4 | * @param {number} value 5 | * @param {number} min 6 | * @param {number} max 7 | * @returns {number} 8 | */ 9 | export function clamp(value: number, min: number, max: number) { 10 | /* eslint-disable no-nested-ternary */ 11 | return min < max 12 | ? value < min 13 | ? min 14 | : value > max 15 | ? max 16 | : value 17 | : value < max 18 | ? max 19 | : value > min 20 | ? min 21 | : value; 22 | /* eslint-enable no-nested-ternary */ 23 | } 24 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/math/clamp01.ts: -------------------------------------------------------------------------------- 1 | import { clamp } from './clamp.js'; 2 | 3 | /** 4 | * Clamp a value in the 0–1 range. 5 | * 6 | * @param {number} value 7 | * @returns {number} 8 | */ 9 | export function clamp01(value: number) { 10 | return clamp(value, 0, 1); 11 | } 12 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/math/createEases.ts: -------------------------------------------------------------------------------- 1 | export type EasingFunction = (progress: number) => number; 2 | 3 | /** 4 | * Create an out easing function. 5 | * 6 | * @param {EasingFunction} easeIn The ease in function. 7 | * @returns {EasingFunction} The out function. 8 | */ 9 | export function createEaseOut(easeIn: EasingFunction): EasingFunction { 10 | return (progress) => 1 - easeIn(1 - progress); 11 | } 12 | 13 | /** 14 | * Create an in-out easing function. 15 | * 16 | * @param {EasingFunction} easeIn The ease in function. 17 | * @returns {EasingFunction} The in-out function. 18 | */ 19 | export function createEaseInOut(easeIn: EasingFunction): EasingFunction { 20 | /* eslint-disable no-nested-ternary */ 21 | // eslint-disable-next-line no-confusing-arrow 22 | return (progress) => 23 | progress === 0 24 | ? 0 25 | : progress === 1 26 | ? 1 27 | : progress < 0.5 28 | ? easeIn(progress * 2) / 2 29 | : 1 - easeIn((1 - progress) * 2) / 2; 30 | } 31 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/math/createRange.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Create an array from a given range and with the given incremental step. 3 | */ 4 | export function createRange(min: number, max: number, step: number): number[] { 5 | const result: number[] = []; 6 | let value = min; 7 | 8 | while (value <= max) { 9 | result.push(value); 10 | value += step; 11 | } 12 | 13 | return result; 14 | } 15 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/math/damp.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Get the next damped value for a given factor. 3 | * 4 | * @param {number} targetValue The final value. 5 | * @param {number} currentValue The current value. 6 | * @param {number} [factor=0.5] The factor used to reach the target value. 7 | * @param {number} [precision=0.01] The precision used to calculate the latest value. 8 | * @returns {number} The next value. 9 | */ 10 | export function damp( 11 | targetValue: number, 12 | currentValue: number, 13 | factor = 0.5, 14 | precision = 0.01, 15 | ) { 16 | return Math.abs(targetValue - currentValue) < precision 17 | ? targetValue 18 | : currentValue + (targetValue - currentValue) * factor; 19 | } 20 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/math/index.ts: -------------------------------------------------------------------------------- 1 | export * as ease from './ease.js'; 2 | export * from './createEases.js'; 3 | export * from './ease.js'; 4 | export { createRange } from './createRange.js'; 5 | export { clamp } from './clamp.js'; 6 | export { clamp01 } from './clamp01.js'; 7 | export { damp } from './damp.js'; 8 | export { inertiaFinalValue } from './inertiaFinalValue.js'; 9 | export { lerp } from './lerp.js'; 10 | export { map } from './map.js'; 11 | export { round } from './round.js'; 12 | export { mean } from './mean.js'; 13 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/math/inertiaFinalValue.ts: -------------------------------------------------------------------------------- 1 | import { clamp } from './clamp.js'; 2 | 3 | /** 4 | * Get the final damped value for a given factor. 5 | * 6 | * @param {number} initialValue The final value. 7 | * @param {number} initialDelta The current value. 8 | * @param {number} [dampFactor=0.85] The speed to reach the target value. 9 | * @returns {number} The next value. 10 | */ 11 | export function inertiaFinalValue(initialValue: number, initialDelta: number, dampFactor = 0.85) { 12 | // eslint-disable-next-line no-param-reassign 13 | dampFactor = clamp(dampFactor, 0.00001, 0.99999); 14 | let delta = initialDelta; 15 | let finalValue = initialValue; 16 | 17 | while (Math.abs(delta) > 0.1) { 18 | finalValue += delta; 19 | delta *= dampFactor; 20 | } 21 | 22 | return finalValue; 23 | } 24 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/math/lerp.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Interpolate the ratio between a given interval. 3 | * 4 | * @param {number} min The interval minimum value. 5 | * @param {number} max The inverval maximum value. 6 | * @param {number} ratio The ratio to get. 7 | * @returns {number} The value between min and max corresponding to ratio. 8 | */ 9 | export function lerp(min: number, max: number, ratio: number) { 10 | return (1 - ratio) * min + ratio * max; 11 | } 12 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/math/map.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Maps the value from one range of [inputMin..inputMax] to another range of [outputMin..outputMax]. 3 | * 4 | * @param {number} value The value to map. 5 | * @param {number} inputMin The input's minimum value. 6 | * @param {number} inputMax The input's maximum value. 7 | * @param {number} outputMin The output's minimum value. 8 | * @param {number} outputMax The output's maximum value. 9 | * @returns {number} The input value mapped to the output range. 10 | */ 11 | export function map( 12 | value: number, 13 | inputMin: number, 14 | inputMax: number, 15 | outputMin: number, 16 | outputMax: number, 17 | ) { 18 | return ((value - inputMin) * (outputMax - outputMin)) / (inputMax - inputMin) + outputMin; 19 | } 20 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/math/mean.ts: -------------------------------------------------------------------------------- 1 | export function mean(numbers: number[]) { 2 | if (numbers.length === 0) { 3 | return 0; 4 | } 5 | 6 | if (numbers.length === 1) { 7 | return numbers[0]; 8 | } 9 | 10 | let sum = 0; 11 | for (const value of numbers) { 12 | sum += value; 13 | } 14 | 15 | return sum / numbers.length; 16 | } 17 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/math/round.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Round a value with a given number of decimals. 3 | * 4 | * @param {number} value The number to round. 5 | * @param {number} [decimals=0] The number of decimals to keep. 6 | * @returns {number} A rounded number to the given decimals length. 7 | */ 8 | export function round(value: number, decimals = 0) { 9 | return Number(value.toFixed(decimals)); 10 | } 11 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/memo.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Memoize the result of a function with a single argument indefinitely. 3 | * This is a simpler implementation of the `memoize` function. 4 | */ 5 | export function memo unknown>(fn: T): T { 6 | const cache = new Map>(); 7 | 8 | // @ts-ignore 9 | return function cached(...args) { 10 | const key = args.join(''); 11 | if (!cache.has(key)) { 12 | cache.set(key, fn(...args) as ReturnType); 13 | } 14 | return cache.get(key); 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/memoize.ts: -------------------------------------------------------------------------------- 1 | export interface MemoizeCache { 2 | has: (key: unknown) => boolean; 3 | set: (key: unknown, value: { data: T; date: number }) => this; 4 | get: (key: unknown) => { data: T; date: number }; 5 | } 6 | 7 | export interface MemoizeOptions { 8 | maxAge?: number; 9 | cacheKey?: (args: unknown[]) => string; 10 | cache?: MemoizeCache; 11 | } 12 | 13 | /** 14 | * Memoize the output of a function. 15 | */ 16 | export function memoize unknown>( 17 | fn: T, 18 | { 19 | maxAge = Number.POSITIVE_INFINITY, 20 | cacheKey = JSON.stringify, 21 | cache = new Map(), 22 | }: MemoizeOptions> = {}, 23 | ): (...args: Parameters) => ReturnType { 24 | return function memoized(...args): ReturnType { 25 | const key = cacheKey(args); 26 | const date = Date.now(); 27 | 28 | if (cache.has(key)) { 29 | const cached = cache.get(key); 30 | 31 | if (date - cached.date < maxAge) { 32 | return cached.data; 33 | } 34 | } 35 | 36 | // @ts-ignore 37 | const data: ReturnType = fn(...args); 38 | 39 | cache.set(key, { 40 | data, 41 | date: Date.now(), 42 | }); 43 | 44 | return data; 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/nextFrame.ts: -------------------------------------------------------------------------------- 1 | import { isFunction } from './is.js'; 2 | import { hasWindow } from './has.js'; 3 | 4 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 5 | type Callback = (time?: DOMHighResTimeStamp) => any; 6 | 7 | /** 8 | * Wait for the next frame to execute a function. 9 | */ 10 | export function nextFrame(): Promise; 11 | export function nextFrame(callback?: T): Promise>; 12 | export function nextFrame(callback?: T): Promise> { 13 | const fn = hasWindow() ? (window?.requestAnimationFrame ?? setTimeout) : setTimeout; 14 | return new Promise((resolve) => { 15 | fn((time) => resolve(isFunction(callback) ? callback(time) : time)); 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/nextMicrotask.ts: -------------------------------------------------------------------------------- 1 | import { isFunction } from './is.js'; 2 | 3 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 4 | type Callback = (...args: any[]) => any; 5 | 6 | /** 7 | * Wait for the next microtask. 8 | */ 9 | export async function nextMicrotask(): Promise; 10 | export async function nextMicrotask(callback?: T): Promise>; 11 | export async function nextMicrotask(callback?: T): Promise> { 12 | return Promise.resolve().then(() => isFunction(callback) && callback()); 13 | } 14 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/nextTick.ts: -------------------------------------------------------------------------------- 1 | import { isFunction } from './is.js'; 2 | import { wait } from './wait.js'; 3 | 4 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 5 | type Callback = (...args: any[]) => any; 6 | 7 | /** 8 | * Wait for the next tick. 9 | */ 10 | export async function nextTick(): Promise; 11 | export async function nextTick(callback?: T): Promise>; 12 | export async function nextTick(callback?: T): Promise> { 13 | return wait().then(isFunction(callback) && (callback as ReturnType)); 14 | } 15 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/noop.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * No operation function. 3 | * 4 | * @returns {void} 5 | */ 6 | export function noop() {} 7 | 8 | /** 9 | * No operation function which return the given value unaltered. 10 | */ 11 | export function noopValue(value: T): T { 12 | return value; 13 | } 14 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/object/getAllProperties.ts: -------------------------------------------------------------------------------- 1 | import { isFunction } from '../is.js'; 2 | 3 | /** 4 | * Gets all non-builtin properties up the prototype chain. 5 | * 6 | * @param {Object} object 7 | * The object to get the propeties from. 8 | * @param {Array} [props=[]] 9 | * The already existing properties. 10 | * @param {(name:string, proto:any) => boolean} testFn 11 | * @returns {Array<[string, Object]>} An array of properties and the prototype they belong to. 12 | */ 13 | export function getAllProperties( 14 | object: unknown, 15 | props: Array<[string, unknown]> = [], 16 | testFn: (name: string, proto: unknown) => boolean = null, 17 | ): Array<[string, unknown]> { 18 | const proto = Object.getPrototypeOf(object); 19 | 20 | if (proto === Object.prototype || proto === null) { 21 | return props; 22 | } 23 | 24 | let foundProps = Object.getOwnPropertyNames(proto); 25 | 26 | if (isFunction(testFn)) { 27 | foundProps = foundProps.filter((name) => testFn(name, proto)); 28 | } 29 | 30 | const formatedProps = foundProps 31 | .map<[string, unknown]>((name) => [name, proto]) 32 | .reduce>((acc, val) => [...acc, val], props); 33 | 34 | return getAllProperties(proto, formatedProps, testFn); 35 | } 36 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/random.ts: -------------------------------------------------------------------------------- 1 | import { isArray, isString } from './is.js'; 2 | 3 | /** 4 | * Get a random integer between bounds 5 | * 6 | * @param {number} a Lower bound 7 | * @param {number} b Upper bound 8 | * @returns {number} 9 | */ 10 | export function randomInt(a: number, b = 0): number { 11 | return Math.floor(Math.random() * (a - b + 1)) + b; 12 | } 13 | 14 | /** 15 | * Get a random item of an array or a random character of a string 16 | * 17 | * @param {T[] | string} items Array or string 18 | * @returns {T | undefined} 19 | */ 20 | export function randomItem(items: T[] | string): T | string | undefined { 21 | if (!isArray(items) && !isString(items)) { 22 | throw new Error('randomItem() expects an array or a string as argument.'); 23 | } 24 | return items.length > 0 ? items[randomInt(items.length - 1)] : undefined; 25 | } 26 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/string/endsWith.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Test if a string starts with another string. 3 | * 4 | * This is a more performant version of the `String.prototype.endsWith` method. 5 | * 6 | * @see https://jsbench.me/1hlkqqd0ff/2 7 | */ 8 | export function endsWith(string: string, search: string): boolean { 9 | if (search.length === 0) { 10 | return true; 11 | } 12 | 13 | if (search.length === 1) { 14 | // eslint-disable-next-line unicorn/prefer-at 15 | return string[string.length - 1] === search; 16 | } 17 | 18 | // eslint-disable-next-line unicorn/prefer-string-slice 19 | return string.substring(string.length - search.length) === search; 20 | } 21 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/string/index.ts: -------------------------------------------------------------------------------- 1 | export * from './withLeadingCharacters.js'; 2 | export * from './withLeadingSlash.js'; 3 | export * from './withoutLeadingCharacters.js'; 4 | export * from './withoutLeadingCharactersRecursive.js'; 5 | export * from './withoutLeadingSlash.js'; 6 | export * from './withoutTrailingCharacters.js'; 7 | export * from './withoutTrailingCharactersRecursive.js'; 8 | export * from './withoutTrailingSlash.js'; 9 | export * from './withTrailingCharacters.js'; 10 | export * from './withTrailingSlash.js'; 11 | export * from './startsWith.js'; 12 | export * from './endsWith.js'; 13 | export * from './changeCase.js'; 14 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/string/startsWith.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Test if a string starts with another string. 3 | * 4 | * This is a more performant version of the `String.prototype.startsWith` method. 5 | * 6 | * @see https://jsbench.me/1hlkqqd0ff/1 7 | */ 8 | export function startsWith(string: string, search: string): boolean { 9 | if (search.length === 0) { 10 | return true; 11 | } 12 | 13 | if (search.length === 1) { 14 | return string[0] === search; 15 | } 16 | 17 | // eslint-disable-next-line unicorn/prefer-string-slice 18 | return string.substring(0, search.length) === search; 19 | } 20 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/string/withLeadingCharacters.ts: -------------------------------------------------------------------------------- 1 | import { withoutLeadingCharacters } from './withoutLeadingCharacters.js'; 2 | 3 | /** 4 | * Add the given characters to the start of the given string. 5 | * 6 | * @param {string} string The string to modify. 7 | * @param {string} characters The characters to add to the start. 8 | * @returns {string} 9 | */ 10 | export function withLeadingCharacters(string: string, characters: string): string { 11 | return `${characters}${withoutLeadingCharacters(string, characters)}`; 12 | } 13 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/string/withLeadingSlash.ts: -------------------------------------------------------------------------------- 1 | import { withLeadingCharacters } from './withLeadingCharacters.js'; 2 | 3 | /** 4 | * Add a leading slash to a string. 5 | * 6 | * @param {string} string The string to modify. 7 | * @returns {string} The string with a leading slash. 8 | */ 9 | export function withLeadingSlash(string: string): string { 10 | return withLeadingCharacters(string, '/'); 11 | } 12 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/string/withTrailingCharacters.ts: -------------------------------------------------------------------------------- 1 | import { withoutTrailingCharacters } from './withoutTrailingCharacters.js'; 2 | 3 | /** 4 | * Add the given characters to the end of the given string. 5 | * 6 | * @param {string} string The string to modify. 7 | * @param {string} characters The characters to add to the end. 8 | * @returns {string} 9 | */ 10 | export function withTrailingCharacters(string: string, characters: string): string { 11 | return `${withoutTrailingCharacters(string, characters)}${characters}`; 12 | } 13 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/string/withTrailingSlash.ts: -------------------------------------------------------------------------------- 1 | import { withTrailingCharacters } from './withTrailingCharacters.js'; 2 | 3 | /** 4 | * Add a trailing slash to a string. 5 | * 6 | * @param {string} string The string to modify. 7 | * @returns {string} The string with a trailing slash. 8 | */ 9 | export function withTrailingSlash(string: string): string { 10 | return withTrailingCharacters(string, '/'); 11 | } 12 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/string/withoutLeadingCharacters.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Remove the given characters from the start of the given string. 3 | * 4 | * @param {string} string The string to modify. 5 | * @param {string} characters The characters to remove from the start. 6 | * @returns {string} 7 | */ 8 | export function withoutLeadingCharacters(string: string, characters: string): string { 9 | return string.replace(new RegExp(`^${characters}`), ''); 10 | } 11 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/string/withoutLeadingCharactersRecursive.ts: -------------------------------------------------------------------------------- 1 | import { withoutLeadingCharacters } from './withoutLeadingCharacters.js'; 2 | 3 | /** 4 | * Remove the given characters to the start of the given string recursively. 5 | * 6 | * @param {string} string The string to modify. 7 | * @param {string} characters The characters to add to the start. 8 | * @returns {string} 9 | */ 10 | export function withoutLeadingCharactersRecursive(string: string, characters: string): string { 11 | let str = withoutLeadingCharacters(string, characters); 12 | const regex = new RegExp(`^${characters}`); 13 | 14 | while (regex.test(str)) { 15 | str = withoutLeadingCharacters(str, characters); 16 | } 17 | 18 | return str; 19 | } 20 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/string/withoutLeadingSlash.ts: -------------------------------------------------------------------------------- 1 | import { withoutLeadingCharacters } from './withoutLeadingCharacters.js'; 2 | 3 | /** 4 | * Remove the leading slash from a string. 5 | * 6 | * @param {string} string The string to modify. 7 | * @returns {string} The string without leading slash. 8 | */ 9 | export function withoutLeadingSlash(string: string): string { 10 | return withoutLeadingCharacters(string, '/'); 11 | } 12 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/string/withoutTrailingCharacters.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Remove the given characters from the end of the given string. 3 | * 4 | * @param {string} string The string to modify. 5 | * @param {string} characters The characters to remove from the end. 6 | * @returns {string} 7 | */ 8 | export function withoutTrailingCharacters(string: string, characters: string): string { 9 | return string.replace(new RegExp(`${characters}$`), ''); 10 | } 11 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/string/withoutTrailingCharactersRecursive.ts: -------------------------------------------------------------------------------- 1 | import { withoutTrailingCharacters } from './withoutTrailingCharacters.js'; 2 | 3 | /** 4 | * Remove the given characters to the start of the given string recursively. 5 | * 6 | * @param {string} string The string to modify. 7 | * @param {string} characters The characters to add to the start. 8 | * @returns {string} 9 | */ 10 | export function withoutTrailingCharactersRecursive(string: string, characters: string): string { 11 | let str = withoutTrailingCharacters(string, characters); 12 | const regex = new RegExp(`${characters}$`); 13 | 14 | while (regex.test(str)) { 15 | str = withoutTrailingCharacters(str, characters); 16 | } 17 | 18 | return str; 19 | } 20 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/string/withoutTrailingSlash.ts: -------------------------------------------------------------------------------- 1 | import { withoutTrailingCharacters } from './withoutTrailingCharacters.js'; 2 | 3 | /** 4 | * Remove the trailing slash from a string. 5 | * 6 | * @param {string} string The string to modify. 7 | * @returns {string} The string without trailing slash. 8 | */ 9 | export function withoutTrailingSlash(string: string): string { 10 | return withoutTrailingCharacters(string, '/'); 11 | } 12 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/throttle.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple throttling helper that limits a function to only run once every {delay}ms. 3 | * 4 | * @param {Function} fn The function to throttle 5 | * @param {number} [delay] The delay in ms 6 | * @returns {Function} The throttled function. 7 | */ 8 | export function throttle( 9 | fn: (...args: unknown[]) => void, 10 | delay = 16, 11 | ): (...args: unknown[]) => void { 12 | let lastCall = 0; 13 | return function throttled(...args) { 14 | const now = Date.now(); 15 | if (now - lastCall < delay) { 16 | return; 17 | } 18 | lastCall = now; 19 | // eslint-disable-next-line consistent-return 20 | return fn(...args); 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /packages/js-toolkit/utils/wait.ts: -------------------------------------------------------------------------------- 1 | export function wait(delay = 0) { 2 | return new Promise((resolve) => { 3 | setTimeout(resolve, delay); 4 | }); 5 | } 6 | -------------------------------------------------------------------------------- /packages/tests/Base/__snapshots__/Base.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`A Base instance > should inherit from parent config 1`] = ` 4 | { 5 | "components": {}, 6 | "emits": [ 7 | "before-mounted", 8 | "mounted", 9 | "updated", 10 | "destroyed", 11 | "terminated", 12 | "ticked", 13 | "scrolled", 14 | "resized", 15 | "moved", 16 | "loaded", 17 | "keyed", 18 | "before-mounted", 19 | "mounted", 20 | "updated", 21 | "destroyed", 22 | "terminated", 23 | "ticked", 24 | "scrolled", 25 | "resized", 26 | "moved", 27 | "loaded", 28 | "keyed", 29 | ], 30 | "log": false, 31 | "name": "D", 32 | "options": { 33 | "color": [Function], 34 | "title": [Function], 35 | }, 36 | "refs": [], 37 | } 38 | `; 39 | -------------------------------------------------------------------------------- /packages/tests/Base/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import * as base from '#private/Base/index.js'; 3 | 4 | describe('Base/index.js exports', () => { 5 | it('should export a specific list of things', () => { 6 | expect(Object.keys(base).toSorted()).toMatchInlineSnapshot(` 7 | [ 8 | "Base", 9 | "getInstances", 10 | ] 11 | `); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/tests/Base/managers/RefsManager.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { Base, BaseConfig } from '@studiometa/js-toolkit'; 3 | import { h } from '#test-utils'; 4 | 5 | describe('The component resolution', () => { 6 | it('should convert refs name to camelCase', async () => { 7 | class App extends Base { 8 | static config: BaseConfig = { 9 | name: 'App', 10 | refs: ['my-long-ref-name', 'myOtherRefName'], 11 | }; 12 | } 13 | 14 | const ref = h('div', { dataRef: 'my-long-ref-name' }); 15 | const ref2 = h('div', { dataRef: 'myOtherRefName' }); 16 | const root = h('div', [ref, ref2]); 17 | const app = new App(root); 18 | await app.$mount(); 19 | expect(app.$refs.myLongRefName).toBe(ref); 20 | expect(app.$refs.myOtherRefName).toBe(ref2); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /packages/tests/__utils__/faketimers.ts: -------------------------------------------------------------------------------- 1 | import { vi } from 'vitest'; 2 | 3 | let isUsingFakeTimers = false; 4 | 5 | export function isFakeTime() { 6 | return isUsingFakeTimers; 7 | } 8 | 9 | export function useFakeTimers(fakeTimersConfig?: Parameters[0]) { 10 | vi.useFakeTimers(fakeTimersConfig); 11 | isUsingFakeTimers = true; 12 | } 13 | 14 | export function useRealTimers() { 15 | vi.useRealTimers(); 16 | isUsingFakeTimers = false; 17 | } 18 | 19 | export function advanceTimersByTime(msToRun: number) { 20 | vi.advanceTimersByTime(msToRun); 21 | } 22 | 23 | export async function advanceTimersByTimeAsync(msToRun: number) { 24 | return vi.advanceTimersByTimeAsync(msToRun); 25 | } 26 | 27 | export function runAllTimers() { 28 | vi.runAllTimers(); 29 | } 30 | -------------------------------------------------------------------------------- /packages/tests/__utils__/h.ts: -------------------------------------------------------------------------------- 1 | import { createElement } from '@studiometa/js-toolkit/utils'; 2 | 3 | /** 4 | * Create an HTMLElement and connect it to a document. 5 | */ 6 | export const hConnected: typeof createElement = (...args) => { 7 | const element = createElement(...args); 8 | const doc = new Document(); 9 | doc.append(element); 10 | return element; 11 | }; 12 | 13 | export { createElement as h }; 14 | -------------------------------------------------------------------------------- /packages/tests/__utils__/happydom.ts: -------------------------------------------------------------------------------- 1 | import { GlobalRegistrator } from '@happy-dom/global-registrator'; 2 | 3 | GlobalRegistrator.register(); 4 | 5 | window.__DEV__ = true; 6 | 7 | let y = 0; 8 | let x = 0; 9 | 10 | Object.defineProperties(window, { 11 | scrollY: { 12 | get: () => { 13 | return y; 14 | }, 15 | set: (value) => { 16 | y = Number(value); 17 | }, 18 | }, 19 | scrollX: { 20 | get: () => { 21 | return x; 22 | }, 23 | set: (value) => { 24 | x = Number(value); 25 | }, 26 | }, 27 | requestAnimationFrame: { 28 | value(callback) { 29 | return setTimeout(() => { 30 | callback(performance.now()); 31 | }, 16); 32 | }, 33 | }, 34 | }); 35 | -------------------------------------------------------------------------------- /packages/tests/__utils__/index.ts: -------------------------------------------------------------------------------- 1 | export * from './event.js'; 2 | export * from './faketimers.js'; 3 | export * from './h.js'; 4 | export * from './matchMedia.js'; 5 | export * from './mockFeatures.js'; 6 | export * from './mockIntersectionObserver.js'; 7 | export * from './mockLoad.js'; 8 | export * from './mockRequestIdleCallback.js'; 9 | export * from './resizeWindow.js'; 10 | export * from './scroll.js'; 11 | -------------------------------------------------------------------------------- /packages/tests/__utils__/mockFeatures.ts: -------------------------------------------------------------------------------- 1 | import { features } from '#private/Base/features'; 2 | 3 | const defaultBreakpoints = { 4 | xxs: '0rem', 5 | xs: '30rem', // 480px 6 | s: '48rem', // 768px 7 | m: '64rem', // 1024px 8 | l: '80rem', // 1280px 9 | xl: '90rem', // 1440px 10 | xxl: '120rem', // 1920px 11 | xxxl: '160rem', // 2560px 12 | }; 13 | 14 | const defaultAttributes = { 15 | component: 'data-component', 16 | option: 'data-option', 17 | ref: 'data-ref', 18 | }; 19 | 20 | type MockFeaturesOptions = Partial<{ 21 | blocking: boolean; 22 | breakpoints: Record; 23 | attributes: { 24 | component: string; 25 | option: string; 26 | ref: string; 27 | }; 28 | }>; 29 | 30 | export function mockFeatures({ 31 | blocking = true, 32 | breakpoints = defaultBreakpoints, 33 | attributes = defaultAttributes, 34 | }: MockFeaturesOptions = {}) { 35 | features.set('blocking', blocking); 36 | features.set('breakpoints', breakpoints); 37 | features.set('attributes', attributes); 38 | 39 | function unmock() { 40 | features.set('blocking', false); 41 | features.set('breakpoints', defaultBreakpoints); 42 | features.set('attributes', defaultAttributes); 43 | } 44 | 45 | return { features, unmock }; 46 | } 47 | -------------------------------------------------------------------------------- /packages/tests/__utils__/mockLoad.ts: -------------------------------------------------------------------------------- 1 | export function mockElementLoad(tag: string, prop = 'src') { 2 | const ctor = document.createElement(tag).constructor as any; 3 | 4 | const backup = ctor.prototype.setAttribute; 5 | ctor.prototype.setAttribute = function (name: string, value: string) { 6 | backup.call(this, name, value); 7 | if (name === prop) { 8 | this.dispatchEvent(new Event('load')); 9 | } 10 | }; 11 | 12 | return { 13 | unmock() { 14 | ctor.prototype.setAttribute = backup; 15 | }, 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /packages/tests/__utils__/mockRequestIdleCallback.ts: -------------------------------------------------------------------------------- 1 | const callbacks = []; 2 | 3 | globalThis.requestIdleCallback = function (callback) { 4 | callbacks.push(callback); 5 | }; 6 | 7 | export function mockRequestIdleCallback() { 8 | callbacks.forEach((callback) => callback()); 9 | } 10 | -------------------------------------------------------------------------------- /packages/tests/__utils__/resizeWindow.ts: -------------------------------------------------------------------------------- 1 | import { 2 | useFakeTimers, 3 | useRealTimers, 4 | advanceTimersByTimeAsync, 5 | isFakeTime, 6 | } from './faketimers.js'; 7 | 8 | export async function resizeWindow({ 9 | width = window.innerWidth, 10 | height = window.innerHeight, 11 | } = {}) { 12 | const hasFakeTimer = isFakeTime(); 13 | if (!hasFakeTimer) { 14 | useFakeTimers(); 15 | } 16 | window.innerWidth = width; 17 | window.innerHeight = height; 18 | window.dispatchEvent(new Event('resize')); 19 | await advanceTimersByTimeAsync(400); 20 | if (!hasFakeTimer) { 21 | useRealTimers(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/tests/decorators/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from 'vitest'; 2 | import * as decorators from '#private/decorators/index.js'; 3 | 4 | test('decorators exports', () => { 5 | expect(Object.keys(decorators).toSorted()).toMatchInlineSnapshot(` 6 | [ 7 | "withBreakpointManager", 8 | "withBreakpointObserver", 9 | "withDrag", 10 | "withExtraConfig", 11 | "withFreezedOptions", 12 | "withIntersectionObserver", 13 | "withMountOnMediaQuery", 14 | "withMountWhenInView", 15 | "withMountWhenPrefersMotion", 16 | "withMutation", 17 | "withName", 18 | "withRelativePointer", 19 | "withResponsiveOptions", 20 | "withScrolledInView", 21 | ] 22 | `); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/tests/decorators/withDrag.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, vi } from 'vitest'; 2 | import { Base, withDrag } from '@studiometa/js-toolkit'; 3 | import { h, createEvent } from '#test-utils'; 4 | 5 | describe('The `withDrag` decorator', () => { 6 | it('should add a `dragged` hook', async () => { 7 | const fn = vi.fn(); 8 | class Foo extends withDrag(Base) { 9 | static config = { name: 'Foo', emits: ['foo'] }; 10 | 11 | dragged(props) { 12 | fn(props); 13 | } 14 | } 15 | 16 | const div = h('div'); 17 | const foo = new Foo(div); 18 | await foo.$mount(); 19 | div.dispatchEvent(createEvent('pointerdown', { button: 0, x: 0, y: 0 })); 20 | expect(fn).toHaveBeenCalledTimes(1); 21 | await foo.$destroy(); 22 | div.dispatchEvent(createEvent('pointerdown', { button: 0, x: 0, y: 0 })); 23 | expect(fn).toHaveBeenCalledTimes(1); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/tests/decorators/withExtraConfig.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, vi } from 'vitest'; 2 | import { Base, BaseConfig, withExtraConfig } from '@studiometa/js-toolkit'; 3 | 4 | describe('The `withExtraConfig` decorator', () => { 5 | it('should merge config of a given class', () => { 6 | class Foo extends Base { 7 | static config: BaseConfig = { 8 | name: 'Foo', 9 | log: true, 10 | debug: true, 11 | }; 12 | } 13 | 14 | const Bar = withExtraConfig(Foo, { log: false, debug: false }); 15 | 16 | expect(Bar.config.log).toBe(false); 17 | expect(Bar.config.debug).toBe(false); 18 | expect(Bar.config.name).toBe('FooWithExtraConfig'); 19 | expect(withExtraConfig(Foo, { name: 'OtherName' }).config.name).toBe('OtherName'); 20 | }); 21 | 22 | it('should use deepmerge options', () => { 23 | class Foo extends Base { 24 | static config = { 25 | name: 'Foo', 26 | }; 27 | } 28 | 29 | const fn = vi.fn(); 30 | withExtraConfig(Foo, { refs: ['one'] }, { arrayMerge: fn }); 31 | expect(fn).toHaveBeenCalledTimes(1); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/tests/decorators/withMutation.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, vi } from 'vitest'; 2 | import { Base, withMutation } from '@studiometa/js-toolkit'; 3 | import { nextTick } from '@studiometa/js-toolkit/utils'; 4 | import { h } from '#test-utils'; 5 | 6 | describe('The `withMutation` decorator', () => { 7 | it('should add a `mutated` hook', async () => { 8 | const fn = vi.fn(); 9 | 10 | class Foo extends withMutation(Base) { 11 | static config = { 12 | name: 'Foo', 13 | }; 14 | 15 | mutated(props) { 16 | fn(props); 17 | } 18 | } 19 | 20 | const div = h('div'); 21 | const foo = new Foo(div); 22 | 23 | await foo.$mount(); 24 | div.classList.add('foo'); 25 | await nextTick(); 26 | 27 | expect(fn).toHaveBeenCalledTimes(1); 28 | 29 | await foo.$destroy(); 30 | div.classList.remove('foo'); 31 | 32 | expect(fn).toHaveBeenCalledTimes(1); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /packages/tests/decorators/withRelativePointer.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, vi } from 'vitest'; 2 | import { Base, withRelativePointer } from '@studiometa/js-toolkit'; 3 | 4 | function createEvent(type: string, data: Record = {}, options?: EventInit) { 5 | const event = new Event(type, options); 6 | for (const [name, value] of Object.entries(data)) { 7 | event[name] = value; 8 | } 9 | 10 | return event; 11 | } 12 | 13 | describe('The `withRelativePointer` decorator', () => { 14 | it('should add a `movedrelative` hook', async () => { 15 | const fn = vi.fn(); 16 | 17 | class Foo extends withRelativePointer(Base) { 18 | static config = { 19 | name: 'Foo', 20 | emits: ['foo'], 21 | }; 22 | 23 | movedrelative(props) { 24 | fn(props); 25 | } 26 | } 27 | 28 | const foo = new Foo(document.createElement('div')); 29 | await foo.$mount(); 30 | document.dispatchEvent(createEvent('mousemove', { button: 0, clientX: 0, clientY: 0 })); 31 | expect(fn).toHaveBeenCalledTimes(1); 32 | await foo.$destroy(); 33 | document.dispatchEvent(createEvent('mousemove', { button: 0, clientX: 0, clientY: 0 })); 34 | expect(fn).toHaveBeenCalledTimes(1); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/tests/helpers/getInstanceFromElement.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { Base, getInstanceFromElement } from '@studiometa/js-toolkit'; 3 | import { h } from '#test-utils'; 4 | 5 | describe('The `getInstanceFromElement` helper function', () => { 6 | class Foo extends Base { 7 | static config = { 8 | name: 'Foo', 9 | }; 10 | } 11 | 12 | it('should return `null` if element not given', () => { 13 | expect(getInstanceFromElement(null, Foo)).toBeNull(); 14 | }); 15 | 16 | it('should return `null` when instance not found', () => { 17 | const div = h('div'); 18 | expect(getInstanceFromElement(div, Foo)).toBeNull(); 19 | }); 20 | 21 | it('should return the instance attached to the given element', () => { 22 | const div = h('div'); 23 | const foo = new Foo(div); 24 | expect(getInstanceFromElement(div, Foo)).toBe(foo); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/tests/helpers/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from 'vitest'; 2 | import * as helpers from '#private/helpers/index.js'; 3 | 4 | test('helpers exports', () => { 5 | expect(Object.keys(helpers).toSorted()).toMatchInlineSnapshot(` 6 | [ 7 | "createApp", 8 | "getClosestParent", 9 | "getDirectChildren", 10 | "getInstanceFromElement", 11 | "importOnInteraction", 12 | "importOnMediaQuery", 13 | "importWhenIdle", 14 | "importWhenPrefersMotion", 15 | "importWhenVisible", 16 | "isDirectChild", 17 | ] 18 | `); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@studiometa/js-toolkit-tests", 3 | "version": "3.0.4", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "test": "vitest" 8 | }, 9 | "dependencies": { 10 | "@happy-dom/global-registrator": "^17.4.4", 11 | "@vitest/coverage-v8": "^3.1.1", 12 | "happy-dom": "^17.4.4", 13 | "vitest": "3.1.1" 14 | }, 15 | "imports": { 16 | "#test-utils": "./__utils__/index.ts", 17 | "#private/*": "../js-toolkit/*" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/tests/services/LoadService.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, vi } from 'vitest'; 2 | import { useLoad } from '@studiometa/js-toolkit'; 3 | import { dispatch } from '#test-utils'; 4 | 5 | describe('The `useLoad` service', () => { 6 | it('should trigger on load', () => { 7 | const service = useLoad(); 8 | const fn = vi.fn(); 9 | service.add('key', fn); 10 | dispatch(window, 'load'); 11 | expect(fn).toHaveBeenLastCalledWith(service.props()); 12 | fn.mockRestore(); 13 | service.remove('key'); 14 | dispatch(window, 'load'); 15 | expect(fn).not.toHaveBeenCalled(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/tests/services/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from 'vitest'; 2 | import * as services from '#private/services'; 3 | 4 | test('components exports', () => { 5 | expect(Object.keys(services).toSorted()).toMatchInlineSnapshot(` 6 | [ 7 | "AbstractService", 8 | "DragService", 9 | "KeyService", 10 | "LoadService", 11 | "MutationService", 12 | "PointerService", 13 | "RafService", 14 | "ResizeService", 15 | "ScrollService", 16 | "useDrag", 17 | "useKey", 18 | "useLoad", 19 | "useMutation", 20 | "usePointer", 21 | "useRaf", 22 | "useResize", 23 | "useScroll", 24 | ] 25 | `); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/tests/utils/Queue.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, vi } from 'vitest'; 2 | import { Queue, nextTick } from '@studiometa/js-toolkit/utils'; 3 | 4 | describe('The `Queue` class', () => { 5 | it('should run multiple functions in queue', async () => { 6 | const queue = new Queue(1, nextTick); 7 | const spy = vi.fn(); 8 | 9 | queue.add(spy); 10 | queue.add(spy); 11 | queue.add(spy); 12 | expect(spy).toHaveBeenCalledTimes(0); 13 | await nextTick(); 14 | expect(spy).toHaveBeenCalledTimes(1); 15 | await nextTick(); 16 | expect(spy).toHaveBeenCalledTimes(2); 17 | await nextTick(); 18 | expect(spy).toHaveBeenCalledTimes(3); 19 | }); 20 | 21 | it('should default to an immediate waiter', () => { 22 | const queue = new Queue(1); 23 | const spy = vi.fn(); 24 | 25 | queue.add(spy); 26 | queue.add(spy); 27 | queue.add(spy); 28 | expect(spy).toHaveBeenCalledTimes(3); 29 | }); 30 | 31 | it('should return a promise when adding a task', async () => { 32 | const queue = new Queue(1, nextTick); 33 | const spy = vi.fn(); 34 | const p = queue.add(spy); 35 | expect(p).toBeInstanceOf(Promise); 36 | expect(spy).toHaveBeenCalledTimes(0); 37 | await p; 38 | expect(spy).toHaveBeenCalledTimes(1); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /packages/tests/utils/SmartQueue.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, vi } from 'vitest'; 2 | import { SmartQueue, nextTick } from '@studiometa/js-toolkit/utils'; 3 | 4 | function task(duration = 1) { 5 | const start = performance.now(); 6 | let now = performance.now(); 7 | while (now - start < duration) { 8 | now = performance.now(); 9 | } 10 | } 11 | 12 | describe('The `SmartQueue` class', () => { 13 | it('should run multiple functions in queue without triggering long tasks', async () => { 14 | const queue = new SmartQueue(); 15 | const spy = vi.fn(task); 16 | 17 | queue.add(() => spy(15)); 18 | queue.add(() => spy(15)); 19 | queue.add(() => spy(15)); 20 | queue.add(() => spy(15)); 21 | 22 | expect(spy).toHaveBeenCalledTimes(0); 23 | await nextTick(); 24 | expect(spy).toHaveBeenCalledTimes(3); 25 | await nextTick(); 26 | expect(spy).toHaveBeenCalledTimes(4); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/tests/utils/cache.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { cache } from '@studiometa/js-toolkit/utils'; 3 | 4 | describe('cache function', () => { 5 | it('caches values with multiple keys', async () => { 6 | const keys = ['one', 'two', 'three']; 7 | const callback = () => new Map(); 8 | expect(cache(keys, callback)).toBe(cache(keys, callback)) 9 | }); 10 | 11 | it('caches values with single key', async () => { 12 | const key = 'one'; 13 | const callback = () => new Map(); 14 | expect(cache(key, callback)).toBe(cache(key, callback)) 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/tests/utils/collide/boundingRectToCircle.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { boundingRectToCircle } from '@studiometa/js-toolkit/utils'; 3 | 4 | describe('boundingRectToCircle method', () => { 5 | it('should be a circle object when a valid DOMRect is passed', () => { 6 | const clientRect = { x: 0, y: 0, width: 100, height: 100 }; 7 | const result = boundingRectToCircle(clientRect); 8 | 9 | expect(result).toEqual({ 10 | x: 50, 11 | y: 50, 12 | radius: 50, 13 | }); 14 | }); 15 | 16 | it('should throw an error when an invalid DOMRect is passed', () => { 17 | expect(() => { 18 | const clientRect = { x: 0, y: 0, width: 50, height: 100 }; 19 | boundingRectToCircle(clientRect); 20 | }).toThrow('Initial DOMElement is not a square. Please use the force mode.'); 21 | }); 22 | 23 | it('should be a circle object when an invalid DOMRect is passed with force mode', () => { 24 | const clientRect = { x: 0, y: 0, width: 150, height: 100 }; 25 | const result = boundingRectToCircle(clientRect, true); 26 | 27 | expect(result).toEqual({ 28 | x: 75, 29 | y: 50, 30 | radius: 62.5, 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/tests/utils/collide/collideCircleCircle.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { collideCircleCircle } from '@studiometa/js-toolkit/utils'; 3 | 4 | describe('collideCircleCircle method', () => { 5 | it('should be true when circles are colliding', () => { 6 | const circle1 = { 7 | x: 40, 8 | y: 40, 9 | radius: 40, 10 | }; 11 | 12 | const circle2 = { 13 | x: 100, 14 | y: 100, 15 | radius: 60, 16 | }; 17 | 18 | const result = collideCircleCircle(circle1, circle2); 19 | 20 | expect(result).toBe(true); 21 | }); 22 | 23 | it('should be false when circles are separated', () => { 24 | const circle1 = { 25 | x: 40, 26 | y: 40, 27 | radius: 40, 28 | }; 29 | 30 | const circle2 = { 31 | x: 120, 32 | y: 120, 33 | radius: 40, 34 | }; 35 | 36 | const result = collideCircleCircle(circle1, circle2); 37 | 38 | expect(result).toBe(false); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /packages/tests/utils/collide/collidePointCircle.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { collidePointCircle } from '@studiometa/js-toolkit/utils'; 3 | 4 | describe('collidePointCircle method', () => { 5 | it('should be true when the point is inside the circle', () => { 6 | const point = { 7 | x: 25, 8 | y: 25, 9 | }; 10 | 11 | const circle = { 12 | x: 40, 13 | y: 40, 14 | radius: 40, 15 | }; 16 | 17 | const result = collidePointCircle(point, circle); 18 | 19 | expect(result).toBe(true); 20 | }); 21 | 22 | it('should be false when the point is outside the circle', () => { 23 | const point = { 24 | x: 10, 25 | y: 10, 26 | }; 27 | 28 | const circle = { 29 | x: 40, 30 | y: 40, 31 | radius: 40, 32 | }; 33 | 34 | const result = collidePointCircle(point, circle); 35 | 36 | expect(result).toBe(false); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /packages/tests/utils/collide/collidePointRect.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { collidePointRect } from '@studiometa/js-toolkit/utils'; 3 | 4 | describe('collidePointRect method', () => { 5 | it('should be true when the point is inside the rectangle', () => { 6 | const point = { 7 | x: 0, 8 | y: 0, 9 | }; 10 | 11 | const rect = { 12 | x: 0, 13 | y: 0, 14 | width: 20, 15 | height: 20, 16 | }; 17 | 18 | const result = collidePointRect(point, rect); 19 | 20 | expect(result).toBe(true); 21 | }); 22 | 23 | it('should be false when the point is outside the rectangle', () => { 24 | const point = { 25 | x: 40, 26 | y: 40, 27 | }; 28 | 29 | const rect = { 30 | x: 0, 31 | y: 0, 32 | width: 20, 33 | height: 20, 34 | }; 35 | 36 | const result = collidePointRect(point, rect); 37 | 38 | expect(result).toBe(false); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /packages/tests/utils/css/matrix.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { matrix } from '@studiometa/js-toolkit/utils'; 3 | 4 | describe('matrix method', () => { 5 | it('should work without arguments', () => { 6 | expect(matrix()).toBe('matrix(1, 0, 0, 1, 0, 0)'); 7 | }); 8 | 9 | it('should work with some arguments', () => { 10 | expect(matrix({ scaleX: 2 })).toBe('matrix(2, 0, 0, 1, 0, 0)'); 11 | }); 12 | 13 | it('should work with 0 values', () => { 14 | expect(matrix({ scaleX: 0, scaleY: 0 })).toBe('matrix(0, 0, 0, 0, 0, 0)'); 15 | }); 16 | 17 | it('should return the default value when the parameter is not an object', () => { 18 | expect(matrix(true)).toBe('matrix(1, 0, 0, 1, 0, 0)'); 19 | expect(matrix(false)).toBe('matrix(1, 0, 0, 1, 0, 0)'); 20 | expect(matrix('string')).toBe('matrix(1, 0, 0, 1, 0, 0)'); 21 | expect(matrix(10)).toBe('matrix(1, 0, 0, 1, 0, 0)'); 22 | expect(matrix([1, 2, 3])).toBe('matrix(1, 0, 0, 1, 0, 0)'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/tests/utils/css/styles.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { addStyle as add, removeStyle as remove } from '@studiometa/js-toolkit/utils'; 3 | 4 | describe('styles methods', () => { 5 | const element = document.createElement('div'); 6 | 7 | it('should add styles to an element', () => { 8 | add(element, { display: 'block', width: '100px' }); 9 | expect(element.style.cssText).toBe('display: block; width: 100px;'); 10 | }); 11 | 12 | it('should remove styles from an element', () => { 13 | remove(element, { display: 'block' }); 14 | expect(element.style.cssText).toBe('width: 100px;'); 15 | remove(element, { width: '100px' }); 16 | expect(element.style.cssText).toBe(''); 17 | }); 18 | 19 | it('should fail silently', () => { 20 | expect(add(element, 'foo')).toBeUndefined(); 21 | expect(add(element)).toBeUndefined(); 22 | expect(add()).toBeUndefined(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/tests/utils/debounce.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; 2 | import { debounce } from '@studiometa/js-toolkit/utils'; 3 | import { useFakeTimers, useRealTimers, advanceTimersByTime } from '#test-utils'; 4 | 5 | beforeEach(() => useFakeTimers()); 6 | afterEach(() => useRealTimers()); 7 | 8 | describe('debounce method', () => { 9 | it('should wait the given delay to call given function', async () => { 10 | const fn = vi.fn(() => true); 11 | const debounced = debounce(fn, 400); 12 | 13 | debounced(); 14 | debounced(); 15 | debounced(); 16 | debounced(); 17 | 18 | expect(fn).not.toHaveBeenCalled(); 19 | 20 | advanceTimersByTime(150); 21 | expect(fn).not.toHaveBeenCalled(); 22 | 23 | advanceTimersByTime(400); 24 | expect(fn).toHaveBeenCalledTimes(1); 25 | }); 26 | 27 | it('should wait for 300ms when used without the delay parameter', async () => { 28 | const fn = vi.fn(); 29 | const debounced = debounce(fn); 30 | 31 | debounced(); 32 | debounced(); 33 | 34 | advanceTimersByTime(200); 35 | expect(fn).not.toHaveBeenCalled(); 36 | advanceTimersByTime(301); 37 | expect(fn).toHaveBeenCalledTimes(1); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /packages/tests/utils/history.server.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { objectToURLSearchParams } from '@studiometa/js-toolkit/utils'; 3 | 4 | describe('The `objectToURLSearchParams` method', () => { 5 | it('should work server side', () => { 6 | expect(objectToURLSearchParams({ foo: 'bar' })).toBeInstanceOf(URLSearchParams); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /packages/tests/utils/isDefined.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, test as it, expect } from 'vitest'; 2 | import { isDefined } from '@studiometa/js-toolkit/utils'; 3 | 4 | describe('The `isDefined` utility function', () => { 5 | it('should return true when given something defined', () => { 6 | expect(isDefined(() => {})).toBe(true); 7 | expect(isDefined(function noop() {})).toBe(true); 8 | expect(isDefined(expect)).toBe(true); 9 | expect(isDefined('string')).toBe(true); 10 | expect(isDefined(123)).toBe(true); 11 | expect(isDefined(true)).toBe(true); 12 | expect(isDefined(false)).toBe(true); 13 | expect(isDefined({})).toBe(true); 14 | expect(isDefined([])).toBe(true); 15 | expect(isDefined(document)).toBe(true); 16 | }); 17 | 18 | it('should return false when given `undefined`', () => { 19 | expect(isDefined({}.bar)).toBe(false); 20 | // eslint-disable-next-line unicorn/no-useless-undefined 21 | expect(isDefined(undefined)).toBe(false); 22 | expect(isDefined([][0])).toBe(false); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/tests/utils/isFunction.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { isFunction } from '@studiometa/js-toolkit/utils'; 3 | 4 | describe('The `isFunction` utility function', () => { 5 | it('should return true when given a function', () => { 6 | expect(isFunction(() => {})).toBe(true); 7 | expect(isFunction(function noop() {})).toBe(true); 8 | expect(isFunction(expect)).toBe(true); 9 | }); 10 | 11 | it('should return false when given a value which is not a function', () => { 12 | expect(isFunction('string')).toBe(false); 13 | expect(isFunction(123)).toBe(false); 14 | expect(isFunction(true)).toBe(false); 15 | expect(isFunction(false)).toBe(false); 16 | expect(isFunction({})).toBe(false); 17 | expect(isFunction([])).toBe(false); 18 | expect(isFunction(document)).toBe(false); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/tests/utils/math/clamp.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { clamp } from '@studiometa/js-toolkit/utils'; 3 | 4 | describe('clamp method', () => { 5 | it('should clamp a value between the given range', () => { 6 | expect(clamp(0, 0, 10)).toBe(0); 7 | expect(clamp(-5, 0, 10)).toBe(0); 8 | expect(clamp(15, 0, 10)).toBe(10); 9 | expect(clamp(5, 0, 10)).toBe(5); 10 | expect(clamp(5, 10, 0)).toBe(5); 11 | expect(clamp(-5, 10, 0)).toBe(0); 12 | expect(clamp(15, 10, 0)).toBe(10); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /packages/tests/utils/math/clamp01.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { clamp01 } from '@studiometa/js-toolkit/utils'; 3 | 4 | describe('clamp01 method', () => { 5 | it('should clamp a value between 0 and 1', () => { 6 | expect(clamp01(0)).toBe(0); 7 | expect(clamp01(0.5)).toBe(0.5); 8 | expect(clamp01(1)).toBe(1); 9 | expect(clamp01(-1)).toBe(0); 10 | expect(clamp01(2)).toBe(1); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/tests/utils/math/createRange.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { createRange } from '@studiometa/js-toolkit/utils'; 3 | 4 | describe('The `createRange` function', () => { 5 | it('should create an array of numbers', () => { 6 | expect(createRange(0, 10, 2)).toEqual([0, 2, 4, 6, 8, 10]); 7 | }); 8 | 9 | it('should not work when min is greater than max', () => { 10 | expect(createRange(10, 0, 2)).not.toEqual([10, 8, 6, 4, 2, 0]); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/tests/utils/math/damp.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { damp } from '@studiometa/js-toolkit/utils'; 3 | 4 | describe('damp method', () => { 5 | it('should give the correct value', () => { 6 | expect(damp(10, 0, 0.5)).toBe(5); 7 | expect(damp(10, 5, 0.5)).toBe(7.5); 8 | expect(damp(10, 10, 0.5)).toBe(10); 9 | expect(damp(10, 0, 0.25)).toBe(2.5); 10 | expect(damp(10, 5, 0.25)).toBe(6.25); 11 | expect(damp(10, 10, 0.25)).toBe(10); 12 | expect(damp(10, 10, 1)).toBe(10); 13 | }); 14 | 15 | it('should round the result when the difference between the current and target value is small', () => { 16 | expect(damp(10, 9.9999)).toBe(10); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/tests/utils/math/ease.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { ease } from '@studiometa/js-toolkit/utils'; 3 | 4 | const values = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]; 5 | 6 | describe('ease methods', () => { 7 | for (const key of Object.keys(ease)) { 8 | if (key.startsWith('ease')) { 9 | for (const value of values) { 10 | it(`the "${key}" method should give the correct value for "${value}"`, () => { 11 | expect(ease[key](value).toFixed(3)).toMatchSnapshot(); 12 | }); 13 | } 14 | } 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /packages/tests/utils/math/inertiaFinalValue.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { inertiaFinalValue } from '@studiometa/js-toolkit/utils'; 3 | 4 | describe('The inertiaFinalValue function', () => { 5 | it('should have a minium theshold of 0.1', () => { 6 | const value = inertiaFinalValue(100, 0.01, 0.5); 7 | expect(value).toBe(100); 8 | }); 9 | 10 | it('should return the correct value', () => { 11 | expect(inertiaFinalValue(100, 10, 0.1)).toBe(111); 12 | expect(inertiaFinalValue(100, 10, 0.5)).toBe(119.84375); 13 | expect(inertiaFinalValue(100, 10, 0.75)).toBe(139.69932212727144); 14 | expect(inertiaFinalValue(100, 10, 0.9)).toBe(199.0302262702125); 15 | }); 16 | 17 | it('should have a default factor of 0.85', () => { 18 | expect(inertiaFinalValue(100, 10)).toBe(166.06817571805576); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/tests/utils/math/lerp.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { lerp } from '@studiometa/js-toolkit/utils'; 3 | 4 | describe('lerp method', () => { 5 | it('should return the correct number', () => { 6 | expect(lerp(0, 10, 0)).toBe(0); 7 | expect(lerp(0, 10, 0.25)).toBe(2.5); 8 | expect(lerp(0, 10, 0.5)).toBe(5); 9 | expect(lerp(0, 10, 0.75)).toBe(7.5); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/tests/utils/math/map.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { map } from '@studiometa/js-toolkit/utils'; 3 | 4 | describe('map method', () => { 5 | it('should map values', () => { 6 | expect(map(0, 0, 1, 0, 10)).toBe(0); 7 | expect(map(0.5, 0, 1, 0, 10)).toBe(5); 8 | expect(map(1, 0, 1, 0, 10)).toBe(10); 9 | expect(map(2, 0, 1, 0, 10)).toBe(20); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/tests/utils/math/mean.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { mean } from '@studiometa/js-toolkit/utils'; 3 | 4 | describe('mean method', () => { 5 | it('should mean values', () => { 6 | expect(mean([])).toBe(0); 7 | expect(mean([1])).toBe(1); 8 | expect(mean([0, 2])).toBe(1); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /packages/tests/utils/math/round.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { round } from '@studiometa/js-toolkit/utils'; 3 | 4 | describe('round method', () => { 5 | const number = 50.23456789; 6 | 7 | it('should round to 0 decimal', () => { 8 | expect(round(number)).toBe(50); 9 | }); 10 | 11 | it('should round to 1 decimal', () => { 12 | expect(round(number, 1)).toBe(50.2); 13 | }); 14 | 15 | it('should round to 2 decimal', () => { 16 | expect(round(number, 2)).toBe(50.23); 17 | }); 18 | 19 | it('should round to 3 decimal', () => { 20 | expect(round(number, 3)).toBe(50.235); 21 | }); 22 | 23 | it('should round to 4 decimal', () => { 24 | expect(round(number, 4)).toBe(50.2346); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/tests/utils/memoize.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, vi } from 'vitest'; 2 | import { memoize } from '@studiometa/js-toolkit/utils'; 3 | import { useFakeTimers, useRealTimers, runAllTimers } from '#test-utils'; 4 | 5 | describe('The `memoize` function', () => { 6 | it('should cache results', () => { 7 | const fn = vi.fn((a, b) => a + b); 8 | const memFn = memoize(fn); 9 | expect(memFn(1, 2)).toBe(3); 10 | expect(memFn(1, 2)).toBe(3); 11 | expect(fn).toHaveBeenCalledTimes(1); 12 | expect(fn).toHaveBeenCalledWith(1, 2); 13 | }); 14 | 15 | it('should cache results of async functions', () => { 16 | const fn = vi.fn((arg) => { 17 | return new Promise((resolve) => { 18 | setTimeout(() => resolve(arg), 500); 19 | }); 20 | }); 21 | useFakeTimers(); 22 | const memFn = memoize(fn); 23 | expect(memFn('foo')).toBe(memFn('foo')); 24 | runAllTimers(); 25 | expect(memFn('foo')).toBe(memFn('foo')); 26 | useRealTimers(); 27 | }); 28 | 29 | it('should return new data if `maxAge` is reached', () => { 30 | const fn = vi.fn((a, b) => a + b); 31 | const memFn = memoize(fn, { maxAge: 0 }); 32 | expect(memFn(1, 2)).toBe(3); 33 | expect(memFn(1, 2)).toBe(3); 34 | expect(fn).toHaveBeenCalledTimes(2); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/tests/utils/nextFrame.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; 2 | import { nextFrame } from '@studiometa/js-toolkit/utils'; 3 | import { useFakeTimers, useRealTimers, runAllTimers, advanceTimersByTime } from '#test-utils'; 4 | 5 | beforeEach(() => useFakeTimers()); 6 | afterEach(() => useRealTimers()); 7 | 8 | describe('nextFrame method', () => { 9 | it('should execute the callback function in the next frame', () => { 10 | const fn = vi.fn(); 11 | nextFrame(fn); 12 | expect(fn).toHaveBeenCalledTimes(0); 13 | runAllTimers(); 14 | expect(fn).toHaveBeenCalledTimes(1); 15 | }); 16 | 17 | it('should work without callback', () => { 18 | expect(nextFrame()).toBeInstanceOf(Promise); 19 | }); 20 | 21 | it('should work server-side', () => { 22 | const fn = vi.fn(); 23 | vi.mock('../../js-toolkit/utils/has.js', () => ({ 24 | hasWindow: () => false, 25 | })); 26 | 27 | nextFrame(fn); 28 | expect(fn).toHaveBeenCalledTimes(0); 29 | advanceTimersByTime(16); 30 | expect(fn).toHaveBeenCalledTimes(1); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/tests/utils/nextTick.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, vi } from 'vitest'; 2 | import { nextTick, nextFrame, nextMicrotask } from '@studiometa/js-toolkit/utils'; 3 | 4 | describe('nextTick method', () => { 5 | it('should execute in order', async () => { 6 | const fn = vi.fn(); 7 | 8 | fn('start'); 9 | const promises = [ 10 | nextTick(() => fn('nextTick #1')), 11 | nextTick().then(() => fn('nextTick #2')), 12 | nextFrame(() => fn('nextFrame #1')), 13 | nextFrame().then(() => fn('nextFrame #2')), 14 | nextMicrotask().then(() => fn('nextMicrotask #2')), 15 | nextMicrotask(() => fn('nextMicrotask #1')), 16 | ]; 17 | fn('end'); 18 | 19 | await Promise.all(promises); 20 | 21 | expect(fn.mock.calls).toEqual([ 22 | ['start'], 23 | ['end'], 24 | ['nextMicrotask #1'], 25 | ['nextMicrotask #2'], 26 | ['nextTick #1'], 27 | ['nextTick #2'], 28 | ['nextFrame #1'], 29 | ['nextFrame #2'], 30 | ]); 31 | expect(fn).toHaveBeenCalledTimes(8); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/tests/utils/string/endsWith.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { endsWith } from '@studiometa/js-toolkit/utils'; 3 | 4 | describe('The endsWith function', () => { 5 | it('should work with 0 letter search', () => { 6 | expect(endsWith('oof', '')).toBe(true); 7 | expect(endsWith('oof', '')).toBe(true); 8 | }); 9 | 10 | it('should work with one letter search', () => { 11 | expect(endsWith('oof', 'f')).toBe(true); 12 | expect(endsWith('oof', 'b')).toBe(false); 13 | }); 14 | 15 | it('should work with multiple letters search', () => { 16 | expect(endsWith('foo', 'foo')).toBe(true); 17 | expect(endsWith('foo', 'bar')).toBe(false); 18 | }); 19 | 20 | it('should work with special characters', () => { 21 | expect(endsWith('foo[]', '[]')).toBe(true); 22 | expect(endsWith('foo', '[]')).toBe(false); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/tests/utils/string/startsWith.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { startsWith } from '@studiometa/js-toolkit/utils'; 3 | 4 | describe('The startsWith function', () => { 5 | it('should work with 0 letter search', () => { 6 | expect(startsWith('foo', '')).toBe(true); 7 | expect(startsWith('foo', '')).toBe(true); 8 | }); 9 | 10 | it('should work with one letter search', () => { 11 | expect(startsWith('foo', 'f')).toBe(true); 12 | expect(startsWith('foo', 'b')).toBe(false); 13 | }); 14 | 15 | it('should work with multiple letters search', () => { 16 | expect(startsWith('foo', 'foo')).toBe(true); 17 | expect(startsWith('foo', 'bar')).toBe(false); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/tests/utils/throttle.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; 2 | import { throttle } from '@studiometa/js-toolkit/utils'; 3 | import { useFakeTimers, useRealTimers, advanceTimersByTime } from '#test-utils'; 4 | 5 | beforeEach(() => useFakeTimers()); 6 | afterEach(() => useRealTimers()); 7 | 8 | describe('throttle method', () => { 9 | it('should call the given function only once in the given delay', async () => { 10 | const fn = vi.fn(() => true); 11 | const throttled = throttle(fn, 300); 12 | 13 | throttled(); 14 | throttled(); 15 | throttled(); 16 | throttled(); 17 | 18 | expect(fn).toHaveBeenCalledTimes(1); 19 | 20 | advanceTimersByTime(400); 21 | 22 | throttled(); 23 | throttled(); 24 | throttled(); 25 | throttled(); 26 | advanceTimersByTime(100); 27 | 28 | expect(fn).toHaveBeenCalledTimes(2); 29 | }); 30 | 31 | it('should call the callback after 16ms when no delay provided', async () => { 32 | const fn = vi.fn(() => true); 33 | const throttled = throttle(fn); 34 | 35 | throttled(); 36 | advanceTimersByTime(10); 37 | throttled(); 38 | expect(fn).toHaveBeenCalledTimes(1); 39 | 40 | advanceTimersByTime(20); 41 | throttled(); 42 | throttled(); 43 | expect(fn).toHaveBeenCalledTimes(2); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /packages/tests/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | root: '..', 6 | environment: 'happy-dom', 7 | alias: { 8 | '^#private/(.*)': '../js-toolkit/$1', 9 | }, 10 | setupFiles: ['./tests/__utils__/happydom.ts'], 11 | coverage: { 12 | provider: 'v8', 13 | include: ['js-toolkit/**'], 14 | }, 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | import config from '@studiometa/prettier-config'; 2 | 3 | export default { 4 | ...config, 5 | overrides: [ 6 | ...config.overrides, 7 | { 8 | files: 'packages/docs/**/*.md', 9 | options: { 10 | printWidth: 78, 11 | }, 12 | }, 13 | ], 14 | }; 15 | -------------------------------------------------------------------------------- /scripts/add-utils-export.js: -------------------------------------------------------------------------------- 1 | import { resolve, dirname } from 'node:path'; 2 | import { writeFileSync } from 'node:fs'; 3 | 4 | const index = resolve( 5 | dirname(new URL(import.meta.url).pathname), 6 | '../packages/js-toolkit/index.ts', 7 | ); 8 | 9 | const content = ` 10 | import * as BASE from './base/index.js'; 11 | import * as DECORATORS from './decorators/index.js'; 12 | import * as HELPERS from './helpers/index.js'; 13 | import * as SERVICES from './services/index.js'; 14 | import * as UTILS from './utils/index.js'; 15 | 16 | export * from './Base/index.js'; 17 | export * from './decorators/index.js'; 18 | export * from './helpers/index.js'; 19 | export * from './services/index.js'; 20 | export * from './utils/index.js'; 21 | 22 | export { BASE, DECORATORS, HELPERS, SERVICES, UTILS }; 23 | 24 | export const FRAMEWORK = { 25 | BASE, 26 | DECORATORS, 27 | HELPERS, 28 | SERVICES, 29 | }; 30 | 31 | export const ALL = { 32 | FRAMEWORK, 33 | UTILS, 34 | }; 35 | `; 36 | 37 | writeFileSync(index, content, { encoding: 'UTF-8' }); 38 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | import { resolve, dirname } from 'node:path'; 2 | import glob from 'fast-glob'; 3 | import esbuild from 'esbuild'; 4 | 5 | const root = resolve(dirname(new URL(import.meta.url).pathname), '..'); 6 | 7 | const options = { 8 | entryPoints: glob.globSync(['packages/js-toolkit/**/*.ts', '!**/node_modules/**'], { 9 | cwd: root, 10 | }), 11 | write: true, 12 | outdir: resolve(root, 'dist'), 13 | target: 'esnext', 14 | format: 'esm', 15 | sourcemap: true, 16 | }; 17 | 18 | console.log(`Building...`); 19 | const { errors, warnings } = await esbuild.build(options); 20 | 21 | errors.forEach(console.error); 22 | warnings.forEach(console.warn); 23 | 24 | console.log(`Done building!`); 25 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": false, 5 | "declaration": true, 6 | "outDir": "dist/", 7 | "emitDeclarationOnly": true, 8 | "types": [] 9 | }, 10 | "include": [ 11 | "packages/global.d.ts", 12 | "packages/js-toolkit/**/*.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "lib": ["ESNext", "dom", "dom.iterable", "ES2015", "ES2016", "ES2017", "ES2018", "ES2019", "ES2020"], 6 | "allowJs": true, 7 | "checkJs": true, 8 | "strict": false, 9 | "noEmit": true, 10 | "noImplicitThis": true, 11 | "esModuleInterop": true, 12 | "moduleResolution": "node", 13 | "baseUrl": "./packages/js-toolkit/", 14 | "paths": { 15 | "#private/*": ["./*"], 16 | "#test-utils": ["../tests/__utils__/index.ts"] 17 | } 18 | }, 19 | "include": [ 20 | "packages/global.d.ts", 21 | "packages/js-toolkit/**/*.ts", 22 | "packages/tests/**/*.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /tsconfig.lint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": true, 5 | "types": [] 6 | }, 7 | "include": [ 8 | "packages/global.d.ts", 9 | "packages/js-toolkit/**/*.ts" 10 | ] 11 | } 12 | --------------------------------------------------------------------------------