svelora 3.0.0 → 3.0.2

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 (109) hide show
  1. package/dist/Accordion/Accordion.svelte +66 -97
  2. package/dist/Alert/Alert.svelte +39 -64
  3. package/dist/Alert/Alert.svelte.d.ts +1 -1
  4. package/dist/Avatar/Avatar.svelte +35 -75
  5. package/dist/AvatarGroup/AvatarGroup.svelte +38 -55
  6. package/dist/Badge/Badge.svelte +28 -50
  7. package/dist/Banner/Banner.svelte +46 -41
  8. package/dist/Banner/Banner.svelte.d.ts +1 -1
  9. package/dist/Breadcrumb/Breadcrumb.svelte +32 -26
  10. package/dist/Button/Button.svelte +70 -138
  11. package/dist/Calendar/Calendar.svelte +94 -157
  12. package/dist/Calendar/Calendar.svelte.d.ts +1 -1
  13. package/dist/Card/Card.svelte +18 -31
  14. package/dist/Carousel/Carousel.svelte +118 -173
  15. package/dist/Checkbox/Checkbox.svelte +52 -97
  16. package/dist/CheckboxGroup/CheckboxGroup.svelte +62 -107
  17. package/dist/CheckboxGroup/CheckboxGroup.svelte.d.ts +1 -1
  18. package/dist/Chip/Chip.svelte +22 -34
  19. package/dist/CodeBlock/CodeBlock.svelte +42 -59
  20. package/dist/Collapsible/Collapsible.svelte +22 -38
  21. package/dist/Collapsible/Collapsible.svelte.d.ts +1 -1
  22. package/dist/Collapsible/CollapsibleTestWrapper.svelte +2 -5
  23. package/dist/Collapsible/CollapsibleTestWrapper.svelte.d.ts +1 -1
  24. package/dist/Command/Command.svelte +40 -77
  25. package/dist/Command/Command.svelte.d.ts +1 -1
  26. package/dist/Command/CommandTestWrapper.svelte +2 -10
  27. package/dist/Command/CommandTestWrapper.svelte.d.ts +1 -1
  28. package/dist/Container/Container.svelte +11 -14
  29. package/dist/ContextMenu/ContextMenu.svelte +51 -114
  30. package/dist/ContextMenu/ContextMenu.svelte.d.ts +1 -1
  31. package/dist/Drawer/Drawer.svelte +72 -110
  32. package/dist/Drawer/DrawerTriggerTestWrapper.svelte +1 -2
  33. package/dist/DropdownMenu/DropdownMenu.svelte +63 -124
  34. package/dist/DropdownMenu/DropdownMenu.svelte.d.ts +1 -1
  35. package/dist/DropdownMenu/DropdownMenuTriggerTestWrapper.svelte +2 -5
  36. package/dist/Editor/Editor.svelte +441 -576
  37. package/dist/Editor/Editor.svelte.d.ts +1 -1
  38. package/dist/Editor/EditorUrlPrompt.svelte +40 -53
  39. package/dist/Editor/SlashPopup.svelte +12 -24
  40. package/dist/Empty/Empty.svelte +32 -63
  41. package/dist/FieldGroup/FieldGroup.svelte +23 -38
  42. package/dist/FileUpload/FileUpload.svelte +242 -320
  43. package/dist/FileUpload/FileUpload.svelte.d.ts +1 -1
  44. package/dist/Fonts/Fonts.svelte +15 -37
  45. package/dist/Form/Form.svelte +112 -170
  46. package/dist/FormField/FormField.svelte +102 -135
  47. package/dist/Icon/Icon.svelte +7 -32
  48. package/dist/Input/Input.svelte +71 -141
  49. package/dist/Input/Input.svelte.d.ts +2 -2
  50. package/dist/Kbd/Kbd.svelte +18 -34
  51. package/dist/Link/Link.svelte +129 -196
  52. package/dist/LocaleButton/LocaleButton.svelte +165 -0
  53. package/dist/LocaleButton/LocaleButton.svelte.d.ts +5 -0
  54. package/dist/LocaleButton/index.d.ts +2 -0
  55. package/dist/LocaleButton/index.js +1 -0
  56. package/dist/LocaleButton/locale-button.types.d.ts +182 -0
  57. package/dist/LocaleButton/locale-button.types.js +1 -0
  58. package/dist/LocaleButton/locale-button.variants.d.ts +61 -0
  59. package/dist/LocaleButton/locale-button.variants.js +34 -0
  60. package/dist/Modal/Modal.svelte +52 -106
  61. package/dist/Modal/ModalTriggerTestWrapper.svelte +1 -2
  62. package/dist/Pagination/Pagination.svelte +48 -92
  63. package/dist/Pagination/pagination.variants.d.ts +1 -1
  64. package/dist/PinInput/PinInput.svelte +57 -111
  65. package/dist/PinInput/PinInput.svelte.d.ts +1 -1
  66. package/dist/Popover/Popover.svelte +28 -61
  67. package/dist/Popover/Popover.svelte.d.ts +1 -1
  68. package/dist/Progress/Progress.svelte +75 -94
  69. package/dist/RadioGroup/RadioGroup.svelte +54 -99
  70. package/dist/RadioGroup/RadioGroup.svelte.d.ts +1 -1
  71. package/dist/Select/Select.svelte +112 -269
  72. package/dist/Select/Select.svelte.d.ts +1 -1
  73. package/dist/SelectMenu/SelectMenu.svelte +211 -409
  74. package/dist/SelectMenu/SelectMenu.svelte.d.ts +1 -1
  75. package/dist/SelectMenu/SelectMenuFormFieldTestWrapper.svelte +3 -6
  76. package/dist/Separator/Separator.svelte +29 -44
  77. package/dist/Skeleton/Skeleton.svelte +11 -23
  78. package/dist/Slideover/Slideover.svelte +52 -106
  79. package/dist/Slideover/SlideoverTriggerTestWrapper.svelte +1 -2
  80. package/dist/Slider/Slider.svelte +48 -84
  81. package/dist/Slider/Slider.svelte.d.ts +1 -1
  82. package/dist/Stepper/Stepper.svelte +139 -132
  83. package/dist/Stepper/Stepper.svelte.d.ts +1 -1
  84. package/dist/Switch/Switch.svelte +62 -98
  85. package/dist/Table/Table.svelte +232 -283
  86. package/dist/Table/table.variants.d.ts +1 -1
  87. package/dist/Tabs/Tabs.svelte +96 -129
  88. package/dist/Tabs/Tabs.svelte.d.ts +1 -1
  89. package/dist/Textarea/Textarea.svelte +90 -173
  90. package/dist/Textarea/Textarea.svelte.d.ts +1 -1
  91. package/dist/ThemeModeButton/ThemeModeButton.svelte +16 -38
  92. package/dist/Timeline/Timeline.svelte +75 -54
  93. package/dist/Toast/Toaster.svelte +8 -25
  94. package/dist/Tooltip/Tooltip.svelte +34 -66
  95. package/dist/Tooltip/Tooltip.svelte.d.ts +1 -1
  96. package/dist/Tooltip/TooltipTestWrapper.svelte +2 -5
  97. package/dist/User/User.svelte +33 -49
  98. package/dist/docs/navigation.d.ts +1 -1
  99. package/dist/docs/navigation.js +8 -1
  100. package/dist/hooks/HookContextProbe.svelte +2 -4
  101. package/dist/hooks/HookContextProvider.svelte +8 -6
  102. package/dist/hooks/HookEmitProbe.svelte +8 -11
  103. package/dist/i18n.d.ts +2 -0
  104. package/dist/i18n.js +19 -0
  105. package/dist/index.d.ts +1 -0
  106. package/dist/index.js +1 -0
  107. package/dist/mcp/svelora-docs.data.json +4 -2
  108. package/dist/theme.css +1 -1
  109. package/package.json +16 -8
