├── .gitignore
├── .npmrc
├── CHANGELOG.md
├── LICENSE
├── README.md
├── package.json
├── prettier.config.js
├── src
├── app.css
├── app.html
├── lib
│ ├── Counter.svelte
│ ├── IconMenu.svelte
│ ├── Menu.svelte
│ └── count.svelte.ts
├── package
│ ├── OutClick.svelte
│ ├── OutClickEvent.ts
│ ├── castArray.ts
│ ├── index.ts
│ └── types.ts
└── routes
│ ├── +layout.svelte
│ ├── +page.svelte
│ ├── exclude
│ └── +page.svelte
│ └── half-click
│ └── +page.svelte
├── static
├── Exo2-VariableFont_wght.woff2
└── favicon.png
├── svelte.config.js
├── tailwind.config.ts
├── tsconfig.json
└── vite.config.ts
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /node_modules/
3 | /.svelte-kit/
4 | /.vercel/
5 | /dist/
6 | vite.config.ts.timestamp-*
7 | pnpm-lock.yaml
8 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: CHANGELOG
3 | ---
4 |
5 | > [!IMPORTANT]
6 | > Restart your app after updating the package.
7 |
8 | ## 4.1.0
9 |
10 | - [feature] Added `tag` prop and rest props type suggestion support with generic type support based on the `tag` prop.
11 |
12 | ## 4.0.1
13 |
14 | - Fixed `"svelte"` `"peerDependencies"` version by changing it to `">= 5"`.
15 |
16 | ## 4.0.0
17 |
18 | > [!IMPORTANT]
19 | > There are breaking changes in this version.
20 |
21 | - [breaking] Use `e.target` instead of `e.detail.target`.
22 | - [breaking] `OutClickEvent` now returns `pointerdown` or `pointerup` or `keydown`, depending on how the outclick event is fired.
23 | - [breaking] Also, take a look at the migration guide below.
24 | - Exports `OutClickEvent` type.
25 |
26 | ### Migration guide
27 |
28 | Before:
29 |
30 | ```svelte
31 |
34 | ```
35 |
36 | After:
37 |
38 | ```svelte
39 |
42 | ```
43 |
44 | ---
45 |
46 | Before:
47 |
48 | ```svelte
49 | {}}>
50 | ```
51 |
52 | After:
53 |
54 | ```svelte
55 | {}}>
56 | ```
57 |
58 | ## 3.7.1
59 |
60 | - [feature] Added `e.detail.target` property.
61 | - [breaking] Requires version `4.2.12` or higher.
62 |
63 | ## 3.7.0
64 |
65 | - Compatible with SvelteKit version `2.0.0`.
66 |
67 | ## 3.6.2
68 |
69 | - Now it's compatible with Svelte 5.
70 |
71 | ## 3.6.1
72 |
73 | - Fixed some issues related to props types.
74 |
75 | ## 3.6.0
76 |
77 | - Removed `e.detail` because it wasn't needed.
78 |
79 | ## 3.5.0
80 |
81 | - Please update to version `3.5.0` if you were using the `3.4.0` version.
82 |
83 | ## 3.4.0
84 |
85 | - Added TypeScript support.
86 |
87 | ## 3.3.1
88 |
89 | - Fixed some JSDocs data types.
90 |
91 | ## 3.3.0
92 |
93 | - [breaking] Renamed `fullClick` to `halfClick` (default: `false`).
94 | - [breaking] Renamed `excludeByDomNode` to `excludeElements`.
95 | - [breaking] Renamed `excludeByQuerySelector` to `excludeQuerySelectorAll`.
96 | - Now you can set the `excludeElements` prop to the element itself instead of wrapping it with an array.
97 | - Now the `excludeQuerySelectorAll` prop works the same as the JavaScript [`querySelectorAll`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll) method. It can contain values like `"#element1, .element2"` and `['#element1', '.element2']`.
98 | - Added custom tag support. Now you can add a prop called `tag` to your `OutClick` component and change the wrapper tag. Added with the help of `svelte:element`.
99 | - Now you can add custom attributes to render on the wrapper. Added with the help of `$$restProps`.
100 |
101 | ## 3.2.0
102 |
103 | - [breaking] Removed `useWrapper` prop, because it was unnecessary.
104 | - [breaking] Removed the default class `outclick` from the wrapper.
105 | - [breaking] Renamed `excludeByDOMNode` to `excludeByDomNode`
106 | - [breaking] Added new `fullClick` prop. Now clicking outside requires `pointerdown` and `pointerup` to fire at outside of your element. Set it to `false` so `pointerdown` can fire the `outclick` event on its own.
107 | - Fix empty `class` attribute showing up when not using a class.
108 | - Fix empty `style` attribute showing up when using the `class` prop.
109 |
110 | ## 3.1.0
111 |
112 | - Changed `on:mousedown` to `on:pointerdown` and fixed [this issue](https://github.com/babakfp/svelte-outclick/issues/6).
113 |
114 | ## 3.0.1
115 |
116 | - Removed `ROADMAP.todo`.
117 | - Fixed typo in `README.md`.
118 | - Rewrite the description in `README.md`.
119 |
120 | ## 3.0.0
121 |
122 | - Remplaced `exclude` prop with `excludeByDOMNode` and `excludeByQuerySelector`.
123 | - Renamed `.outclick-wrapper` component wrapper class to `.outclick`.
124 | - Using `on:mousedown` and `on:keydown` instead of `on:click`.
125 | - Removed `useMousedown` and `useKeydown` props.
126 | - If you use `class` prop, `display: contents` will be removed by default.
127 |
128 | ## 2.6.5
129 |
130 | - Added `useMousedown` and `useKeydown` props.
131 | - Fixed [this issue](https://github.com/babakfp/svelte-outclick/issues/4).
132 |
133 | ## 2.5.4
134 |
135 | - Replaced `on:click` with `on:mousedown` and fixed [this issue](https://github.com/babakfp/svelte-outclick/issues/4).
136 |
137 | ## 2.4.3
138 |
139 | - The `exclude` prop now accepts HTML `class` and `id` instead of DOM nodes.
140 |
141 | ## 2.3.1
142 |
143 | - Renamed dispatch `detail.self` to `detail.wrapper`.
144 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 babakfp
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Svelte OutClick
2 |
3 | [](https://www.npmjs.com/package/svelte-outclick)
4 |
5 | A Svelte component that allows you to listen to the clicks that happen outside of an element.
6 |
7 | - 📺 [Demo](https://svelte-outclick.vercel.app)
8 | - 🪵 [CHANGELOG](https://github.com/babakfp/svelte-outclick/blob/main/README.md)
9 |
10 | > [!IMPORTANT]
11 | > The latest version of this package is not compatible with Svelte 4. Please use the version `3.7.1` for Svelte 4 compatibility.
12 |
13 | ---
14 |
15 | `onOutClick`
16 |
17 | A Svelte component that allows you to listen to the clicks that happen outside of an element.
18 |
19 | Why choose this over the other packages?
20 |
21 | - [No extra wrapper](#no-extra-wrapper)
22 | - Supports [`class`](#class-prop) prop
23 | - [Exclude elements](#excluding-elements) from triggering the event
24 | - [Half click](#halfclick) support
25 | - TypeScript support
26 |
27 | ## Install
28 |
29 | Please check out the [**CHANGELOG**](svelte-outclick/changelog) before updating to the newest version. Restart your app after the update.
30 |
31 | ```bash
32 | pnpm add -D svelte-outclick
33 | ```
34 |
35 | ## How it works
36 |
37 | It works the same as the Javascript click event. A few events are attached to the window and it checks whether the event target is contained within the element. If the element didn't contain the event target, it means the click happened outside of the element.
38 |
39 |
40 | ```svelte
41 |
45 |
46 | count += 1}
48 | >
49 | {count} times clicked outside
50 |
51 | ```
52 |
53 | ## Props
54 |
55 | ### Excluding elements
56 |
57 | You may want to exclude some elements from triggering the event. For example, a button that opens a popup must be excluded, so the popup doesn't get closed immediately after you open it. Since the button that triggers the popup is located outside of the popup itself, in any time clicking on it will trigger the event.
58 |
59 | #### `excludeElements`
60 |
61 | - Type: `HTMLElement | HTMLElement[]`
62 |
63 | This prop expects HTML elements. Clicks on those elements (and their children) will be ignored. Learn about [`bind:this`](https://svelte.dev/tutorial/bind-this).
64 |
65 |
66 | ```svelte
67 |
72 |
73 | count += 1}
75 | excludeElements={excludedElement}
76 | >
77 | {count} times clicked outside
78 |
79 |
80 |
81 | This doesn't trigger outclick
82 |
83 | ```
84 |
85 | This prop can receive a single element or an array of elements.
86 |
87 | #### `excludeQuerySelectorAll`
88 |
89 | - Type: `string`
90 |
91 | This prop expects a string of css selectors. Clicks on those elements (and their children) will be ignored.
92 |
93 |
94 | ```svelte
95 |
99 |
100 | count += 1}
102 | excludeQuerySelectorAll=".excluded-element"
103 | >
104 | {count} times clicked outside
105 |
106 |
107 |
108 | This doesn't trigger outclick
109 |
110 | ```
111 |
112 | This prop works the same as the `querySelectorAll` method. It can contain any valid CSS selectors, for example: `"#element1, .element2"`.
113 |
114 | ### `class` prop
115 |
116 | - Type: `string`
117 |
118 | This is equivalent to the CSS class attribute. You can seamlessly utilize tools such as Tailwind CSS by adding your classes in the usual manner, without encountering any issues.
119 |
120 |
121 | ```svelte
122 |
126 |
127 |
135 |
136 |
142 | ```
143 |
144 | ### `includeSelf`
145 |
146 | - Type: `boolean`
147 | - Default: `false`
148 |
149 | For example, if you want to close the dropdown when you click on any of its items, set the value of this option to `true`, so a self-click can trigger the event.
150 |
151 | ### `halfClick`
152 |
153 | - Type: `boolean`
154 | - Default: `true`
155 |
156 | If `true`, `pointerdown` can solely determine the click outside. If `false`, outclick will happen when `pointerdown` and `pointerup` events happen after each other, outside of the element.
157 |
158 | ### `tag`
159 |
160 | - Type: `string`
161 | - Default: `"div"`
162 |
163 | You can change the tag name of the wrapper element.
164 |
165 | ### Custom attributes
166 |
167 | You can add any custom attributes to the wrapper element.
168 |
169 | ```svelte
170 |
171 | ```
172 |
173 | ## No extra wrapper
174 |
175 | Actually, there is an HTML `
` wrapper, but it doesn't affect the layout because of [`display: contents`](https://caniuse.com/css-display-contents). If you use the `class` prop, the default display will be automatically removed.
176 |
177 | ## FAQ
178 |
179 | ### Why are we not using the `click` event to capture the `outclick` event?
180 |
181 | At first, we were using the `click` event to capture the `outclick` event, but later because of [this issue](https://github.com/babakfp/svelte-outclick/issues/4) we started using the `mousedown` event instead; and later because of [this issue](https://github.com/babakfp/svelte-outclick/issues/6) we started using the `pointerdown` even. Later I added the ability to use `pointerup` event with the `pointerdown` event to fully simulate the click event.
182 |
183 | ### Learn more
184 |
185 | - `keydown` event on `document.body` is ignored because this is how it works when you use `click` event instead.
186 | - `keydown` event only triggers with `Enter`, `NumpadEnter`, and `Space` keys (because this is how it works when you use the `click` event instead).
187 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "svelte-outclick",
3 | "version": "4.1.2",
4 | "description": "A Svelte component that allows you to listen to the clicks that happen outside of an element.",
5 | "license": "MIT",
6 | "repository": "github:babakfp/svelte-outclick",
7 | "scripts": {
8 | "dev": "vite dev",
9 | "build": "vite build && pnpm package",
10 | "preview": "vite preview",
11 | "prepare": "svelte-kit sync",
12 | "package": "svelte-kit sync && svelte-package -i src/package && publint",
13 | "prepublishOnly": "pnpm package",
14 | "format": "prettier -w .",
15 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
16 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
17 | },
18 | "peerDependencies": {
19 | "svelte": ">= 5"
20 | },
21 | "devDependencies": {
22 | "@ianvs/prettier-plugin-sort-imports": "4.4.1",
23 | "@sveltejs/adapter-vercel": "5.7.2",
24 | "@sveltejs/kit": "2.21.1",
25 | "@sveltejs/package": "2.3.11",
26 | "@sveltejs/vite-plugin-svelte": "5.0.3",
27 | "@tailwindcss/vite": "4.1.7",
28 | "prettier": "3.5.3",
29 | "prettier-plugin-svelte": "3.4.0",
30 | "prettier-plugin-tailwindcss": "0.6.11",
31 | "publint": "0.3.12",
32 | "svelte": "5.32.1",
33 | "svelte-check": "4.2.1",
34 | "svelte-loading-bar": "2.0.4",
35 | "tailwindcss": "4.1.7",
36 | "tslib": "2.8.1",
37 | "typescript": "5.8.3",
38 | "vite": "6.3.5"
39 | },
40 | "files": [
41 | "dist"
42 | ],
43 | "exports": {
44 | ".": {
45 | "types": "./dist/index.d.ts",
46 | "svelte": "./dist/index.js"
47 | }
48 | },
49 | "types": "./dist/index.d.ts",
50 | "svelte": "./dist/index.js",
51 | "keywords": [
52 | "svelte",
53 | "outclick",
54 | "outside",
55 | "click"
56 | ],
57 | "type": "module"
58 | }
59 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import("prettier").Config & import("prettier-plugin-svelte").PluginConfig & import("@ianvs/prettier-plugin-sort-imports").PluginConfig} */
2 | export default {
3 | semi: false,
4 | tabWidth: 4,
5 | plugins: [
6 | "prettier-plugin-svelte",
7 | "prettier-plugin-tailwindcss",
8 | "@ianvs/prettier-plugin-sort-imports",
9 | ],
10 | importOrder: [
11 | "^@",
12 | "",
13 | "^\\$(?!lib/)",
14 | "^\\$lib/",
15 | "^[.]",
16 | ],
17 | }
18 |
--------------------------------------------------------------------------------
/src/app.css:
--------------------------------------------------------------------------------
1 | @import "tailwindcss";
2 | @config "../tailwind.config.ts";
3 |
4 | @layer base {
5 | * {
6 | @apply select-none;
7 | }
8 | svg.icon {
9 | @apply pointer-events-none inline-block h-[1em] w-[1em] fill-current;
10 | }
11 | }
12 |
13 | @layer components {
14 | .box {
15 | @apply relative z-10 flex h-20 items-center justify-center rounded border-2 border-dashed border-gray-700 bg-gray-900 text-center text-xs;
16 | }
17 | .box--blue {
18 | @apply bg-stripes-blue-400/20 border-blue-400/60;
19 | }
20 | .box--green {
21 | @apply bg-stripes-green-400/20 border-green-400/60;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Svelte OutClick
10 |
14 |
15 |
22 |
23 |
33 |
34 | %sveltekit.head%
35 |
36 |
37 |