├── CNAME ├── .gitignore ├── docs ├── .npmrc ├── src │ ├── routes │ │ ├── +layout.server.ts │ │ ├── +layout.svelte │ │ ├── docs │ │ │ ├── guide │ │ │ │ └── [slug] │ │ │ │ │ ├── +page.server.ts │ │ │ │ │ └── +page.svelte │ │ │ ├── core │ │ │ │ └── [utility] │ │ │ │ │ ├── +page.ts │ │ │ │ │ └── +page.server.ts │ │ │ └── OnThisPage.svelte │ │ ├── +page.server.ts │ │ ├── ThemeHandler.svelte │ │ ├── +page.svelte │ │ └── Navigation.svelte │ ├── lib │ │ ├── utils │ │ │ ├── paths.ts │ │ │ ├── cn.ts │ │ │ └── text-transform.ts │ │ ├── components │ │ │ └── atoms │ │ │ │ ├── index.ts │ │ │ │ ├── Button.svelte │ │ │ │ ├── TextArea.svelte │ │ │ │ └── Input.svelte │ │ ├── contexts │ │ │ ├── navigation.svelte.ts │ │ │ └── theme.svelte.ts │ │ └── types │ │ │ └── markdown.ts │ ├── app.d.ts │ ├── app.css │ └── app.html ├── README.md ├── static │ ├── favicon.png │ └── logos │ │ └── github │ │ ├── dark.svg │ │ └── light.svg ├── postcss.config.js ├── .prettierignore ├── content │ └── docs │ │ ├── core │ │ ├── get-fps │ │ │ ├── Demo.svelte │ │ │ └── index.md │ │ ├── get-battery │ │ │ ├── Demo.svelte │ │ │ └── index.md │ │ ├── get-network │ │ │ ├── Demo.svelte │ │ │ └── index.md │ │ ├── has-left-page │ │ │ ├── Demo.svelte │ │ │ └── index.md │ │ ├── get-geolocation │ │ │ ├── Demo.svelte │ │ │ └── index.md │ │ ├── get-device-motion │ │ │ ├── Demo.svelte │ │ │ └── index.md │ │ ├── get-text-direction │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── get-device-pixel-ratio │ │ │ ├── Demo.svelte │ │ │ └── index.md │ │ ├── get-device-orientation │ │ │ ├── Demo.svelte │ │ │ └── index.md │ │ ├── get-mouse │ │ │ ├── Demo.svelte │ │ │ └── index.md │ │ ├── get-permission │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── is-window-focused │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── get-active-element │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── get-mouse-pressed │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── local-state │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── on-swipe │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── session-state │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── handle-event-listener │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── get-last-changed │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── debounce │ │ │ ├── Demo.svelte │ │ │ └── index.md │ │ ├── get-text-selection │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── debounced-state │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── create-eye-dropper │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── create-web-notification │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── observe-mutation │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── on-long-press │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── on-start-typing │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── get-document-visibility │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── auto-reset-state │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── observe-performance │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── whenever │ │ │ └── index.md │ │ ├── on-hover │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── get-scrollbar-width │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── get-previous │ │ │ ├── Demo.svelte │ │ │ └── index.md │ │ ├── on-click-outside │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── get-element-size │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── observe-intersection │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── track-history │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── observe-resize │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── create-object-url │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── get-clipboard-text │ │ │ ├── Demo.svelte │ │ │ └── index.md │ │ ├── create-speech-recognition │ │ │ └── index.md │ │ ├── default-state │ │ │ └── index.md │ │ ├── handle-wake-lock │ │ │ ├── Demo.svelte │ │ │ └── index.md │ │ ├── create-share │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── create-drop-zone │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── create-file-dialog │ │ │ ├── Demo.svelte │ │ │ └── index.md │ │ ├── history-state │ │ │ ├── index.md │ │ │ └── Demo.svelte │ │ ├── create-vibration │ │ │ ├── Demo.svelte │ │ │ └── index.md │ │ ├── async-state │ │ │ ├── Demo.svelte │ │ │ └── index.md │ │ └── watch │ │ │ └── index.md │ │ └── guide │ │ ├── introduction.md │ │ └── limitations.md ├── vite.config.ts ├── .gitignore ├── .prettierrc ├── svelte.config.js ├── tailwind.config.ts ├── eslint.config.js ├── tsconfig.json └── package.json ├── packages └── core │ ├── .npmrc │ ├── .prettierignore │ ├── svelte.config.js │ ├── .prettierrc │ ├── src │ ├── __internal__ │ │ ├── is.svelte.ts │ │ ├── configurable.ts │ │ ├── types.ts │ │ └── utils.svelte.ts │ ├── get-last-changed │ │ └── index.svelte.ts │ ├── auto-reset-state │ │ └── index.svelte.ts │ ├── get-fps │ │ └── index.svelte.ts │ ├── debounce │ │ └── index.svelte.ts │ ├── whenever │ │ ├── index.svelte.test.ts │ │ └── index.svelte.ts │ ├── create-object-url │ │ └── index.svelte.ts │ ├── get-previous │ │ └── index.svelte.ts │ ├── local-state │ │ └── index.svelte.ts │ ├── session-state │ │ └── index.svelte.ts │ ├── debounced-state │ │ └── index.svelte.ts │ ├── default-state │ │ └── index.svelte.ts │ ├── get-document-visibility │ │ └── index.svelte.ts │ ├── create-vibration │ │ └── index.svelte.ts │ ├── get-text-direction │ │ ├── index.svelte.test.ts │ │ └── index.svelte.ts │ ├── is-window-focused │ │ └── index.svelte.ts │ ├── get-scrollbar-width │ │ └── index.svelte.ts │ ├── history-state │ │ └── index.svelte.ts │ ├── create-share │ │ └── index.svelte.ts │ ├── get-network │ │ └── index.svelte.ts │ ├── has-left-page │ │ └── index.svelte.ts │ ├── get-mouse │ │ └── index.svelte.ts │ ├── get-device-pixel-ratio │ │ └── index.svelte.ts │ ├── get-text-selection │ │ └── index.svelte.ts │ ├── create-eye-dropper │ │ └── index.svelte.ts │ ├── get-active-element │ │ └── index.svelte.ts │ ├── on-start-typing │ │ └── index.svelte.ts │ ├── watch │ │ └── index.svelte.ts │ ├── get-permission │ │ └── index.svelte.ts │ ├── index.ts │ ├── get-element-size │ │ └── index.svelte.ts │ ├── get-device-orientation │ │ └── index.svelte.ts │ ├── create-file-dialog │ │ └── index.svelte.ts │ ├── track-history │ │ └── index.svelte.ts │ ├── on-hover │ │ └── index.svelte.ts │ ├── on-click-outside │ │ └── index.svelte.ts │ ├── get-geolocation │ │ └── index.svelte.ts │ └── async-state │ │ └── index.svelte.ts │ ├── .gitignore │ ├── tsconfig.json │ ├── tests │ └── pointer-event-polyfill.ts │ ├── vite.config.ts │ ├── eslint.config.js │ ├── LICENSE │ ├── README.md │ └── package.json ├── .prettierignore ├── .github └── workflows │ ├── test-core.yml │ ├── build-website.yml │ ├── gh-pages.yml │ └── npm-publish.yml ├── LICENSE └── README.md /CNAME: -------------------------------------------------------------------------------- 1 | www.sv-use.org -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .idea 3 | -------------------------------------------------------------------------------- /docs/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /packages/core/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /docs/src/routes/+layout.server.ts: -------------------------------------------------------------------------------- 1 | export const prerender = true; 2 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | The website behind the documentation for `@sv-use/core`. 4 | -------------------------------------------------------------------------------- /docs/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svelte-librarian/sv-use/HEAD/docs/static/favicon.png -------------------------------------------------------------------------------- /packages/core/.prettierignore: -------------------------------------------------------------------------------- 1 | # Package Managers 2 | package-lock.json 3 | pnpm-lock.yaml 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /docs/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {} 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Package Managers 2 | package-lock.json 3 | pnpm-lock.yaml 4 | yarn.lock 5 | 6 | # Don't format docs 7 | **/*.md 8 | -------------------------------------------------------------------------------- /docs/.prettierignore: -------------------------------------------------------------------------------- 1 | # Package Managers 2 | package-lock.json 3 | pnpm-lock.yaml 4 | yarn.lock 5 | 6 | # Don't format docs 7 | **/*.md 8 | -------------------------------------------------------------------------------- /docs/src/lib/utils/paths.ts: -------------------------------------------------------------------------------- 1 | export const GUIDE_DIRECTORY = './content/docs/guide'; 2 | export const CORE_DIRECTORY = './content/docs/core'; 3 | export const DIST_CORE_DIRECTORY = '../packages/core/dist'; 4 | -------------------------------------------------------------------------------- /docs/src/lib/components/atoms/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Button } from './Button.svelte'; 2 | export { default as Input } from './Input.svelte'; 3 | export { default as TextArea } from './TextArea.svelte'; 4 | -------------------------------------------------------------------------------- /docs/src/lib/utils/cn.ts: -------------------------------------------------------------------------------- 1 | import { twMerge } from 'tailwind-merge'; 2 | import clsx, { type ClassValue } from 'clsx'; 3 | 4 | export function cn(...classes: ClassValue[]) { 5 | return twMerge(clsx(classes)); 6 | } 7 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-fps/Demo.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |

Current FPS : {fps.current}

8 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-battery/Demo.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
{JSON.stringify(battery, null, 2)}
8 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-network/Demo.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
{JSON.stringify(network, null, 2)}
8 | -------------------------------------------------------------------------------- /docs/content/docs/core/has-left-page/Demo.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |

Has left page : {hasLeft.current}

8 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-geolocation/Demo.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
{JSON.stringify(geolocation, null, 2)}
8 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-device-motion/Demo.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
{JSON.stringify(deviceMotion, null, 2)}
8 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-text-direction/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: browser 3 | --- 4 | 5 | # getTextDirection 6 | 7 | Indicates the text writing directionality of the content of an element. 8 | 9 | You can set a value to change the `dir` property on the element. 10 | -------------------------------------------------------------------------------- /docs/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import { sveltekit } from '@sveltejs/kit/vite'; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()], 6 | server: { 7 | fs: { 8 | allow: ['../packages/core', './content'] 9 | } 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /packages/core/svelte.config.js: -------------------------------------------------------------------------------- 1 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 2 | 3 | /** @type {import('@sveltejs/kit').Config} */ 4 | export default { 5 | preprocess: vitePreprocess(), 6 | kit: { 7 | files: { 8 | lib: './src' 9 | } 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-device-pixel-ratio/Demo.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
{JSON.stringify(devicePixelRatio, null, 2)}
8 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-device-orientation/Demo.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
{JSON.stringify(deviceOrientation, null, 2)}
8 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-mouse/Demo.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 |

x : {mouse.x}

9 |

y : {mouse.y}

10 |
11 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-fps/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: sensors 3 | --- 4 | 5 | # getFps 6 | 7 | Get the current frames per second of the device. 8 | 9 | ## Usage 10 | 11 | ```svelte 12 | 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-mouse/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: sensors 3 | --- 4 | 5 | # getMouse 6 | 7 | Retrieves information about the mouse. 8 | 9 | ## Usage 10 | 11 | ```svelte 12 | 17 | ``` 18 | -------------------------------------------------------------------------------- /packages/core/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "overrides": [ 8 | { 9 | "files": "*.svelte", 10 | "options": { 11 | "parser": "svelte" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # Output 4 | .output 5 | .vercel 6 | .netlify 7 | .wrangler 8 | /.svelte-kit 9 | /build 10 | /dist 11 | 12 | # OS 13 | .DS_Store 14 | Thumbs.db 15 | 16 | # Env 17 | .env 18 | .env.* 19 | !.env.example 20 | !.env.test 21 | 22 | # Vite 23 | vite.config.js.timestamp-* 24 | vite.config.ts.timestamp-* 25 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-permission/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: browser 3 | --- 4 | 5 | # getPermission 6 | 7 | Retrieves the status of a given permission. 8 | 9 | ## Usage 10 | 11 | ```svelte 12 | 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://svelte.dev/docs/kit/types#app.d.ts 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface PageState {} 9 | // interface Platform {} 10 | } 11 | } 12 | 13 | export {}; 14 | -------------------------------------------------------------------------------- /packages/core/src/__internal__/is.svelte.ts: -------------------------------------------------------------------------------- 1 | import { BROWSER } from 'esm-env'; 2 | 3 | /** Only runs in browser. */ 4 | export function isSupported(callback: () => boolean) { 5 | let _isSupported = $state(false); 6 | 7 | if (BROWSER) { 8 | _isSupported = callback(); 9 | } 10 | 11 | return { current: _isSupported }; 12 | } 13 | -------------------------------------------------------------------------------- /docs/content/docs/core/is-window-focused/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: elements 3 | --- 4 | 5 | # isWindowFocused 6 | 7 | Tracks whether the window is focused or not. 8 | 9 | ## Usage 10 | 11 | ```svelte 12 | 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/src/lib/contexts/navigation.svelte.ts: -------------------------------------------------------------------------------- 1 | import type { MarkdownHeading } from '$types/markdown.js'; 2 | 3 | let _onThisPageHeadings = $state([]); 4 | 5 | export const onThisPageHeadings = { 6 | get current() { 7 | return _onThisPageHeadings; 8 | }, 9 | set current(v) { 10 | _onThisPageHeadings = v; 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-active-element/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: elements 3 | --- 4 | 5 | # getActiveElement 6 | 7 | Gets the current active element in the DOM. 8 | 9 | ## Usage 10 | 11 | ```svelte 12 | 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/content/docs/core/has-left-page/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: sensors 3 | --- 4 | 5 | # hasLeftPage 6 | 7 | Reactive value that tracks whether the mouse has left the page or not. 8 | 9 | ## Usage 10 | 11 | ```svelte 12 | 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/src/app.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss/base'; 2 | @import 'tailwindcss/components'; 3 | @import 'tailwindcss/utilities'; 4 | 5 | * { 6 | font-family: 'Poppins', Arial, Helvetica, sans-serif; 7 | font-weight: 400; 8 | } 9 | 10 | html, 11 | body { 12 | position: relative; 13 | width: 100%; 14 | height: 100dvh; 15 | scroll-behavior: smooth; 16 | } 17 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-mouse-pressed/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: sensors 3 | --- 4 | 5 | # getMousePressed 6 | 7 | Reactive values for mouse/touch/drag pressing state. 8 | 9 | ## Usage 10 | 11 | ```svelte 12 | 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-permission/Demo.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 | Camera status 9 |
{JSON.stringify(permission, null, 2)}
10 |
11 | -------------------------------------------------------------------------------- /packages/core/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # Output 4 | .output 5 | .vercel 6 | .netlify 7 | .wrangler 8 | /.svelte-kit 9 | /build 10 | /dist 11 | /coverage 12 | 13 | # OS 14 | .DS_Store 15 | Thumbs.db 16 | 17 | # Env 18 | .env 19 | .env.* 20 | !.env.example 21 | !.env.test 22 | 23 | # Vite 24 | vite.config.js.timestamp-* 25 | vite.config.ts.timestamp-* 26 | -------------------------------------------------------------------------------- /docs/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], 7 | "tailwindFunctions": ["clsx"], 8 | "overrides": [ 9 | { 10 | "files": "*.svelte", 11 | "options": { 12 | "parser": "svelte" 13 | } 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-network/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: sensors 3 | --- 4 | 5 | # getNetwork 6 | 7 | Provides information about the connection a device is using to communicate with 8 | the network. 9 | 10 | ## Usage 11 | 12 | ```svelte 13 | 18 | ``` 19 | -------------------------------------------------------------------------------- /docs/content/docs/core/local-state/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: state 3 | --- 4 | 5 | # localState 6 | 7 | A state that is synced with local storage. 8 | 9 | ## Usage 10 | 11 | ```svelte 12 | 17 | 18 | counter : {counter.current} 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/content/docs/core/on-swipe/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: sensors 3 | --- 4 | 5 | # onSwipe 6 | 7 | Reactive swipe detection for mobile devices using [TouchEvents](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent). 8 | 9 | ## Usage 10 | 11 | ```js 12 | import { onSwipe } from '@sv-use/core'; 13 | 14 | let el = $state(); 15 | const swipe = onSwipe(() => el); 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/content/docs/core/session-state/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: state 3 | --- 4 | 5 | # sessionState 6 | 7 | A state that is synced with session storage. 8 | 9 | ## Usage 10 | 11 | ```svelte 12 | 17 | 18 | counter : {counter.current} 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-device-motion/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: sensors 3 | --- 4 | 5 | # getDeviceMotion 6 | 7 | Provides information about the device's motion, including acceleration and 8 | rotation rate. 9 | 10 | ## Usage 11 | 12 | ```svelte 13 | 18 | ``` 19 | -------------------------------------------------------------------------------- /docs/content/docs/core/handle-event-listener/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: browser 3 | --- 4 | 5 | # handleEventListener 6 | 7 | Convenience wrapper for event listeners. 8 | 9 | ## Usage 10 | 11 | ```svelte 12 | 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/src/lib/utils/text-transform.ts: -------------------------------------------------------------------------------- 1 | export function toTitleCase(str: string): string { 2 | return str.at(0)!.toUpperCase() + str.slice(1).toLowerCase(); 3 | } 4 | 5 | /** @note Removes hyphens, underscores, and spaces. */ 6 | export function toCamelCase(str: string): string { 7 | const parts = str.toLowerCase().split(/[-_ ]+/); 8 | return parts.at(0)! + parts.slice(1).map(toTitleCase).join(''); 9 | } 10 | -------------------------------------------------------------------------------- /docs/src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 | 11 |
12 | {@render children()} 13 |
14 |
15 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "module": "NodeNext", 13 | "moduleResolution": "NodeNext" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-last-changed/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: reactivity 3 | --- 4 | 5 | # getLastChanged 6 | 7 | Get the last time the reactive value changed. It is returned as a number in milliseconds. 8 | 9 | ## Usage 10 | 11 | ```svelte 12 | 18 | ``` 19 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-mouse-pressed/Demo.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 |

