Skip to content

Commit 26dba29

Browse files
committed
chore: wrap <a> in Anchor helper component
1 parent 8459546 commit 26dba29

File tree

4 files changed

+94
-65
lines changed

4 files changed

+94
-65
lines changed

src/lib/marks/Area.svelte

Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import { maybeCurve } from '$lib/helpers/curves.js';
3030
import { isValid } from '$lib/helpers/index.js';
3131
import AreaCanvas from './helpers/AreaCanvas.svelte';
32+
import Anchor from './helpers/Anchor.svelte';
3233
3334
import type {
3435
CurveName,
@@ -37,7 +38,6 @@
3738
BaseMarkProps,
3839
ConstantAccessor,
3940
ChannelAccessor,
40-
FacetContext,
4141
ScaledDataRecord,
4242
LinkableMarkProps
4343
} from '../types.js';
@@ -113,32 +113,24 @@
113113
{:else}
114114
<GroupMultiple length={grouped.length}>
115115
{#each grouped as areaData, i (i)}
116-
{#snippet el(datum: ScaledDataRecord)}
117-
{@const title = resolveProp(options.title, datum.datum, '')}
118-
{@const [style, styleClass] = resolveStyles(
119-
plot,
120-
datum,
121-
options,
122-
'fill',
123-
usedScales
124-
)}
125-
<path
126-
class={['svelteplot-area', className, styleClass]}
127-
clip-path={options.clipPath}
128-
d={areaPath(areaData)}
129-
{style}
130-
>{#if title}<title>{title}</title>{/if}</path>
131-
{/snippet}
116+
{@const datum = areaData[0]}
132117
{#if areaData.length > 0}
133-
{#if options.href}
134-
<a
135-
href={resolveProp(options.href, areaData[0].datum, '')}
136-
target={resolveProp(options.target, areaData[0].datum, '_self')}>
137-
{@render el(areaData[0])}
138-
</a>
139-
{:else}
140-
{@render el(areaData[0])}
141-
{/if}
118+
<Anchor {options} {datum}>
119+
{@const title = resolveProp(options.title, datum.datum, '')}
120+
{@const [style, styleClass] = resolveStyles(
121+
plot,
122+
datum,
123+
options,
124+
'fill',
125+
usedScales
126+
)}
127+
<path
128+
class={['svelteplot-area', className, styleClass]}
129+
clip-path={options.clipPath}
130+
d={areaPath(areaData)}
131+
{style}
132+
>{#if title}<title>{title}</title>{/if}</path>
133+
</Anchor>
142134
{/if}
143135
{/each}
144136
</GroupMultiple>

src/lib/marks/Geo.svelte

Lines changed: 27 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
geoType?: 'sphere' | 'graticule';
88
dragRotate: boolean;
99
canvas: boolean;
10-
href: ConstantAccessor<string>;
11-
target: ConstantAccessor<string>;
10+
/**
11+
* simple browser tooltip to be displayed on mouseover
12+
*/
13+
title: ConstantAccessor<string>;
1214
} & BaseMarkProps &
1315
LinkableMarkProps;
1416
</script>
@@ -31,6 +33,7 @@
3133
import GeoCanvas from './helpers/GeoCanvas.svelte';
3234
import { recordize } from '$lib/transforms/recordize.js';
3335
import { GEOJSON_PREFER_STROKE } from '$lib/helpers/index.js';
36+
import Anchor from './helpers/Anchor.svelte';
3437
3538
const { getPlotState } = getContext<PlotContext>('svelteplot');
3639
const plot = $derived(getPlotState());
@@ -68,28 +71,6 @@
6871
channels={['fill', 'stroke', 'opacity', 'fillOpacity', 'strokeOpacity', 'r']}
6972
{...args}>
7073
{#snippet children({ mark, scaledData, usedScales })}
71-
{#snippet el(d)}
72-
{@const title = resolveProp(args.title, d.datum, '')}
73-
{@const geometry = resolveProp(args.geometry, d.datum, d.datum)}
74-
{@const [style, styleClass] = resolveStyles(
75-
plot,
76-
d,
77-
args,
78-
GEOJSON_PREFER_STROKE.has(geometry.type) ? 'stroke' : 'fill',
79-
usedScales
80-
)}
81-
<path
82-
d={path(geometry)}
83-
{style}
84-
class={[styleClass]}
85-
use:addEventHandlers={{
86-
getPlotState,
87-
options: args,
88-
datum: d.datum
89-
}}>
90-
{#if title}<title>{title}</title>{/if}
91-
</path>
92-
{/snippet}
9374
<g
9475
aria-label="geo"
9576
class={['geo', geoType && `geo-${geoType}`, className]}
@@ -99,15 +80,28 @@
9980
{:else}
10081
{#each scaledData as d, i (i)}
10182
{#if d.valid}
102-
{#if options.href}
103-
<a
104-
href={resolveProp(args.href, d.datum, '')}
105-
target={resolveProp(args.target, d.datum, '_self')}>
106-
{@render el(d)}
107-
</a>
108-
{:else}
109-
{@render el(d)}
110-
{/if}
83+
<Anchor {options} datum={d.datum}>
84+
{@const title = resolveProp(args.title, d.datum, '')}
85+
{@const geometry = resolveProp(args.geometry, d.datum, d.datum)}
86+
{@const [style, styleClass] = resolveStyles(
87+
plot,
88+
d,
89+
args,
90+
GEOJSON_PREFER_STROKE.has(geometry.type) ? 'stroke' : 'fill',
91+
usedScales
92+
)}
93+
<path
94+
d={path(geometry)}
95+
{style}
96+
class={[styleClass]}
97+
use:addEventHandlers={{
98+
getPlotState,
99+
options: args,
100+
datum: d.datum
101+
}}>
102+
{#if title}<title>{title}</title>{/if}
103+
</path>
104+
</Anchor>
111105
{/if}
112106
{/each}
113107
{/if}

src/lib/marks/helpers/Anchor.svelte

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<script>
2+
import { resolveProp } from '$lib/helpers/resolve';
3+
4+
let { datum = {}, options = {}, children } = $props();
5+
6+
const href = $derived(resolveProp(options.href, datum, null));
7+
const target = $derived(resolveProp(options.target, datum, null));
8+
const rel = $derived(resolveProp(options.rel, datum, null));
9+
const type = $derived(resolveProp(options.type, datum, null));
10+
const download = $derived(resolveProp(options.download, datum, null));
11+
</script>
12+
13+
{#if href}
14+
<!-- we can't use <a> directly here because Svelte confuses it with the
15+
HTMLAElement which breaks the rendering -->
16+
<svelte:element
17+
this={'a'}
18+
{href}
19+
{target}
20+
{rel}
21+
{type}
22+
{download}
23+
aria-label="link"
24+
xmlns="http://www.w3.org/2000/svg">
25+
{@render children?.()}
26+
</svelte:element>
27+
{:else}
28+
{@render children?.()}
29+
{/if}

src/lib/types.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -634,15 +634,29 @@ export type FacetContext = {
634634

635635
export type LinkableMarkProps = {
636636
/**
637-
* simple browser tooltip to be displayed on mouseover
637+
* if set, the mark element will be wrapped in a <a> link element
638638
*/
639-
title: ConstantAccessor<string>;
639+
href: ConstantAccessor<string>;
640640
/**
641-
* if set, the mark element will be wrapped in a <a> link
642-
* element
641+
* the relationship of the target object to the link object (e.g. "noopener")
642+
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/a#rel
643643
*/
644-
href: ConstantAccessor<string>;
645-
target: ConstantAccessor<'_self' | '_blank' | string>;
644+
rel: ConstantAccessor<string>;
645+
/**
646+
* the link target mime type, e.g. "text/csv"
647+
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/a#type
648+
*/
649+
type: ConstantAccessor<string>;
650+
/**
651+
* the target of the link, e.g. "_blank" or "_self"
652+
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/a#target
653+
*/
654+
target: ConstantAccessor<'_self' | '_blank' | '_parent' | '_top' | string>;
655+
/**
656+
* if set to true, the link will be downloaded instead of navigating to it
657+
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#download
658+
*/
659+
download: ConstantAccessor<boolean>;
646660
};
647661

648662
export type BaseMarkProps = Partial<{

0 commit comments

Comments
 (0)