@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 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 { onDestroy, onMount, type Snippet } from 'svelte';
2
+ import { type Snippet } from 'svelte';
3
3
  import type { ClassNameValue } from 'tailwind-merge';
4
- import { fade, fly } from 'svelte/transition';
4
+ import { fly } from 'svelte/transition';
5
5
  import {twMerge} from 'tailwind-merge';
6
- import { browser } from '$app/environment';
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
- const lockScroll = () => document.body.style.overflow = 'hidden';
40
- const unlockScroll = () => document.body.style.overflow = '';
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
- <!-- Overlay -->
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
@@ -5,3 +5,5 @@ export * from './hamburger-menu/index.js';
5
5
  export * from './drawer/index.js';
6
6
  export * from './modal/index.js';
7
7
  export * from './overlay/index.js';
8
+ export * from './actions/disable-scroll.js';
9
+ export * from './actions/on-keydown.js';
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...
@@ -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=0, ariaLabel="Modal", overlayClasses="", children, ...props}: ModalProps = $props();
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 (event: { key: string; }) {
48
- if(open && escapeKeyClose && event.key === "Escape") {
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
- <!-- Overlay -->
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}
@@ -1 +1 @@
1
- export { default as Modal } from './modal.svelte';
1
+ export { default as Modal } from './Modal.svelte';
@@ -1 +1 @@
1
- export { default as Modal } from './modal.svelte';
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=0, ariaLabel="Modal", onclick, children, ...props}: OverlayProps = $props();
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}></div>
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
 
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/hashrytech/quick-components-kit.git"
7
7
  },
8
- "version": "0.8.5",
8
+ "version": "0.10.0",
9
9
  "license": "MIT",
10
10
  "author": "Hashry Tech",
11
11
  "files": [