Skip to content

Commit 9b2a282

Browse files
committed
feat!: Friendlier typing for the ActiveState.aria property
1 parent 6bf8cd0 commit 9b2a282

10 files changed

+35
-281
lines changed

src/lib/Link/Link.svelte

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import type { Hash, RouteStatus } from '$lib/types.js';
1010
import { assertAllowedRoutingMode, joinStyles } from '$lib/utils.js';
1111
import { type Snippet } from 'svelte';
12-
import type { HTMLAnchorAttributes } from 'svelte/elements';
12+
import type { AriaAttributes, HTMLAnchorAttributes } from 'svelte/elements';
1313
1414
type Props = HTMLAnchorAttributes &
1515
ILinkContext & {
@@ -98,10 +98,17 @@
9898
const result = { ...activeState };
9999
result.class ??= linkContext?.activeState?.class;
100100
result.style ??= linkContext?.activeState?.style;
101-
result.aria = {
101+
return result;
102+
});
103+
const calcActiveStateAria = $derived.by(() => {
104+
const result = {} as AriaAttributes;
105+
for (let [k, v] of Object.entries({
102106
...linkContext?.activeState?.aria,
103-
...activeState.aria
104-
};
107+
...activeState?.aria
108+
})) {
109+
// @ts-expect-error TS7053 - Since k is typed as string, the relationship can't be established.
110+
result[`aria-${k}`] = v;
111+
}
105112
return result;
106113
});
107114
const isActive = $derived(isRouteActive(router, activeFor));
@@ -128,7 +135,7 @@
128135
class={[cssClass, (isActive && calcActiveState?.class) || undefined]}
129136
style={isActive ? joinStyles(style, calcActiveState?.style) : style}
130137
onclick={handleClick}
131-
{...(isActive ? calcActiveState?.aria : undefined)}
138+
{...(isActive ? calcActiveStateAria : undefined)}
132139
{...restProps}
133140
>
134141
{@render children?.(location.getState(resolvedHash), router?.routeStatus)}

src/lib/Link/Link.svelte.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,8 @@ function activeStateTests(setup: ReturnType<typeof createRouterTestSetup>) {
259259
activeFor: activeKey,
260260
activeState: {
261261
aria: {
262-
'aria-selected': 'true',
263-
'aria-current': 'page'
262+
selected: 'true',
263+
current: 'page'
264264
}
265265
},
266266
children: content
@@ -296,8 +296,8 @@ function activeStateTests(setup: ReturnType<typeof createRouterTestSetup>) {
296296
activeFor: activeKey,
297297
activeState: {
298298
aria: {
299-
'aria-selected': 'true',
300-
'aria-current': 'page'
299+
selected: 'true',
300+
current: 'page'
301301
}
302302
},
303303
children: content
@@ -761,7 +761,7 @@ function linkContextTests(ru: RoutingUniverse) {
761761
const linkCtx: ILinkContext = {
762762
activeState: {
763763
class: 'context-active',
764-
aria: { 'aria-current': 'page' }
764+
aria: { current: 'page' }
765765
}
766766
};
767767
const activeFor = 'test-route';

src/lib/behaviors/active.svelte.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ describe("activeBehavior", () => {
422422
key: routeKey,
423423
class: "active-class",
424424
style: "color: red;",
425-
aria: { "aria-current": "page", "aria-selected": "true" }
425+
aria: { current: "page", selected: "true" }
426426
};
427427

428428
// Act
@@ -440,7 +440,7 @@ describe("activeBehavior", () => {
440440
const activeState: ActiveState & { key: string } = {
441441
key: "test-route",
442442
class: "active-class",
443-
aria: { "aria-current": "page", "aria-selected": "true" }
443+
aria: { current: "page", selected: "true" }
444444
};
445445

446446
// Act
@@ -464,7 +464,7 @@ describe("activeBehavior", () => {
464464
const activeState: ActiveState & { key: string } = {
465465
key: routeKey,
466466
class: "active-class",
467-
aria: { "aria-current": "page", "aria-selected": "true" }
467+
aria: { current: "page", selected: "true" }
468468
};
469469

470470
// Act
@@ -579,7 +579,7 @@ describe("activeBehavior", () => {
579579

580580
test("Should apply new ARIA attributes whenever 'activeState.aria' changes.", async () => {
581581
// Arrange.
582-
const newAria = { "aria-current": "page" as const, "aria-selected": true };
582+
const newAria = { current: "page" as const, selected: true };
583583
const routeKey = "home";
584584
const routeStatus: Record<string, RouteStatus> = {
585585
[routeKey]: {
@@ -838,7 +838,7 @@ describe("activeBehavior", () => {
838838

839839
test("Should apply new ARIA attributes whenever 'activeState.aria' changes.", () => {
840840
// Arrange.
841-
const newAria = { "aria-current": "page" as const, "aria-selected": true };
841+
const newAria = { current: "page" as const, selected: true };
842842
const routeKey = "home";
843843
const routeStatus = $state<Record<string, RouteStatus>>({
844844
[routeKey]: {

src/lib/behaviors/active.svelte.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export function activeBehavior(
5252
}
5353
if (activeState.aria) {
5454
for (let [attr, value] of Object.entries(activeState.aria)) {
55-
el.setAttribute(attr, value);
55+
el.setAttribute(`aria-${attr}`, (value ?? '').toString());
5656
}
5757
}
5858
return () => {
@@ -62,7 +62,7 @@ export function activeBehavior(
6262
}
6363
if (activeState.aria) {
6464
for (let attr of Object.keys(activeState.aria)) {
65-
el.removeAttribute(attr);
65+
el.removeAttribute(`aria-${attr}`);
6666
}
6767
}
6868
};

src/lib/types.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,16 @@ export type NavigationCancelledEvent = NavigationEvent & {
294294
cause: any;
295295
};
296296

297+
/**
298+
* Friendlier type mapping of ARIA attributes for use in the `ActiveState.aria` property.
299+
*
300+
* Instead of asking the user to write `{ aria: { 'aria-selected': true } }`, they can simply write
301+
* `{ aria: { selected: true } }`.
302+
*/
303+
export type ActiveStateAriaAttributes = {
304+
[K in keyof AriaAttributes as K extends `aria-${infer N}` ? `${N}` : never]?: AriaAttributes[K];
305+
}
306+
297307
/**
298308
* Defines the possible settings that can be set in `Link` components to control when they are considered active and
299309
* how they look like when active.
@@ -329,7 +339,7 @@ export type ActiveState = {
329339
*
330340
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-current)
331341
*/
332-
aria?: AriaAttributes;
342+
aria?: ActiveStateAriaAttributes;
333343
}
334344

335345
/**

src/testing/LinkContextSpy.svelte.d.ts

Lines changed: 0 additions & 7 deletions
This file was deleted.

src/testing/TestActiveBehavior.svelte.d.ts

Lines changed: 0 additions & 10 deletions
This file was deleted.

src/testing/TestLinkContextWithContextSpy.svelte.d.ts

Lines changed: 0 additions & 7 deletions
This file was deleted.

src/testing/TestRouteWithRouter.svelte.d.ts

Lines changed: 0 additions & 16 deletions
This file was deleted.

0 commit comments

Comments
 (0)