Click anywhere in the document

9 |
{JSON.stringify(mousePressed, null, 2)}
10 |
11 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-battery/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: sensors 3 | --- 4 | 5 | # getBattery 6 | 7 | Provides information about the system's battery charge level and lets you be notified by events that are sent when the battery level or charging status change. 8 | 9 | ## Usage 10 | 11 | ```svelte 12 | 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-device-orientation/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: sensors 3 | --- 4 | 5 | # getDeviceOrientation 6 | 7 | Provides web developers with information from the physical orientation of the 8 | device running the web page. 9 | 10 | ## Usage 11 | 12 | ```svelte 13 | 18 | ``` 19 | -------------------------------------------------------------------------------- /docs/src/routes/docs/guide/[slug]/+page.server.ts: -------------------------------------------------------------------------------- 1 | import { convertMarkdownFileToHTML } from '$lib/utils/markdown.server.js'; 2 | import { GUIDE_DIRECTORY } from '$utils/paths.js'; 3 | import type { PageServerLoad } from './$types.js'; 4 | 5 | export const load: PageServerLoad = async ({ params }) => { 6 | const data = await convertMarkdownFileToHTML(`${GUIDE_DIRECTORY}/${params.slug}.md`); 7 | 8 | return { 9 | ...data 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-device-pixel-ratio/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: sensors 3 | --- 4 | 5 | # getDevicePixelRatio 6 | 7 | Returns the ratio of the resolution in physical pixels to the resolution in CSS 8 | pixels for the current display device. 9 | 10 | ## Usage 11 | 12 | ```svelte 13 | 18 | ``` 19 | -------------------------------------------------------------------------------- /docs/content/docs/core/debounce/Demo.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 |

Search : {debouncedSearch.current}

11 | 12 |
13 | -------------------------------------------------------------------------------- /docs/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-static'; 2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | export default { 6 | preprocess: vitePreprocess(), 7 | kit: { 8 | adapter: adapter(), 9 | alias: { 10 | $types: './src/lib/types', 11 | $utils: './src/lib/utils', 12 | '$sv-use/core': '../packages/core/dist/index.js' 13 | } 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-text-selection/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: browser 3 | --- 4 | 5 | # getTextSelection 6 | 7 | Gets the range of text selected by the user or the current position of the 8 | caret, based on [window.getSelection](https://developer.mozilla.org/en-US/docs/Web/API/Window/getSelection). 9 | 10 | ## Usage 11 | 12 | ```js 13 | import { getTextSelection } from '@sv-use/core'; 14 | 15 | const selection = getTextSelection(); 16 | ``` 17 | -------------------------------------------------------------------------------- /packages/core/tests/pointer-event-polyfill.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * JSDom hasn't implemented the constructor for PointerEvent 3 | */ 4 | 5 | if (!globalThis.PointerEvent) { 6 | globalThis.PointerEvent = class PointerEvent extends MouseEvent { 7 | constructor(type: string, eventInitDict: PointerEventInit = {}) { 8 | super(type, eventInitDict); 9 | } 10 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 11 | } as any; 12 | } 13 | 14 | export {}; 15 | -------------------------------------------------------------------------------- /docs/content/docs/core/debounced-state/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: state 3 | --- 4 | 5 | # debouncedState 6 | 7 | A reactive state that updates its value after a delay. 8 | 9 | ## Usage 10 | 11 | > [!TIP] 12 | > If you'd rather have them separate, check out [debounce](/docs/core/debounce). 13 | 14 | ```svelte 15 | 20 | ``` 21 | -------------------------------------------------------------------------------- /packages/core/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | import { sveltekit } from '@sveltejs/kit/vite'; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()], 6 | resolve: process.env.VITEST 7 | ? { 8 | conditions: ['browser'] 9 | } 10 | : undefined, 11 | test: { 12 | environment: 'jsdom', 13 | include: ['src/**/*.{test,spec}.{js,ts}'], 14 | setupFiles: ['./tests/pointer-event-polyfill.ts'] 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /docs/content/docs/core/create-eye-dropper/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: browser 3 | --- 4 | 5 | # createEyeDropper 6 | 7 | Provides a mechanism for creating an eye dropper tool. 8 | 9 | Using this tool, users can sample colors from their screens, including outside of the browser window. 10 | 11 | ## Usage 12 | 13 | ```svelte 14 | 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/content/docs/core/create-web-notification/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: browser 3 | --- 4 | 5 | # createWebNotification 6 | 7 | Configure and display desktop notifications to the user. 8 | 9 | ## Usage 10 | 11 | ```svelte 12 | 17 | 18 | 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/content/docs/core/observe-mutation/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: observers 3 | --- 4 | 5 | # observeMutation 6 | 7 | Watch for changes being made to the DOM tree. 8 | 9 | ## Usage 10 | 11 | ```svelte 12 | 21 | 22 |
23 | ``` 24 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-geolocation/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: sensors 3 | --- 4 | 5 | # getGeolocation 6 | 7 | It allows the user to provide their location to web applications if they so 8 | desire. 9 | 10 | For privacy reasons, the user is asked for permission to report location 11 | information. 12 | 13 | ## Usage 14 | 15 | ```svelte 16 | 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/content/docs/core/on-long-press/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: sensors 3 | --- 4 | 5 | # onLongPress 6 | 7 | Runs a callback when a long press occurs on a given element. 8 | 9 | ## Usage 10 | 11 | ```svelte 12 | 21 | 22 | 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/src/lib/types/markdown.ts: -------------------------------------------------------------------------------- 1 | export type MarkdownHeading = { 2 | depth: number; 3 | value: string; 4 | data: { 5 | id: string; 6 | }; 7 | }; 8 | 9 | export type MarkdownReturn = Record> = { 10 | attributes: T; 11 | title: string; 12 | lede: string; 13 | html: string; 14 | headings: MarkdownHeading[]; 15 | }; 16 | 17 | export type Category = { 18 | category: string; 19 | utilities: { slug: string; label: string }[]; 20 | }; 21 | -------------------------------------------------------------------------------- /docs/content/docs/core/debounce/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: reactivity 3 | --- 4 | 5 | # debounce 6 | 7 | Debounces the update of the value after a delay. 8 | 9 | ## Usage 10 | 11 | > [!TIP] 12 | > If you'd rather have them combined in one variable, check out [debouncedState](/docs/core/debounced-state). 13 | 14 | ```svelte 15 | 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/content/docs/core/on-start-typing/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: sensors 3 | --- 4 | 5 | # onStartTyping 6 | 7 | Fires when users start typing on non-editable elements. 8 | 9 | ## Usage 10 | 11 | ```svelte 12 | 23 | 24 | 25 | ``` 26 | -------------------------------------------------------------------------------- /docs/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import { Config } from 'tailwindcss'; 2 | import defaultTheme from 'tailwindcss/defaultTheme'; 3 | 4 | export default { 5 | darkMode: 'selector', 6 | content: ['./src/**/*.{html,js,svelte,ts}', './content/**/*.{html,js,svelte,ts}'], 7 | theme: { 8 | screens: { 9 | xsm: '480px', 10 | ...defaultTheme.screens 11 | }, 12 | extend: { 13 | colors: { 14 | svelte: '#ff3e00', 15 | darksvelte: '#f96743' 16 | } 17 | } 18 | }, 19 | plugins: [] 20 | } satisfies Config; 21 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-document-visibility/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: elements 3 | --- 4 | 5 | # getDocumentVisibility 6 | 7 | Returns the visibility of the document. 8 | 9 | It can be used to check whether the document is in the background or in a 10 | minimized window, or is otherwise not visible to the user. 11 | 12 | ## Usage 13 | 14 | ```svelte 15 | 20 | ``` 21 | -------------------------------------------------------------------------------- /packages/core/src/__internal__/configurable.ts: -------------------------------------------------------------------------------- 1 | import { BROWSER } from 'esm-env'; 2 | 3 | export interface ConfigurableWindow { 4 | window?: Window; 5 | } 6 | 7 | export interface ConfigurableDocument { 8 | document?: Document; 9 | } 10 | 11 | export interface ConfigurableNavigator { 12 | navigator?: Navigator; 13 | } 14 | 15 | export const defaultWindow = BROWSER ? window : undefined; 16 | export const defaultDocument = BROWSER ? document : undefined; 17 | export const defaultNavigator = BROWSER ? navigator : undefined; 18 | -------------------------------------------------------------------------------- /packages/core/src/get-last-changed/index.svelte.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Get the last time a state changed. 3 | * @param value The state to track as a getter function. 4 | * @see https://svelte-librarian.github.io/sv-use/docs/core/get-last-changed 5 | */ 6 | export function getLastChanged(value: () => T) { 7 | let _lastChanged = $state(0); 8 | 9 | $effect(() => { 10 | value(); 11 | 12 | _lastChanged = Date.now(); 13 | }); 14 | 15 | return { 16 | get current() { 17 | return _lastChanged; 18 | } 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /docs/content/docs/core/auto-reset-state/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: state 3 | --- 4 | 5 | # autoResetState 6 | 7 | A state that automatically resets to the default value after a delay. 8 | 9 | ## Usage 10 | 11 | ```svelte 12 | 22 | ``` 23 | -------------------------------------------------------------------------------- /docs/content/docs/core/observe-performance/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: observers 3 | --- 4 | 5 | # observePerformance 6 | 7 | Observes performance metrics. 8 | 9 | The observer callback is invoked when performance entry events are recorded for 10 | the entry types that have been registered. 11 | 12 | ## Usage 13 | 14 | ```svelte 15 | 22 | ``` 23 | -------------------------------------------------------------------------------- /docs/content/docs/core/whenever/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: lifecycle 3 | --- 4 | 5 | # whenever 6 | 7 | Shorthand for running a callback when a dependency is truthy. 8 | 9 | It can also take an array of dependencies, in which case it will run if all 10 | dependencies are truthy. 11 | 12 | ## Usage 13 | 14 | ```svelte 15 | 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/content/docs/core/on-hover/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: sensors 3 | --- 4 | 5 | # onHover 6 | 7 | Tracks whether the given element is hovered or not. 8 | 9 | You can also pass a callback that is called when the element is being hovered. 10 | 11 | ## Usage 12 | 13 | ```svelte 14 | 23 | 24 |
hover me
25 | ``` 26 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-scrollbar-width/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: sensors 3 | --- 4 | 5 | # getScrollbarWidth 6 | 7 | Gets the scrollbar width of an element. 8 | 9 | This works in browsers that do not use absolute positioning for scrollbars, 10 | such as Chrome on desktop. 11 | 12 | ## Usage 13 | 14 | ```svelte 15 | 21 | 22 |
23 | ``` 24 | -------------------------------------------------------------------------------- /docs/content/docs/core/auto-reset-state/Demo.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 |
13 |

Message : {message.current}

14 | 15 |
16 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-previous/Demo.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 |

Counter : {counter}

11 |

Previous counter : {previousCounter.current ?? 'undefined'}

12 | 13 |
14 | -------------------------------------------------------------------------------- /docs/content/docs/core/on-click-outside/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: sensors 3 | --- 4 | 5 | # onClickOutside 6 | 7 | Runs a callback when a click occurs outside the target element. 8 | 9 | ## Usage 10 | 11 | ```svelte 12 | 21 | 22 |
23 | i'm the target element 24 |
25 |

i'm outside the target element

26 | ``` 27 | -------------------------------------------------------------------------------- /docs/src/routes/docs/core/[utility]/+page.ts: -------------------------------------------------------------------------------- 1 | import type { Component } from 'svelte'; 2 | import type { PageLoad } from './$types.js'; 3 | 4 | export const load: PageLoad = async ({ data, params }) => { 5 | return { 6 | ...data, 7 | Component: await getDemoComponent(params.utility) 8 | }; 9 | }; 10 | 11 | async function getDemoComponent(utility: string) { 12 | try { 13 | const component = await import(`../../../../../content/docs/core/${utility}/Demo.svelte`); 14 | return component.default as Component; 15 | } catch { 16 | return undefined; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /docs/content/docs/core/debounced-state/Demo.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
14 |

Search : {search.current}

15 | 16 |
17 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-last-changed/Demo.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 |

Value : {value}

11 |

12 | Value last changed : {new Date(lastChanged.current).toLocaleString('fr-CH')} 13 |

14 | 15 |
16 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-element-size/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: elements 3 | --- 4 | 5 | # getElementSize 6 | 7 | Tracks the size of an element using the [Resize Observer API](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver). 8 | 9 | ## Usage 10 | 11 | > [!IMPORTANT] 12 | > You can only use `getElementSize` in the component initialization lifecycle. 13 | 14 | ```svelte 15 | 21 | 22 | 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/content/docs/core/is-window-focused/Demo.svelte: -------------------------------------------------------------------------------- 1 | 18 | 19 |

