@spaethtech/svelte-ui 0.21.1-dev.112.9bdc11a → 0.21.1-dev.113.5a2e853

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -148,6 +148,10 @@
148
148
  const pageCount = $derived(Math.max(1, Math.ceil(total / grid.perPage)));
149
149
  // "Settled" = a result came back AND we're not fetching — only then is empty genuinely "No results".
150
150
  const settled = $derived(!!result.current && !result.loading);
151
+ // A fetch error → shown in the empty-state row (styled) instead of "No results" / spinner. A string
152
+ // is used as-is, an Error's `.message`, anything else falls back to the localized `loadFailed`.
153
+ const errorText = (e: unknown): string =>
154
+ typeof e === "string" ? e : e instanceof Error ? e.message : i18n.messages.loadFailed;
151
155
  $effect(() => {
152
156
  grid.loading = result.loading;
153
157
  if (result.current) {
@@ -358,11 +362,13 @@
358
362
  </div>
359
363
  {/if}
360
364
  <div class="grow self-center text-center {subtle}">
361
- {#if settled}
365
+ {#if result.error}
366
+ <span class="[color:var(--ui-color-error)]">{errorText(result.error)}</span>
367
+ {:else if settled}
362
368
  {i18n.messages.noResults}.
363
369
  {:else}
364
370
  <span class="inline-flex items-center gap-2"
365
- ><IconLoading class="w-[1em] h-[1em] animate-spin" /> Loading…</span
371
+ ><IconLoading class="w-[1em] h-[1em] animate-spin" /> {i18n.messages.loading}</span
366
372
  >
367
373
  {/if}
368
374
  </div>
@@ -6,6 +6,12 @@ export interface LocalFetchOptions<T> {
6
6
  caseInsensitive?: boolean;
7
7
  /** Instant for resolving `now()` in the query. Called per fetch. Default: `new Date()`. */
8
8
  now?: () => Date;
9
+ /** True while `source` is still arriving (an async load not yet resolved). The table shows its
10
+ * Loading row instead of "No results". Called per fetch. Default: `false` (data is ready). */
11
+ loading?: () => boolean;
12
+ /** A load failure. Surfaced in the table's empty-state row (styled as an error) instead of
13
+ * "No results"; if any rows are present they still render. Called per fetch. Default: none. */
14
+ error?: () => string | null | undefined;
9
15
  }
10
16
  /**
11
17
  * Build a `FetchFn` that runs entirely in memory over `source`.
@@ -58,6 +58,10 @@ export function localFetch(source, columns, opts = {}) {
58
58
  const total = filtered.length;
59
59
  const start = (state.page - 1) * state.perPage;
60
60
  const rows = filtered.slice(start, start + state.perPage);
61
- return { current: { rows, total }, loading: false, error: undefined };
61
+ return {
62
+ current: { rows, total },
63
+ loading: opts.loading?.() ?? false,
64
+ error: opts.error?.() ?? undefined,
65
+ };
62
66
  };
63
67
  }
package/dist/i18n.d.ts CHANGED
@@ -15,6 +15,10 @@ export type UiMessages = {
15
15
  copyToClipboard: Msg;
16
16
  done: Msg;
17
17
  noResults: Msg;
18
+ /** DataTable empty-state while the source is still arriving. */
19
+ loading: Msg;
20
+ /** DataTable empty-state when the fetch result carries a non-string error. */
21
+ loadFailed: Msg;
18
22
  previous: Msg;
19
23
  next: Msg;
20
24
  first: Msg;
package/dist/i18n.js CHANGED
@@ -16,6 +16,8 @@ export const defaultUiMessages = {
16
16
  copyToClipboard: "Copy to clipboard",
17
17
  done: "Done",
18
18
  noResults: "No results",
19
+ loading: "Loading…",
20
+ loadFailed: "Failed to load",
19
21
  previous: "Previous",
20
22
  next: "Next",
21
23
  first: "First",
@@ -433,6 +433,23 @@ Config-driven, responsive 12-column data table over a `DataGrid`.
433
433
  whole cell). The cell is a flex box, so a plain inline `<a>text</a>` is text-width — the rest of the
434
434
  cell stays an expand/collapse target. Row-click already ignores clicks on `a`/`button`/`input`/…, so
435
435
  a link navigates without toggling expand; don't stretch the anchor with `block`/`w-full`.
436
+ - **Empty-state row (Loading → rows | No results | error)**: with no rows the table renders one
437
+ standard-height row reflecting the fetch result — a **spinner + "Loading…"** while `result.loading`,
438
+ **"No results"** once settled and empty, or the **error** (styled `--ui-color-error`) when
439
+ `result.error` is set (a string as-is, an `Error`'s `.message`, else the localized `loadFailed`). An
440
+ errored result never spins, and the row keeps its height across all three states — so you don't need
441
+ a separate page banner that contradicts a "No results" table. With `localFetch`, drive this by
442
+ passing `loading`/`error` getters (below); a remote `query()` that exposes `.error` gets it for free.
443
+ (`DataGrid.error` is the query-*parse* error surfaced by `<Query>` — a bad filter — which is distinct
444
+ from this fetch/load error.)
445
+ - **`localFetch(rows, columns, opts?)`** options now include **`loading?: () => boolean`** (show the
446
+ Loading row instead of "No results" while the source is still arriving) and **`error?: () => string
447
+ | null | undefined`** (surface a load failure in the empty-state row; rows still render if present).
448
+ State both once beside the data source:
449
+ ```ts
450
+ const fetch = localFetch(() => rows, columns, { getValue: valueOf, loading: () => !loaded, error: () => loadError });
451
+ const grid = createGrid({ columns, getRowId: (r) => r.id, fetch });
452
+ ```
436
453
 
437
454
  ### Query
438
455
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spaethtech/svelte-ui",
3
- "version": "0.21.1-dev.112.9bdc11a",
3
+ "version": "0.21.1-dev.113.5a2e853",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/spaethtech/svelte-ui.git"