@@ -1,146 +1,76 @@
1
- <script lang="ts" module>
2
- import type { InputProps, InputValue } from './input.types.js'
3
-
4
- export type Props<T extends InputValue = InputValue> = InputProps<T>
1
+ <script lang="ts" module>export {};
5
2
  </script>
6
3
 
7
- <script lang="ts" generics="T extends InputValue = InputValue">
8
- import { getContext } from 'svelte'
9
- import Avatar from '../Avatar/Avatar.svelte'
10
- import type { AvatarSize } from '../Avatar/avatar.types.js'
11
- import { getComponentConfig, iconsDefaults } from '../config.js'
12
- import {
13
- type FieldGroupVariantProps,
14
- fieldGroupVariantWithRoot
15
- } from '../FieldGroup/field-group.variants.js'
16
- import { useFormField, useFormFieldEmit } from '../hooks/useFormField.svelte.js'
17
- import Icon from '../Icon/Icon.svelte'
18
- import { inputDefaults, inputVariants } from './input.variants.js'
19
-
20
- const config = getComponentConfig('input', inputDefaults)
21
- const icons = getComponentConfig('icons', iconsDefaults)
22
-
23
- let {
24
- ref = $bindable(null),
25
- value = $bindable(),
26
- ui,
27
- id,
28
- name,
29
- color = config.defaultVariants.color,
30
- variant = config.defaultVariants.variant,
31
- size,
32
- type = 'text',
33
- highlight,
34
- loading = false,
35
- loadingIcon = icons.loading,
36
- disabled = false,
37
- icon,
38
- leadingIcon,
39
- trailingIcon,
40
- trailing = false,
41
- avatar,
42
- leadingSlot,
43
- trailingSlot,
44
- class: className,
45
- onblur,
46
- oninput,
47
- onchange,
48
- onfocus,
49
- ...restProps
50
- }: Props<T> = $props()
51
-
52
- const formFieldContext = useFormField()
53
- const emit = useFormFieldEmit()
54
-
55
- function handleBlur(event: FocusEvent & { currentTarget: HTMLInputElement }) {
56
- emit.onBlur()
57
- onblur?.(event)
58
- }
59
- function handleInput(event: Event & { currentTarget: HTMLInputElement }) {
60
- emit.onInput()
61
- oninput?.(event)
62
- }
63
- function handleChange(event: Event & { currentTarget: HTMLInputElement }) {
64
- emit.onChange()
65
- onchange?.(event)
66
- }
67
- function handleFocus(event: FocusEvent & { currentTarget: HTMLInputElement }) {
68
- emit.onFocus()
69
- onfocus?.(event)
70
- }
71
-
72
- const fieldGroupContext = getContext<
73
- | {
74
- orientation: NonNullable<FieldGroupVariantProps['orientation']>
75
- size: NonNullable<FieldGroupVariantProps['size']>
76
- }
77
- | undefined
78
- >('fieldGroup')
79
-
80
- const hasError = $derived(
81
- formFieldContext?.error !== undefined && formFieldContext?.error !== false
82
- )
83
- const resolvedSize = $derived(
84
- size ?? formFieldContext?.size ?? fieldGroupContext?.size ?? config.defaultVariants.size
85
- )
86
- const resolvedColor = $derived(hasError ? 'error' : color)
87
- const resolvedHighlight = $derived(highlight ?? hasError)
88
- const fieldGroupClass = $derived(
89
- fieldGroupContext
90
- ? fieldGroupVariantWithRoot.fieldGroup[fieldGroupContext.orientation ?? 'horizontal']
91
- : undefined
92
- )
93
-
94
- const resolvedId = $derived(id ?? formFieldContext?.ariaId)
95
- const resolvedName = $derived(name ?? formFieldContext?.name)
96
-
97
- const loadingLeading = $derived(loading && !trailing)
98
- const loadingTrailing = $derived(loading && trailing)
99
-
100
- const isLeading = $derived((!!icon && !trailing) || !!leadingIcon || !!avatar || loadingLeading)
101
- const isTrailing = $derived((!!icon && trailing) || !!trailingIcon || loadingTrailing)
102
-
103
- const leadingIconName = $derived(leadingIcon || (icon && !trailing ? icon : undefined))
104
- const trailingIconName = $derived(trailingIcon || (icon && trailing ? icon : undefined))
105
-
106
- const ariaDescribedBy = $derived(
107
- !formFieldContext
108
- ? undefined
109
- : hasError
110
- ? `${formFieldContext.ariaId}-error`
111
- : `${formFieldContext.ariaId}-description ${formFieldContext.ariaId}-help`
112
- )
113
-
114
- const variantSlots = $derived(
115
- inputVariants({
116
- variant,
117
- color: resolvedColor,
118
- size: resolvedSize,
119
- leading: isLeading,
120
- trailing: isTrailing,
121
- highlight: resolvedHighlight
122
- })
123
- )
124
- const classes = $derived({
125
- root: variantSlots.root({
126
- class: [config.slots.root, fieldGroupClass?.root, className, ui?.root]
127
- }),
128
- base: variantSlots.base({
129
- class: [config.slots.base, fieldGroupClass?.base, ui?.base]
130
- }),
131
- leading: variantSlots.leading({ class: [config.slots.leading, ui?.leading] }),
132
- leadingIcon: variantSlots.leadingIcon({
133
- class: [config.slots.leadingIcon, ui?.leadingIcon]
134
- }),
135
- leadingAvatar: variantSlots.leadingAvatar({
136
- class: [config.slots.leadingAvatar, ui?.leadingAvatar]
137
- }),
138
- leadingAvatarSize: variantSlots.leadingAvatarSize() as AvatarSize,
139
- trailing: variantSlots.trailing({ class: [config.slots.trailing, ui?.trailing] }),
140
- trailingIcon: variantSlots.trailingIcon({
141
- class: [config.slots.trailingIcon, ui?.trailingIcon]
142
- })
143
- })
4
+ <script lang="ts" generics="T extends InputValue = InputValue">import { getContext } from "svelte";
5
+ import Avatar from "../Avatar/Avatar.svelte";
6
+ import { getComponentConfig, iconsDefaults } from "../config.js";
7
+ import { fieldGroupVariantWithRoot } from "../FieldGroup/field-group.variants.js";
8
+ import { useFormField, useFormFieldEmit } from "../hooks/useFormField.svelte.js";
9
+ import Icon from "../Icon/Icon.svelte";
10
+ import { inputDefaults, inputVariants } from "./input.variants.js";
11
+ const config = getComponentConfig("input", inputDefaults);
12
+ const icons = getComponentConfig("icons", iconsDefaults);
13
+ let { ref = $bindable(null), value = $bindable(), ui, id, name, color = config.defaultVariants.color, variant = config.defaultVariants.variant, size, type = "text", highlight, loading = false, loadingIcon = icons.loading, disabled = false, icon, leadingIcon, trailingIcon, trailing = false, avatar, leadingSlot, trailingSlot, class: className, onblur, oninput, onchange, onfocus, ...restProps } = $props();
14
+ const formFieldContext = useFormField();
15
+ const emit = useFormFieldEmit();
16
+ function handleBlur(event) {
17
+ emit.onBlur();
18
+ onblur?.(event);
19
+ }
20
+ function handleInput(event) {
21
+ emit.onInput();
22
+ oninput?.(event);
23
+ }
24
+ function handleChange(event) {
25
+ emit.onChange();
26
+ onchange?.(event);
27
+ }
28
+ function handleFocus(event) {
29
+ emit.onFocus();
30
+ onfocus?.(event);
31
+ }
32
+ const fieldGroupContext = getContext("fieldGroup");
33
+ const hasError = $derived(formFieldContext?.error !== undefined && formFieldContext?.error !== false);
34
+ const resolvedSize = $derived(size ?? formFieldContext?.size ?? fieldGroupContext?.size ?? config.defaultVariants.size);
35
+ const resolvedColor = $derived(hasError ? "error" : color);
36
+ const resolvedHighlight = $derived(highlight ?? hasError);
37
+ const fieldGroupClass = $derived(fieldGroupContext ? fieldGroupVariantWithRoot.fieldGroup[fieldGroupContext.orientation ?? "horizontal"] : undefined);
38
+ const resolvedId = $derived(id ?? formFieldContext?.ariaId);
39
+ const resolvedName = $derived(name ?? formFieldContext?.name);
40
+ const loadingLeading = $derived(loading && !trailing);
41
+ const loadingTrailing = $derived(loading && trailing);
42
+ const isLeading = $derived(!!icon && !trailing || !!leadingIcon || !!avatar || loadingLeading);
43
+ const isTrailing = $derived(!!icon && trailing || !!trailingIcon || loadingTrailing);
44
+ const leadingIconName = $derived(leadingIcon || (icon && !trailing ? icon : undefined));
45
+ const trailingIconName = $derived(trailingIcon || (icon && trailing ? icon : undefined));
46
+ const ariaDescribedBy = $derived(!formFieldContext ? undefined : hasError ? `${formFieldContext.ariaId}-error` : `${formFieldContext.ariaId}-description ${formFieldContext.ariaId}-help`);
47
+ const variantSlots = $derived(inputVariants({
48
+ variant,
49
+ color: resolvedColor,
50
+ size: resolvedSize,
51
+ leading: isLeading,
52
+ trailing: isTrailing,
53
+ highlight: resolvedHighlight
54
+ }));
55
+ const classes = $derived({
56
+ root: variantSlots.root({ class: [
57
+ config.slots.root,
58
+ fieldGroupClass?.root,
59
+ className,
60
+ ui?.root
61
+ ] }),
62
+ base: variantSlots.base({ class: [
63
+ config.slots.base,
64
+ fieldGroupClass?.base,
65
+ ui?.base
66
+ ] }),
67
+ leading: variantSlots.leading({ class: [config.slots.leading, ui?.leading] }),
68
+ leadingIcon: variantSlots.leadingIcon({ class: [config.slots.leadingIcon, ui?.leadingIcon] }),
69
+ leadingAvatar: variantSlots.leadingAvatar({ class: [config.slots.leadingAvatar, ui?.leadingAvatar] }),
70
+ leadingAvatarSize: variantSlots.leadingAvatarSize(),
71
+ trailing: variantSlots.trailing({ class: [config.slots.trailing, ui?.trailing] }),
72
+ trailingIcon: variantSlots.trailingIcon({ class: [config.slots.trailingIcon, ui?.trailingIcon] })
73
+ });
144
74
  </script>
