5 Commits

Author SHA1 Message Date
end 5461061e7e 1.3.1 2026-07-14 18:01:12 -07:00
end 39e1f9e9ad fix(error): GraphQL-aligned GraphError type
Expand GraphError.path to accept strings and numbers for field names
and list indexes, rather than strings only. Constrain isRawError to
check this assumption. Add custom path formatter to use dot notation for
fields and brackets for indexes.
2026-07-14 18:01:03 -07:00
end d75b8333e5 1.3.0 2026-05-06 19:10:58 -07:00
end a2097567a0 improve tab padding controls 2026-05-06 19:10:48 -07:00
end 7386bff7be fix PhoneInput combobox icon override 2026-05-06 19:06:41 -07:00
5 changed files with 49 additions and 31 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
"type": "git", "type": "git",
"url": "https://gitea.auvem.com/svelte-toolkit/sui.git" "url": "https://gitea.auvem.com/svelte-toolkit/sui.git"
}, },
"version": "1.2.0", "version": "1.3.1",
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",
"build": "vite build && pnpm run prepack", "build": "vite build && pnpm run prepack",
+8 -9
View File
@@ -50,8 +50,7 @@
const options: ComboboxOption[] = countries.map((country) => ({ const options: ComboboxOption[] = countries.map((country) => ({
value: country.isoCode, value: country.isoCode,
label: `${country.name} (+${country.phonecode})`, label: `${country.name} (+${country.phonecode})`,
preview: `+${country.phonecode}`, preview: `+${country.phonecode}`
icon: renderIcon
})); }));
let phonecode: string = $derived.by(() => { let phonecode: string = $derived.by(() => {
@@ -118,12 +117,6 @@
}); });
</script> </script>
{#snippet renderIcon(item: ComboboxOption)}
{#if countrycodeMap[item.value]?.flag}
{countrycodeMap[item.value].flag}
{/if}
{/snippet}
<div class={classValue}> <div class={classValue}>
{#if label} {#if label}
<Label for={id}>{label}</Label> <Label for={id}>{label}</Label>
@@ -149,7 +142,13 @@
countriesValid = e.detail.valid; countriesValid = e.detail.valid;
}} }}
invalidMessage={null} invalidMessage={null}
/> >
{#snippet iconRender(opt)}
{#if countrycodeMap[opt.value]?.flag}
{countrycodeMap[opt.value].flag}
{/if}
{/snippet}
</Combobox>
</div> </div>
<div class="w-full"> <div class="w-full">
+9 -14
View File
@@ -23,22 +23,17 @@
activeIndex?: number; activeIndex?: number;
/** Callback fired when the active tab changes */ /** Callback fired when the active tab changes */
onchange?: (event: { index: number; tab: TabPage }) => void; onchange?: (event: { index: number; tab: TabPage }) => void;
/** Applies layout padding to the tab header (default: false) */ /**
padHeader?: boolean; * Controls padding of content areas. True applies padding to content
/** Applies layout padding to the content areas (default: false) */ * and header, false applies no padding (default), and 'content' and
padContent?: 'padding' | 'margin' | 'none'; * 'header' apply padding to their respective areas only.
*/
padded?: boolean | 'content' | 'header';
/** Additional classes applied to the outer container */ /** Additional classes applied to the outer container */
class?: ClassValue | null; class?: ClassValue | null;
} }
let { let { pages, activeIndex = 0, onchange, padded = false, class: classValue }: Props = $props();
pages,
activeIndex = 0,
onchange,
padHeader = false,
padContent = 'none',
class: classValue
}: Props = $props();
let primaryContainerEl: HTMLDivElement; let primaryContainerEl: HTMLDivElement;
let tabContainerEl: HTMLDivElement; let tabContainerEl: HTMLDivElement;
@@ -109,7 +104,7 @@
bind:this={tabContainerEl} bind:this={tabContainerEl}
class={[ class={[
'border-sui-text/15 relative mb-4 flex items-center gap-5 border-b-2', 'border-sui-text/15 relative mb-4 flex items-center gap-5 border-b-2',
padHeader && 'px-layout' padded === true || padded === 'header' ? 'px-layout' : ''
]} ]}
> >
{#each pages as page, i (page.title)} {#each pages as page, i (page.title)}
@@ -144,7 +139,7 @@
{#key activeIndex} {#key activeIndex}
<div <div
class={[padContent === 'padding' && 'px-layout', padContent === 'margin' && 'mx-layout']} class={[padded === true || padded === 'content' ? 'px-layout' : '']}
in:flyX={{ direction: activeIndex > prevIndex ? 1 : -1, duration: 180, delay: 181 }} in:flyX={{ direction: activeIndex > prevIndex ? 1 : -1, duration: 180, delay: 181 }}
out:flyX={{ direction: activeIndex > prevIndex ? -1 : 1, duration: 180 }} out:flyX={{ direction: activeIndex > prevIndex ? -1 : 1, duration: 180 }}
onoutrostart={lockHeight} onoutrostart={lockHeight}
+30 -6
View File
@@ -3,7 +3,8 @@
*/ */
export interface GraphError { export interface GraphError {
message: string; message: string;
path?: string[]; /** GraphQL response path - field names and list indexes. */
path?: ReadonlyArray<string | number>;
} }
/** RawError is an error that can be converted to a string by ErrorMessage */ /** RawError is an error that can be converted to a string by ErrorMessage */
@@ -15,14 +16,22 @@ export type RawError = ErrorMessage | Error | string | GraphError[];
* @returns true if the error is a RawError, false otherwise * @returns true if the error is a RawError, false otherwise
*/ */
export const isRawError = (error: unknown): error is RawError => { export const isRawError = (error: unknown): error is RawError => {
const isGraphError = (entry: unknown): entry is GraphError => {
if (!entry || typeof entry !== 'object') return false;
const candidate = entry as { message?: unknown; path?: unknown };
if (typeof candidate.message !== 'string') return false;
if (candidate.path === undefined) return true;
return (
Array.isArray(candidate.path) &&
candidate.path.every((p) => typeof p === 'string' || typeof p === 'number')
);
};
return ( return (
error instanceof ErrorMessage || error instanceof ErrorMessage ||
error instanceof Error || error instanceof Error ||
typeof error === 'string' || typeof error === 'string' ||
(Array.isArray(error) && (Array.isArray(error) && error.every(isGraphError))
error.every(
(e) => typeof e.message === 'string' && (e.path === undefined || Array.isArray(e.path))
))
); );
}; };
@@ -73,6 +82,21 @@ export const checkGraphResponse = (
export class ErrorMessage { export class ErrorMessage {
private _lines: string[] = []; private _lines: string[] = [];
/** formats a GraphQL path using dot notation for fields and brackets for indexes */
private static formatGraphPath(path: ReadonlyArray<string | number>): string {
let formatted = '';
for (const segment of path) {
if (typeof segment === 'number') {
formatted += `[${segment}]`;
} else if (formatted.length === 0) {
formatted = segment;
} else {
formatted += `.${segment}`;
}
}
return formatted;
}
/** /**
* Always creates a new ErrorMessage instance, even if there are no errors. * Always creates a new ErrorMessage instance, even if there are no errors.
* @param errors The raw errors to convert and store, or null/undefined for no error. * @param errors The raw errors to convert and store, or null/undefined for no error.
@@ -136,7 +160,7 @@ export class ErrorMessage {
errorLines = raw.map((e) => { errorLines = raw.map((e) => {
const messageString = e.message || 'Unknown error'; const messageString = e.message || 'Unknown error';
if (e.path && e.path.length > 0) { if (e.path && e.path.length > 0) {
return `"${messageString}" at ${e.path.join('.')}`; return `"${messageString}" at ${ErrorMessage.formatGraphPath(e.path)}`;
} }
return messageString; return messageString;
}); });
+1 -1
View File
@@ -448,7 +448,7 @@
<p class="title">Tabs</p> <p class="title">Tabs</p>
<Tabs <Tabs
padded={true} padded
pages={[ pages={[
{ {
title: 'Dashboard', title: 'Dashboard',