svelora 3.0.5 → 3.0.7

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.
Files changed (189) hide show
  1. package/dist/BentoGrid/BentoCard.svelte +45 -0
  2. package/dist/BentoGrid/BentoCard.svelte.d.ts +4 -0
  3. package/dist/BentoGrid/BentoGrid.svelte +9 -0
  4. package/dist/BentoGrid/BentoGrid.svelte.d.ts +4 -0
  5. package/dist/BentoGrid/bento-grid.types.d.ts +47 -0
  6. package/dist/BentoGrid/bento-grid.types.js +1 -0
  7. package/dist/BentoGrid/bento-grid.variants.d.ts +30 -0
  8. package/dist/BentoGrid/bento-grid.variants.js +16 -0
  9. package/dist/BentoGrid/index.d.ts +5 -0
  10. package/dist/BentoGrid/index.js +5 -0
  11. package/dist/Chart/Chart.svelte +47 -0
  12. package/dist/Chart/Chart.svelte.d.ts +4 -0
  13. package/dist/Chart/chart.types.d.ts +20 -0
  14. package/dist/Chart/chart.types.js +1 -0
  15. package/dist/Chart/chart.variants.d.ts +3 -0
  16. package/dist/Chart/chart.variants.js +4 -0
  17. package/dist/Chart/index.d.ts +4 -0
  18. package/dist/Chart/index.js +4 -0
  19. package/dist/Chat/ChatBubble.svelte +30 -0
  20. package/dist/Chat/ChatBubble.svelte.d.ts +4 -0
  21. package/dist/Chat/ChatInput.svelte +50 -0
  22. package/dist/Chat/ChatInput.svelte.d.ts +4 -0
  23. package/dist/Chat/ChatMessage.svelte +15 -0
  24. package/dist/Chat/ChatMessage.svelte.d.ts +4 -0
  25. package/dist/Chat/chat.types.d.ts +63 -0
  26. package/dist/Chat/chat.types.js +1 -0
  27. package/dist/Chat/chat.variants.d.ts +117 -0
  28. package/dist/Chat/chat.variants.js +47 -0
  29. package/dist/Chat/index.d.ts +6 -0
  30. package/dist/Chat/index.js +6 -0
  31. package/dist/ColorPicker/ColorPicker.svelte +109 -0
  32. package/dist/ColorPicker/ColorPicker.svelte.d.ts +4 -0
  33. package/dist/ColorPicker/color-picker.types.d.ts +26 -0
  34. package/dist/ColorPicker/color-picker.types.js +1 -0
  35. package/dist/ColorPicker/color-picker.variants.d.ts +69 -0
  36. package/dist/ColorPicker/color-picker.variants.js +13 -0
  37. package/dist/ColorPicker/index.d.ts +4 -0
  38. package/dist/ColorPicker/index.js +4 -0
  39. package/dist/DateRangePicker/DateRangePicker.svelte +59 -0
  40. package/dist/DateRangePicker/DateRangePicker.svelte.d.ts +4 -0
  41. package/dist/DateRangePicker/date-range-picker.types.d.ts +34 -0
  42. package/dist/DateRangePicker/date-range-picker.types.js +1 -0
  43. package/dist/DateRangePicker/date-range-picker.variants.d.ts +39 -0
  44. package/dist/DateRangePicker/date-range-picker.variants.js +20 -0
  45. package/dist/DateRangePicker/index.d.ts +4 -0
  46. package/dist/DateRangePicker/index.js +4 -0
  47. package/dist/Fonts/fonts.js +3 -1
  48. package/dist/Link/Link.context-harness.svelte +8 -0
  49. package/dist/Link/Link.context-harness.svelte.d.ts +7 -0
  50. package/dist/Link/Link.svelte +57 -30
  51. package/dist/Link/index.d.ts +2 -0
  52. package/dist/Link/index.js +1 -0
  53. package/dist/Link/location-context.d.ts +4 -0
  54. package/dist/Link/location-context.js +1 -0
  55. package/dist/List/List.svelte +14 -0
  56. package/dist/List/List.svelte.d.ts +4 -0
  57. package/dist/List/ListItem.svelte +64 -0
  58. package/dist/List/ListItem.svelte.d.ts +4 -0
  59. package/dist/List/index.d.ts +5 -0
  60. package/dist/List/index.js +5 -0
  61. package/dist/List/list.types.d.ts +62 -0
  62. package/dist/List/list.types.js +1 -0
  63. package/dist/List/list.variants.d.ts +99 -0
  64. package/dist/List/list.variants.js +42 -0
  65. package/dist/Marquee/Marquee.svelte +50 -0
  66. package/dist/Marquee/Marquee.svelte.d.ts +4 -0
  67. package/dist/Marquee/index.d.ts +4 -0
  68. package/dist/Marquee/index.js +4 -0
  69. package/dist/Marquee/marquee.types.d.ts +38 -0
  70. package/dist/Marquee/marquee.types.js +1 -0
  71. package/dist/Marquee/marquee.variants.d.ts +78 -0
  72. package/dist/Marquee/marquee.variants.js +28 -0
  73. package/dist/Menu/Menu.svelte +134 -0
  74. package/dist/Menu/Menu.svelte.d.ts +4 -0
  75. package/dist/Menu/index.d.ts +4 -0
  76. package/dist/Menu/index.js +4 -0
  77. package/dist/Menu/menu.types.d.ts +82 -0
  78. package/dist/Menu/menu.types.js +1 -0
  79. package/dist/Menu/menu.variants.d.ts +46 -0
  80. package/dist/Menu/menu.variants.js +32 -0
  81. package/dist/NumberTicker/NumberTicker.svelte +59 -0
  82. package/dist/NumberTicker/NumberTicker.svelte.d.ts +4 -0
  83. package/dist/NumberTicker/index.d.ts +4 -0
  84. package/dist/NumberTicker/index.js +4 -0
  85. package/dist/NumberTicker/number-ticker.types.d.ts +26 -0
  86. package/dist/NumberTicker/number-ticker.types.js +1 -0
  87. package/dist/NumberTicker/number-ticker.variants.d.ts +27 -0
  88. package/dist/NumberTicker/number-ticker.variants.js +6 -0
  89. package/dist/PasswordInput/PasswordInput.svelte +74 -0
  90. package/dist/PasswordInput/PasswordInput.svelte.d.ts +4 -0
  91. package/dist/PasswordInput/index.d.ts +4 -0
  92. package/dist/PasswordInput/index.js +4 -0
  93. package/dist/PasswordInput/password-input.types.d.ts +18 -0
  94. package/dist/PasswordInput/password-input.types.js +1 -0
  95. package/dist/PasswordInput/password-input.variants.d.ts +57 -0
  96. package/dist/PasswordInput/password-input.variants.js +11 -0
  97. package/dist/Prose/Prose.svelte +13 -0
  98. package/dist/Prose/Prose.svelte.d.ts +4 -0
  99. package/dist/Prose/index.d.ts +4 -0
  100. package/dist/Prose/index.js +4 -0
  101. package/dist/Prose/prose.types.d.ts +22 -0
  102. package/dist/Prose/prose.types.js +1 -0
  103. package/dist/Prose/prose.variants.d.ts +45 -0
  104. package/dist/Prose/prose.variants.js +45 -0
  105. package/dist/Rating/Rating.svelte +93 -0
  106. package/dist/Rating/Rating.svelte.d.ts +4 -0
  107. package/dist/Rating/index.d.ts +4 -0
  108. package/dist/Rating/index.js +4 -0
  109. package/dist/Rating/rating.types.d.ts +59 -0
  110. package/dist/Rating/rating.types.js +1 -0
  111. package/dist/Rating/rating.variants.d.ts +93 -0
  112. package/dist/Rating/rating.variants.js +32 -0
  113. package/dist/Resizable/Resizable.svelte +9 -0
  114. package/dist/Resizable/Resizable.svelte.d.ts +4 -0
  115. package/dist/Resizable/index.d.ts +4 -0
  116. package/dist/Resizable/index.js +4 -0
  117. package/dist/Resizable/resizable.types.d.ts +18 -0
  118. package/dist/Resizable/resizable.types.js +1 -0
  119. package/dist/Resizable/resizable.variants.d.ts +48 -0
  120. package/dist/Resizable/resizable.variants.js +17 -0
  121. package/dist/ScrollArea/ScrollArea.svelte +54 -0
  122. package/dist/ScrollArea/ScrollArea.svelte.d.ts +4 -0
  123. package/dist/ScrollArea/index.d.ts +4 -0
  124. package/dist/ScrollArea/index.js +4 -0
  125. package/dist/ScrollArea/scroll-area.types.d.ts +27 -0
  126. package/dist/ScrollArea/scroll-area.types.js +1 -0
  127. package/dist/ScrollArea/scroll-area.variants.d.ts +45 -0
  128. package/dist/ScrollArea/scroll-area.variants.js +27 -0
  129. package/dist/SelectMenu/SelectMenu.svelte +46 -14
  130. package/dist/Sidebar/Sidebar.svelte +30 -0
  131. package/dist/Sidebar/Sidebar.svelte.d.ts +4 -0
  132. package/dist/Sidebar/index.d.ts +4 -0
  133. package/dist/Sidebar/index.js +4 -0
  134. package/dist/Sidebar/sidebar.types.d.ts +31 -0
  135. package/dist/Sidebar/sidebar.types.js +1 -0
  136. package/dist/Sidebar/sidebar.variants.d.ts +69 -0
  137. package/dist/Sidebar/sidebar.variants.js +23 -0
  138. package/dist/Spotlight/Spotlight.svelte +31 -0
  139. package/dist/Spotlight/Spotlight.svelte.d.ts +4 -0
  140. package/dist/Spotlight/index.d.ts +4 -0
  141. package/dist/Spotlight/index.js +4 -0
  142. package/dist/Spotlight/spotlight.types.d.ts +22 -0
  143. package/dist/Spotlight/spotlight.types.js +1 -0
  144. package/dist/Spotlight/spotlight.variants.d.ts +39 -0
  145. package/dist/Spotlight/spotlight.variants.js +8 -0
  146. package/dist/Stepper/Stepper.svelte +12 -9
  147. package/dist/TagsInput/TagsInput.svelte +100 -0
  148. package/dist/TagsInput/TagsInput.svelte.d.ts +4 -0
  149. package/dist/TagsInput/index.d.ts +4 -0
  150. package/dist/TagsInput/index.js +4 -0
  151. package/dist/TagsInput/tags-input.types.d.ts +32 -0
  152. package/dist/TagsInput/tags-input.types.js +1 -0
  153. package/dist/TagsInput/tags-input.variants.d.ts +45 -0
  154. package/dist/TagsInput/tags-input.variants.js +22 -0
  155. package/dist/TreeView/TreeView.svelte +95 -0
  156. package/dist/TreeView/TreeView.svelte.d.ts +4 -0
  157. package/dist/TreeView/index.d.ts +4 -0
  158. package/dist/TreeView/index.js +4 -0
  159. package/dist/TreeView/tree-view.types.d.ts +68 -0
  160. package/dist/TreeView/tree-view.types.js +1 -0
  161. package/dist/TreeView/tree-view.variants.d.ts +69 -0
  162. package/dist/TreeView/tree-view.variants.js +30 -0
  163. package/dist/docs/navigation.js +162 -0
  164. package/dist/hooks/index.d.ts +14 -0
  165. package/dist/hooks/index.js +9 -0
  166. package/dist/hooks/useDebouncedState.svelte.d.ts +30 -0
  167. package/dist/hooks/useDebouncedState.svelte.js +45 -0
  168. package/dist/hooks/useEventListener.svelte.d.ts +30 -0
  169. package/dist/hooks/useEventListener.svelte.js +16 -0
  170. package/dist/hooks/useFocusTrap.svelte.d.ts +42 -0
  171. package/dist/hooks/useFocusTrap.svelte.js +87 -0
  172. package/dist/hooks/useIntersectionObserver.svelte.d.ts +30 -0
  173. package/dist/hooks/useIntersectionObserver.svelte.js +46 -0
  174. package/dist/hooks/useLocalStorage.svelte.d.ts +39 -0
  175. package/dist/hooks/useLocalStorage.svelte.js +73 -0
  176. package/dist/hooks/useResizeObserver.svelte.d.ts +50 -0
  177. package/dist/hooks/useResizeObserver.svelte.js +71 -0
  178. package/dist/hooks/useScrollLock.svelte.d.ts +28 -0
  179. package/dist/hooks/useScrollLock.svelte.js +79 -0
  180. package/dist/hooks/useThrottle.svelte.d.ts +37 -0
  181. package/dist/hooks/useThrottle.svelte.js +72 -0
  182. package/dist/hooks/useTimers.svelte.d.ts +62 -0
  183. package/dist/hooks/useTimers.svelte.js +90 -0
  184. package/dist/hooks/utils.d.ts +1 -0
  185. package/dist/hooks/utils.js +3 -0
  186. package/dist/index.d.ts +18 -0
  187. package/dist/index.js +18 -0
  188. package/dist/mcp/svelora-docs.data.json +59 -5
  189. package/package.json +8 -6
