Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

trap-focus-svelte

Package Overview
Dependencies
Maintainers
1
Versions
11
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

trap-focus-svelte - npm Package Compare versions

Comparing version 0.1.1 to 0.2.0

dist/trap-focus-svelte.cjs

12

dist/index.d.ts

@@ -1,12 +0,8 @@

interface TrapOptions {
/** enables or disables wrap */
active?: boolean;
/** wrapper element */
wrap?: HTMLElement;
}
/** Traps focus within a wrapper element */
declare function trapFocus(node: HTMLElement, options?: TrapOptions): {
update(options: TrapOptions): void;
declare function trapFocus(wrap: HTMLElement, active?: boolean): {
/** Enables / disables trap */
update(active: boolean): void;
/** Destroys trap and removes event listeners */
destroy(): void;
};
export { trapFocus };

@@ -1,1 +0,1 @@

import{listen as t}from"svelte/internal";let e=[],o=!1;function c(c,n={active:!0}){var u,l;let{wrap:r=c,active:a}=n;const s=[...r.querySelectorAll("*")].filter(t=>t.tabIndex>=0),i=null!=(u=s.at(0))?u:r,f=null!=(l=s.at(-1))?l:r,d=document.activeElement;e.push(r),i.focus();const m=(t=r)=>e.at(-1).contains(t),v=t(i,"blur",()=>{a&&m()&&o&&f.focus()}),p=t(f,"blur",()=>{a&&m()&&!o&&i.focus()}),y=t(document,"focusin",t=>{a&&!m(t.target)&&(o?f:i).focus()});return{update(t){a=t.active},destroy(){y(),v(),p(),e=e.filter(t=>t!==r),d.focus()}}}t(document,"keydown",t=>{o=t.shiftKey&&"Tab"===t.key});export{c as trapFocus};
import{listen as t}from"svelte/internal";let e=[],o=0;function n(n,c=1){function u(){const t=[...n.querySelectorAll("*")].filter((t=>t.tabIndex>=0));return[t.at(0)??n,t.at(-1)??n]}let f;function r(){e.push(n),f=document.activeElement,u().at(0).focus()}function s(){e=e.filter((t=>t!=n)),f.focus()}c&&r();const i=t=>e.at(-1)?.contains(t),a=t(n,"focusout",(t=>{if(i(n)){const[e,n]=u();t.target==e&&o?n.focus():t.target!=n||o||e.focus()}})),l=t(document,"focusin",(t=>{if(i(n)&&!i(t.target)){const[t,e]=u();(o?e:t).focus()}}));return{update(t){t?r():s()},destroy(){l(),a(),s()}}}t(document,"keydown",(t=>o=t.shiftKey&&"Tab"==t.key));export{n as trapFocus};
import { listen } from 'svelte/internal'
interface TrapOptions {
/** enables or disables wrap */
active?: boolean
/** wrapper element */
wrap?: HTMLElement
}
let stack: HTMLElement[] = []

@@ -15,45 +8,54 @@

listen(document, 'keydown', (e: KeyboardEvent) => {
shiftTab = e.shiftKey && e.key === 'Tab'
})
listen(document, 'keydown', (e: KeyboardEvent) => (shiftTab = e.shiftKey && e.key == 'Tab'))
/** Traps focus within a wrapper element */
function trapFocus(node: HTMLElement, options: TrapOptions = { active: true }) {
let { wrap = node, active } = options
function trapFocus(wrap: HTMLElement, active = true) {
// return the first and last focusable children
function getFirstAndLastFocusable() {
const els = [...wrap.querySelectorAll('*')].filter(
(element: HTMLElement) => element.tabIndex >= 0
)
return [els.at(0) ?? wrap, els.at(-1) ?? wrap] as HTMLElement[]
}
const focusableEls = [...wrap.querySelectorAll('*')].filter(
(element: HTMLElement) => element.tabIndex >= 0
) as HTMLElement[]
// store document.activeElement to restore focus when untrapped
let lastActiveElement: HTMLElement
const firstFocusableEl = focusableEls.at(0) ?? wrap
const lastFocusableEl = focusableEls.at(-1) ?? wrap
const lastActiveElement = document.activeElement as HTMLElement
/** activates trap (adds to stack) and focuses inside */
function addToStack() {
stack.push(wrap)
lastActiveElement = document.activeElement as HTMLElement
getFirstAndLastFocusable().at(0).focus()
}
/** deactivates trap (removes from stack) and restores focus to lastActiveElement */
function removeFromStack() {
stack = stack.filter((el) => el != wrap)
lastActiveElement.focus()
}
// add to stack
stack.push(wrap)
// add to stack if active
if (active) {
addToStack()
}
// set initial focus on first focusable el
firstFocusableEl.focus()
/** true if element is in the trap most recently added to stack */
const inCurrentTrap = (el: HTMLElement) => stack.at(-1)?.contains(el)
/** true if element is in the last trap added to stack */
const inCurrentTrap = (el: HTMLElement = wrap) => stack.at(-1).contains(el)
// use blur listeners to redirect focus before it leaves container\
/** focus last element if focus leaves first element with shift */
const firstElBlurListener = listen(firstFocusableEl, 'blur', () => {
if (active && inCurrentTrap()) {
shiftTab && lastFocusableEl.focus()
/** loop focus if leaving first of last focusable element in wrap */
const focusOutListener = listen(wrap, 'focusout', (e: FocusEvent) => {
if (inCurrentTrap(wrap)) {
const [firstFocusableEl, lastFocusableEl] = getFirstAndLastFocusable()
if (e.target == firstFocusableEl && shiftTab) {
lastFocusableEl.focus()
} else if (e.target == lastFocusableEl && !shiftTab) {
firstFocusableEl.focus()
}
}
})
/** focus first element if focus leaves last element without shift */
const lastElBlurListener = listen(lastFocusableEl, 'blur', () => {
if (active && inCurrentTrap()) {
!shiftTab && firstFocusableEl.focus()
}
})
/** listener for focus event, moves focus to container if away */
/** moves focus back to wrap if something outside the wrap is focused */
const focusListener = listen(document, 'focusin', (e: FocusEvent) => {
if (active && !inCurrentTrap(e.target as HTMLElement)) {
let focusEl = shiftTab ? lastFocusableEl : firstFocusableEl
if (inCurrentTrap(wrap) && !inCurrentTrap(e.target as HTMLElement)) {
const [first, last] = getFirstAndLastFocusable()
let focusEl = shiftTab ? last : first
focusEl.focus()

@@ -64,13 +66,15 @@ }

return {
update(options: TrapOptions) {
active = options.active
/** Enables / disables trap */
update(active: boolean) {
if (active) {
addToStack()
} else {
removeFromStack()
}
},
/** Destroys trap and removes event listeners */
destroy() {
// remove listeners
focusListener()
firstElBlurListener()
lastElBlurListener()
// remove from stack & focus previously focused element
stack = stack.filter((el) => el !== wrap)
lastActiveElement.focus()
focusOutListener()
removeFromStack()
},

@@ -77,0 +81,0 @@ }

{
"name": "trap-focus-svelte",
"version": "0.1.1",
"license": "MIT",
"description": "Small 0.4kB focus trap that supports stacking, toggling, and custom scope",
"keywords": [
"focus",
"focus trap",
"focus lock",
"svelte"
],
"author": "Hank Dollman <hank@henrygd.me> (https://henrygd.me)",
"repository": {
"type": "git",
"url": "https://github.com/henrygd/trap-focus-svelte.git"
},
"bugs": {
"url": "https://github.com/henrygd/trap-focus-svelte/issues"
},
"homepage": "https://trap-focus-svelte.henrygd.me",
"type": "module",
"source": "index.ts",
"exports": {
".": {
"import": "./dist/trap-focus-svelte.svelte.js",
"require": "./dist/trap-focus-svelte.cjs",
"default": "./dist/trap-focus-svelte.svelte.js"
},
"./svelte": "./dist/trap-focus-svelte.svelte.js",
"./vanilla": "./dist/trap-focus-svelte.mjs"
},
"svelte": "./dist/trap-focus-svelte.svelte.js",
"types": "dist/index.d.ts",
"typesVersions": {
"*": {
"vanilla": [
"dist/index.d.ts"
],
"svelte": [
"dist/index.d.ts"
]
}
},
"scripts": {
"build": "run-p build-cjs build-module build-svelte && sed -i 's/Promise.resolve();//' dist/*",
"build-cjs": "microbundle -i index.ts -o ./dist/trap-focus-svelte.cjs.js --no-pkg-main -f cjs --sourcemap false",
"build-module": "microbundle -i index.ts -o ./dist/trap-focus-svelte.mjs --no-pkg-main -f modern --sourcemap false",
"build-svelte": "microbundle -i index.ts -o ./dist/trap-focus-svelte.svelte.js --no-pkg-main -f modern --external svelte --sourcemap false",
"dev": "vite demo",
"demo-build": "vite build demo",
"preview": "vite demo preview",
"check": "svelte-check --tsconfig ./tsconfig.json"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^2.0.2",
"@tsconfig/svelte": "^3.0.0",
"hide-show-scroll": "^2.0.0",
"microbundle": "^0.15.1",
"npm-run-all": "^4.1.5",
"svelte": "^3.55.1",
"svelte-check": "^2.10.3",
"tslib": "^2.5.0",
"typescript": "^4.9.3",
"vite": "^4.1.0"
}
}
"name": "trap-focus-svelte",
"version": "0.2.0",
"license": "MIT",
"description": "Small 0.4kB focus trap that supports stacking, toggling, and dynamic content. Compatible with any framework.",
"keywords": [
"focus",
"focus trap",
"focus lock",
"svelte"
],
"author": "Hank Dollman <hank@henrygd.me> (https://henrygd.me)",
"repository": {
"type": "git",
"url": "https://github.com/henrygd/trap-focus-svelte.git"
},
"bugs": {
"url": "https://github.com/henrygd/trap-focus-svelte/issues"
},
"homepage": "https://trap-focus-svelte.henrygd.me",
"type": "module",
"source": "index.ts",
"exports": {
".": {
"import": "./dist/trap-focus-svelte.svelte.js",
"require": "./dist/trap-focus-svelte.cjs",
"default": "./dist/trap-focus-svelte.svelte.js"
},
"./svelte": "./dist/trap-focus-svelte.svelte.js",
"./vanilla": "./dist/trap-focus-svelte.mjs"
},
"svelte": "./dist/trap-focus-svelte.svelte.js",
"types": "dist/index.d.ts",
"typesVersions": {
"*": {
"vanilla": [
"dist/index.d.ts"
],
"svelte": [
"dist/index.d.ts"
]
}
},
"devDependencies": {
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-terser": "^0.4.0",
"@rollup/plugin-typescript": "^11.0.0",
"@sveltejs/vite-plugin-svelte": "^2.0.2",
"@tsconfig/svelte": "^3.0.0",
"hide-show-scroll": "^2.0.0",
"npm-run-all": "^4.1.5",
"rollup": "^3.14.0",
"rollup-plugin-modify": "^3.0.0",
"rollup-plugin-size": "^0.3.1",
"svelte": "^3.55.1",
"svelte-check": "^2.10.3",
"tslib": "^2.5.0",
"typescript": "^4.9.3",
"vite": "^4.1.0"
},
"scripts": {
"build": "run-p rollup-build generate-types",
"rollup-build": "rollup -c",
"generate-types": "tsc -p . --declaration --emitDeclarationOnly --outDir dist",
"dev": "vite demo",
"demo-build": "vite build demo",
"preview": "vite demo preview",
"check": "svelte-check --tsconfig ./tsconfig.json"
}
}

@@ -0,5 +1,13 @@

[npm-image]: https://flat.badgen.net/npm/v/trap-focus-svelte?color=blue
[npm-url]: https://www.npmjs.com/package/trap-focus-svelte
[size-image]: https://flat.badgen.net/badgesize/gzip/henrygd/trap-focus-svelte/main/dist/trap-focus-svelte.svelte.js?color=green
[license-image]: https://flat.badgen.net/github/license/henrygd/trap-focus-svelte?color=purple
[license-url]: /license
# trap-focus-svelte
Small 0.4kB focus trap that supports stacking, toggling, and custom scope. Designed for Svelte but usable with vanilla js or any framework.
[![npm][npm-image]][npm-url] ![File Size][size-image] [![MIT license][license-image]][license-url]
Small 0.4kB focus trap that supports stacking, toggling, and dynamic content. Designed for Svelte but compatible with plain JavaScript or any framework.
Demo: https://trap-focus-svelte.henrygd.me

@@ -17,12 +25,76 @@

## Options
## Usage with Svelte
ADD OPTIONS INFO
Add directly to an element as an action.
## Usage with Svelte
If the element is removed, the trap and event listeners are destroyed automatically.
ADD USAGE INFO
```html
<script>
import { trapFocus } from 'trap-focus-svelte'
</script>
## Usage with vanilla / other frameworks
<div use:trapFocus>
<button>Inside trap</button>
<button>Inside trap</button>
</div>
<button>Outside trap</button>
```
ADD USAGE INFO
You can also toggle the trap on they fly:
<!-- prettier-ignore-start -->
```html
<script>
import { trapFocus } from 'trap-focus-svelte'
let active = true
const toggleTrap = () => (active = !active)
</script>
<div use:trapFocus={active}>
<button on:click={toggleTrap}>Toggle trap</button>
<button>Inside trap</button>
</div>
<button>Outside trap</button>
```
<!-- prettier-ignore-end -->
## Usage with vanilla JavaScript or other frameworks
Import from `trap-focus-svelte/vanilla`
For an example of the demo site made without Svelte (with TypeScript / Vite) see [this StackBlitz](https://stackblitz.com/edit/vitejs-vite-sesxte?file=src/main.ts).
```html
<div id="buttons">
<button>Inside trap</button>
<button>Inside trap</button>
</div>
<button>Outside trap</button>
```
```js
import { trapFocus } from 'trap-focus-svelte/vanilla'
const div = document.getElementById('buttons')
// create trap (pass false as a second argument to start disabled)
const buttonTrap = trapFocus(buttons)
// toggle trap
buttonTrap.update(false)
// destory trap
buttonTrap.destroy()
```
## Notes
If you have multiple traps active at the same time, the focus will be within the latest trap created or activated.
When that trap is destroyed or deactivated, the focus will work backwards down the chain, eventually restoring focus to the [`document.activeElement`](https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement) at the time of the oldest trap's activation.
## License
MIT
{
"include": ["index.ts"],
"compilerOptions": {

@@ -3,0 +4,0 @@ "target": "esnext",

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc