Skip to content

Commit 781333d

Browse files
committed
feat(attachment): adds active attachment
1 parent 8d67481 commit 781333d

File tree

11 files changed

+226
-117
lines changed

11 files changed

+226
-117
lines changed

README.md

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -366,13 +366,29 @@ Note: The action requires the router context to be present in the component tree
366366

367367
```svelte
368368
<script lang="ts">
369-
import { active } from '@dvcol/svelte-simple-router/router';
369+
import { active } from '@dvcol/svelte-simple-router/action';
370370
</script>
371371
372372
<a href="/path" use:active>simple link</a>
373373
<a href="/path" data-name="route-name" use:active>named link</a>
374-
<button :use:active="{ path: '/path' }">button link</button>
375-
<div :use:active="{ name: 'route-name' }">div link</div>
374+
<button use:active="{ path: '/path' }">button link</button>
375+
<div use:active="{ name: 'route-name' }">div link</div>
376+
```
377+
#### Active attachment
378+
379+
The `active attachment` adds an active state (class, style or attribute) to an element when the route matches.
380+
381+
Similar to the `active` action, see the [active action](#active-action) for more information.
382+
383+
```svelte
384+
<script lang="ts">
385+
import { useActive } from '@dvcol/svelte-simple-router/attachment';
386+
</script>
387+
388+
<a href="/path" {@attach useActive()}>simple link</a>
389+
<a href="/path" data-name="route-name" {@attach useActive()}>named link</a>
390+
<button {@attach useActive({ path: '/path' })}>button link</button>
391+
<div {@attach useActive({ name: 'route-name' })}>div link</div>
376392
```
377393

378394
### Programmatic navigation

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@
5050
"types": "./dist/action/index.d.ts",
5151
"import": "./dist/action/index.js"
5252
},
53+
"./attachment": {
54+
"types": "./dist/attachment/index.d.ts",
55+
"import": "./dist/attachment/index.js"
56+
},
5357
"./components": {
5458
"types": "./dist/components/index.d.ts",
5559
"import": "./dist/components/index.js"

src/lib/action/active.action.svelte.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import type { Action } from 'svelte/action';
22

3-
import type { ActiveOptions } from '~/action/action.model.js';
3+
import type { ActiveOptions } from '~/models/action.model.js';
44
import type { RouteName } from '~/models/route.model.js';
55

6-
import { activeStyles, doNameMatch, doPathMatch, ensurePathName, ensureRouter, getOriginalStyle, restoreStyles } from '~/action/action.model.js';
6+
import { activeStyles, doNameMatch, doPathMatch, ensurePathName, ensureRouter, getOriginalStyle, restoreStyles } from '~/models/action.model.js';
77
import { Matcher } from '~/models/index.js';
88
import { getRouter } from '~/router/context.svelte.js';
99

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type { Attachment } from 'svelte/attachments';
2+
3+
import type { ActiveOptions } from '~/models/action.model.js';
4+
import type { RouteName } from '~/models/route.model.js';
5+
6+
import { activeStyles, doNameMatch, doPathMatch, ensurePathName, ensureRouter, getOriginalStyle, restoreStyles } from '~/models/action.model.js';
7+
import { Matcher } from '~/models/index.js';
8+
import { getRouter } from '~/router/context.svelte.js';
9+
10+
export function useActive(options: ActiveOptions): Attachment {
11+
const router = getRouter();
12+
13+
return (element) => {
14+
if (!ensureRouter(element, router)) return;
15+
16+
const _options = $derived(options);
17+
const _path: string | null = $derived(options?.path || element.getAttribute('data-path') || element.getAttribute('href'));
18+
const _name: RouteName | null = $derived(options?.name || element.getAttribute('data-name'));
19+
20+
const caseSensitive = $derived(_options?.caseSensitive ?? router.options?.caseSensitive);
21+
const matchName = $derived(doNameMatch(router.route, _name, { caseSensitive, exact: _options?.exact }));
22+
23+
const location = $derived(router.location?.path);
24+
const matcher = $derived(_path ? new Matcher(_path) : undefined);
25+
const matchPath = $derived(doPathMatch(matcher, _name, location, _options?.exact));
26+
27+
const match = $derived(matchName || matchPath);
28+
29+
const originalStyle = $derived(getOriginalStyle(element, _options?.style));
30+
31+
$effect(() => {
32+
if (match) activeStyles(element, _options);
33+
else restoreStyles(element, originalStyle, _options);
34+
});
35+
36+
$effect(() => {
37+
ensurePathName(element, { path: _path, name: _name });
38+
});
39+
};
40+
}

src/lib/attachment/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './active.attachment.svelte.js';

src/lib/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
export * from './action/index.js';
2+
export * from './attachment/index.js';
13
export * from './components/index.js';
24
export * from './models/index.js';
35
export * from './router/index.js';

src/lib/models/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export type * from './action.model.js';
12
export * from './component.model.js';
23
export * from './error.model.js';
34
export * from './matcher.model.js';
File renamed without changes.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<script lang="ts">
2+
import type { IRouter } from '~/models/router.model.js';
3+
4+
import { useActive } from '~/attachment/active.attachment.svelte.js';
5+
import RouterView from '~/components/RouterView.svelte';
6+
7+
const { router }: { router: IRouter } = $props();
8+
</script>
9+
10+
<RouterView {router}>
11+
<span data-testid="span-error" {@attach useActive()}>error</span>
12+
13+
<a data-testid="anchor-path" href="/home" {@attach useActive()}>path</a>
14+
<a data-testid="anchor-name" href="/other" data-name="home" {@attach useActive()}>name</a>
15+
16+
<span data-testid="span-path" data-path="/home" {@attach useActive()}>path</span>
17+
<span data-testid="span-name" data-name="home" {@attach useActive()}>name</span>
18+
19+
<div data-testid="param-path" data-path="/other" {@attach useActive({ path: '/home' })}></div>
20+
<div data-testid="param-name" data-name="other" {@attach useActive({ name: 'home' })}></div>
21+
22+
<a data-testid="anchor-parent" href="/other" {@attach useActive()}>path-parent</a>
23+
<a data-testid="anchor-child" href="/other/target" {@attach useActive()}>path-child</a>
24+
<a data-testid="anchor-exact-parent" href="/other" {@attach useActive({ exact: true })}>path-exact-parent</a>
25+
26+
<a data-testid="anchor-name-sensitive" href="/home" {@attach useActive({ name: 'Other', caseSensitive: true })}>path</a>
27+
<a data-testid="anchor-name-insensitive" href="/home" {@attach useActive({ name: 'Other' })}>path</a>
28+
29+
<a data-testid="anchor-class-active" href="/home" {@attach useActive({ class: 'active' })}>path</a>
30+
<a data-testid="anchor-class-existing" href="/home" class="some-class" {@attach useActive({ class: 'active' })}>path</a>
31+
32+
<a data-testid="anchor-style-active" href="/home" {@attach useActive({ style: { color: 'red' } })}>path</a>
33+
<a data-testid="anchor-style-existing" href="/home" style="color:blue; font-weight: bold;" {@attach useActive({ style: { color: 'red' } })}>path</a>
34+
</RouterView>

0 commit comments

Comments
 (0)