@@ -0,0 +1,30 @@
1
+ type MaybeGetter<T> = T | (() => T);
2
+ /**
3
+ * Attach event listener(s) to a target with automatic cleanup.
4
+ *
5
+ * Binds inside an `$effect` and removes the listener(s) on teardown. The target
6
+ * may be a value or a getter; when it is a getter that reads reactive state, the
7
+ * listener re-binds as the target changes. SSR-safe: a nullish target is a no-op.
8
+ *
9
+ * @example
10
+ * ```svelte
11
+ * <script>
12
+ * import { useEventListener } from 'svelora'
13
+ *
14
+ * let el = $state<HTMLElement>()
15
+ *
16
+ * // Window target (static)
17
+ * useEventListener(window, 'resize', () => console.log('resized'))
18
+ *
19
+ * // Element target (reactive getter) + multiple event types
20
+ * useEventListener(() => el, ['pointerenter', 'focus'], () => console.log('active'))
21
+ * </script>
22
+ *
23
+ * <div bind:this={el}>hover or focus me</div>
24
+ * ```
25
+ */
26
+ export declare function useEventListener<K extends keyof WindowEventMap>(target: MaybeGetter<Window | null | undefined>, type: K | K[], handler: (this: Window, event: WindowEventMap[K]) => void, options?: boolean | AddEventListenerOptions): void;
27
+ export declare function useEventListener<K extends keyof DocumentEventMap>(target: MaybeGetter<Document | null | undefined>, type: K | K[], handler: (this: Document, event: DocumentEventMap[K]) => void, options?: boolean | AddEventListenerOptions): void;
28
+ export declare function useEventListener<K extends keyof HTMLElementEventMap>(target: MaybeGetter<HTMLElement | null | undefined>, type: K | K[], handler: (this: HTMLElement, event: HTMLElementEventMap[K]) => void, options?: boolean | AddEventListenerOptions): void;
29
+ export declare function useEventListener(target: MaybeGetter<EventTarget | null | undefined>, type: string | string[], handler: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
30
+ export {};
@@ -0,0 +1,16 @@
1
+ import { toGetter } from './utils.js';
2
+ export function useEventListener(target, type, handler, options) {
3
+ const resolveTarget = toGetter(target);
4
+ const types = Array.isArray(type) ? type : [type];
5
+ $effect(() => {
6
+ const el = resolveTarget();
7
+ if (!el)
8
+ return;
9
+ for (const t of types)
10
+ el.addEventListener(t, handler, options);
11
+ return () => {
12
+ for (const t of types)
13
+ el.removeEventListener(t, handler, options);
14
+ };
15
+ });
16
+ }
@@ -0,0 +1,42 @@
1
+ type ElementTarget = HTMLElement | null | undefined | (() => HTMLElement | null | undefined);
2
+ export interface UseFocusTrapOptions {
3
+ /**
4
+ * Whether the trap is active. Accepts a value or a reactive getter.
5
+ * @default true
6
+ */
7
+ active?: boolean | (() => boolean);
8
+ /**
9
+ * Element to focus when the trap activates. A getter is resolved on activate.
10
+ * Pass `false` to skip auto-focus. Defaults to the first focusable element.
11
+ */
12
+ initialFocus?: HTMLElement | (() => HTMLElement | null) | false;
13
+ /**
14
+ * Restore focus to the previously focused element when the trap deactivates.
15
+ * @default true
16
+ */
17
+ restoreFocus?: boolean;
18
+ }
19
+ /**
20
+ * Trap keyboard focus within an element while active, and restore focus on exit.
21
+ *
22
+ * Cycles Tab / Shift+Tab among the focusable descendants of `target`, focuses an
23
+ * initial element on activation, and (by default) returns focus to the previously
24
+ * focused element when it deactivates or unmounts. The target and `active` may be
25
+ * values or reactive getters. SSR-safe: a nullish/inactive target is a no-op.
26
+ *
27
+ * Intended for custom focus-scoped UI. Components built on bits-ui primitives
28
+ * (Modal, Slideover, Drawer, Popover) already trap focus — do not double-wrap them.
29
+ *
30
+ * @example
31
+ * ```svelte
32
+ * <script>
33
+ * import { useFocusTrap } from 'svelora'
34
+ *
35
+ * let open = $state(false)
36
+ * let panel = $state<HTMLElement>()
37
+ * useFocusTrap(() => panel, { active: () => open })
38
+ * </script>
39
+ * ```
40
+ */
41
+ export declare function useFocusTrap(target: ElementTarget, options?: UseFocusTrapOptions): void;
42
+ export {};
@@ -0,0 +1,87 @@
1
+ import { toGetter } from './utils.js';
2
+ import { useEventListener } from './useEventListener.svelte.js';
3
+ const FOCUSABLE_SELECTOR = [
4
+ 'a[href]',
5
+ 'button:not([disabled])',
6
+ 'input:not([disabled])',
7
+ 'select:not([disabled])',
8
+ 'textarea:not([disabled])',
9
+ '[tabindex]:not([tabindex="-1"])',
10
+ 'audio[controls]',
11
+ 'video[controls]',
12
+ '[contenteditable]:not([contenteditable="false"])',
13
+ 'details > summary:first-of-type'
14
+ ].join(',');
15
+ function getFocusable(container) {
16
+ return Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter((el) => el.offsetWidth > 0 || el.offsetHeight > 0 || el === document.activeElement);
17
+ }
18
+ /**
19
+ * Trap keyboard focus within an element while active, and restore focus on exit.
20
+ *
21
+ * Cycles Tab / Shift+Tab among the focusable descendants of `target`, focuses an
22
+ * initial element on activation, and (by default) returns focus to the previously
23
+ * focused element when it deactivates or unmounts. The target and `active` may be
24
+ * values or reactive getters. SSR-safe: a nullish/inactive target is a no-op.
25
+ *
26
+ * Intended for custom focus-scoped UI. Components built on bits-ui primitives
27
+ * (Modal, Slideover, Drawer, Popover) already trap focus — do not double-wrap them.
28
+ *
29
+ * @example
30
+ * ```svelte
31
+ * <script>
32
+ * import { useFocusTrap } from 'svelora'
33
+ *
34
+ * let open = $state(false)
35
+ * let panel = $state<HTMLElement>()
36
+ * useFocusTrap(() => panel, { active: () => open })
37
+ * </script>
38
+ * ```
39
+ */
40
+ export function useFocusTrap(target, options = {}) {
41
+ const resolveTarget = toGetter(target);
42
+ const resolveActive = toGetter(options.active ?? true);
43
+ const { initialFocus, restoreFocus = true } = options;
44
+ $effect(() => {
45
+ if (!resolveActive())
46
+ return;
47
+ const resolved = resolveTarget();
48
+ if (!resolved)
49
+ return;
50
+ const el = resolved;
51
+ const previouslyFocused = document.activeElement;
52
+ if (initialFocus !== false) {
53
+ const initial = typeof initialFocus === 'function' ? initialFocus() : initialFocus;
54
+ (initial ?? getFocusable(el)[0] ?? el).focus();
55
+ }
56
+ return () => {
57
+ if (restoreFocus && previouslyFocused && document.contains(previouslyFocused)) {
58
+ previouslyFocused.focus();
59
+ }
60
+ };
61
+ });
62
+ useEventListener(() => (resolveActive() && resolveTarget() ? document : null), 'keydown', (event) => {
63
+ if (event.key !== 'Tab')
64
+ return;
65
+ const el = resolveTarget();
66
+ if (!el)
67
+ return;
68
+ const focusable = getFocusable(el);
69
+ if (focusable.length === 0) {
70
+ event.preventDefault();
71
+ return;
72
+ }
73
+ const first = focusable[0];
74
+ const last = focusable[focusable.length - 1];
75
+ const active = document.activeElement;
76
+ if (event.shiftKey) {
77
+ if (active === first || !el.contains(active)) {
78
+ event.preventDefault();
79
+ last.focus();
80
+ }
81
+ }
82
+ else if (active === last || !el.contains(active)) {
83
+ event.preventDefault();
84
+ first.focus();
85
+ }
86
+ }, true);
87
+ }
@@ -0,0 +1,30 @@
1
+ type ElementTarget = Element | null | undefined | (() => Element | null | undefined);
2
+ export type UseIntersectionObserverOptions = IntersectionObserverInit;
3
+ interface UseIntersectionObserverReturn {
4
+ /** Whether the target currently intersects the root, per the latest entry. */
5
+ readonly isIntersecting: boolean;
6
+ }
7
+ /**
8
+ * Observe an element's intersection with the viewport (or a root) via
9
+ * `IntersectionObserver`, with automatic cleanup.
10
+ *
11
+ * The target may be a value or a getter; when it is a getter that reads reactive
12
+ * state, the observer re-attaches as the target changes. SSR-safe: a nullish
13
+ * target is a no-op.
14
+ *
15
+ * @example
16
+ * ```svelte
17
+ * <script>
18
+ * import { useIntersectionObserver } from 'svelora'
19
+ *
20
+ * let el = $state<HTMLElement>()
21
+ * const io = useIntersectionObserver(() => el, (entry) => {
22
+ * if (entry.isIntersecting) loadImage()
23
+ * }, { rootMargin: '200px' })
24
+ * </script>
25
+ *
26
+ * <div bind:this={el}>{io.isIntersecting ? 'visible' : 'hidden'}</div>
27
+ * ```
28
+ */
29
+ export declare function useIntersectionObserver(target: ElementTarget, callback?: (entry: IntersectionObserverEntry) => void, options?: UseIntersectionObserverOptions): UseIntersectionObserverReturn;
30
+ export {};
@@ -0,0 +1,46 @@
1
+ import { toGetter } from './utils.js';
2
+ /**
3
+ * Observe an element's intersection with the viewport (or a root) via
4
+ * `IntersectionObserver`, with automatic cleanup.
5
+ *
6
+ * The target may be a value or a getter; when it is a getter that reads reactive
7
+ * state, the observer re-attaches as the target changes. SSR-safe: a nullish
8
+ * target is a no-op.
9
+ *
10
+ * @example
11
+ * ```svelte
12
+ * <script>
13
+ * import { useIntersectionObserver } from 'svelora'
14
+ *
15
+ * let el = $state<HTMLElement>()
16
+ * const io = useIntersectionObserver(() => el, (entry) => {
17
+ * if (entry.isIntersecting) loadImage()
18
+ * }, { rootMargin: '200px' })
19
+ * </script>
20
+ *
21
+ * <div bind:this={el}>{io.isIntersecting ? 'visible' : 'hidden'}</div>
22
+ * ```
23
+ */
24
+ export function useIntersectionObserver(target, callback, options) {
25
+ const resolveTarget = toGetter(target);
26
+ let isIntersecting = $state(false);
27
+ $effect(() => {
28
+ const el = resolveTarget();
29
+ if (!el)
30
+ return;
31
+ const observer = new IntersectionObserver((entries) => {
32
+ const entry = entries[entries.length - 1];
33
+ if (!entry)
34
+ return;
35
+ isIntersecting = entry.isIntersecting;
36
+ callback?.(entry);
37
+ }, options);
38
+ observer.observe(el);
39
+ return () => observer.disconnect();
40
+ });
41
+ return {
42
+ get isIntersecting() {
43
+ return isIntersecting;
44
+ }
45
+ };
46
+ }
@@ -0,0 +1,39 @@
1
+ export interface UseLocalStorageSerializer<T> {
2
+ parse: (raw: string) => T;
3
+ stringify: (value: T) => string;
4
+ }
5
+ export interface UseLocalStorageOptions<T> {
6
+ /**
7
+ * Custom serializer. Defaults to JSON.
8
+ */
9
+ serializer?: UseLocalStorageSerializer<T>;
10
+ /**
11
+ * Update the value when another tab changes the same key (via the `storage` event).
12
+ * @default true
13
+ */
14
+ syncTabs?: boolean;
15
+ }
16
+ export interface UseLocalStorageReturn<T> {
17
+ /** The persisted value. Read it, or assign to write through to `localStorage`. */
18
+ current: T;
19
+ }
20
+ /**
21
+ * Reactive `localStorage`-backed value with cross-tab sync.
22
+ *
23
+ * Reads the stored value on mount (falling back to `initial`), writes through to
24
+ * `localStorage` whenever `.current` changes, and — when `syncTabs` is on — updates
25
+ * when another tab changes the same key. SSR-safe: renders `initial` on the server
26
+ * and hydrates on mount; parse/quota errors are tolerated.
27
+ *
28
+ * @example
29
+ * ```svelte
30
+ * <script>
31
+ * import { useLocalStorage } from 'svelora'
32
+ *
33
+ * const theme = useLocalStorage('theme', 'system')
34
+ * </script>
35
+ *
36
+ * <button onclick={() => (theme.current = 'dark')}>{theme.current}</button>
37
+ * ```
38
+ */
39
+ export declare function useLocalStorage<T>(key: string, initial: T, options?: UseLocalStorageOptions<T>): UseLocalStorageReturn<T>;
@@ -0,0 +1,73 @@
1
+ import { useEventListener } from './useEventListener.svelte.js';
2
+ const jsonSerializer = {
3
+ parse: (raw) => JSON.parse(raw),
4
+ stringify: (value) => JSON.stringify(value)
5
+ };
6
+ /**
7
+ * Reactive `localStorage`-backed value with cross-tab sync.
8
+ *
9
+ * Reads the stored value on mount (falling back to `initial`), writes through to
10
+ * `localStorage` whenever `.current` changes, and — when `syncTabs` is on — updates
11
+ * when another tab changes the same key. SSR-safe: renders `initial` on the server
12
+ * and hydrates on mount; parse/quota errors are tolerated.
13
+ *
14
+ * @example
15
+ * ```svelte
16
+ * <script>
17
+ * import { useLocalStorage } from 'svelora'
18
+ *
19
+ * const theme = useLocalStorage('theme', 'system')
20
+ * </script>
21
+ *
22
+ * <button onclick={() => (theme.current = 'dark')}>{theme.current}</button>
23
+ * ```
24
+ */
25
+ export function useLocalStorage(key, initial, options = {}) {
26
+ const serializer = options.serializer ?? jsonSerializer;
27
+ const { syncTabs = true } = options;
28
+ let value = $state(initial);
29
+ $effect(() => {
30
+ const raw = localStorage.getItem(key);
31
+ if (raw !== null) {
32
+ try {
33
+ value = serializer.parse(raw);
34
+ }
35
+ catch {
36
+ value = initial;
37
+ }
38
+ }
39
+ });
40
+ $effect(() => {
41
+ try {
42
+ const serialized = serializer.stringify(value);
43
+ if (localStorage.getItem(key) !== serialized) {
44
+ localStorage.setItem(key, serialized);
45
+ }
46
+ }
47
+ catch {
48
+ // ignore serialization / quota errors
49
+ }
50
+ });
51
+ useEventListener(() => (syncTabs ? window : null), 'storage', (event) => {
52
+ if (event.key !== key || event.storageArea !== localStorage)
53
+ return;
54
+ if (event.newValue === null) {
55
+ value = initial;
56
+ return;
57
+ }
58
+ try {
59
+ value = serializer.parse(event.newValue);
60
+ }
61
+ catch {
62
+ // ignore malformed external writes
63
+ }
64
+ });
65
+ return {
66
+ get current() {
67
+ return value;
68
+ },
69
+ set current(next) {
70
+ value = next;
71
+ }
72
+ };
73
+ }
@@ -0,0 +1,50 @@
1
+ type ElementTarget = HTMLElement | null | undefined | (() => HTMLElement | null | undefined);
2
+ /**
3
+ * Observe an element's size changes with a `ResizeObserver` and automatic cleanup.
4
+ *
5
+ * The target may be a value or a getter; when it is a getter that reads reactive
6
+ * state, the observer re-attaches as the target changes. SSR-safe: a nullish
7
+ * target is a no-op.
8
+ *
9
+ * @example
10
+ * ```svelte
11
+ * <script>
12
+ * import { useResizeObserver } from 'svelora'
13
+ *
14
+ * let el = $state<HTMLElement>()
15
+ * useResizeObserver(() => el, ([entry]) => {
16
+ * console.log(entry.contentRect.width)
17
+ * })
18
+ * </script>
19
+ *
20
+ * <div bind:this={el}>resize me</div>
21
+ * ```
22
+ */
23
+ export declare function useResizeObserver(target: ElementTarget, callback: ResizeObserverCallback, options?: ResizeObserverOptions): void;
24
+ interface UseElementSizeReturn {
25
+ /** Current content-box width in pixels. */
26
+ readonly width: number;
27
+ /** Current content-box height in pixels. */
28
+ readonly height: number;
29
+ }
30
+ /**
31
+ * Track an element's content-box size reactively.
32
+ *
33
+ * Built on `useResizeObserver`. The size starts at `0` and updates on the
34
+ * observer's initial callback and on every subsequent resize. SSR-safe.
35
+ *
36
+ * @example
37
+ * ```svelte
38
+ * <script>
39
+ * import { useElementSize } from 'svelora'
40
+ *
41
+ * let el = $state<HTMLElement>()
42
+ * const size = useElementSize(() => el)
43
+ * </script>
44
+ *
45
+ * <div bind:this={el}>...</div>
46
+ * <p>{size.width} × {size.height}</p>
47
+ * ```
48
+ */
49
+ export declare function useElementSize(target: ElementTarget): UseElementSizeReturn;
50
+ export {};
@@ -0,0 +1,71 @@
1
+ import { toGetter } from './utils.js';
2
+ /**
3
+ * Observe an element's size changes with a `ResizeObserver` and automatic cleanup.
4
+ *
5
+ * The target may be a value or a getter; when it is a getter that reads reactive
6
+ * state, the observer re-attaches as the target changes. SSR-safe: a nullish
7
+ * target is a no-op.
8
+ *
9
+ * @example
10
+ * ```svelte
11
+ * <script>
12
+ * import { useResizeObserver } from 'svelora'
13
+ *
14
+ * let el = $state<HTMLElement>()
15
+ * useResizeObserver(() => el, ([entry]) => {
16
+ * console.log(entry.contentRect.width)
17
+ * })
18
+ * </script>
19
+ *
20
+ * <div bind:this={el}>resize me</div>
21
+ * ```
22
+ */
23
+ export function useResizeObserver(target, callback, options) {
24
+ const resolveTarget = toGetter(target);
25
+ $effect(() => {
26
+ const el = resolveTarget();
27
+ if (!el)
28
+ return;
29
+ const observer = new ResizeObserver(callback);
30
+ observer.observe(el, options);
31
+ return () => observer.disconnect();
32
+ });
33
+ }
34
+ /**
35
+ * Track an element's content-box size reactively.
36
+ *
37
+ * Built on `useResizeObserver`. The size starts at `0` and updates on the
38
+ * observer's initial callback and on every subsequent resize. SSR-safe.
39
+ *
40
+ * @example
41
+ * ```svelte
42
+ * <script>
43
+ * import { useElementSize } from 'svelora'
44
+ *
45
+ * let el = $state<HTMLElement>()
46
+ * const size = useElementSize(() => el)
47
+ * </script>
48
+ *
49
+ * <div bind:this={el}>...</div>
50
+ * <p>{size.width} × {size.height}</p>
51
+ * ```
52
+ */
53
+ export function useElementSize(target) {
54
+ let width = $state(0);
55
+ let height = $state(0);
56
+ useResizeObserver(target, (entries) => {
57
+ const entry = entries[0];
58
+ if (!entry)
59
+ return;
60
+ width = entry.contentRect.width;
61
+ height = entry.contentRect.height;
62
+ });
63
+ return {
64
+ get width() {
65
+ return width;
66
+ },
67
+ get height() {
68
+ return height;
69
+ }
70
+ };
71
+ }
@@ -0,0 +1,28 @@
1
+ type ElementTarget = HTMLElement | null | undefined | (() => HTMLElement | null | undefined);
2
+ interface UseScrollLockReturn {
3
+ lock: () => void;
4
+ unlock: () => void;
5
+ }
6
+ /**
7
+ * Lock scroll on an element (default `document.body`) while `locked` is true.
8
+ *
9
+ * Sets `overflow: hidden` and compensates for the scrollbar width to avoid a
10
+ * layout jump. Reference-counted per target, so nested overlays locking the same
11
+ * element do not unlock it prematurely. Original styles are restored when the
12
+ * last lock releases or on teardown. SSR-safe: a nullish target is a no-op.
13
+ *
14
+ * Pass a reactive `() => open` getter for the common case, or use the returned
15
+ * `lock` / `unlock` for imperative control (do not mix both on one instance).
16
+ *
17
+ * @example
18
+ * ```svelte
19
+ * <script>
20
+ * import { useScrollLock } from 'svelora'
21
+ *
22
+ * let open = $state(false)
23
+ * useScrollLock(() => open)
24
+ * </script>
25
+ * ```
26
+ */
27
+ export declare function useScrollLock(locked: boolean | (() => boolean), target?: ElementTarget): UseScrollLockReturn;
28
+ export {};
@@ -0,0 +1,79 @@
1
+ import { toGetter } from './utils.js';
2
+ const registry = new WeakMap();
3
+ function scrollbarWidth(el) {
4
+ if (el === document.body || el === document.documentElement) {
5
+ return window.innerWidth - document.documentElement.clientWidth;
6
+ }
7
+ return el.offsetWidth - el.clientWidth;
8
+ }
9
+ function acquire(el) {
10
+ let state = registry.get(el);
11
+ if (!state) {
12
+ state = { count: 0, overflow: el.style.overflow, paddingRight: el.style.paddingRight };
13
+ registry.set(el, state);
14
+ }
15
+ if (state.count === 0) {
16
+ const gap = scrollbarWidth(el);
17
+ el.style.overflow = 'hidden';
18
+ if (gap > 0) {
19
+ const current = parseFloat(getComputedStyle(el).paddingRight) || 0;
20
+ el.style.paddingRight = `${current + gap}px`;
21
+ }
22
+ }
23
+ state.count++;
24
+ }
25
+ function release(el) {
26
+ const state = registry.get(el);
27
+ if (!state)
28
+ return;
29
+ state.count--;
30
+ if (state.count <= 0) {
31
+ el.style.overflow = state.overflow;
32
+ el.style.paddingRight = state.paddingRight;
33
+ registry.delete(el);
34
+ }
35
+ }
36
+ /**
37
+ * Lock scroll on an element (default `document.body`) while `locked` is true.
38
+ *
39
+ * Sets `overflow: hidden` and compensates for the scrollbar width to avoid a
40
+ * layout jump. Reference-counted per target, so nested overlays locking the same
41
+ * element do not unlock it prematurely. Original styles are restored when the
42
+ * last lock releases or on teardown. SSR-safe: a nullish target is a no-op.
43
+ *
44
+ * Pass a reactive `() => open` getter for the common case, or use the returned
45
+ * `lock` / `unlock` for imperative control (do not mix both on one instance).
46
+ *
47
+ * @example
48
+ * ```svelte
49
+ * <script>
50
+ * import { useScrollLock } from 'svelora'
51
+ *
52
+ * let open = $state(false)
53
+ * useScrollLock(() => open)
54
+ * </script>
55
+ * ```
56
+ */
57
+ export function useScrollLock(locked, target) {
58
+ const resolveLocked = toGetter(locked);
59
+ const resolveTarget = typeof target === 'function' ? target : () => target ?? document.body;
60
+ let manual = $state(null);
61
+ const isLocked = $derived(manual ?? resolveLocked());
62
+ $effect(() => {
63
+ if (!isLocked)
64
+ return;
65
+ const el = resolveTarget();
66
+ if (!el)
67
+ return;
68
+ acquire(el);
69
+ return () => release(el);
70
+ });
71
+ return {
72
+ lock() {
73
+ manual = true;
74
+ },
75
+ unlock() {
76
+ manual = false;
77
+ }
78
+ };
79
+ }
@@ -0,0 +1,37 @@
1
+ export interface UseThrottleOptions {
2
+ /**
3
+ * Minimum time in milliseconds between invocations.
4
+ * @default 200
5
+ */
6
+ delay?: number;
7
+ }
8
+ /**
9
+ * Reactive throttle hook. Caps how often a callback runs to at most once per
10
+ * `delay`, with leading and trailing invocation.
11
+ *
12
+ * The companion to `useDebounce`: debounce waits for a pause, throttle
13
+ * guarantees a steady maximum rate — ideal for scroll, resize, mousemove, and
14
+ * drag handlers. The first call runs immediately; calls during the cooldown
15
+ * window are coalesced into a single trailing call (latest wins). Any pending
16
+ * timer is cleared on teardown.
17
+ *
18
+ * @example
19
+ * ```svelte
20
+ * <script>
21
+ * import { useThrottle } from 'svelora'
22
+ *
23
+ * const throttle = useThrottle({ delay: 100 })
24
+ *
25
+ * function onScroll() {
26
+ * throttle.run(() => updatePosition(window.scrollY))
27
+ * }
28
+ * </script>
29
+ *
30
+ * <svelte:window onscroll={onScroll} />
31
+ * ```
32
+ */
33
+ export declare function useThrottle(options?: UseThrottleOptions): {
34
+ readonly pending: boolean;
35
+ run: (callback: () => void) => void;
36
+ cancel: () => void;
37
+ };