Skip to content

Commit 7b081bb

Browse files
committed
2 parents f53efe6 + 8a23f6b commit 7b081bb

File tree

5 files changed

+41
-13
lines changed

5 files changed

+41
-13
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'svelte-ux': patch
3+
---
4+
5+
Added new `IconInput` and `IconData` types to enable inclusive & seamless passing of icon arguments between components. Also provides a `asIconData` utility function for type-safe conversion.
6+
Fixed type errors for Button & TextField's use of Icon data.

packages/svelte-ux/src/lib/components/Button.svelte

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@
1010
import type { TailwindColors } from '$lib/types';
1111
import { getComponentTheme } from './theme';
1212
import { getButtonGroup } from './ButtonGroup.svelte';
13+
import { asIconData, type IconInput } from '$lib/utils/icons';
1314
1415
export let type: 'button' | 'submit' | 'reset' = 'button';
1516
export let href: string | undefined = undefined;
1617
export let target: string | undefined = undefined;
1718
export let fullWidth: boolean = false;
18-
export let icon: ComponentProps<Icon>['data'] | ComponentProps<Icon> | undefined = undefined;
19+
export let icon: IconInput = undefined;
1920
export let iconOnly = icon !== undefined && $$slots.default !== true;
2021
export let actions: Actions<HTMLAnchorElement | HTMLButtonElement> | undefined = undefined;
2122
@@ -261,7 +262,7 @@
261262
<span in:slide={{ axis: 'x', duration: 200 }}>
262263
{#if typeof icon === 'string' || 'icon' in icon}
263264
<!-- font path/url/etc or font-awesome IconDefinition -->
264-
<Icon data={icon} class={cls('pointer-events-none', theme.icon, classes.icon)} />
265+
<Icon data={asIconData(icon)} class={cls('pointer-events-none', theme.icon, classes.icon)} />
265266
{:else}
266267
<Icon class={cls('pointer-events-none', theme.icon, classes.icon)} {...icon} />
267268
{/if}

packages/svelte-ux/src/lib/components/Icon.svelte

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
export let height = size;
1515
export let viewBox = '0 0 24 24';
1616
export let path: string | string[] = '';
17-
export let data: IconDefinition | string | undefined = undefined;
17+
export let data: IconDefinition | string | null | undefined = undefined;
1818
export let svg: string | undefined = undefined;
1919
export let svgUrl: string | undefined = undefined;
2020
@@ -31,7 +31,7 @@
3131
} = {};
3232
const theme = getComponentTheme('Icon');
3333
34-
$: if (typeof data === 'object' && 'icon' in data) {
34+
$: if (typeof data === 'object' && data && 'icon' in data) {
3535
// Font Awesome
3636
const [_width, _height, _ligatures, _unicode, _path] = data.icon;
3737
viewBox = `0 0 ${_width} ${_height}`;
@@ -59,15 +59,18 @@
5959
$: if (svgUrl) {
6060
let promise;
6161
if (cache.has(svgUrl)) {
62-
cache.get(svgUrl).then((text) => (svg = text));
62+
cache.get(svgUrl)?.then((text) => (svg = text));
6363
} else {
6464
promise = fetch(svgUrl)
6565
.then((resp) => resp.text())
66-
.catch((e) => {
66+
.catch(() => {
6767
// Failed request, remove promise so fetched again
68-
cache.delete(svgUrl);
68+
if (svgUrl && typeof(svgUrl) === "string") {
69+
cache.delete(svgUrl);
70+
}
6971
// TODO: Consider showing error icon
7072
// throw e;
73+
return "";
7174
});
7275
cache.set(svgUrl, promise);
7376
promise.then((text) => {

packages/svelte-ux/src/lib/components/TextField.svelte

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@
1414
import Button from './Button.svelte';
1515
import Icon from './Icon.svelte';
1616
import Input from './Input.svelte';
17+
import { type IconInput, asIconData } from '$lib/utils/icons';
1718
1819
type InputValue = string | number;
1920
2021
const dispatch = createEventDispatcher<{
2122
clear: null;
22-
change: { value: typeof value; inputValue: InputValue; operator: string };
23+
change: { value: typeof value; inputValue: InputValue; operator?: string };
2324
}>();
2425
2526
export let name: string | undefined = undefined;
@@ -45,8 +46,8 @@
4546
export let base = false;
4647
export let rounded = false;
4748
export let dense = false;
48-
export let icon: string | null = null;
49-
export let iconRight: string | null = null;
49+
export let icon: IconInput = null;
50+
export let iconRight: IconInput = null;
5051
export let align: 'left' | 'center' | 'right' = 'left';
5152
export let autofocus: boolean | Parameters<typeof autoFocus>[1] = false;
5253
// TODO: Find way to conditionally set type based on `multiline` value
@@ -176,7 +177,7 @@
176177
$: hasInputValue = inputValue != null && inputValue !== '';
177178
$: hasInsetLabel = ['inset', 'float'].includes(labelPlacement) && label !== '';
178179
179-
$: hasPrepend = $$slots.prepend || icon != null;
180+
$: hasPrepend = $$slots.prepend || !!icon;
180181
$: hasAppend =
181182
$$slots.append || iconRight != null || clearable || error || operators || type === 'password';
182183
$: hasPrefix = $$slots.prefix || type === 'currency';
@@ -247,7 +248,7 @@
247248
<slot name="prepend" />
248249
{#if icon}
249250
<span class="mr-3">
250-
<Icon path={icon} class="text-black/50" />
251+
<Icon data={asIconData(icon)} class="text-black/50" />
251252
</span>
252253
{/if}
253254
</div>
@@ -417,7 +418,7 @@
417418
{#if error}
418419
<Icon path={mdiInformationOutline} class="text-red-500" />
419420
{:else if iconRight}
420-
<Icon path={iconRight} class="text-black/50" />
421+
<Icon data={asIconData(iconRight)} class="text-black/50" />
421422
{/if}
422423
</div>
423424
{/if}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
import type { ComponentProps } from "svelte";
3+
import type { default as Icon } from "$lib/components/Icon.svelte";
4+
import { isLiteralObject } from "./object";
5+
6+
export type IconInput = ComponentProps<Icon>['data'] | ComponentProps<Icon>;
7+
export type IconData = ComponentProps<Icon>['data'];
8+
9+
export function asIconData(v: IconInput): IconData {
10+
return isIconComponentProps(v) ? v.data : v;
11+
}
12+
13+
function isIconComponentProps(v: any): v is ComponentProps<Icon> {
14+
// `iconName` is a required property of `IconDefinition`, the only other object that `IconInput` supports.
15+
// If it is undefined, then only ComponentProps<Icon> is viable.
16+
return isLiteralObject(v) && typeof(v['iconName']) === "undefined";
17+
}

0 commit comments

Comments
 (0)