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",
"url": "https://gitea.auvem.com/svelte-toolkit/sui.git"
},
"version": "1.2.0",
"version": "1.3.1",
"scripts": {
"dev": "vite dev",
"build": "vite build && pnpm run prepack",
+8 -9
View File
@@ -50,8 +50,7 @@
const options: ComboboxOption[] = countries.map((country) => ({
value: country.isoCode,
label: `${country.name} (+${country.phonecode})`,
preview: `+${country.phonecode}`,
icon: renderIcon
preview: `+${country.phonecode}`
}));
let phonecode: string = $derived.by(() => {
@@ -118,12 +117,6 @@
});
</script>
{#snippet renderIcon(item: ComboboxOption)}
{#if countrycodeMap[item.value]?.flag}
{countrycodeMap[item.value].flag}
{/if}
{/snippet}
<div class={classValue}>
{#if label}
<Label for={id}>{label}</Label>
@@ -149,7 +142,13 @@
countriesValid = e.detail.valid;
}}
invalidMessage={null}
/>
>
{#snippet iconRender(opt)}
{#if countrycodeMap[opt.value]?.flag}
{countrycodeMap[opt.value].flag}
{/if}
{/snippet}
</Combobox>
</div>
<div class="w-full">
+9 -14
View File
@@ -23,22 +23,17 @@
activeIndex?: number;
/** Callback fired when the active tab changes */
onchange?: (event: { index: number; tab: TabPage }) => void;
/** Applies layout padding to the tab header (default: false) */
padHeader?: boolean;
/** Applies layout padding to the content areas (default: false) */
padContent?: 'padding' | 'margin' | 'none';
/**
* Controls padding of content areas. True applies padding to content
* and header, false applies no padding (default), and 'content' and
* 'header' apply padding to their respective areas only.
*/
padded?: boolean | 'content' | 'header';
/** Additional classes applied to the outer container */
class?: ClassValue | null;
}
let {
pages,
activeIndex = 0,
onchange,
padHeader = false,
padContent = 'none',
class: classValue
}: Props = $props();
let { pages, activeIndex = 0, onchange, padded = false, class: classValue }: Props = $props();
let primaryContainerEl: HTMLDivElement;
let tabContainerEl: HTMLDivElement;
@@ -109,7 +104,7 @@
bind:this={tabContainerEl}
class={[
'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)}
@@ -144,7 +139,7 @@
{#key activeIndex}
<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 }}
out:flyX={{ direction: activeIndex > prevIndex ? -1 : 1, duration: 180 }}
onoutrostart={lockHeight}
+30 -6
View File
@@ -3,7 +3,8 @@
*/
export interface GraphError {
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 */
@@ -15,14 +16,22 @@ export type RawError = ErrorMessage | Error | string | GraphError[];
* @returns true if the error is a RawError, false otherwise
*/
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 (
error instanceof ErrorMessage ||
error instanceof Error ||
typeof error === 'string' ||
(Array.isArray(error) &&
error.every(
(e) => typeof e.message === 'string' && (e.path === undefined || Array.isArray(e.path))
))
(Array.isArray(error) && error.every(isGraphError))
);
};
@@ -73,6 +82,21 @@ export const checkGraphResponse = (
export class ErrorMessage {
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.
* @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) => {
const messageString = e.message || 'Unknown error';
if (e.path && e.path.length > 0) {
return `"${messageString}" at ${e.path.join('.')}`;
return `"${messageString}" at ${ErrorMessage.formatGraphPath(e.path)}`;
}
return messageString;
});
+1 -1
View File
@@ -448,7 +448,7 @@
<p class="title">Tabs</p>
<Tabs
padded={true}
padded
pages={[
{
title: 'Dashboard',