├── 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 |
19 | Show notification
20 |
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 | Long press
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 |
Change message
15 |
16 |
--------------------------------------------------------------------------------
/docs/content/docs/core/get-previous/Demo.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
Counter : {counter}
11 |
Previous counter : {previousCounter.current ?? 'undefined'}
12 |
counter++}>Increment counter
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 |
value++}>Increment
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 |
20 | {@render children?.()}
21 |
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 |
eyeDropper.open()}>Open Eye Dropper
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 |
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 |
window.location.reload()}>Refresh page
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 | clipboard.copyText(inputValue)}>Copy
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 |
15 | {wakeLock.isActive ? 'Deactivate' : 'Activate'}
16 |
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 |
notification.show()}>Show notification
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 |
Search
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 |
Search
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 |
Open file dialog
19 |
Reset
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 |
Share
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 | counter.current++}> Increment
35 | counter.current--}> Decrement
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 |
Search
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 |
10 | {#if onThisPageHeadings.current.length > 0}
11 |
12 |
On this page
13 |
14 | {#each onThisPageHeadings.current as heading}
15 | {@const hash = `#${heading.data.id}`}
16 |
26 | {heading.value}
27 |
28 | {/each}
29 |
30 |
31 | {/if}
32 |
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 | Vibrate
21 | Stop
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 |
{dir.current.toUpperCase()}
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 |
15 | Hover me
16 |
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 | clipboard.copyText(inputValue)}>
41 | Copy text from input
42 |
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 |
38 | Open file dialog
39 |
40 |
44 | Reset
45 |
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 | id--}>Previous recipe
44 | id++}>Next recipe
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 |
counter++}>Increment
17 |
history.undo()} disabled={!history.canUndo}>Undo
18 |
history.redo()} disabled={!history.canRedo}>Redo
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 |
20 |
25 | Is Active
26 |
27 |
28 |
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 |
12 |
18 |
19 |
31 |
32 |
33 |
36 |
37 |
38 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/docs/content/docs/core/on-long-press/Demo.svelte:
--------------------------------------------------------------------------------
1 |
49 |
50 |
51 |
Is long press ? {isLongPress}
52 |
Is click ? {isClick}
53 |
54 |
55 | Long press (500ms)
56 |
57 |
58 | Long press (1000ms)
59 |
60 |
61 | Long press (1000ms) or click
62 |
63 | Reset
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 | id++}>Next recipe
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 |
Reset
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 |
13 |
14 | SvelteUse
15 |
16 |
35 |
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