145
75
 
146
76
  <div class={classes.root}>
@@ -3,7 +3,7 @@ export type Props<T extends InputValue = InputValue> = InputProps<T>;
3
3
  declare function $$render<T extends InputValue = InputValue>(): {
4
4
  props: Props<T>;
5
5
  exports: {};
6
- bindings: "value" | "ref";
6
+ bindings: "ref" | "value";
7
7
  slots: {};
8
8
  events: {};
9
9
  };
@@ -11,7 +11,7 @@ declare class __sveltets_Render<T extends InputValue = InputValue> {
11
11
  props(): ReturnType<typeof $$render<T>>['props'];
12
12
  events(): ReturnType<typeof $$render<T>>['events'];
13
13
  slots(): ReturnType<typeof $$render<T>>['slots'];
14
- bindings(): "value" | "ref";
14
+ bindings(): "ref" | "value";
15
15
  exports(): {};
16
16
  }
17
17
  interface $$IsomorphicComponent {
@@ -1,39 +1,23 @@
1
- <script lang="ts" module>
2
- import type { KbdProps } from './kbd.types.js'
3
- import { resolveKey } from './useKbd.svelte.js'
4
-
5
- export type Props = KbdProps
1
+ <script lang="ts" module>import { resolveKey } from "./useKbd.svelte.js";
6
2
  </script>
7
3
 
8
- <script lang="ts">
9
- import { onMount } from 'svelte'
10
- import { getComponentConfig } from '../config.js'
11
- import { kbdDefaults, kbdVariants } from './kbd.variants.js'
12
-
13
- const config = getComponentConfig('kbd', kbdDefaults)
14
-
15
- let {
16
- ref = $bindable(null),
17
- as = 'kbd',
18
- value,
19
- color = config.defaultVariants.color,
20
- size = config.defaultVariants.size,
21
- variant = config.defaultVariants.variant,
22
- ui,
23
- class: className,
24
- children,
25
- ...restProps
26
- }: Props = $props()
27
-
28
- let mounted = $state(false)
29
- onMount(() => (mounted = true))
30
-
31
- const displayValue = $derived(resolveKey(value, mounted))
32
- const kbdClass = $derived(
33
- kbdVariants({ color, size, variant }).base({
34
- class: [config.slots.base, className, ui?.base]
35
- })
36
- )
4
+ <script lang="ts">import { onMount } from "svelte";
5
+ import { getComponentConfig } from "../config.js";
6
+ import { kbdDefaults, kbdVariants } from "./kbd.variants.js";
7
+ const config = getComponentConfig("kbd", kbdDefaults);
8
+ let { ref = $bindable(null), as = "kbd", value, color = config.defaultVariants.color, size = config.defaultVariants.size, variant = config.defaultVariants.variant, ui, class: className, children, ...restProps } = $props();
9
+ let mounted = $state(false);
10
+ onMount(() => mounted = true);
11
+ const displayValue = $derived(resolveKey(value, mounted));
12
+ const kbdClass = $derived(kbdVariants({
13
+ color,
14
+ size,
15
+ variant
16
+ }).base({ class: [
17
+ config.slots.base,
18
+ className,
19
+ ui?.base
20
+ ] }));
37
21
  </script>
38
22
 
39
23
  <svelte:element this={as} bind:this={ref} class={kbdClass} {...restProps}>
@@ -1,201 +1,134 @@
1
- <script lang="ts" module>
2
- import type { LinkProps } from './link.types.js'
3
-
4
- const navigationEvent = 'svelora:navigation'
5
- let isHistoryPatched = false
6
-
7
- export type Props = LinkProps
8
-
9
- function parseUrl(url: string, baseUrl: URL) {
10
- try {
11
- const parsed = new URL(url, baseUrl.origin)
12
- return {
13
- pathname: parsed.pathname,
14
- query: parsed.searchParams,
15
- hash: parsed.hash
16
- }
17
- } catch {
18
- return {
19
- pathname: url,
20
- query: new URLSearchParams(),
21
- hash: ''
22
- }
23
- }
24
- }
25
-
26
- function isQueryMatch(
27
- linkQuery: URLSearchParams,
28
- currentQuery: URLSearchParams,
29
- mode: boolean | 'partial'
30
- ): boolean {
31
- if (mode === false) return true
32
- if (mode === 'partial') {
33
- for (const [key, value] of linkQuery) {
34
- if (!currentQuery.getAll(key).includes(value)) return false
35
- }
36
- return true
37
- }
38
-
39
- // Exact: check size first (O(1) bail-out), then compare sorted strings
40
- if (linkQuery.size !== currentQuery.size) return false
41
- const sorted = (p: URLSearchParams) => new URLSearchParams([...p].sort()).toString()
42
- return sorted(linkQuery) === sorted(currentQuery)
43
- }
44
-
45
- function isPathnameMatch(linkPath: string, currentPath: string, exactMatch: boolean): boolean {
46
- if (exactMatch) return linkPath === currentPath
47
-
48
- const link = linkPath.replace(/\/$/, '') || '/'
49
- const current = currentPath.replace(/\/$/, '') || '/'
50
-
51
- return link === '/' ? current === '/' : current === link || current.startsWith(`${link}/`)
52
- }
53
-
54
- function dispatchNavigationEvent() {
55
- if (typeof window === 'undefined') {
56
- return
57
- }
58
-
59
- window.dispatchEvent(new Event(navigationEvent))
60
- }
61
-
62
- function patchHistoryMethod(method: 'pushState' | 'replaceState') {
63
- const historyMethod = window.history[method].bind(window.history) as (
64
- ...args: Parameters<History['pushState']>
65
- ) => void
66
-
67
- Object.defineProperty(window.history, method, {
68
- configurable: true,
69
- value: (...args: Parameters<History['pushState']>) => {
70
- historyMethod(...args)
71
- dispatchNavigationEvent()
72
- }
73
- })
74
- }
75
-
76
- function ensureNavigationEvents() {
77
- if (typeof window === 'undefined' || isHistoryPatched) {
78
- return
79
- }
80
-
81
- isHistoryPatched = true
82
- patchHistoryMethod('pushState')
83
- patchHistoryMethod('replaceState')
84
- }
85
-
86
- function subscribeToLocation(callback: () => void) {
87
- if (typeof window === 'undefined') {
88
- return () => undefined
89
- }
90
-
91
- ensureNavigationEvents()
92
-
93
- const handleLocationChange = () => callback()
94
-
95
- handleLocationChange()
96
- window.addEventListener('popstate', handleLocationChange)
97
- window.addEventListener('hashchange', handleLocationChange)
98
- window.addEventListener(navigationEvent, handleLocationChange)
99
-
100
- return () => {
101
- window.removeEventListener('popstate', handleLocationChange)
102
- window.removeEventListener('hashchange', handleLocationChange)
103
- window.removeEventListener(navigationEvent, handleLocationChange)
104
- }
105
- }
1
+ <script lang="ts" module>const navigationEvent = "svelora:navigation";
2
+ let isHistoryPatched = false;
3
+ function parseUrl(url, baseUrl) {
4
+ try {
5
+ const parsed = new URL(url, baseUrl.origin);
6
+ return {
7
+ pathname: parsed.pathname,
8
+ query: parsed.searchParams,
9
+ hash: parsed.hash
10
+ };
11
+ } catch {
12
+ return {
13
+ pathname: url,
14
+ query: new URLSearchParams(),
15
+ hash: ""
16
+ };
17
+ }
18
+ }
19
+ function isQueryMatch(linkQuery, currentQuery, mode) {
20
+ if (mode === false) return true;
21
+ if (mode === "partial") {
22
+ for (const [key, value] of linkQuery) {
23
+ if (!currentQuery.getAll(key).includes(value)) return false;
24
+ }
25
+ return true;
26
+ }
27
+ // Exact: check size first (O(1) bail-out), then compare sorted strings
28
+ if (linkQuery.size !== currentQuery.size) return false;
29
+ const sorted = (p) => new URLSearchParams([...p].sort()).toString();
30
+ return sorted(linkQuery) === sorted(currentQuery);
31
+ }
32
+ function isPathnameMatch(linkPath, currentPath, exactMatch) {
33
+ if (exactMatch) return linkPath === currentPath;
34
+ const link = linkPath.replace(/\/$/, "") || "/";
35
+ const current = currentPath.replace(/\/$/, "") || "/";
36
+ return link === "/" ? current === "/" : current === link || current.startsWith(`${link}/`);
37
+ }
38
+ function dispatchNavigationEvent() {
39
+ if (typeof window === "undefined") {
40
+ return;
41
+ }
42
+ window.dispatchEvent(new Event(navigationEvent));
43
+ }
44
+ function patchHistoryMethod(method) {
45
+ const historyMethod = window.history[method].bind(window.history);
46
+ Object.defineProperty(window.history, method, {
47
+ configurable: true,
48
+ value: (...args) => {
49
+ historyMethod(...args);
50
+ dispatchNavigationEvent();
51
+ }
52
+ });
53
+ }
54
+ function ensureNavigationEvents() {
55
+ if (typeof window === "undefined" || isHistoryPatched) {
56
+ return;
57
+ }
58
+ isHistoryPatched = true;
59
+ patchHistoryMethod("pushState");
60
+ patchHistoryMethod("replaceState");
61
+ }
62
+ function subscribeToLocation(callback) {
63
+ if (typeof window === "undefined") {
64
+ return () => undefined;
65
+ }
66
+ ensureNavigationEvents();
67
+ const handleLocationChange = () => callback();
68
+ handleLocationChange();
69
+ window.addEventListener("popstate", handleLocationChange);
70
+ window.addEventListener("hashchange", handleLocationChange);
71
+ window.addEventListener(navigationEvent, handleLocationChange);
72
+ return () => {
73
+ window.removeEventListener("popstate", handleLocationChange);
74
+ window.removeEventListener("hashchange", handleLocationChange);
75
+ window.removeEventListener(navigationEvent, handleLocationChange);
76
+ };
77
+ }
78
+ export {};
106
79
  </script>
107
80
 
108
- <script lang="ts">
109
- import { onMount } from 'svelte'
110
- import { twMerge } from 'tailwind-merge'
111
- import { getComponentConfig } from '../config.js'
112
- import { linkDefaults, linkVariants } from './link.variants.js'
113
-
114
- const config = getComponentConfig('link', linkDefaults)
115
-
116
- let {
117
- ref = $bindable(null),
118
- href,
119
- type,
120
- active,
121
- exact = false,
122
- exactQuery = false,
123
- exactHash = false,
124
- activeClass,
125
- inactiveClass,
126
- disabled = false,
127
- raw = false,
128
- external,
129
- children,
130
- class: className,
131
- ui,
132
- target,
133
- rel,
134
- onclick,
135
- ...restProps
136
- }: Props = $props()
137
-
138
- const isLink = $derived(!!href)
139
- let currentUrl = $state<URL | undefined>(undefined)
140
-
141
- onMount(() => {
142
- return subscribeToLocation(() => {
143
- currentUrl = new URL(window.location.href)
144
- })
145
- })
146
-
147
- const isExternal = $derived(
148
- isLink &&
149
- (external ??
150
- (href!.startsWith('http://') ||
151
- href!.startsWith('https://') ||
152
- href!.startsWith('//')))
153
- )
154
-
155
- const resolvedTarget = $derived(
156
- isLink ? (target ?? (isExternal ? '_blank' : undefined)) : undefined
157
- )
158
-
159
- const resolvedRel = $derived(
160
- isLink
161
- ? (rel ??
162
- (isExternal || resolvedTarget === '_blank' ? 'noopener noreferrer' : undefined))
163
- : undefined
164
- )
165
-
166
- const isActive = $derived.by(() => {
167
- if (active !== undefined) return active
168
- if (!isLink || !currentUrl || isExternal) return false
169
-
170
- const link = parseUrl(href!, currentUrl)
171
-
172
- if (exactHash && link.hash !== currentUrl.hash) return false
173
- if (!isQueryMatch(link.query, currentUrl.searchParams, exactQuery)) return false
174
-
175
- return isPathnameMatch(link.pathname, currentUrl.pathname, exact)
176
- })
177
-
178
- const baseClass = $derived.by(() => {
179
- const stateClass = isActive ? activeClass : inactiveClass
180
- if (raw) return twMerge(stateClass, className)
181
-
182
- const slots = linkVariants({ active: isActive, disabled, raw })
183
- return slots.base({ class: [config.slots.base, stateClass, className, ui?.base] })
184
- })
185
-
186
- const ariaCurrent = $derived(isActive && exact ? ('page' as const) : undefined)
187
-
188
- function handleClick(e: MouseEvent) {
189
- if (disabled) {
190
- e.preventDefault()
191
- e.stopPropagation()
192
- return
193
- }
194
-
195
- if (typeof onclick === 'function') {
196
- ;(onclick as (e: MouseEvent) => void)(e)
197
- }
198
- }
81
+ <script lang="ts">import { onMount } from "svelte";
82
+ import { twMerge } from "tailwind-merge";
83
+ import { getComponentConfig } from "../config.js";
84
+ import { linkDefaults, linkVariants } from "./link.variants.js";
85
+ const config = getComponentConfig("link", linkDefaults);
86
+ let { ref = $bindable(null), href, type, active, exact = false, exactQuery = false, exactHash = false, activeClass, inactiveClass, disabled = false, raw = false, external, children, class: className, ui, target, rel, onclick, ...restProps } = $props();
87
+ const isLink = $derived(!!href);
88
+ let currentUrl = $state(undefined);
89
+ onMount(() => {
90
+ return subscribeToLocation(() => {
91
+ currentUrl = new URL(window.location.href);
92
+ });
93
+ });
94
+ const isExternal = $derived(isLink && (external ?? (href.startsWith("http://") || href.startsWith("https://") || href.startsWith("//"))));
95
+ const resolvedTarget = $derived(isLink ? target ?? (isExternal ? "_blank" : undefined) : undefined);
96
+ const resolvedRel = $derived(isLink ? rel ?? (isExternal || resolvedTarget === "_blank" ? "noopener noreferrer" : undefined) : undefined);
97
+ const isActive = $derived.by(() => {
98
+ if (active !== undefined) return active;
99
+ if (!isLink || !currentUrl || isExternal) return false;
100
+ const link = parseUrl(href, currentUrl);
101
+ if (exactHash && link.hash !== currentUrl.hash) return false;
102
+ if (!isQueryMatch(link.query, currentUrl.searchParams, exactQuery)) return false;
103
+ return isPathnameMatch(link.pathname, currentUrl.pathname, exact);
104
+ });
105
+ const baseClass = $derived.by(() => {
106
+ const stateClass = isActive ? activeClass : inactiveClass;
107
+ if (raw) return twMerge(stateClass, className);
108
+ const slots = linkVariants({
109
+ active: isActive,
110
+ disabled,
111
+ raw
112
+ });
113
+ return slots.base({ class: [
114
+ config.slots.base,
115
+ stateClass,
116
+ className,
117
+ ui?.base
118
+ ] });
119
+ });
120
+ const ariaCurrent = $derived(isActive && exact ? "page" : undefined);
121
+ function handleClick(e) {
122
+ if (disabled) {
123
+ e.preventDefault();
124
+ e.stopPropagation();
125
+ return;
126
+ }
127
+ if (typeof onclick === "function") {
128
+ ;
129
+ onclick(e);
130
+ }
131
+ }
199
132
  </script>
200
133
 
201
134
  {#if isLink}