@hashrytech/quick-components-kit 0.8.5 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/dist/actions/disable-scroll.d.ts +13 -0
- package/dist/actions/disable-scroll.js +46 -0
- package/dist/actions/lock-scroll.d.ts +16 -0
- package/dist/actions/lock-scroll.js +45 -0
- package/dist/actions/on-keydown.d.ts +11 -0
- package/dist/actions/on-keydown.js +24 -0
- package/dist/drawer/Drawer.svelte +12 -31
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/modal/Modal.svelte +9 -26
- package/dist/modal/index.d.ts +1 -1
- package/dist/modal/index.js +1 -1
- package/dist/overlay/Overlay.svelte +5 -14
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @hashrytech/quick-components-kit
|
|
2
2
|
|
|
3
|
+
## 0.10.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- feat: Adding lock-scroll action
|
|
8
|
+
|
|
9
|
+
## 0.9.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- fix: disable scroll on Overlay component control properly enabled and disabled
|
|
14
|
+
|
|
15
|
+
## 0.9.0
|
|
16
|
+
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- feat: Adding new disable scroll and onkeyboard actions along with updates to Modal, Drawer and Overlay
|
|
20
|
+
|
|
3
21
|
## 0.8.5
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte action to disable page scroll (commonly used for modals, drawers, or mobile menus).
|
|
3
|
+
*
|
|
4
|
+
* It locks the scroll position by applying `position: fixed` to the `<body>` and
|
|
5
|
+
* preserving the current scroll offset. This approach prevents visual jumps and maintains scroll state.
|
|
6
|
+
*
|
|
7
|
+
* @param node - The element the action is bound to (required for Svelte actions, though unused here).
|
|
8
|
+
* @param enabled - Whether disable scroll should be enabled immediately (default: true).
|
|
9
|
+
*/
|
|
10
|
+
export declare function disableScroll(node: HTMLElement, enabled?: boolean): {
|
|
11
|
+
update(newValue: boolean): void;
|
|
12
|
+
destroy(): void;
|
|
13
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte action to disable page scroll (commonly used for modals, drawers, or mobile menus).
|
|
3
|
+
*
|
|
4
|
+
* It locks the scroll position by applying `position: fixed` to the `<body>` and
|
|
5
|
+
* preserving the current scroll offset. This approach prevents visual jumps and maintains scroll state.
|
|
6
|
+
*
|
|
7
|
+
* @param node - The element the action is bound to (required for Svelte actions, though unused here).
|
|
8
|
+
* @param enabled - Whether disable scroll should be enabled immediately (default: true).
|
|
9
|
+
*/
|
|
10
|
+
export function disableScroll(node, enabled = true) {
|
|
11
|
+
const scrollTop = window.scrollY;
|
|
12
|
+
const originalBodyPosition = document.body.style.position;
|
|
13
|
+
const originalBodyWidth = document.body.style.width;
|
|
14
|
+
const originalTop = document.body.style.top;
|
|
15
|
+
const originalOverflow = document.documentElement.style.overflowY;
|
|
16
|
+
function applyLock() {
|
|
17
|
+
document.body.style.top = `-${scrollTop}px`;
|
|
18
|
+
document.documentElement.style.overflowY = 'scroll';
|
|
19
|
+
document.body.style.position = 'fixed';
|
|
20
|
+
document.body.style.width = '100%';
|
|
21
|
+
}
|
|
22
|
+
function removeLock() {
|
|
23
|
+
document.body.style.position = originalBodyPosition;
|
|
24
|
+
document.body.style.width = originalBodyWidth;
|
|
25
|
+
document.body.style.top = originalTop;
|
|
26
|
+
document.documentElement.style.overflowY = originalOverflow;
|
|
27
|
+
window.scrollTo(0, scrollTop);
|
|
28
|
+
}
|
|
29
|
+
if (node && enabled)
|
|
30
|
+
applyLock();
|
|
31
|
+
return {
|
|
32
|
+
update(newValue) {
|
|
33
|
+
if (enabled) {
|
|
34
|
+
if (newValue)
|
|
35
|
+
applyLock();
|
|
36
|
+
else
|
|
37
|
+
removeLock();
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
destroy() {
|
|
41
|
+
if (enabled) {
|
|
42
|
+
removeLock();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte action to lock all page scrolling by intercepting scroll events and keys.
|
|
3
|
+
* It prevents:
|
|
4
|
+
* - Mouse wheel scrolling
|
|
5
|
+
* - Touch-based scrolling
|
|
6
|
+
* - Arrow key and spacebar scrolling
|
|
7
|
+
* - Scroll position changes via `window.onscroll`
|
|
8
|
+
*
|
|
9
|
+
* This is useful for modals or mobile menus where background scrolling should be disabled.
|
|
10
|
+
*
|
|
11
|
+
* @param node - The element the action is applied to (typically unused, but required by Svelte).
|
|
12
|
+
* @param enabled - Whether scroll locking should be applied immediately (default: true).
|
|
13
|
+
*/
|
|
14
|
+
export declare function lockScroll(node: HTMLElement, enabled?: boolean): {
|
|
15
|
+
destroy(): void;
|
|
16
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte action to lock all page scrolling by intercepting scroll events and keys.
|
|
3
|
+
* It prevents:
|
|
4
|
+
* - Mouse wheel scrolling
|
|
5
|
+
* - Touch-based scrolling
|
|
6
|
+
* - Arrow key and spacebar scrolling
|
|
7
|
+
* - Scroll position changes via `window.onscroll`
|
|
8
|
+
*
|
|
9
|
+
* This is useful for modals or mobile menus where background scrolling should be disabled.
|
|
10
|
+
*
|
|
11
|
+
* @param node - The element the action is applied to (typically unused, but required by Svelte).
|
|
12
|
+
* @param enabled - Whether scroll locking should be applied immediately (default: true).
|
|
13
|
+
*/
|
|
14
|
+
export function lockScroll(node, enabled = true) {
|
|
15
|
+
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
|
16
|
+
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
|
|
17
|
+
// Prevent all scrolling interaction
|
|
18
|
+
function preventDefault(e) {
|
|
19
|
+
e.preventDefault();
|
|
20
|
+
}
|
|
21
|
+
function preventDefaultForScrollKeys(e) {
|
|
22
|
+
const keys = [' ', 'ArrowLeft', 'ArrowUp', 'ArrowRight', 'ArrowDown'];
|
|
23
|
+
if (keys.includes(e.key)) {
|
|
24
|
+
e.preventDefault();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// Attach scroll-lock event listeners if enabled
|
|
28
|
+
if (node && enabled) {
|
|
29
|
+
window.addEventListener('wheel', preventDefault, { passive: false });
|
|
30
|
+
window.addEventListener('touchmove', preventDefault, { passive: false });
|
|
31
|
+
window.addEventListener('keydown', preventDefaultForScrollKeys);
|
|
32
|
+
window.onscroll = () => window.scrollTo(scrollLeft, scrollTop);
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
// Svelte action cleanup
|
|
36
|
+
destroy() {
|
|
37
|
+
if (enabled) {
|
|
38
|
+
window.removeEventListener('wheel', preventDefault);
|
|
39
|
+
window.removeEventListener('touchmove', preventDefault);
|
|
40
|
+
window.removeEventListener('keydown', preventDefaultForScrollKeys);
|
|
41
|
+
window.onscroll = null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type KeyHandler = {
|
|
2
|
+
key: string;
|
|
3
|
+
callback: (event: KeyboardEvent) => void;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Action to call a function callback when a specific key is pressed.
|
|
7
|
+
*/
|
|
8
|
+
export declare function onKeydown(node: HTMLElement, { key, callback }: KeyHandler): {
|
|
9
|
+
update(newParams: KeyHandler): void;
|
|
10
|
+
destroy(): void;
|
|
11
|
+
} | undefined;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Action to call a function callback when a specific key is pressed.
|
|
3
|
+
*/
|
|
4
|
+
export function onKeydown(node, { key, callback }) {
|
|
5
|
+
if (typeof window === 'undefined' || !node)
|
|
6
|
+
return;
|
|
7
|
+
const handle = (event) => {
|
|
8
|
+
if (event.key === key) {
|
|
9
|
+
callback(event);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
window.addEventListener('keydown', handle);
|
|
13
|
+
return {
|
|
14
|
+
update(newParams) {
|
|
15
|
+
window.removeEventListener('keydown', handle);
|
|
16
|
+
key = newParams.key;
|
|
17
|
+
callback = newParams.callback;
|
|
18
|
+
window.addEventListener('keydown', handle);
|
|
19
|
+
},
|
|
20
|
+
destroy() {
|
|
21
|
+
window.removeEventListener('keydown', handle);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
|
-
import {
|
|
2
|
+
import { type Snippet } from 'svelte';
|
|
3
3
|
import type { ClassNameValue } from 'tailwind-merge';
|
|
4
|
-
import {
|
|
4
|
+
import { fly } from 'svelte/transition';
|
|
5
5
|
import {twMerge} from 'tailwind-merge';
|
|
6
|
-
|
|
6
|
+
import Overlay from '../overlay/Overlay.svelte';
|
|
7
|
+
import { onKeydown } from '../actions/on-keydown.js';
|
|
7
8
|
|
|
8
9
|
export type DrawerProps = {
|
|
9
10
|
open?: boolean;
|
|
@@ -36,46 +37,26 @@
|
|
|
36
37
|
bottom: "bottom-0 left-0 right-0 h-60",
|
|
37
38
|
};
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
$effect(() => {
|
|
43
|
-
if (open && disableBodyScroll) lockScroll(); else unlockScroll();
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
onMount(() => {
|
|
47
|
-
if(browser){
|
|
48
|
-
window.addEventListener('keydown', handleKeydown);
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
onDestroy(() => {
|
|
53
|
-
if(browser){
|
|
54
|
-
window.removeEventListener('keydown', handleKeydown);
|
|
40
|
+
function handleKeydown() {
|
|
41
|
+
if(open && escapeKeyClose) {
|
|
42
|
+
closeDrawer();
|
|
55
43
|
}
|
|
56
|
-
}
|
|
57
|
-
|
|
44
|
+
};
|
|
58
45
|
|
|
59
46
|
export function closeDrawer() {
|
|
60
47
|
open = false;
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
function handleKeydown (event: { key: string; }) {
|
|
64
|
-
if(open && escapeKeyClose && event.key === "Escape") {
|
|
65
|
-
closeDrawer();
|
|
66
|
-
}
|
|
67
|
-
};
|
|
48
|
+
};
|
|
68
49
|
|
|
69
50
|
</script>
|
|
70
51
|
|
|
71
52
|
{#if open}
|
|
72
|
-
|
|
73
|
-
<div transition:fade={{duration: transitionDuration}} class={twMerge("fixed inset-0 bg-overlay-primary", overlayClasses)} role="presentation" onclick={() => open = false}></div>
|
|
53
|
+
<Overlay {transitionDuration} {disableBodyScroll} class={overlayClasses} onclick={() => open = false} />
|
|
74
54
|
|
|
75
55
|
<div role="dialog" aria-modal="true" aria-label={ariaLabel} tabindex="{open ? 0 : -1}" aria-hidden="{!open}"
|
|
76
56
|
class={twMerge("fixed flex flex-col items-center gap-2 bg-white outline-0 focus:outline-0 active:outline-focus-primary focus:outline-focus-primary overflow-y-auto", postionClasses[position], props.class)}
|
|
77
57
|
in:fly={transitionProperties}
|
|
78
|
-
out:fly={transitionProperties}
|
|
58
|
+
out:fly={transitionProperties}
|
|
59
|
+
use:onKeydown={{key: "Escape", callback: handleKeydown}}>
|
|
79
60
|
{@render children?.()}
|
|
80
61
|
</div>
|
|
81
62
|
{/if}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -7,4 +7,6 @@ export * from './hamburger-menu/index.js';
|
|
|
7
7
|
export * from './drawer/index.js';
|
|
8
8
|
export * from './modal/index.js';
|
|
9
9
|
export * from './overlay/index.js';
|
|
10
|
+
export * from './actions/disable-scroll.js';
|
|
11
|
+
export * from './actions/on-keydown.js';
|
|
10
12
|
// Add more components here...
|
package/dist/modal/Modal.svelte
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
import { fade } from 'svelte/transition';
|
|
5
5
|
import {twMerge} from 'tailwind-merge';
|
|
6
6
|
import { browser } from '$app/environment';
|
|
7
|
+
import Overlay from '../overlay/Overlay.svelte';
|
|
8
|
+
import { onKeydown } from '../actions/on-keydown.js';
|
|
7
9
|
|
|
8
10
|
export type ModalProps = {
|
|
9
11
|
open?: boolean;
|
|
@@ -19,33 +21,14 @@
|
|
|
19
21
|
</script>
|
|
20
22
|
|
|
21
23
|
<script lang="ts">
|
|
22
|
-
let {open=$bindable(false), escapeKeyClose=true, disableBodyScroll=true, overlayTransitionDuration=
|
|
23
|
-
|
|
24
|
-
const lockScroll = () => document.body.style.overflow = 'hidden';
|
|
25
|
-
const unlockScroll = () => document.body.style.overflow = '';
|
|
26
|
-
|
|
27
|
-
$effect(() => {
|
|
28
|
-
if (open && disableBodyScroll) lockScroll(); else unlockScroll();
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
onMount(() => {
|
|
32
|
-
if(browser){
|
|
33
|
-
window.addEventListener('keydown', handleKeydown);
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
onDestroy(() => {
|
|
38
|
-
if(browser){
|
|
39
|
-
window.removeEventListener('keydown', handleKeydown);
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
|
|
24
|
+
let {open=$bindable(false), escapeKeyClose=true, disableBodyScroll=true, overlayTransitionDuration=100, ariaLabel="Modal", overlayClasses="", children, ...props}: ModalProps = $props();
|
|
25
|
+
|
|
43
26
|
export function closeModal() {
|
|
44
27
|
open = false;
|
|
45
28
|
};
|
|
46
29
|
|
|
47
|
-
function handleKeydown
|
|
48
|
-
if(open && escapeKeyClose
|
|
30
|
+
function handleKeydown() {
|
|
31
|
+
if(open && escapeKeyClose){
|
|
49
32
|
closeModal();
|
|
50
33
|
}
|
|
51
34
|
};
|
|
@@ -53,11 +36,11 @@
|
|
|
53
36
|
</script>
|
|
54
37
|
|
|
55
38
|
{#if open}
|
|
56
|
-
|
|
57
|
-
<div transition:fade={{duration: overlayTransitionDuration}} class={twMerge("fixed inset-0 bg-overlay-primary", overlayClasses)} role="presentation" onclick={() => open = false}></div>
|
|
39
|
+
<Overlay transitionDuration={overlayTransitionDuration} {disableBodyScroll} class={overlayClasses} onclick={() => open = false} />
|
|
58
40
|
|
|
59
41
|
<div role="dialog" aria-modal="true" aria-label={ariaLabel} tabindex="{open ? 0 : -1}" aria-hidden="{!open}"
|
|
60
|
-
class={twMerge("fixed bg-white top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 outline-0 focus:outline-0 active:outline-focus-primary focus:outline-focus-primary overflow-y-auto w-full max-w-md h-96", props.class)}
|
|
42
|
+
class={twMerge("fixed bg-white rounded top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 outline-0 focus:outline-0 active:outline-focus-primary focus:outline-focus-primary overflow-y-auto w-full max-w-md h-96", props.class)}
|
|
43
|
+
use:onKeydown={{key: "Escape", callback: handleKeydown}}>
|
|
61
44
|
{@render children?.()}
|
|
62
45
|
</div>
|
|
63
46
|
{/if}
|
package/dist/modal/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { default as Modal } from './
|
|
1
|
+
export { default as Modal } from './Modal.svelte';
|
package/dist/modal/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { default as Modal } from './
|
|
1
|
+
export { default as Modal } from './Modal.svelte';
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import type { ClassNameValue } from 'tailwind-merge';
|
|
4
4
|
import { fade } from 'svelte/transition';
|
|
5
5
|
import {twMerge} from 'tailwind-merge';
|
|
6
|
+
import { disableScroll } from '../actions/disable-scroll.js';
|
|
6
7
|
|
|
7
8
|
export type OverlayProps = {
|
|
8
9
|
disableBodyScroll?: boolean;
|
|
@@ -16,20 +17,10 @@
|
|
|
16
17
|
</script>
|
|
17
18
|
|
|
18
19
|
<script lang="ts">
|
|
19
|
-
let {disableBodyScroll=true, transitionDuration=
|
|
20
|
-
|
|
21
|
-
const lockScroll = () => document.body.style.overflow = 'hidden';
|
|
22
|
-
const unlockScroll = () => document.body.style.overflow = '';
|
|
23
|
-
|
|
24
|
-
onMount(() => {
|
|
25
|
-
lockScroll();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
onDestroy(() => {
|
|
29
|
-
unlockScroll();
|
|
30
|
-
});
|
|
31
|
-
|
|
20
|
+
let {disableBodyScroll=true, transitionDuration=100, ariaLabel="Overlay", onclick, children, ...props}: OverlayProps = $props();
|
|
32
21
|
</script>
|
|
33
22
|
|
|
34
|
-
<div transition:fade={{duration: transitionDuration}} class={twMerge("fixed top-0 left-0 right-0 bottom-0 bg-overlay-primary", props.class)} role="presentation" {onclick}
|
|
23
|
+
<div transition:fade={{duration: transitionDuration}} class={twMerge("fixed top-0 left-0 right-0 bottom-0 bg-overlay-primary", props.class)} role="presentation" {onclick}
|
|
24
|
+
use:disableScroll={disableBodyScroll}>
|
|
25
|
+
</div>
|
|
35
26
|
|