From 84ea64b124484e552df52dfdb7493ab0456e61c7 Mon Sep 17 00:00:00 2001 From: willnationsdev Date: Sat, 11 Nov 2023 15:46:45 -0600 Subject: [PATCH] Fix type errors for Button's use of Icon data. --- .changeset/slimy-starfishes-report.md | 6 ++++++ .../svelte-ux/src/lib/components/Button.svelte | 5 +++-- .../svelte-ux/src/lib/components/Icon.svelte | 13 ++++++++----- .../src/lib/components/TextField.svelte | 13 +++++++------ packages/svelte-ux/src/lib/utils/icons.ts | 17 +++++++++++++++++ 5 files changed, 41 insertions(+), 13 deletions(-) create mode 100644 .changeset/slimy-starfishes-report.md create mode 100644 packages/svelte-ux/src/lib/utils/icons.ts diff --git a/.changeset/slimy-starfishes-report.md b/.changeset/slimy-starfishes-report.md new file mode 100644 index 000000000..b691f6652 --- /dev/null +++ b/.changeset/slimy-starfishes-report.md @@ -0,0 +1,6 @@ +--- +'svelte-ux': patch +--- + +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. +Fixed type errors for Button & TextField's use of Icon data. \ No newline at end of file diff --git a/packages/svelte-ux/src/lib/components/Button.svelte b/packages/svelte-ux/src/lib/components/Button.svelte index 5ec447cfc..5c53299f7 100644 --- a/packages/svelte-ux/src/lib/components/Button.svelte +++ b/packages/svelte-ux/src/lib/components/Button.svelte @@ -10,12 +10,13 @@ import type { TailwindColors } from '$lib/types'; import { getComponentTheme } from './theme'; import { getButtonGroup } from './ButtonGroup.svelte'; + import { asIconData, type IconInput } from '$lib/utils/icons'; export let type: 'button' | 'submit' | 'reset' = 'button'; export let href: string | undefined = undefined; export let target: string | undefined = undefined; export let fullWidth: boolean = false; - export let icon: ComponentProps['data'] | ComponentProps | undefined = undefined; + export let icon: IconInput = undefined; export let iconOnly = icon !== undefined && $$slots.default !== true; export let actions: Actions | undefined = undefined; @@ -261,7 +262,7 @@ {#if typeof icon === 'string' || 'icon' in icon} - + {:else} {/if} diff --git a/packages/svelte-ux/src/lib/components/Icon.svelte b/packages/svelte-ux/src/lib/components/Icon.svelte index 39538dcfc..c29608ee2 100644 --- a/packages/svelte-ux/src/lib/components/Icon.svelte +++ b/packages/svelte-ux/src/lib/components/Icon.svelte @@ -14,7 +14,7 @@ export let height = size; export let viewBox = '0 0 24 24'; export let path: string | string[] = ''; - export let data: IconDefinition | string | undefined = undefined; + export let data: IconDefinition | string | null | undefined = undefined; export let svg: string | undefined = undefined; export let svgUrl: string | undefined = undefined; @@ -31,7 +31,7 @@ } = {}; const theme = getComponentTheme('Icon'); - $: if (typeof data === 'object' && 'icon' in data) { + $: if (typeof data === 'object' && data && 'icon' in data) { // Font Awesome const [_width, _height, _ligatures, _unicode, _path] = data.icon; viewBox = `0 0 ${_width} ${_height}`; @@ -59,15 +59,18 @@ $: if (svgUrl) { let promise; if (cache.has(svgUrl)) { - cache.get(svgUrl).then((text) => (svg = text)); + cache.get(svgUrl)?.then((text) => (svg = text)); } else { promise = fetch(svgUrl) .then((resp) => resp.text()) - .catch((e) => { + .catch(() => { // Failed request, remove promise so fetched again - cache.delete(svgUrl); + if (svgUrl && typeof(svgUrl) === "string") { + cache.delete(svgUrl); + } // TODO: Consider showing error icon // throw e; + return ""; }); cache.set(svgUrl, promise); promise.then((text) => { diff --git a/packages/svelte-ux/src/lib/components/TextField.svelte b/packages/svelte-ux/src/lib/components/TextField.svelte index 92ed6184d..e4c5cb8ff 100644 --- a/packages/svelte-ux/src/lib/components/TextField.svelte +++ b/packages/svelte-ux/src/lib/components/TextField.svelte @@ -14,12 +14,13 @@ import Button from './Button.svelte'; import Icon from './Icon.svelte'; import Input from './Input.svelte'; + import { type IconInput, asIconData } from '$lib/utils/icons'; type InputValue = string | number; const dispatch = createEventDispatcher<{ clear: null; - change: { value: typeof value; inputValue: InputValue; operator: string }; + change: { value: typeof value; inputValue: InputValue; operator?: string }; }>(); export let name: string | undefined = undefined; @@ -45,8 +46,8 @@ export let base = false; export let rounded = false; export let dense = false; - export let icon: string | null = null; - export let iconRight: string | null = null; + export let icon: IconInput = null; + export let iconRight: IconInput = null; export let align: 'left' | 'center' | 'right' = 'left'; export let autofocus: boolean | Parameters[1] = false; // TODO: Find way to conditionally set type based on `multiline` value @@ -176,7 +177,7 @@ $: hasInputValue = inputValue != null && inputValue !== ''; $: hasInsetLabel = ['inset', 'float'].includes(labelPlacement) && label !== ''; - $: hasPrepend = $$slots.prepend || icon != null; + $: hasPrepend = $$slots.prepend || !!icon; $: hasAppend = $$slots.append || iconRight != null || clearable || error || operators || type === 'password'; $: hasPrefix = $$slots.prefix || type === 'currency'; @@ -247,7 +248,7 @@ {#if icon} - + {/if} @@ -417,7 +418,7 @@ {#if error} {:else if iconRight} - + {/if} {/if} diff --git a/packages/svelte-ux/src/lib/utils/icons.ts b/packages/svelte-ux/src/lib/utils/icons.ts new file mode 100644 index 000000000..a862ed49b --- /dev/null +++ b/packages/svelte-ux/src/lib/utils/icons.ts @@ -0,0 +1,17 @@ + +import type { ComponentProps } from "svelte"; +import type { default as Icon } from "$lib/components/Icon.svelte"; +import { isLiteralObject } from "./object"; + +export type IconInput = ComponentProps['data'] | ComponentProps; +export type IconData = ComponentProps['data']; + +export function asIconData(v: IconInput): IconData { + return isIconComponentProps(v) ? v.data : v; +} + +function isIconComponentProps(v: any): v is ComponentProps { + // `iconName` is a required property of `IconDefinition`, the only other object that `IconInput` supports. + // If it is undefined, then only ComponentProps is viable. + return isLiteralObject(v) && typeof(v['iconName']) === "undefined"; +}