{message.current}

20 | -------------------------------------------------------------------------------- /packages/core/src/__internal__/types.ts: -------------------------------------------------------------------------------- 1 | export type Getter = () => T; 2 | export type Setter = (value: T) => void; 3 | export type MaybeGetter = T | Getter; 4 | 5 | export type Arrayable = T | T[]; 6 | 7 | export type CleanupFunction = () => void; 8 | 9 | export interface AutoCleanup { 10 | /** 11 | * Whether to auto-cleanup the event listener or not. 12 | * 13 | * If set to `true`, it must run in the component initialization lifecycle. 14 | * @default true 15 | */ 16 | autoCleanup?: boolean; 17 | } 18 | 19 | export type MaybeElement = HTMLElement | SVGElement | undefined | null; 20 | -------------------------------------------------------------------------------- /docs/content/docs/core/observe-intersection/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: observers 3 | --- 4 | 5 | # observeIntersection 6 | 7 | Runs a callback when the targets are visible on the screen. 8 | 9 | ## Usage 10 | 11 | ```svelte 12 | 22 | 23 |
24 | i'm the target element 25 |
26 | ``` 27 | -------------------------------------------------------------------------------- /docs/content/docs/core/track-history/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: reactivity 3 | --- 4 | 5 | # trackHistory 6 | 7 | Tracks the change history of a reactive value. Provides undo and redo 8 | capabilities as well as access to the histories. 9 | 10 | ## Usage 11 | 12 | > [!TIP] 13 | > If you prefer to have them combined, check out [historyState](/docs/core/history-state). 14 | 15 | ```svelte 16 | 25 | ``` 26 | -------------------------------------------------------------------------------- /docs/src/routes/docs/core/[utility]/+page.server.ts: -------------------------------------------------------------------------------- 1 | import { convertMarkdownFileToHTML } from '$lib/utils/markdown.server.js'; 2 | import { CORE_DIRECTORY, DIST_CORE_DIRECTORY } from '$utils/paths.js'; 3 | import type { PageServerLoad } from './$types.js'; 4 | 5 | export const load: PageServerLoad = async ({ params }) => { 6 | const data = await convertMarkdownFileToHTML<{ category: string }>( 7 | `${CORE_DIRECTORY}/${params.utility}/index.md`, 8 | { 9 | typeDefinitionsPath: `${DIST_CORE_DIRECTORY}/${params.utility}/index.svelte.d.ts` 10 | } 11 | ); 12 | 13 | return { 14 | ...data 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /docs/src/lib/components/atoms/Button.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 22 | -------------------------------------------------------------------------------- /docs/content/docs/core/on-click-outside/Demo.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 |
16 |
17 | click outside of me 18 |
19 |

See the logs for details...

20 |
21 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-document-visibility/Demo.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 |
18 | {message.current} 19 |
20 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-previous/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: reactivity 3 | --- 4 | 5 | # getPrevious 6 | 7 | A reactive state of a given state's previous value. 8 | 9 | It is set to `undefined` until the first change if `initial` is not set. 10 | 11 | ## Usage 12 | 13 | > [!TIP] 14 | > If you only care about the previous value when the value changes, you can use [watch](/docs/core/watch). 15 | > 16 | > It supplies the previous value in the callback. 17 | 18 | ```svelte 19 | 25 | ``` 26 | -------------------------------------------------------------------------------- /docs/content/docs/core/create-eye-dropper/Demo.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 | {#if eyeDropper.isSupported} 10 |

11 | Current value : 12 | {eyeDropper.current} 13 |

14 | 15 | {:else} 16 |

Your browser doesn't support the Eye Dropper API :(

17 | {/if} 18 |
19 | -------------------------------------------------------------------------------- /docs/content/docs/core/on-start-typing/Demo.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 |
15 |

Type anything

16 |
17 | 18 | 19 |
20 |
21 | -------------------------------------------------------------------------------- /docs/src/lib/components/atoms/TextArea.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 | 26 | -------------------------------------------------------------------------------- /.github/workflows/test-core.yml: -------------------------------------------------------------------------------- 1 | name: Test the core package 2 | 3 | on: 4 | push: 5 | branches: 6 | - "**" 7 | workflow_dispatch: 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | test: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Set up Node 22.12.0 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: "22.12.0" 21 | - name: Test the 'core' package 22 | working-directory: ./packages/core 23 | run: | 24 | npm install 25 | npm test 26 | -------------------------------------------------------------------------------- /docs/src/lib/components/atoms/Input.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 | 28 | -------------------------------------------------------------------------------- /docs/content/docs/core/observe-performance/Demo.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 |
13 | 14 |
{JSON.stringify(entries, null, 2)}
15 |
16 | -------------------------------------------------------------------------------- /docs/content/docs/core/observe-resize/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: observers 3 | --- 4 | 5 | # observeResize 6 | 7 | Watch for changes to the dimensions of a given element's content or its border-box. 8 | 9 | It can also watch multiple elements if given an array of elements. 10 | 11 | ## Usage 12 | 13 | ```svelte 14 | 24 | 25 | 26 | ``` 27 | -------------------------------------------------------------------------------- /docs/content/docs/core/create-object-url/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: browser 3 | --- 4 | 5 | # createObjectUrl 6 | 7 | Creates a reactive URL representing the given object. 8 | 9 | Automatically releases the URL when the object changes or the component is 10 | unmounted. 11 | 12 | ## Usage 13 | 14 | > [!NOTE] 15 | > It uses `$effect`. 16 | 17 | ```svelte 18 | 26 | 27 | 28 | Upload file 29 | 30 | ``` 31 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-clipboard-text/Demo.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 | {#if clipboard.isSupported} 11 | Currently copied : {clipboard.text} 12 | 13 | 14 | {:else} 15 | Your browser doesn't support the Clipboard API... 16 | {/if} 17 |
18 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-active-element/Demo.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 |
11 | {#each { length: 6 } as _, i} 12 | {@const id = i + 1} 13 | 14 | {/each} 15 |
16 | 17 | Current Active Element : {key} 18 | 19 |
20 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-element-size/Demo.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 |
15 | Resize the box to see changes 16 | 17 |
18 | -------------------------------------------------------------------------------- /docs/content/docs/core/observe-resize/Demo.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 |
19 | Resize the box to see changes 20 | 21 |
22 | -------------------------------------------------------------------------------- /docs/src/lib/contexts/theme.svelte.ts: -------------------------------------------------------------------------------- 1 | import { browser } from '$app/environment'; 2 | 3 | type Theme = 'light' | 'dark'; 4 | 5 | export const theme = $state({ 6 | current: 'light' as Theme 7 | }); 8 | 9 | if (browser) { 10 | if (localStorage.getItem('theme')!) { 11 | theme.current = localStorage.getItem('theme')! as Theme; 12 | } else if ( 13 | !('theme' in localStorage) && 14 | window.matchMedia('(prefers-color-scheme: dark)').matches 15 | ) { 16 | theme.current = 'dark'; 17 | } 18 | } 19 | 20 | export function toggleTheme() { 21 | theme.current = theme.current === 'light' ? 'dark' : 'light'; 22 | localStorage.setItem('theme', theme.current); 23 | document.documentElement.classList.toggle('dark', theme.current === 'dark'); 24 | } 25 | -------------------------------------------------------------------------------- /docs/content/docs/core/create-speech-recognition/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: sensors 3 | --- 4 | 5 | # createSpeechRecognition 6 | 7 | Reactive controller interface for the recognition service. 8 | 9 | ## Usage 10 | 11 | > [!IMPORTANT] 12 | > You can only call it during component initialization. 13 | 14 | ```ts 15 | import { createSpeechRecognition } from '@sv-use/core'; 16 | 17 | const speech = createSpeechRecognition({ 18 | lang: 'en', 19 | interimResults: false, 20 | onResult(transcript) { 21 | console.log("Transcript received", transcript); 22 | }, 23 | onError(error) { 24 | console.log('Oops, something went wrong', error); 25 | } 26 | }); 27 | 28 | if (speech.isSupported) { 29 | speech.start(); 30 | } 31 | ``` 32 | -------------------------------------------------------------------------------- /docs/content/docs/core/default-state/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: state 3 | --- 4 | 5 | # defaultState 6 | 7 | A reactive state that falls back to `defaultValue` if set to `null` or `undefined`. 8 | 9 | ## Usage 10 | 11 | > [!NOTE] 12 | > Although the `current` property is typed as nullable, it will never return a nullable value. 13 | > 14 | > This is to ensure that you can set a nullable value when changing the state without TS complaining. 15 | 16 | ```svelte 17 | 26 | ``` 27 | -------------------------------------------------------------------------------- /docs/content/docs/core/handle-wake-lock/Demo.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | {#if wakeLock.isSupported} 13 |

Is Active: {wakeLock.isActive}

14 | 17 | {:else} 18 |

Your browser doesn't support the Screen Wake Lock API :(

19 | {/if} 20 |
21 | -------------------------------------------------------------------------------- /docs/content/docs/core/create-web-notification/Demo.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | {#if notification.isSupported} 13 | Permission granted : {notification.isPermissionGranted} 14 | 15 | {:else} 16 |

Your browser doesn't support the Web Notifications API...

17 | {/if} 18 |
19 | -------------------------------------------------------------------------------- /docs/content/docs/core/create-share/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: browser 3 | --- 4 | 5 | # createShare 6 | 7 | Invokes the native sharing mechanism of the device to share data such as text, 8 | URLs, or files. 9 | 10 | The available share targets depend on the device, but might include the 11 | clipboard, contacts and email applications, websites, Bluetooth, etc. 12 | 13 | ## Usage 14 | 15 | > [!IMPORTANT] 16 | > To prevent abuse, it must be triggered off a UI event like a button click and 17 | > cannot be launched at arbitrary points by a script. 18 | 19 | ```js 20 | import { createShare } from '@sv-use/core'; 21 | 22 | const share = createShare({ 23 | title: 'SvelteUse', 24 | text: 'SvelteUse is awesome !', 25 | url: 'https://sv-use.org' 26 | }); 27 | ``` 28 | -------------------------------------------------------------------------------- /docs/content/docs/core/local-state/Demo.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 |
15 |

Search : {search.current}

16 | 17 | 18 |

19 | Try opening the page in another tab after searching 20 |

21 |
22 | -------------------------------------------------------------------------------- /docs/content/docs/core/session-state/Demo.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 |
15 |

Search : {search.current}

16 | 17 | 18 |

19 | Try opening the page in another tab after searching 20 |

21 |
22 | -------------------------------------------------------------------------------- /docs/content/docs/core/handle-event-listener/Demo.svelte: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 | Click me ! 23 | 24 | Open the console and change the page to see the logs 25 | 26 |
27 | -------------------------------------------------------------------------------- /docs/src/routes/+page.server.ts: -------------------------------------------------------------------------------- 1 | import { CORE_DIRECTORY } from '$utils/paths.js'; 2 | import fs from 'node:fs/promises'; 3 | import path from 'node:path'; 4 | 5 | export const load = async () => { 6 | return { 7 | totalUtilities: await countUtilities(CORE_DIRECTORY) 8 | }; 9 | }; 10 | 11 | async function countUtilities(dirPath: string) { 12 | let count = 0; 13 | const coreEntries = await fs.readdir(dirPath, { withFileTypes: true }); 14 | 15 | for (const entry of coreEntries) { 16 | const filePath = path.join(dirPath, entry.name); 17 | const stat = await fs.stat(filePath); 18 | 19 | if (stat.isFile() && path.extname(filePath) === '.md') { 20 | count += 1; 21 | } else if (stat.isDirectory()) { 22 | count += await countUtilities(filePath); 23 | } 24 | } 25 | 26 | return count; 27 | } 28 | -------------------------------------------------------------------------------- /docs/content/docs/core/create-drop-zone/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: elements 3 | --- 4 | 5 | # createDropZone 6 | 7 | Creates a zone where files can be dropped. 8 | 9 | ## Usage 10 | 11 | > [!IMPORTANT] 12 | > Since it uses `$effect` internally, you must either call `createDropZone` in 13 | > the component initialization lifecycle or call it inside `$effect.root`. 14 | 15 | ```svelte 16 | 29 | 30 |
31 | Drop images here 32 |
33 | ``` 34 | -------------------------------------------------------------------------------- /packages/core/src/auto-reset-state/index.svelte.ts: -------------------------------------------------------------------------------- 1 | import { untrack } from 'svelte'; 2 | 3 | /** 4 | * A state that automatically resets to the default value after a delay. 5 | * @param defaultValue The default value of the state (can be an object). 6 | * @param delay The delay in milliseconds. 7 | * @see https://svelte-librarian.github.io/sv-use/docs/core/auto-reset-state 8 | */ 9 | export function autoResetState(defaultValue: T, delay: number = 3000) { 10 | let timeout: number; 11 | const _state = $state({ current: defaultValue }); 12 | 13 | $effect(() => { 14 | $state.snapshot(_state); 15 | 16 | if (timeout) { 17 | clearTimeout(timeout); 18 | } 19 | 20 | untrack(() => { 21 | timeout = setTimeout(() => { 22 | _state.current = defaultValue; 23 | }, delay) as unknown as number; 24 | }); 25 | }); 26 | 27 | return _state; 28 | } 29 | -------------------------------------------------------------------------------- /.github/workflows/build-website.yml: -------------------------------------------------------------------------------- 1 | name: Build website 2 | 3 | on: 4 | push: 5 | branches: 6 | - "**" 7 | - "!main" 8 | workflow_dispatch: 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: Set up Node 22.12 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version: "22.12.0" 22 | - name: Building the 'core' package 23 | working-directory: ./packages/core 24 | run: | 25 | npm ci 26 | npx svelte-package 27 | - name: Build the website 28 | working-directory: ./docs 29 | run: | 30 | npm ci 31 | npm run build 32 | -------------------------------------------------------------------------------- /docs/eslint.config.js: -------------------------------------------------------------------------------- 1 | import prettier from 'eslint-config-prettier'; 2 | import js from '@eslint/js'; 3 | import { includeIgnoreFile } from '@eslint/compat'; 4 | import svelte from 'eslint-plugin-svelte'; 5 | import globals from 'globals'; 6 | import { fileURLToPath } from 'node:url'; 7 | import ts from 'typescript-eslint'; 8 | const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url)); 9 | 10 | export default ts.config( 11 | includeIgnoreFile(gitignorePath), 12 | js.configs.recommended, 13 | ...ts.configs.recommended, 14 | ...svelte.configs['flat/recommended'], 15 | prettier, 16 | ...svelte.configs['flat/prettier'], 17 | { 18 | languageOptions: { 19 | globals: { 20 | ...globals.browser, 21 | ...globals.node 22 | } 23 | } 24 | }, 25 | { 26 | files: ['**/*.svelte'], 27 | 28 | languageOptions: { 29 | parserOptions: { 30 | parser: ts.parser 31 | } 32 | } 33 | } 34 | ); 35 | -------------------------------------------------------------------------------- /docs/content/docs/core/handle-wake-lock/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: browser 3 | --- 4 | 5 | # handleWakeLock 6 | 7 | Provides a way to prevent devices from dimming or locking the screen when an application needs to keep running. 8 | 9 | You may read more about the [Screen Wake Lock API](https://developer.mozilla.org/en-US/docs/Web/API/Screen_Wake_Lock_API). 10 | 11 | ## Usage 12 | 13 | > [!IMPORTANT] 14 | > Since it uses `$effect` internally, you must either call `handleWakeLock` in 15 | > the component initialization lifecycle or call it inside `$effect.root`. 16 | 17 | ```svelte 18 | 31 | ``` 32 | -------------------------------------------------------------------------------- /packages/core/eslint.config.js: -------------------------------------------------------------------------------- 1 | import prettier from 'eslint-config-prettier'; 2 | import js from '@eslint/js'; 3 | import { includeIgnoreFile } from '@eslint/compat'; 4 | import svelte from 'eslint-plugin-svelte'; 5 | import globals from 'globals'; 6 | import { fileURLToPath } from 'node:url'; 7 | import ts from 'typescript-eslint'; 8 | const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url)); 9 | 10 | export default ts.config( 11 | includeIgnoreFile(gitignorePath), 12 | js.configs.recommended, 13 | ...ts.configs.recommended, 14 | ...svelte.configs['flat/recommended'], 15 | prettier, 16 | ...svelte.configs['flat/prettier'], 17 | { 18 | languageOptions: { 19 | globals: { 20 | ...globals.browser, 21 | ...globals.node 22 | } 23 | } 24 | }, 25 | { 26 | files: ['**/*.svelte'], 27 | 28 | languageOptions: { 29 | parserOptions: { 30 | parser: ts.parser 31 | } 32 | } 33 | } 34 | ); 35 | -------------------------------------------------------------------------------- /docs/content/docs/core/create-object-url/Demo.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 |
13 |

Select file :

14 | 15 |

Object URL :

16 | 17 | {#if url.current} 18 | 19 | {url.current} 20 | 21 | {:else} 22 |

none

23 | {/if} 24 |
25 |
26 | -------------------------------------------------------------------------------- /docs/content/docs/core/create-file-dialog/Demo.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 |
18 | 19 | 20 |
21 | Selected Files ({dialog.files.length}) 22 |
    23 | {#each dialog.files as file (file.name)} 24 |
  • {file.name}
  • 25 | {:else} 26 |

    Empty...

    27 | {/each} 28 |
29 |
30 |
31 | -------------------------------------------------------------------------------- /docs/content/docs/core/create-share/Demo.svelte: -------------------------------------------------------------------------------- 1 | 23 | 24 |
25 | {#if share.isSupported} 26 | 27 | 28 | {:else} 29 |

Web share is not supported in your browser :(

30 | {/if} 31 |
32 | -------------------------------------------------------------------------------- /docs/content/docs/core/history-state/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: state 3 | --- 4 | 5 | # historyState 6 | 7 | A reactive state that tracks its change history. Provides undo and redo 8 | capabilities as well as access to the histories. 9 | 10 | ## Usage 11 | 12 | > [!TIP] 13 | > If you prefer to have them separate, check out [trackHistory](/docs/core/track-history). 14 | 15 | ```svelte 16 | 21 | ``` 22 | 23 | ## Examples 24 | 25 | ```svelte 26 | 31 | 32 | counter : {counter.current} 33 | change history : {JSON.stringify(counter.history, null, 2)} 34 | 35 | 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/static/logos/github/dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/static/logos/github/light.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/content/docs/core/observe-mutation/Demo.svelte: -------------------------------------------------------------------------------- 1 | 27 | 28 |
29 | {#each messages as text, i (i)} 30 | Mutation Attribute: {text} 31 | {/each} 32 |
33 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-text-selection/Demo.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 |

You can select any text on the page.

12 |
13 | Selected Text 14 |

20 | {selection.text || 'No text selected'} 21 |

22 |
23 |
24 | Selected rects 25 |
{JSON.stringify(selection.rects, null, 2)}
26 |
27 |
28 | -------------------------------------------------------------------------------- /packages/core/src/__internal__/utils.svelte.ts: -------------------------------------------------------------------------------- 1 | import type { Getter } from './types.js'; 2 | 3 | export const noop = () => {}; 4 | 5 | export function toArray(v: T): T extends Array ? T : T[] { 6 | // @ts-expect-error Bypass type error 7 | return Array.isArray(v) ? v : [v]; 8 | } 9 | 10 | /** If function, return function value. Else, return value. */ 11 | export function normalizeValue(v: T | Getter): T { 12 | // Using instanceof instead of typeof 13 | // https://github.com/microsoft/TypeScript/issues/37663 14 | return v instanceof Function ? v() : v; 15 | } 16 | 17 | /** `true` if the value is not `null` nor `undefined`. `false` otherwise. */ 18 | export function notNullish(v: T | null | undefined): v is T { 19 | return v !== null && v !== undefined; 20 | } 21 | 22 | export function asyncEffectRoot(cb: () => Promise) { 23 | let promise: Promise; 24 | 25 | const cleanup = $effect.root(() => { 26 | promise = cb(); 27 | }); 28 | 29 | return () => promise.finally(cleanup); 30 | } 31 | -------------------------------------------------------------------------------- /docs/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | %sveltekit.head% 14 | 21 | 22 | 23 |
%sveltekit.body%
24 | 25 | 26 | -------------------------------------------------------------------------------- /docs/content/docs/core/history-state/Demo.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 |
15 | 16 | 17 |
18 |

Search history

19 | {#if search.history.length > 0} 20 |
    21 | {#each search.history as item} 22 |
  • 23 | Value : {item.snapshot} ({new Date(item.timestamp).toLocaleTimeString()}) 24 |
  • 25 | {/each} 26 |
27 | {:else} 28 |

Empty...

29 | {/if} 30 |
31 | -------------------------------------------------------------------------------- /packages/core/src/get-fps/index.svelte.ts: -------------------------------------------------------------------------------- 1 | import { BROWSER } from 'esm-env'; 2 | 3 | type GetFpsOptions = { 4 | /** Re-calculate the frames per second every `x` frames. */ 5 | every?: number; 6 | }; 7 | 8 | type GetFpsReturn = { 9 | readonly current: number; 10 | }; 11 | 12 | /** 13 | * @see https://svelte-librarian.github.io/sv-use/docs/core/get-fps 14 | */ 15 | export function getFps(options: GetFpsOptions = {}): GetFpsReturn { 16 | const { every = 10 } = options; 17 | 18 | let _fps = $state(0); 19 | 20 | let last = performance.now(); 21 | let ticks = 0; 22 | 23 | if (BROWSER) { 24 | window.requestAnimationFrame(callback); 25 | } 26 | 27 | function callback() { 28 | ticks += 1; 29 | 30 | if (ticks >= every) { 31 | const now = performance.now(); 32 | const delta = now - last; 33 | 34 | _fps = Math.round(1000 / (delta / ticks)); 35 | 36 | last = now; 37 | ticks = 0; 38 | } 39 | 40 | window.requestAnimationFrame(callback); 41 | } 42 | 43 | return { 44 | get current() { 45 | return _fps; 46 | } 47 | }; 48 | } 49 | -------------------------------------------------------------------------------- /docs/src/routes/docs/OnThisPage.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 33 | -------------------------------------------------------------------------------- /docs/content/docs/core/create-vibration/Demo.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
14 | {#if vibration.isSupported} 15 |

16 | Try changing the pattern. It must be a list of numbers separated by commas. 17 |

18 | 19 |
20 | 21 | 22 |
23 | {:else} 24 |

Your browser doesn't support the Vibration API :(

25 | {/if} 26 |
27 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-text-direction/Demo.svelte: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 |

{text}

23 |
24 |
25 | 26 |

Click to change the direction

27 |
28 |
29 | -------------------------------------------------------------------------------- /packages/core/src/debounce/index.svelte.ts: -------------------------------------------------------------------------------- 1 | import { untrack } from 'svelte'; 2 | 3 | type DebounceOptions = { 4 | /** 5 | * The delay in milliseconds before updating the state. 6 | * @default 1000 7 | */ 8 | delay?: number; 9 | }; 10 | 11 | /** 12 | * Debounces the update of the value after a delay. 13 | * @param initial The reactive value as a getter. 14 | * @param options Additional options to customize the behavior. 15 | * @see https://svelte-librarian.github.io/sv-use/docs/core/debounce 16 | */ 17 | export function debounce(value: () => T, options: DebounceOptions = {}) { 18 | const { delay = 1000 } = options; 19 | 20 | let timeout: number; 21 | const _state = $state<{ current: T | undefined }>({ current: undefined }); 22 | 23 | $effect(() => { 24 | value(); 25 | 26 | if (timeout) { 27 | clearTimeout(timeout); 28 | } 29 | 30 | untrack(() => { 31 | timeout = setTimeout(() => { 32 | _state.current = value(); 33 | }, delay) as unknown as number; 34 | }); 35 | 36 | return () => { 37 | clearTimeout(timeout); 38 | }; 39 | }); 40 | 41 | return _state; 42 | } 43 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "module": "NodeNext", 13 | "moduleResolution": "NodeNext" 14 | }, 15 | "include": [ 16 | "./.svelte-kit/ambient.d.ts", 17 | "./.svelte-kit/non-ambient.d.ts", 18 | "./.svelte-kit/types/**/$types.d.ts", 19 | "./vite.config.js", 20 | "./vite.config.ts", 21 | "./src/**/*.js", 22 | "./src/**/*.ts", 23 | "./src/**/*.svelte", 24 | "./content/**/*.js", 25 | "./content/**/*.ts", 26 | "./content/**/*.svelte", 27 | "./tests/**/*.js", 28 | "./tests/**/*.ts", 29 | "./tests/**/*.svelte" 30 | ], 31 | "exclude": [ 32 | "./node_modules/**", 33 | "./src/service-worker.js", 34 | "./src/service-worker/**/*.js", 35 | "./src/service-worker.ts", 36 | "./src/service-worker/**/*.ts", 37 | "./src/service-worker.d.ts", 38 | "./src/service-worker/**/*.d.ts" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /packages/core/src/whenever/index.svelte.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, vi } from 'vitest'; 2 | import { whenever } from './index.svelte.js'; 3 | import { flushSync } from 'svelte'; 4 | 5 | describe("Tests 'whenever'", () => { 6 | it('Works with booleans', () => { 7 | const cleanup = $effect.root(() => { 8 | const callback = vi.fn(() => {}); 9 | 10 | let isActive = $state(false); 11 | 12 | whenever(() => isActive, callback); 13 | 14 | expect(callback).not.toHaveBeenCalled(); 15 | 16 | flushSync(() => { 17 | isActive = true; 18 | }); 19 | 20 | expect(callback).toHaveBeenCalledOnce(); 21 | }); 22 | 23 | cleanup(); 24 | }); 25 | 26 | it('Works with comparisons', () => { 27 | const cleanup = $effect.root(() => { 28 | const callback = vi.fn(() => {}); 29 | 30 | let counter = $state(1); 31 | 32 | whenever(() => counter % 2 === 0, callback); 33 | 34 | expect(callback).not.toHaveBeenCalled(); 35 | 36 | flushSync(() => { 37 | counter = 2; 38 | }); 39 | 40 | expect(callback).toHaveBeenCalledOnce(); 41 | }); 42 | 43 | cleanup(); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /packages/core/src/create-object-url/index.svelte.ts: -------------------------------------------------------------------------------- 1 | import { normalizeValue } from '../__internal__/utils.svelte.js'; 2 | import type { CleanupFunction, MaybeGetter } from '../__internal__/types.js'; 3 | 4 | type CreateObjectUrlReturn = { 5 | readonly current: string | null; 6 | cleanup: CleanupFunction; 7 | }; 8 | 9 | /** 10 | * Creates a reactive URL representing the given object. 11 | * @param object The object to generate the url for. 12 | * @see https://svelte-librarian.github.io/sv-use/docs/create-object-url 13 | */ 14 | export function createObjectUrl( 15 | object: MaybeGetter 16 | ): CreateObjectUrlReturn { 17 | let current = $state(null); 18 | const _object = $derived(normalizeValue(object)); 19 | 20 | $effect(() => { 21 | if (_object) { 22 | current = URL.createObjectURL(_object); 23 | } 24 | 25 | return cleanup; 26 | }); 27 | 28 | function cleanup() { 29 | if (current) { 30 | URL.revokeObjectURL(current); 31 | } 32 | 33 | current = null; 34 | } 35 | 36 | return { 37 | get current() { 38 | return current; 39 | }, 40 | cleanup 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Sajidur Rahman 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 | -------------------------------------------------------------------------------- /packages/core/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Sajidur Rahman 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 | -------------------------------------------------------------------------------- /docs/content/docs/core/create-vibration/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: browser 3 | --- 4 | 5 | # createVibration 6 | 7 | Most modern mobile devices include vibration hardware, which lets software code 8 | provide physical feedback to the user by causing the device to shake. 9 | 10 | The Vibration API offers Web apps the ability to access this hardware, if it 11 | exists, and does nothing if the device doesn't support it. 12 | 13 | ## Usage 14 | 15 | Vibration is described as a pattern of on-off pulses, which may be of varying 16 | lengths. 17 | 18 | The pattern may consist of either a single integer, describing the number of 19 | milliseconds to vibrate, or an array of integers describing a pattern of 20 | vibrations and pauses. 21 | 22 | ```js 23 | import { createVibration } from '@sv-use/core'; 24 | 25 | // 1. Vibrates the device for 300ms 26 | // 2. Pauses for 100ms 27 | // 3. Vibrates the device again for 200ms 28 | const vibration = createVibration({ pattern: [300, 100, 200] }); 29 | 30 | // Start the vibration 31 | // It will automatically stop when the pattern is completed 32 | vibration.vibrate(); 33 | 34 | // Or you can manually stop it 35 | vibration.stop(); 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/content/docs/core/on-hover/Demo.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 |
13 |
14 | 17 | {#if isHovering.current} 18 |
25 | SvelteUse is awesome 26 |
29 |
30 | {/if} 31 |
32 |
33 | -------------------------------------------------------------------------------- /packages/core/src/get-previous/index.svelte.ts: -------------------------------------------------------------------------------- 1 | import { watch } from '../watch/index.svelte.js'; 2 | 3 | type GetPreviousReturn = { 4 | current: T; 5 | }; 6 | 7 | /** 8 | * A reactive state of a given state's previous value. 9 | * @param getter The state as a getter function. 10 | * @note The state is `undefined` until the given state is updated for the first time. 11 | * @see https://svelte-librarian.github.io/sv-use/docs/get-previous 12 | */ 13 | export function getPrevious(getter: () => T): GetPreviousReturn; 14 | 15 | /** 16 | * A reactive state of a given state's previous value. 17 | * @param getter The state as a getter function. 18 | * @param initial The initial value of the state. 19 | * @see https://svelte-librarian.github.io/sv-use/docs/get-previous 20 | */ 21 | export function getPrevious(getter: () => T, initial: T): GetPreviousReturn; 22 | 23 | export function getPrevious( 24 | getter: () => T, 25 | initial: T | undefined = undefined 26 | ): GetPreviousReturn | GetPreviousReturn { 27 | const _previous = $state({ current: initial }); 28 | 29 | watch( 30 | () => $state.snapshot(getter()) as T, 31 | (_, prev) => { 32 | _previous.current = prev; 33 | } 34 | ); 35 | 36 | return _previous; 37 | } 38 | -------------------------------------------------------------------------------- /docs/content/docs/core/get-clipboard-text/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: browser 3 | --- 4 | 5 | # getClipboardText 6 | 7 | Provides write (and optionally read) access to the text clipboard. 8 | 9 | ## Usage 10 | 11 | Set `options.legacyCopy: true` to keep the ability to copy if the [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API) is not available. It will handle copy with [document.execCommand](https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand) as the fallback. 12 | 13 | ```svelte 14 | 22 | ``` 23 | 24 | ## Examples 25 | 26 | ```svelte 27 | 36 | 37 |
38 | Currently copied : {clipboard.text} 39 | 40 | 43 |
44 | ``` 45 | -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: Deploy To GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | deploy: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: write 14 | concurrency: 15 | group: ${{ github.workflow }}-${{ github.ref }} 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: Set up Node 22.12 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version: "22.12.0" 22 | 23 | - name: Build the 'core' package 24 | working-directory: ./packages/core 25 | run: | 26 | npm ci 27 | npx svelte-package 28 | - name: Build the website 29 | working-directory: ./docs 30 | run: | 31 | npm ci 32 | npm run build 33 | 34 | - name: Deploy 35 | uses: peaceiris/actions-gh-pages@v4 36 | if: github.ref == 'refs/heads/main' 37 | with: 38 | github_token: ${{ secrets.GITHUB_TOKEN }} 39 | publish_dir: ./docs/build 40 | keep_files: true 41 | -------------------------------------------------------------------------------- /docs/content/docs/core/create-file-dialog/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: browser 3 | --- 4 | 5 | # createFileDialog 6 | 7 | Creates a file dialog to interact with programatically. 8 | 9 | ## Usage 10 | 11 | ```svelte 12 | 17 | ``` 18 | 19 | ### Examples 20 | 21 | ```svelte 22 | 36 | 37 | 40 | 46 |
47 | Selected Files ({dialog.files.length}) 48 | {#if dialog.files} 49 |
    50 | {#each dialog.files as file (file.name)} 51 |
  • {file.name}
  • 52 | {/each} 53 |
54 | {:else} 55 |

No files detected

56 | {/if} 57 |
58 | ``` 59 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | name: Test and publish 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | permissions: 8 | contents: read 9 | id-token: write 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Set up Node 22.12 17 | uses: actions/setup-node@v4 18 | with: 19 | node-version: "22.12.0" 20 | - name: Test the 'core' package 21 | working-directory: ./packages/core 22 | run: | 23 | npm install 24 | npm test 25 | 26 | publish-npm: 27 | needs: test 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v4 31 | - name: Set up Node 22.12 32 | uses: actions/setup-node@v4 33 | with: 34 | node-version: "22.12.0" 35 | registry-url: https://registry.npmjs.org/ 36 | - name: Publish the 'core' package 37 | working-directory: ./packages/core 38 | run: | 39 | npm install 40 | npm publish --provenance --access public 41 | env: 42 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 43 | -------------------------------------------------------------------------------- /packages/core/src/local-state/index.svelte.ts: -------------------------------------------------------------------------------- 1 | import { BROWSER } from 'esm-env'; 2 | 3 | type LocalStateOptions = { 4 | /** Defaults to `JSON.stringify`. */ 5 | serialize?: (value: T) => string; 6 | /** Defaults to `JSON.parse`. */ 7 | deserialize?: (value: string) => T; 8 | /** If the key is present in local storage, override that value or not. */ 9 | overrideDefault?: boolean; 10 | }; 11 | 12 | /** 13 | * A state that is synced with local storage. 14 | * @param key The key to use in local storage. 15 | * @param value The initial value of the state. 16 | * @param options Additional options to customize the behavior. 17 | * @returns A reactive `current` property. 18 | * @see https://svelte-librarian.github.io/sv-use/docs/core/local-state 19 | */ 20 | export function localState(key: string, value: T, options: LocalStateOptions = {}) { 21 | const { serialize = JSON.stringify, deserialize = JSON.parse, overrideDefault = false } = options; 22 | 23 | const _state = $state({ current: value }); 24 | 25 | if (BROWSER) { 26 | if (localStorage.getItem(key) && !overrideDefault) { 27 | _state.current = deserialize(localStorage.getItem(key)!); 28 | } else { 29 | localStorage.setItem(key, serialize(value)); 30 | } 31 | } 32 | 33 | $effect(() => { 34 | localStorage.setItem(key, serialize(_state.current)); 35 | }); 36 | 37 | return _state; 38 | } 39 | -------------------------------------------------------------------------------- /packages/core/src/session-state/index.svelte.ts: -------------------------------------------------------------------------------- 1 | import { BROWSER } from 'esm-env'; 2 | 3 | type SessionStateOptions = { 4 | /** Defaults to `JSON.stringify`. */ 5 | serialize?: (value: T) => string; 6 | /** Defaults to `JSON.parse`. */ 7 | deserialize?: (value: string) => T; 8 | /** If the key is present in session storage, override that value or not. */ 9 | overrideDefault?: boolean; 10 | }; 11 | 12 | /** 13 | * A state that is synced with session storage. 14 | * @param key The key to use in session storage. 15 | * @param value The initial value of the state. 16 | * @param options Additional options to customize the behavior. 17 | * @returns A reactive `current` property. 18 | * @see https://svelte-librarian.github.io/sv-use/docs/core/session-state 19 | */ 20 | export function sessionState(key: string, value: T, options: SessionStateOptions = {}) { 21 | const { serialize = JSON.stringify, deserialize = JSON.parse, overrideDefault = false } = options; 22 | 23 | const _state = $state({ current: value }); 24 | 25 | if (BROWSER) { 26 | if (sessionStorage.getItem(key) && !overrideDefault) { 27 | _state.current = deserialize(sessionStorage.getItem(key)!); 28 | } else { 29 | sessionStorage.setItem(key, serialize(value)); 30 | } 31 | } 32 | 33 | $effect(() => { 34 | sessionStorage.setItem(key, serialize(_state.current)); 35 | }); 36 | 37 | return _state; 38 | } 39 | -------------------------------------------------------------------------------- /docs/content/docs/core/async-state/Demo.svelte: -------------------------------------------------------------------------------- 1 | 27 | 28 |
29 | {#if !recipe.isReady} 30 |

Loading the recipe with id {id}...

31 | {:else} 32 |
33 | {#if 'message' in recipe.current} 34 |

Oops ! {recipe.current.message}

35 | {:else} 36 |

ID : {recipe.current.id}

37 |

Title : {recipe.current.title}

38 |

Tags : {recipe.current.tags.join(', ')}

39 | {/if} 40 |
41 | {/if} 42 |
43 | 44 | 45 |
46 |
47 | -------------------------------------------------------------------------------- /packages/core/src/debounced-state/index.svelte.ts: -------------------------------------------------------------------------------- 1 | type DebouncedStateOptions = { 2 | /** The delay in milliseconds before updating the state. */ 3 | delay?: number; 4 | }; 5 | 6 | /** 7 | * A reactive state that updates its value after a delay. 8 | * @param initial The initial value of the state. 9 | * @param options Additional options to customize the behavior. 10 | * @see https://svelte-librarian.github.io/sv-use/docs/core/debounced-state 11 | */ 12 | export function debouncedState(initial: T, options: DebouncedStateOptions = {}): { current: T } { 13 | const { delay = 1000 } = options; 14 | 15 | let timeout: number; 16 | const _state = $state({ current: initial }); 17 | 18 | const handler: ProxyHandler<{ current: T }> = { 19 | get(target: Record, key: string) { 20 | if ( 21 | target && 22 | typeof target === 'object' && 23 | typeof target[key] === 'object' && 24 | target[key] !== null 25 | ) { 26 | return new Proxy(target[key], handler); 27 | } else { 28 | return target[key]; 29 | } 30 | }, 31 | set(target: Record, key: string, value: unknown) { 32 | if (timeout) { 33 | clearTimeout(timeout); 34 | } 35 | 36 | timeout = setTimeout(() => { 37 | target[key] = value; 38 | }, delay) as unknown as number; 39 | 40 | return true; 41 | } 42 | }; 43 | 44 | return new Proxy(_state, handler); 45 | } 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SvelteUse 2 | 3 | A collection of Svelte 5 utilities. 4 | 5 | ## Features 6 | 7 | - Feature Rich (not exactly *yet*, but we're getting there) 8 | - Complete documentation and demos 9 | - Strongly-typed with Typescript 10 | - Supports Svelte and SvelteKit (even with SSR enabled) 11 | 12 | ## Installation 13 | 14 | ```bash 15 | npm install @sv-use/core 16 | ``` 17 | 18 | For more information and documentation, see the [official website](https://svelte-librarian.github.io/sv-use). 19 | 20 | ## FAQ 21 | 22 | 1. Aren't there other libraries that do this already ? 23 | Yes there are. However, most of the libraries haven't transitioned to Svelte 5. 24 | 1. Why use this one instead of [`svecosystem/runed`](https://github.com/svecosystem/runed) (or other libraries) ? 25 | 1. I wanted to make a library that had a lot of utilities to the level of [vueuse/vueuse](https://github.com/vueuse/vueuse) and/or [streamich/react-use](https://github.com/streamich/react-use). It's not there yet, but it's slowly growing. 26 | 1. I'm not a big fan of using the `use` prefix *everywhere*, especially when other verbs could be more descriptive. 27 | 28 | ## Acknowledgements 29 | 30 | Though it's not a 1:1 port, I heavily took inspiration from these libraries to adapt the library for the Svelte ecosystem : 31 | 32 | - [vueuse/vueuse](https://github.com/vueuse/vueuse) 33 | - [streamich/react-use](https://github.com/streamich/react-use) 34 | - [svecosystem/runed](https://github.com/svecosystem/runed) 35 | -------------------------------------------------------------------------------- /docs/content/docs/core/track-history/Demo.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 |
15 |

Counter : {counter}

16 | 17 | 18 | 19 |
20 | {@render renderHistory('Undo History', history.history)} 21 | {@render renderHistory('Redo History', history.redoHistory)} 22 |
23 |
24 | 25 | {#snippet renderHistory(title: string, history: Snapshot)} 26 |
27 |

{title}

28 | {#if history.length > 0} 29 |
    30 | {#each history as item} 31 |
  • 32 | Value : {item.snapshot} ({new Date(item.timestamp).toLocaleTimeString()}) 33 |
  • 34 | {/each} 35 |
36 | {:else} 37 |

Empty...

38 | {/if} 39 |
40 | {/snippet} 41 | -------------------------------------------------------------------------------- /docs/content/docs/core/watch/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: lifecycle 3 | --- 4 | 5 | # watch 6 | 7 | Triggers a callback when a dependency changes. 8 | 9 | Provides the previous value(s) as well as the current one(s) as parameters in the callback. 10 | 11 | ## Usage 12 | 13 | You can watch changes on a single value : 14 | 15 | ```svelte 16 | 25 | ``` 26 | 27 | Or on multiple values by supplying an array : 28 | 29 | ```svelte 30 | 43 | ``` 44 | 45 | ### onMount 46 | 47 | By default, the callback runs when the component is first mounted in the DOM, 48 | as well as when a dependency changes. 49 | 50 | You might not want that and only run when a dependency changes. You can set 51 | this in the options. 52 | 53 | ```svelte 54 | 63 | ``` 64 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 | # SvelteUse 2 | 3 | A collection of Svelte 5 utilities. 4 | 5 | ## Features 6 | 7 | - Feature Rich (not exactly *yet*, but we're getting there) 8 | - Complete documentation and demos 9 | - Strongly-typed with Typescript 10 | - Supports Svelte and SvelteKit (even with SSR enabled) 11 | 12 | ## Installation 13 | 14 | ```bash 15 | npm install @sv-use/core 16 | ``` 17 | 18 | For more information and documentation, see the [official website](https://svelte-librarian.github.io/sv-use). 19 | 20 | ## FAQ 21 | 22 | 1. Aren't there other libraries that do this already ? 23 | Yes there are. However, most of the libraries haven't transitioned to Svelte 5. 24 | 1. Why use this one instead of [`svecosystem/runed`](https://github.com/svecosystem/runed) (or other libraries) ? 25 | 1. I wanted to make a library that had a lot of utilities to the level of [vueuse/vueuse](https://github.com/vueuse/vueuse) and/or [streamich/react-use](https://github.com/streamich/react-use). It's not there yet, but it's slowly growing. 26 | 1. I'm not a big fan of using the `use` prefix *everywhere*, especially when other verbs could be more descriptive. 27 | 28 | ## Acknowledgements 29 | 30 | Though it's not a 1:1 port, I heavily took inspiration from these libraries to adapt the library for the Svelte ecosystem : 31 | 32 | - [vueuse/vueuse](https://github.com/vueuse/vueuse) 33 | - [streamich/react-use](https://github.com/streamich/react-use) 34 | - [svecosystem/runed](https://github.com/svecosystem/runed) 35 | -------------------------------------------------------------------------------- /docs/content/docs/core/observe-intersection/Demo.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 |
18 |
19 | 27 |
28 |
32 |
33 | Scroll down... 34 |
38 | i'm the target element 39 |
40 |
41 |
42 |

43 | Element 44 | 45 | {isVisible ? 'inside' : 'outside'} 46 | the viewport 47 |

48 |
49 | -------------------------------------------------------------------------------- /packages/core/src/default-state/index.svelte.ts: -------------------------------------------------------------------------------- 1 | type DefaultStateReturn = { 2 | // ? Is there a way to have the getter be defined but the setter be nullable 3 | /** 4 | * @note Although it is typed as nullable, reading the value will never return a nullable value. 5 | * 6 | * This is to ensure that you can set a nullable value when changing the state without TS complaining. 7 | */ 8 | current: T | null | undefined; 9 | }; 10 | 11 | /** 12 | * A reactive state that falls back to `defaultValue` if set to `null` or `undefined`. 13 | * @param defaultValue The fallback value when the value is set to `null` or `undefined`. 14 | * @param initialValue The initial value of the state. Defaults to `defaultValue` if omitted. 15 | * @see https://svelte-librarian.github.io/sv-use/docs/core/default-state 16 | */ 17 | export function defaultState(defaultValue: T, initialValue?: T): DefaultStateReturn { 18 | const _default = $state({ current: initialValue ?? defaultValue }); 19 | 20 | const handler: ProxyHandler<{ current: T }> = { 21 | get(target: Record, key: string) { 22 | if ( 23 | target && 24 | typeof target === 'object' && 25 | typeof target[key] === 'object' && 26 | target[key] !== null 27 | ) { 28 | return new Proxy(target[key], handler); 29 | } else { 30 | return target[key]; 31 | } 32 | }, 33 | set(target: Record, key: string, value: unknown) { 34 | if (value || JSON.stringify(target) !== JSON.stringify($state.snapshot(_default))) { 35 | target[key] = value; 36 | } else { 37 | target[key] = defaultValue; 38 | } 39 | 40 | return true; 41 | } 42 | }; 43 | 44 | return new Proxy(_default, handler); 45 | } 46 | -------------------------------------------------------------------------------- /packages/core/src/get-document-visibility/index.svelte.ts: -------------------------------------------------------------------------------- 1 | import { handleEventListener } from '../handle-event-listener/index.svelte.js'; 2 | import { noop } from '../__internal__/utils.svelte.js'; 3 | import { defaultDocument, type ConfigurableDocument } from '../__internal__/configurable.js'; 4 | import type { CleanupFunction } from '../__internal__/types.js'; 5 | 6 | interface GetDocumentVisibilityOptions extends ConfigurableDocument { 7 | /** 8 | * Whether to auto-cleanup the event listener or not. 9 | * 10 | * If set to `true`, it must run in the component initialization lifecycle. 11 | * @default true 12 | */ 13 | autoCleanup?: boolean; 14 | } 15 | 16 | type GetDocumentVisibilityReturn = { 17 | readonly current: DocumentVisibilityState; 18 | /** 19 | * Cleans up the event listener. 20 | * @note Is called automatically if `options.autoCleanup` is `true`. 21 | */ 22 | cleanup: CleanupFunction; 23 | }; 24 | 25 | /** 26 | * Whether the document is visible or not. 27 | * @see https://svelte-librarian.github.io/sv-use/docs/core/get-document-visibility 28 | */ 29 | export function getDocumentVisibility( 30 | options: GetDocumentVisibilityOptions = {} 31 | ): GetDocumentVisibilityReturn { 32 | const { autoCleanup = true, document = defaultDocument } = options; 33 | 34 | let cleanup: CleanupFunction = noop; 35 | let _current = $state(document?.visibilityState ?? 'visible'); 36 | 37 | if (document) { 38 | cleanup = handleEventListener( 39 | document, 40 | 'visibilitychange', 41 | () => (_current = document.visibilityState), 42 | { autoCleanup } 43 | ); 44 | } 45 | 46 | return { 47 | get current() { 48 | return _current; 49 | }, 50 | cleanup 51 | }; 52 | } 53 | -------------------------------------------------------------------------------- /packages/core/src/create-vibration/index.svelte.ts: -------------------------------------------------------------------------------- 1 | import { normalizeValue } from '../__internal__/utils.svelte.js'; 2 | import { defaultNavigator, type ConfigurableNavigator } from '../__internal__/configurable.js'; 3 | import type { MaybeGetter } from '../__internal__/types.js'; 4 | 5 | interface CreateVibrationOptions extends ConfigurableNavigator { 6 | /** 7 | * An array of values describes alternating periods in which the device is 8 | * vibrating and not vibrating. Each value in the array is converted to an 9 | * integer, then interpreted alternately as the number of milliseconds the 10 | * device should vibrate and the number of milliseconds it should not be 11 | * vibrating. 12 | * @default [] 13 | */ 14 | pattern?: MaybeGetter; 15 | } 16 | 17 | type CreateVibrationReturn = { 18 | readonly isSupported: boolean; 19 | vibrate: () => void; 20 | stop: () => void; 21 | }; 22 | 23 | /** 24 | * Reactive vibrate. 25 | * @param options Additional options to customize the behavior. 26 | * @see https://svelte-librarian.github.io/sv-use/docs/core/create-vibration 27 | */ 28 | export function createVibration(options: CreateVibrationOptions = {}): CreateVibrationReturn { 29 | const { pattern = [], navigator = defaultNavigator } = options; 30 | 31 | const isSupported = $derived(typeof navigator !== 'undefined' && 'vibrate' in navigator); 32 | const _pattern = $derived(normalizeValue(pattern)); 33 | 34 | function vibrate() { 35 | if (!isSupported) return; 36 | 37 | navigator!.vibrate(_pattern); 38 | } 39 | 40 | function stop() { 41 | if (!isSupported) return; 42 | 43 | navigator!.vibrate(0); 44 | } 45 | 46 | return { 47 | get isSupported() { 48 | return isSupported; 49 | }, 50 | vibrate, 51 | stop 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /packages/core/src/whenever/index.svelte.ts: -------------------------------------------------------------------------------- 1 | import type { Arrayable, Getter } from '../__internal__/types.js'; 2 | import { normalizeValue, toArray } from '../__internal__/utils.svelte.js'; 3 | 4 | type WheneverOptions = { 5 | /** 6 | * Whether to run the effect on mount or not. 7 | * @default true 8 | */ 9 | runOnMount?: boolean; 10 | }; 11 | 12 | /** 13 | * Triggers a callback when the dependency is `true`. 14 | * @param dep The dependency to watch. 15 | * @param fn The callback to trigger when the dependency is `true`. 16 | * @param options Additional options to customize the behavior. 17 | * @see https://svelte-librarian.github.io/sv-use/docs/core/whenever 18 | */ 19 | export function whenever(dep: Getter, fn: () => void, options?: WheneverOptions): void; 20 | 21 | /** 22 | * Triggers a callback when the dependencies are `true`. 23 | * @param deps The dependencies to watch. 24 | * @param fn The callback to trigger when the dependencies are `true`. 25 | * @param options Additional options to customize the behavior. 26 | * @see https://svelte-librarian.github.io/sv-use/docs/core/whenever 27 | */ 28 | export function whenever( 29 | deps: Array>, 30 | fn: () => void, 31 | options?: WheneverOptions 32 | ): void; 33 | 34 | export function whenever( 35 | deps: Arrayable>, 36 | fn: () => void, 37 | options: WheneverOptions = {} 38 | ): void { 39 | const { runOnMount = true } = options; 40 | 41 | let active = runOnMount; 42 | 43 | $effect(() => { 44 | // Allows to run even with deeply nested object changes 45 | const values = $state.snapshot(toArray(deps).map(normalizeValue)); 46 | 47 | if (!active) { 48 | active = true; 49 | return; 50 | } 51 | 52 | if (values.every((v) => v)) { 53 | fn(); 54 | } 55 | }); 56 | } 57 | -------------------------------------------------------------------------------- /packages/core/src/get-text-direction/index.svelte.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { getTextDirection } from './index.svelte.js'; 3 | import { flushSync } from 'svelte'; 4 | 5 | describe('getTextDirection', () => { 6 | it("has a default value of 'ltr'", () => { 7 | const cleanup = $effect.root(() => { 8 | const dir = getTextDirection({ autoCleanup: false }); 9 | 10 | expect(dir.current).toBe('ltr'); 11 | }); 12 | 13 | cleanup(); 14 | }); 15 | 16 | it("takes 'initial' as default value if passed", () => { 17 | const cleanup = $effect.root(() => { 18 | const dir = getTextDirection({ initial: 'rtl', autoCleanup: false }); 19 | 20 | expect(dir.current).toBe('rtl'); 21 | }); 22 | 23 | cleanup(); 24 | }); 25 | 26 | it('reflects the changes on the dom when current is set', () => { 27 | const cleanup = $effect.root(() => { 28 | const element = $state(document.createElement('div')); 29 | const dir = getTextDirection({ element: () => element, autoCleanup: false }); 30 | 31 | expect(element.getAttribute('dir')).toBeNull(); 32 | 33 | dir.current = 'rtl'; 34 | 35 | expect(element.getAttribute('dir')).toBe('rtl'); 36 | }); 37 | 38 | cleanup(); 39 | }); 40 | 41 | it('observes the changes in the dom', () => { 42 | const cleanup = $effect.root(() => { 43 | const element = $state(document.createElement('div')); 44 | const dir = getTextDirection({ element: () => element, observe: true, autoCleanup: false }); 45 | 46 | expect(dir.current).toBe('ltr'); 47 | expect(element.getAttribute('dir')).toBeNull(); 48 | 49 | element.setAttribute('dir', 'rtl'); 50 | 51 | flushSync(); 52 | 53 | expect(dir.current).toBe('rtl'); 54 | expect(element.getAttribute('dir')).toBe('rtl'); 55 | }); 56 | 57 | cleanup(); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /docs/src/routes/ThemeHandler.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 55 | -------------------------------------------------------------------------------- /docs/content/docs/core/on-long-press/Demo.svelte: -------------------------------------------------------------------------------- 1 | 49 | 50 |
51 |

Is long press ? {isLongPress}

52 |

Is click ? {isClick}

53 |
54 | 57 | 60 | 63 | 64 |
65 |
66 | -------------------------------------------------------------------------------- /docs/content/docs/core/async-state/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: state 3 | --- 4 | 5 | # asyncState 6 | 7 | A reactive state that handles the loading and error states of a promise. 8 | 9 | ## Usage 10 | 11 | ```svelte 12 | 21 | ``` 22 | 23 | ## Examples 24 | 25 | A basic example where you wait for the value to be resolved. 26 | 27 | ```svelte 28 | 37 | 38 | {#if recipe.isLoading} 39 |

Loading your recipe...

40 | {:else} 41 |
{JSON.stringify(recipe.current, null, 4)}
42 | {/if} 43 | ``` 44 | 45 | A more advanced usage where the recipe is fetched again when the `id` changes and shows the last result before swapping instead of showing the loading tag. 46 | 47 | Note that you have to set `immediate` to `false` if you are using a function that depends on arguments for the promise parameter, and then manually call the `execute` function. 48 | 49 | ```svelte 50 | 63 | 64 | {#if !recipe.current} 65 |

Loading your recipe...

66 | {:else} 67 |
{JSON.stringify(recipe.current, null, 4)}
68 | 69 | {/if} 70 | ``` 71 | -------------------------------------------------------------------------------- /packages/core/src/is-window-focused/index.svelte.ts: -------------------------------------------------------------------------------- 1 | import { handleEventListener } from '../handle-event-listener/index.svelte.js'; 2 | import { defaultWindow, type ConfigurableWindow } from '../__internal__/configurable.js'; 3 | import type { CleanupFunction } from '../__internal__/types.js'; 4 | import { onDestroy } from 'svelte'; 5 | 6 | interface IsWindowFocusedOptions extends ConfigurableWindow { 7 | /** 8 | * Whether to automatically cleanup the event listeners or not. 9 | * 10 | * If set to `true`, it must run in the component initialization lifecycle. 11 | * @default true 12 | */ 13 | autoCleanup?: boolean; 14 | } 15 | 16 | type IsWindowFocusedReturn = { 17 | readonly current: boolean; 18 | /** 19 | * Cleans up the event listeners. 20 | * @note Called automatically if `options.autoCleanup` is `true`. 21 | */ 22 | cleanup: CleanupFunction; 23 | }; 24 | 25 | /** 26 | * Tracks whether the window is focused or not. 27 | * @param options Additional options to customize the behavior. 28 | * @see https://svelte-librarian.github.io/sv-use/docs/core/is-window-focused 29 | */ 30 | export function isWindowFocused(options: IsWindowFocusedOptions = {}): IsWindowFocusedReturn { 31 | const { window = defaultWindow, autoCleanup = true } = options; 32 | 33 | const cleanups: CleanupFunction[] = []; 34 | 35 | let _isFocused = $state(!!window && window.document.hasFocus()); 36 | 37 | if (window) { 38 | cleanups.push( 39 | handleEventListener('blur', () => (_isFocused = false), { passive: true, autoCleanup }), 40 | handleEventListener('focus', () => (_isFocused = true), { passive: true, autoCleanup }) 41 | ); 42 | } 43 | 44 | if (autoCleanup) { 45 | onDestroy(() => { 46 | cleanup(); 47 | }); 48 | } 49 | 50 | function cleanup() { 51 | cleanups.map((fn) => fn()); 52 | } 53 | 54 | return { 55 | get current() { 56 | return _isFocused; 57 | }, 58 | cleanup 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /packages/core/src/get-scrollbar-width/index.svelte.ts: -------------------------------------------------------------------------------- 1 | import { untrack } from 'svelte'; 2 | import { handleEventListener } from '../handle-event-listener/index.svelte.js'; 3 | import { observeResize } from '../observe-resize/index.svelte.js'; 4 | import { observeMutation } from '../observe-mutation/index.svelte.js'; 5 | import { normalizeValue } from '../__internal__/utils.svelte.js'; 6 | import type { CleanupFunction, MaybeGetter } from '../__internal__/types.js'; 7 | 8 | type GetScrollbarWidthReturn = { 9 | readonly x: number; 10 | readonly y: number; 11 | cleanup: CleanupFunction; 12 | }; 13 | 14 | /** 15 | * Gets the scrollbar width of an element. 16 | * @param element The element on which to get the scrollbar width from. 17 | * @see https://svelte-librarian.github.io/sv-use/core/get-scrollbar-width 18 | */ 19 | export function getScrollbarWidth( 20 | element: MaybeGetter 21 | ): GetScrollbarWidthReturn { 22 | let cleanups: CleanupFunction[] = []; 23 | 24 | let x = $state(0); 25 | let y = $state(0); 26 | const _target = $derived(normalizeValue(element)); 27 | 28 | $effect(() => untrack(() => calculate())); 29 | 30 | $effect(() => { 31 | if (_target) { 32 | cleanups.push(handleEventListener('resize', calculate)); 33 | } 34 | 35 | return cleanup; 36 | }); 37 | 38 | cleanups.push( 39 | observeResize(() => _target, calculate, { autoCleanup: false }).cleanup, 40 | observeMutation(() => _target, calculate, { autoCleanup: false, attributes: true }).cleanup 41 | ); 42 | 43 | function calculate() { 44 | if (!_target) return; 45 | 46 | x = _target.offsetWidth - _target.clientWidth; 47 | y = _target.offsetHeight - _target.clientHeight; 48 | } 49 | 50 | function cleanup() { 51 | cleanups.forEach((fn) => fn()); 52 | cleanups = []; 53 | } 54 | 55 | return { 56 | get x() { 57 | return x; 58 | }, 59 | get y() { 60 | return y; 61 | }, 62 | cleanup 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /docs/src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | SvelteUse 9 | 10 | 11 |
12 |
13 |

SvelteUse

14 |

A collection of Svelte 5 utilities

15 | 30 |
31 |
32 |

Features

33 |
34 | {@render feature( 35 | 'Feature Rich', 36 | `More than ${data.totalUtilities} utilities to choose from. (Alright, it's not that much yet...)` 37 | )} 38 | {@render feature('SSR Friendly', 'Supports SSR out of the box.')} 39 | {@render feature('Strongly typed', 'Written in Typescript for type safety.')} 40 | {@render feature('Interactive demos', 'Every function has an interactive demo.')} 41 |
42 |
43 |
44 | 45 | {#snippet feature(title: string, description: string)} 46 |
49 |

{title}

50 |

{description}

51 |
52 | {/snippet} 53 | -------------------------------------------------------------------------------- /packages/core/src/history-state/index.svelte.ts: -------------------------------------------------------------------------------- 1 | import { 2 | trackHistory, 3 | type TrackHistoryOptions, 4 | type TrackHistoryReturn 5 | } from '../track-history/index.svelte.js'; 6 | 7 | type HistoryStateOptions = TrackHistoryOptions; 8 | type HistoryStateReturn = TrackHistoryReturn & { 9 | current: T; 10 | }; 11 | 12 | /** 13 | * A reactive state that allows for undo and redo operations by tracking the change history. 14 | * @param initial The initial value of the state. 15 | * @see https://svelte-librarian.github.io/sv-use/docs/core/history-state 16 | */ 17 | export function historyState( 18 | initial: T, 19 | options: HistoryStateOptions = {} 20 | ): HistoryStateReturn { 21 | const { includeCurrent = false } = options; 22 | 23 | const _state = $state({ current: initial }); 24 | const _history = trackHistory( 25 | () => $state.snapshot(_state.current) as T, 26 | (v) => (_state.current = v), 27 | { includeCurrent } 28 | ); 29 | 30 | const handler: ProxyHandler<{ current: T }> = { 31 | get(target: Record, key: string) { 32 | if ( 33 | target && 34 | typeof target === 'object' && 35 | typeof target[key] === 'object' && 36 | target[key] !== null 37 | ) { 38 | return new Proxy(target[key], handler); 39 | } else { 40 | return target[key]; 41 | } 42 | }, 43 | set(target: Record, key: string, value: unknown) { 44 | target[key] = value; 45 | 46 | return true; 47 | } 48 | }; 49 | 50 | return { 51 | get current() { 52 | return new Proxy(_state, handler).current; 53 | }, 54 | set current(v: T) { 55 | _state.current = v; 56 | }, 57 | get canUndo() { 58 | return _history.canUndo; 59 | }, 60 | get canRedo() { 61 | return _history.canRedo; 62 | }, 63 | get history() { 64 | return _history.history; 65 | }, 66 | get redoHistory() { 67 | return _history.redoHistory; 68 | }, 69 | undo() { 70 | return _history.undo(); 71 | }, 72 | redo() { 73 | return _history.redo(); 74 | } 75 | }; 76 | } 77 | -------------------------------------------------------------------------------- /docs/content/docs/guide/introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | SvelteUse is a collection of Svelte 5 utilities based on [runes](https://svelte.dev/docs/svelte/what-are-runes). 4 | It is assumed that you are at least somewhat familiar with the runes system. 5 | 6 | You can use it with SvelteKit and with Svelte-only apps as it doesn't rely on 7 | Sveltekit-specific features. 8 | 9 | ## Installation 10 | 11 | ```bash 12 | npm install @sv-use/core 13 | ``` 14 | 15 | ## Usage 16 | 17 | > [!TIP] 18 | > Refer to the documentation of each function to see how to use it and examples. 19 | 20 | You can simply import the utility you need from `@sv-use/core` and use it. 21 | 22 | An example using a state that is persisted via local storage : 23 | 24 | ```svelte 25 | 30 | 31 | counter : {counter.current} 32 | ``` 33 | 34 | ## Best Practices 35 | 36 | ### Cleanup 37 | 38 | Some utilities produce side-effects, such as invoking an event listener. By 39 | default, they are automatically cleaned up in an `onDestroy` hook. 40 | 41 | However, this requires the function to be called in the component 42 | initialization lifecycle. 43 | 44 | To opt out of this, every utility that produces a side-effect returns a cleanup 45 | function that can be used to clean it manually. 46 | 47 | Here is an example using [handleEventListener](/docs/core/handle-event-listener) : 48 | 49 | ```svelte 50 | 56 | ``` 57 | 58 | And how to cleanup manually : 59 | 60 | ```svelte 61 | 73 | ``` 74 | -------------------------------------------------------------------------------- /docs/content/docs/core/on-swipe/Demo.svelte: -------------------------------------------------------------------------------- 1 | 44 | 45 |
46 |

47 | Try dragging the element for half the container. 48 |

49 |
53 |
61 |

62 | Swipe right 63 |

64 |
65 |
66 | 67 |

68 | Direction: {swipe.direction ? swipe.direction : '-'}
69 | lengthX: {swipe.lengthX} | lengthY: {swipe.lengthY} 70 |

71 |
72 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sv-use/website", 3 | "description": "The website for @sv-use/core utilities.", 4 | "type": "module", 5 | "version": "1.0.0", 6 | "private": true, 7 | "license": "MIT", 8 | "author": "Sajidur Rahman", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/sv-use/sv-use.git" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/sv-use/sv-use/issues" 15 | }, 16 | "homepage": "https://github.com/sv-use/sv-use#readme", 17 | "scripts": { 18 | "dev": "vite dev", 19 | "build": "vite build", 20 | "preview": "vite preview", 21 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 22 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 23 | "format": "prettier --write .", 24 | "lint": "prettier --check . && eslint .", 25 | "test:unit": "vitest", 26 | "test": "npm run test:unit -- --run" 27 | }, 28 | "devDependencies": { 29 | "@eslint/compat": "^1.2.3", 30 | "@sveltejs/adapter-static": "^3.0.8", 31 | "@sveltejs/kit": "^2.9.0", 32 | "@sveltejs/vite-plugin-svelte": "^5.0.0", 33 | "@types/node": "^22.10.2", 34 | "@types/remark-heading-id": "^1.0.0", 35 | "autoprefixer": "^10.4.20", 36 | "eslint": "^9.7.0", 37 | "eslint-config-prettier": "^9.1.0", 38 | "eslint-plugin-svelte": "^2.36.0", 39 | "globals": "^15.0.0", 40 | "postcss": "^8.4.49", 41 | "prettier": "^3.4.2", 42 | "prettier-plugin-svelte": "^3.2.6", 43 | "prettier-plugin-tailwindcss": "^0.6.9", 44 | "svelte": "^5.20.2", 45 | "tailwindcss": "^3.4.16", 46 | "typescript": "^5.0.0", 47 | "typescript-eslint": "^8.0.0", 48 | "vite": "^6.1.0" 49 | }, 50 | "dependencies": { 51 | "@vcarl/remark-headings": "^0.1.0", 52 | "gray-matter": "^4.0.3", 53 | "rehype-external-links": "^3.0.0", 54 | "rehype-pretty-code": "^0.14.0", 55 | "rehype-stringify": "^10.0.1", 56 | "remark-github-blockquote-alert": "^1.3.0", 57 | "remark-heading-id": "^1.0.1", 58 | "remark-parse": "^11.0.0", 59 | "remark-rehype": "^11.1.1", 60 | "tailwind-merge": "^2.5.5", 61 | "unified": "^11.0.5" 62 | }, 63 | "overrides": { 64 | "cookie": "^0.7.0" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /packages/core/src/create-share/index.svelte.ts: -------------------------------------------------------------------------------- 1 | import { normalizeValue } from '../__internal__/utils.svelte.js'; 2 | import { defaultNavigator, type ConfigurableNavigator } from '../__internal__/configurable.js'; 3 | import type { MaybeGetter } from '../__internal__/types.js'; 4 | 5 | type NavigatorShareData = { 6 | title?: string; 7 | files?: File[]; 8 | text?: string; 9 | url?: string; 10 | }; 11 | 12 | interface NavigatorWithShare { 13 | share?: (data: NavigatorShareData) => Promise; 14 | canShare?: (data: NavigatorShareData) => boolean; 15 | } 16 | 17 | type CreateShareData = { 18 | title?: MaybeGetter; 19 | files?: MaybeGetter; 20 | text?: MaybeGetter; 21 | url?: MaybeGetter; 22 | }; 23 | 24 | type CreateShareOptions = ConfigurableNavigator; 25 | 26 | type CreateShareReturn = { 27 | readonly isSupported: boolean; 28 | share(): Promise; 29 | }; 30 | 31 | /** 32 | * Invokes the native sharing mechanism of the device to share data such as text, URLs, or files. 33 | * @param data The data to share. 34 | * @param options Additional options to customize the behavior. 35 | * @see https://svelte-librarian.github.io/sv-use/docs/core/create-share 36 | */ 37 | export function createShare( 38 | data: CreateShareData, 39 | options: CreateShareOptions = {} 40 | ): CreateShareReturn { 41 | const { navigator = defaultNavigator } = options; 42 | 43 | const _data = $derived({ 44 | files: normalizeValue(data.files), 45 | text: normalizeValue(data.text), 46 | title: normalizeValue(data.title), 47 | url: normalizeValue(data.url) 48 | }); 49 | 50 | const _navigator = navigator as NavigatorWithShare; 51 | const isSupported = $derived(_navigator && 'canShare' in _navigator); 52 | 53 | async function share() { 54 | if (!isSupported) return; 55 | let granted = true; 56 | 57 | if (data.files && _navigator.canShare) { 58 | granted = _navigator.canShare({ files: normalizeValue(data.files) }); 59 | } 60 | 61 | if (granted) { 62 | return _navigator.share!(_data); 63 | } 64 | } 65 | 66 | return { 67 | get isSupported() { 68 | return isSupported; 69 | }, 70 | share 71 | }; 72 | } 73 | -------------------------------------------------------------------------------- /packages/core/src/get-network/index.svelte.ts: -------------------------------------------------------------------------------- 1 | import { BROWSER } from 'esm-env'; 2 | 3 | type NetworkInformation = { 4 | /** The effective bandwidth estimate in megabits per second, rounded to the nearest multiple of 25 kilobits per seconds. */ 5 | readonly downlink: number; 6 | /** The maximum downlink speed, in megabits per second (Mbps), for the underlying connection technology. */ 7 | readonly downlinkMax: number; 8 | /** @see https://developer.mozilla.org/en-US/docs/Glossary/Effective_connection_type */ 9 | readonly effectiveType: 'slow-2g' | '2g' | '3g' | '4g'; 10 | /** The estimated effective round-trip time of the current connection, rounded to the nearest multiple of 25 milliseconds. */ 11 | readonly rtt: number; 12 | /** Whether the user has set a reduced data usage option on the user agent or not. */ 13 | readonly saveData: boolean; 14 | /** The type of connection a device is using to communicate with the network. */ 15 | readonly type: 16 | | 'bluetooth' 17 | | 'cellular' 18 | | 'ethernet' 19 | | 'none' 20 | | 'wifi' 21 | | 'wimax' 22 | | 'other' 23 | | 'unknown'; 24 | }; 25 | 26 | type NavigatorWithConnection = Navigator & { 27 | readonly connection: NetworkInformation; 28 | }; 29 | 30 | type GetNetworkReturn = { 31 | readonly isSupported: boolean; 32 | readonly current: NetworkInformation; 33 | }; 34 | 35 | /** 36 | * Provides information about the connection a device is using to communicate with the network. 37 | * @see https://svelte-librarian.github.io/sv-use/docs/core/get-network 38 | */ 39 | export function getNetwork(): GetNetworkReturn { 40 | const _isSupported = $derived.by(() => navigator && 'connection' in navigator); 41 | let _current = $state({ 42 | downlink: 0, 43 | downlinkMax: 0, 44 | effectiveType: 'slow-2g', 45 | rtt: 0, 46 | saveData: false, 47 | type: 'unknown' 48 | }); 49 | 50 | if (BROWSER && _isSupported) { 51 | _current = { ..._current, ...(navigator as NavigatorWithConnection).connection }; 52 | } 53 | 54 | return { 55 | get isSupported() { 56 | return _isSupported; 57 | }, 58 | get current() { 59 | return _current; 60 | } 61 | }; 62 | } 63 | -------------------------------------------------------------------------------- /packages/core/src/has-left-page/index.svelte.ts: -------------------------------------------------------------------------------- 1 | import { onDestroy } from 'svelte'; 2 | import { handleEventListener } from '../handle-event-listener/index.svelte.js'; 3 | import { defaultWindow, type ConfigurableWindow } from '../__internal__/configurable.js'; 4 | import type { CleanupFunction } from '../__internal__/types.js'; 5 | 6 | interface HasLeftPageOptions extends ConfigurableWindow { 7 | /** 8 | * Whether to automatically clean up the event listeners or not. 9 | * 10 | * If set to `true`, it must run in the component initialization lifecycle. 11 | * @default true 12 | */ 13 | autoCleanup?: boolean; 14 | } 15 | 16 | type HasLeftPageReturn = { 17 | readonly current: boolean; 18 | /** 19 | * Cleans up the event listeners. 20 | * @note Is called automatically if `options.autoCleanup` is set to `true`. 21 | */ 22 | cleanup: CleanupFunction; 23 | }; 24 | 25 | /** 26 | * Whether the mouse has left the page or not. 27 | * @param options Additional options to customize the behavior. 28 | * @see https://svelte-librarian.github.io/sv-use/docs/core/has-left-page 29 | */ 30 | export function hasLeftPage(options: HasLeftPageOptions = {}): HasLeftPageReturn { 31 | const { autoCleanup = true, window = defaultWindow } = options; 32 | 33 | const cleanups: CleanupFunction[] = []; 34 | let _current = $state(false); 35 | 36 | const handler = (event: MouseEvent) => { 37 | if (!window) return; 38 | 39 | event = event || (window.event as unknown); 40 | // @ts-expect-error missing types 41 | const from = event.relatedTarget || event.toElement; 42 | 43 | _current = !from; 44 | }; 45 | 46 | if (window) { 47 | cleanups.push( 48 | handleEventListener(window, 'mouseout', handler, { 49 | autoCleanup, 50 | passive: true 51 | }), 52 | handleEventListener(document, ['mouseleave', 'mouseenter'], handler, { 53 | autoCleanup, 54 | passive: true 55 | }) 56 | ); 57 | } 58 | 59 | if (autoCleanup) { 60 | onDestroy(() => cleanup()); 61 | } 62 | 63 | function cleanup() { 64 | cleanups.forEach((cleanup) => cleanup()); 65 | } 66 | 67 | return { 68 | get current() { 69 | return _current; 70 | }, 71 | cleanup 72 | }; 73 | } 74 | -------------------------------------------------------------------------------- /packages/core/src/get-mouse/index.svelte.ts: -------------------------------------------------------------------------------- 1 | import { onDestroy } from 'svelte'; 2 | import { BROWSER } from 'esm-env'; 3 | import { handleEventListener } from '../handle-event-listener/index.svelte.js'; 4 | import { noop } from '../__internal__/utils.svelte.js'; 5 | import type { CleanupFunction } from '../__internal__/types.js'; 6 | 7 | type GetMouseOptions = { 8 | /** 9 | * Whether to auto-cleanup the event listener or not. 10 | * 11 | * If set to `true`, it must run in the component initialization lifecycle. 12 | * @default true 13 | */ 14 | autoCleanup?: boolean; 15 | /** 16 | * The initial position of the mouse. 17 | * @default { x: 0; y: 0 } 18 | */ 19 | initial?: { x: number; y: number }; 20 | /** 21 | * A callback for when the mouse moves. 22 | * @default () => {} 23 | */ 24 | onMove?: (event: MouseEvent) => void; 25 | }; 26 | 27 | type GetMouseReturn = { 28 | /** The horizontal position of the mouse. */ 29 | readonly x: number; 30 | /** The vertical position of the mouse. */ 31 | readonly y: number; 32 | /** 33 | * Cleans up the event listener. 34 | * @note Is called automatically if `options.autoCleanup` is set to `true`. 35 | */ 36 | cleanup: CleanupFunction; 37 | }; 38 | 39 | /** 40 | * Retrieves information about the mouse. 41 | * @param options Additional options to customize the behavior. 42 | * @see https://svelte-librarian.github.io/sv-use/docs/core/get-mouse 43 | */ 44 | export function getMouse(options: GetMouseOptions = {}): GetMouseReturn { 45 | const { autoCleanup = true, initial = { x: 0, y: 0 }, onMove = noop } = options; 46 | 47 | let cleanup: CleanupFunction = noop; 48 | 49 | let _x = $state(initial.x); 50 | let _y = $state(initial.y); 51 | 52 | if (BROWSER) { 53 | cleanup = handleEventListener('mousemove', onMouseMove, { autoCleanup }); 54 | } 55 | 56 | if (autoCleanup) { 57 | onDestroy(() => cleanup()); 58 | } 59 | 60 | function onMouseMove(event: MouseEvent) { 61 | _x = event.pageX; 62 | _y = event.pageY; 63 | 64 | onMove(event); 65 | } 66 | 67 | return { 68 | get x() { 69 | return _x; 70 | }, 71 | get y() { 72 | return _y; 73 | }, 74 | cleanup 75 | }; 76 | } 77 | -------------------------------------------------------------------------------- /packages/core/src/get-device-pixel-ratio/index.svelte.ts: -------------------------------------------------------------------------------- 1 | import { onDestroy } from 'svelte'; 2 | import { handleEventListener } from '../handle-event-listener/index.svelte.js'; 3 | import { noop } from '../__internal__/utils.svelte.js'; 4 | import { isSupported } from '../__internal__/is.svelte.js'; 5 | import type { CleanupFunction } from '../__internal__/types.js'; 6 | 7 | type GetDevicePixelRatioOptions = { 8 | /** 9 | * Whether to auto-cleanup the event listener or not. 10 | * 11 | * If set to `true`, it must run in the component initialization lifecycle. 12 | * @default true 13 | */ 14 | autoCleanup?: boolean; 15 | }; 16 | 17 | type GetDevicePixelRatioReturn = { 18 | /** Whether the {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio | devicePixelRatio property} is supported or not. */ 19 | readonly isSupported: boolean; 20 | /** The current device pixel ratio. */ 21 | readonly current: number; 22 | /** 23 | * Cleans up the event listener. 24 | * @note Is called automatically if `options.autoCleanup` is set to `true`. 25 | */ 26 | cleanup: CleanupFunction; 27 | }; 28 | 29 | /** 30 | * Returns the ratio of the resolution in physical pixels to the resolution in CSS pixels for the current display device. 31 | * @see https://svelte-librarian.github.io/sv-use/docs/core/get-device-pixel-ratio 32 | */ 33 | export function getDevicePixelRatio( 34 | options: GetDevicePixelRatioOptions = {} 35 | ): GetDevicePixelRatioReturn { 36 | const { autoCleanup = true } = options; 37 | 38 | let cleanup: CleanupFunction = noop; 39 | 40 | const _isSupported = isSupported(() => window && 'devicePixelRatio' in window); 41 | let _current = $state(1); 42 | 43 | if (_isSupported.current) { 44 | updatePixelRatio(); 45 | } 46 | 47 | if (autoCleanup) { 48 | onDestroy(() => cleanup()); 49 | } 50 | 51 | function updatePixelRatio() { 52 | _current = window.devicePixelRatio; 53 | cleanup(); 54 | 55 | const media = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`); 56 | cleanup = handleEventListener(media, 'change', updatePixelRatio, { autoCleanup, once: true }); 57 | } 58 | 59 | return { 60 | get isSupported() { 61 | return _isSupported.current; 62 | }, 63 | get current() { 64 | return _current; 65 | }, 66 | cleanup 67 | }; 68 | } 69 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sv-use/core", 3 | "version": "1.15.1", 4 | "license": "MIT", 5 | "description": "A collection of Svelte 5 utilities.", 6 | "main": "./dist/index.js", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/svelte-librarian/sv-use.git" 10 | }, 11 | "keywords": [ 12 | "svelte", 13 | "svelte-5", 14 | "utilities", 15 | "svelte-utilities", 16 | "svelte-5-utilities", 17 | "svelte-use", 18 | "svelteuse", 19 | "typescript" 20 | ], 21 | "author": "Sajidur Rahman", 22 | "bugs": { 23 | "url": "https://github.com/svelte-librarian/sv-use/issues" 24 | }, 25 | "homepage": "https://www.sv-use.org/", 26 | "scripts": { 27 | "build": "svelte-package", 28 | "build:watch": "npm run build -- --watch", 29 | "package": "svelte-kit sync && npm run build && publint", 30 | "prepublishOnly": "npm run package", 31 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 32 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 33 | "format": "prettier --write .", 34 | "lint": "prettier --check . && eslint .", 35 | "test:unit": "vitest", 36 | "test": "npm run test:unit -- --run", 37 | "coverage": "vitest run --coverage" 38 | }, 39 | "files": [ 40 | "dist", 41 | "!dist/**/*.test.*", 42 | "!dist/**/*.spec.*" 43 | ], 44 | "svelte": "./dist/index.js", 45 | "types": "./dist/index.d.ts", 46 | "type": "module", 47 | "exports": { 48 | ".": { 49 | "types": "./dist/index.d.ts", 50 | "svelte": "./dist/index.js" 51 | } 52 | }, 53 | "peerDependencies": { 54 | "svelte": "^5.0.0" 55 | }, 56 | "devDependencies": { 57 | "@eslint/compat": "^1.2.4", 58 | "@sveltejs/kit": "^2.15.0", 59 | "@sveltejs/package": "^2.3.7", 60 | "@sveltejs/vite-plugin-svelte": "^5.0.3", 61 | "@vitest/coverage-v8": "^3.0.5", 62 | "eslint": "^9.17.0", 63 | "eslint-config-prettier": "^9.1.0", 64 | "eslint-plugin-svelte": "^2.46.1", 65 | "globals": "^15.14.0", 66 | "jsdom": "^26.0.0", 67 | "prettier": "^3.4.2", 68 | "prettier-plugin-svelte": "^3.3.2", 69 | "publint": "^0.2.12", 70 | "svelte": "^5.0.0", 71 | "svelte-check": "^4.1.1", 72 | "typescript": "^5.7.2", 73 | "typescript-eslint": "^8.18.2", 74 | "vitest": "^3.0.5" 75 | }, 76 | "overrides": { 77 | "cookie": "^0.7.0" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /packages/core/src/get-text-selection/index.svelte.ts: -------------------------------------------------------------------------------- 1 | import { onDestroy } from 'svelte'; 2 | import { handleEventListener } from '../handle-event-listener/index.svelte.js'; 3 | import { noop } from '../__internal__/utils.svelte.js'; 4 | import { defaultWindow, type ConfigurableWindow } from '../__internal__/configurable.js'; 5 | import type { AutoCleanup, CleanupFunction } from '../__internal__/types.js'; 6 | 7 | interface GetTextSelectionOptions extends ConfigurableWindow, AutoCleanup {} 8 | 9 | type GetTextSelectionReturn = { 10 | readonly text: string; 11 | readonly rects: DOMRect[]; 12 | readonly ranges: Range[]; 13 | current: Selection | null; 14 | cleanup: CleanupFunction; 15 | }; 16 | 17 | /** 18 | * Gets the range of text selected by the user or the current position of the caret. 19 | * @param options Additional options to customize the behavior. 20 | * @see https://svelte-librarian.github.io/sv-use/docs/core/browser/get-text-selection 21 | */ 22 | export function getTextSelection(options: GetTextSelectionOptions = {}): GetTextSelectionReturn { 23 | const { autoCleanup = true, window = defaultWindow } = options; 24 | 25 | let cleanup: CleanupFunction = noop; 26 | 27 | let current = $state(null); 28 | const text = $derived.by(() => current?.toString() ?? ''); 29 | const ranges = $derived.by(() => (current ? getRangesFromSelection(current) : [])); 30 | const rects = $derived.by(() => ranges.map((range) => range.getBoundingClientRect())); 31 | 32 | if (window) { 33 | cleanup = handleEventListener(window.document, 'selectionchange', onSelectionChange, { 34 | passive: true 35 | }); 36 | } 37 | 38 | if (autoCleanup) { 39 | onDestroy(() => cleanup()); 40 | } 41 | 42 | function getRangesFromSelection(selection: Selection) { 43 | const rangeCount = selection.rangeCount ?? 0; 44 | return Array.from({ length: rangeCount }, (_, i) => selection.getRangeAt(i)); 45 | } 46 | 47 | function onSelectionChange() { 48 | current = null; 49 | 50 | if (window) { 51 | current = window.getSelection(); 52 | } 53 | } 54 | 55 | return { 56 | get current() { 57 | return current; 58 | }, 59 | set current(v) { 60 | current = v; 61 | }, 62 | get text() { 63 | return text; 64 | }, 65 | get rects() { 66 | return rects; 67 | }, 68 | get ranges() { 69 | return ranges; 70 | }, 71 | cleanup 72 | }; 73 | } 74 | -------------------------------------------------------------------------------- /packages/core/src/create-eye-dropper/index.svelte.ts: -------------------------------------------------------------------------------- 1 | import { isSupported } from '../__internal__/is.svelte.js'; 2 | import { defaultWindow, type ConfigurableWindow } from '../__internal__/configurable.js'; 3 | 4 | type SRGBHex = `#${string}`; 5 | 6 | interface EyeDropperOpenOptions { 7 | /** @see https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal */ 8 | signal?: AbortSignal; 9 | } 10 | 11 | export interface EyeDropper { 12 | // eslint-disable-next-line @typescript-eslint/no-misused-new 13 | new (): EyeDropper; 14 | open: (options?: EyeDropperOpenOptions) => Promise<{ sRGBHex: SRGBHex }>; 15 | [Symbol.toStringTag]: 'EyeDropper'; 16 | } 17 | 18 | type WindowWithEyeDropper = Window & { 19 | EyeDropper: EyeDropper; 20 | }; 21 | 22 | interface CreateEyeDropperOptions extends ConfigurableWindow { 23 | /** 24 | * Initial sRGBHex value of the eye dropper. 25 | * @default undefined 26 | */ 27 | initialValue?: SRGBHex; 28 | } 29 | 30 | type CreateEyeDropperReturn = { 31 | /** Whether the {@link https://developer.mozilla.org/en-US/docs/Web/API/EyeDropper_API | `Eye Dropper API`} is supported or not. */ 32 | readonly isSupported: boolean; 33 | /** The current value selected in the eye dropper tool. */ 34 | readonly current: SRGBHex | undefined; 35 | /** A callback to open the eye dropper tool. */ 36 | open: (options?: EyeDropperOpenOptions) => Promise<{ sRGBHex: SRGBHex } | undefined>; 37 | }; 38 | 39 | /** 40 | * Provides a mechanism for creating an eye dropper tool. 41 | * @see https://svelte-librarian.github.io/sv-use/docs/core/create-eye-dropper 42 | */ 43 | export function createEyeDropper(options: CreateEyeDropperOptions = {}): CreateEyeDropperReturn { 44 | const { initialValue = undefined, window = defaultWindow } = options; 45 | 46 | const _isSupported = isSupported(() => !!window && 'EyeDropper' in window); 47 | let _current = $state(initialValue); 48 | 49 | async function open(openOptions?: EyeDropperOpenOptions) { 50 | if (!_isSupported.current || !window) return; 51 | 52 | const eyeDropper: EyeDropper = new (window as WindowWithEyeDropper).EyeDropper(); 53 | const result = await eyeDropper.open(openOptions); 54 | 55 | _current = result.sRGBHex; 56 | 57 | return result; 58 | } 59 | 60 | return { 61 | get isSupported() { 62 | return _isSupported.current; 63 | }, 64 | get current() { 65 | return _current; 66 | }, 67 | open 68 | }; 69 | } 70 | -------------------------------------------------------------------------------- /packages/core/src/get-active-element/index.svelte.ts: -------------------------------------------------------------------------------- 1 | import { onDestroy } from 'svelte'; 2 | import { BROWSER } from 'esm-env'; 3 | import { handleEventListener } from '../handle-event-listener/index.svelte.js'; 4 | import type { CleanupFunction } from '../__internal__/types.js'; 5 | 6 | type GetActiveElementOptions = { 7 | /** 8 | * Whether to automatically cleanup the event listener or not. 9 | * 10 | * If set to `true`, it must run in the component initialization lifecycle. 11 | * @default true 12 | */ 13 | autoCleanup?: boolean; 14 | /** 15 | * Whether to search for the active element inside shadow DOM or not. 16 | * @default true 17 | */ 18 | searchInShadow?: boolean; 19 | }; 20 | 21 | type GetActiveElementReturn = { 22 | /** The current active element or `null`. */ 23 | readonly current: HTMLElement | null; 24 | /** 25 | * The function to cleanup the event listener. 26 | * @note Is called automatically if `options.autoCleanup` is set to `true`. 27 | */ 28 | cleanup: () => void; 29 | }; 30 | 31 | /** 32 | * Returns the element within the DOM that currently has focus. 33 | * @see https://svelte-librarian.github.io/sv-use/docs/core/get-active-element 34 | */ 35 | export function getActiveElement(options: GetActiveElementOptions = {}): GetActiveElementReturn { 36 | const { autoCleanup = true, searchInShadow = true } = options; 37 | 38 | const cleanups: CleanupFunction[] = []; 39 | let _current = $state(null); 40 | 41 | if (BROWSER) { 42 | cleanups.push( 43 | handleEventListener('blur', onBlur, { autoCleanup, capture: true }), 44 | handleEventListener('focus', onFocus, { autoCleanup, capture: true }) 45 | ); 46 | } 47 | 48 | if (autoCleanup) { 49 | onDestroy(() => { 50 | cleanup(); 51 | }); 52 | } 53 | 54 | function getDeepActiveElement() { 55 | let element = document?.activeElement; 56 | 57 | if (searchInShadow) { 58 | while (element?.shadowRoot) { 59 | element = element?.shadowRoot?.activeElement; 60 | } 61 | } 62 | 63 | return element; 64 | } 65 | 66 | function onFocus() { 67 | _current = getDeepActiveElement() as HTMLElement | null; 68 | } 69 | 70 | function onBlur(event: FocusEvent) { 71 | if (event.relatedTarget !== null) return; 72 | 73 | onFocus(); 74 | } 75 | 76 | function cleanup() { 77 | cleanups.forEach((fn) => fn()); 78 | } 79 | 80 | return { 81 | get current() { 82 | return _current; 83 | }, 84 | cleanup 85 | }; 86 | } 87 | -------------------------------------------------------------------------------- /docs/src/routes/Navigation.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 36 | 37 | 38 | 39 | 82 | -------------------------------------------------------------------------------- /packages/core/src/on-start-typing/index.svelte.ts: -------------------------------------------------------------------------------- 1 | import { handleEventListener } from '../handle-event-listener/index.svelte.js'; 2 | import { noop } from '../__internal__/utils.svelte.js'; 3 | import { defaultDocument, type ConfigurableDocument } from '../__internal__/configurable.js'; 4 | import type { CleanupFunction } from '../__internal__/types.js'; 5 | 6 | interface OnStartTypingOptions extends ConfigurableDocument { 7 | /** 8 | * Whether to auto-cleanup the event listener or not. 9 | * 10 | * If set to `true`, it must run in the component initialization lifecycle. 11 | * @default true 12 | */ 13 | autoCleanup?: boolean; 14 | } 15 | 16 | /** 17 | * Fires when users start typing on non-editable elements. 18 | * @param callback The callback for when users start typing on non-editable elements. 19 | * @param options Additional options to customize the behavior. 20 | * @see https://svelte-librarian.github.io/sv-use/docs/core/on-start-typing 21 | */ 22 | export function onStartTyping( 23 | callback: (event: KeyboardEvent) => void, 24 | options: OnStartTypingOptions = {} 25 | ): CleanupFunction { 26 | const { autoCleanup = true, document = defaultDocument } = options; 27 | 28 | let cleanup: CleanupFunction = noop; 29 | 30 | if (document) { 31 | cleanup = handleEventListener(document, 'keydown', onKeydown, { autoCleanup, passive: true }); 32 | } 33 | 34 | function onKeydown(event: KeyboardEvent) { 35 | if (!isFocusedElementEditable() && isTypedCharValid(event)) { 36 | callback(event); 37 | } 38 | } 39 | 40 | function isFocusedElementEditable() { 41 | if (!document) return; 42 | 43 | if (!document.activeElement) return false; 44 | if (document.activeElement === document.body) return false; 45 | 46 | // Assume and