@poodle64/ui 2026.8.11 → 2026.8.13
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.
- package/README.md +164 -20
- package/dist/components/ui/collection-detail/collection-detail.svelte +169 -0
- package/dist/components/ui/collection-detail/collection-detail.svelte.d.ts +47 -0
- package/dist/components/ui/collection-detail/index.d.ts +3 -0
- package/dist/components/ui/collection-detail/index.js +2 -0
- package/dist/components/ui/document-detail/document-detail.svelte +184 -0
- package/dist/components/ui/document-detail/document-detail.svelte.d.ts +34 -0
- package/dist/components/ui/document-detail/index.d.ts +3 -0
- package/dist/components/ui/document-detail/index.js +2 -0
- package/dist/components/ui/library-browse/document-table.svelte +101 -0
- package/dist/components/ui/library-browse/document-table.svelte.d.ts +10 -0
- package/dist/components/ui/library-browse/facet-rail.svelte +62 -0
- package/dist/components/ui/library-browse/facet-rail.svelte.d.ts +8 -0
- package/dist/components/ui/library-browse/index.d.ts +3 -0
- package/dist/components/ui/library-browse/index.js +2 -0
- package/dist/components/ui/library-browse/library-browse.svelte +235 -0
- package/dist/components/ui/library-browse/library-browse.svelte.d.ts +44 -0
- package/dist/components/ui/library-browse/types.d.ts +101 -0
- package/dist/components/ui/library-browse/types.js +1 -0
- package/dist/components/ui/schema-form/context.d.ts +31 -0
- package/dist/components/ui/schema-form/context.js +10 -0
- package/dist/components/ui/schema-form/data.d.ts +42 -0
- package/dist/components/ui/schema-form/data.js +97 -0
- package/dist/components/ui/schema-form/dispatch.d.ts +35 -0
- package/dist/components/ui/schema-form/dispatch.js +139 -0
- package/dist/components/ui/schema-form/index.d.ts +5 -0
- package/dist/components/ui/schema-form/index.js +5 -0
- package/dist/components/ui/schema-form/schema-form-control.svelte +146 -0
- package/dist/components/ui/schema-form/schema-form-control.svelte.d.ts +7 -0
- package/dist/components/ui/schema-form/schema-form-element.svelte +105 -0
- package/dist/components/ui/schema-form/schema-form-element.svelte.d.ts +7 -0
- package/dist/components/ui/schema-form/schema-form-widget.svelte +169 -0
- package/dist/components/ui/schema-form/schema-form-widget.svelte.d.ts +18 -0
- package/dist/components/ui/schema-form/schema-form.svelte +153 -0
- package/dist/components/ui/schema-form/schema-form.svelte.d.ts +4 -0
- package/dist/components/ui/schema-form/types.d.ts +61 -0
- package/dist/components/ui/schema-form/types.js +21 -0
- package/dist/components/ui/schema-form/widgets/radio-field.svelte +48 -0
- package/dist/components/ui/schema-form/widgets/radio-field.svelte.d.ts +15 -0
- package/dist/components/ui/schema-form/widgets/slider-field.svelte +51 -0
- package/dist/components/ui/schema-form/widgets/slider-field.svelte.d.ts +13 -0
- package/dist/components/ui/schema-form/widgets/tags-field.svelte +98 -0
- package/dist/components/ui/schema-form/widgets/tags-field.svelte.d.ts +12 -0
- package/dist/components/ui/schema-form/widgets/unknown-field.svelte +123 -0
- package/dist/components/ui/schema-form/widgets/unknown-field.svelte.d.ts +20 -0
- package/dist/components/ui/search-results/index.d.ts +3 -0
- package/dist/components/ui/search-results/index.js +2 -0
- package/dist/components/ui/search-results/search-results.svelte +187 -0
- package/dist/components/ui/search-results/search-results.svelte.d.ts +44 -0
- package/package.json +2 -1
- package/registry/component-map.json +52 -3
- package/registry/component-map.md +20 -1
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import TriangleAlert from '@lucide/svelte/icons/triangle-alert';
|
|
3
|
+
import StatusBadge from '../../status-badge/status-badge.svelte';
|
|
4
|
+
import Input from '../../input/input.svelte';
|
|
5
|
+
import Label from '../../label/label.svelte';
|
|
6
|
+
import type { JsonSchema, UnknownReason } from '../types.js';
|
|
7
|
+
import { schemaType } from '../dispatch.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* THE LOUD FALLBACK — the reason this package exists rather than a fourth
|
|
11
|
+
* hand-rolled renderer.
|
|
12
|
+
*
|
|
13
|
+
* The estate's outgoing renderers failed silently in three ways: an
|
|
14
|
+
* unrecognised `widget` hint rendered no input, a config group missing from a
|
|
15
|
+
* hardcoded order rendered nothing at all, and a nested hint was simply
|
|
16
|
+
* inert. Every one of those made the field VANISH, and a form with a missing
|
|
17
|
+
* field looks exactly like a form. Nobody noticed for months.
|
|
18
|
+
*
|
|
19
|
+
* So there is no silent branch anywhere in `<SchemaForm>`: everything the
|
|
20
|
+
* renderer cannot dispatch lands here, and here always renders. It says what
|
|
21
|
+
* it could not do, names the pointer so the fix is a copy-paste, shows the
|
|
22
|
+
* value that would otherwise have been lost, and keeps it editable whenever
|
|
23
|
+
* editing a raw value is safe — which is when the value is a primitive. An
|
|
24
|
+
* object or an array is shown, not edited: a text box over structured data
|
|
25
|
+
* is a data-loss affordance, not a fallback.
|
|
26
|
+
*/
|
|
27
|
+
let {
|
|
28
|
+
label,
|
|
29
|
+
scope,
|
|
30
|
+
path,
|
|
31
|
+
reason,
|
|
32
|
+
detail,
|
|
33
|
+
value,
|
|
34
|
+
schema,
|
|
35
|
+
disabled = false,
|
|
36
|
+
id,
|
|
37
|
+
onchange
|
|
38
|
+
}: {
|
|
39
|
+
label: string;
|
|
40
|
+
/** JSON Pointer this control addressed, or the property's own pointer. */
|
|
41
|
+
scope: string;
|
|
42
|
+
/** Dot path into the value. */
|
|
43
|
+
path: string;
|
|
44
|
+
reason: UnknownReason;
|
|
45
|
+
/** What specifically could not be done, in one sentence. */
|
|
46
|
+
detail: string;
|
|
47
|
+
value: unknown;
|
|
48
|
+
schema?: JsonSchema;
|
|
49
|
+
disabled?: boolean;
|
|
50
|
+
id: string;
|
|
51
|
+
/** Emitted when the raw editor commits a primitive. */
|
|
52
|
+
onchange?: (next: unknown) => void;
|
|
53
|
+
} = $props();
|
|
54
|
+
|
|
55
|
+
// Editing is offered only where a text box can round-trip the value without
|
|
56
|
+
// destroying structure: a primitive, or an absent value under a primitive
|
|
57
|
+
// schema. Anything structured is shown read-only.
|
|
58
|
+
const declared = $derived(schemaType(schema));
|
|
59
|
+
const structural = $derived(
|
|
60
|
+
(value !== null && typeof value === 'object') || declared === 'object' || declared === 'array'
|
|
61
|
+
);
|
|
62
|
+
const editable = $derived(!structural);
|
|
63
|
+
|
|
64
|
+
const raw = $derived(
|
|
65
|
+
value === undefined || value === null
|
|
66
|
+
? ''
|
|
67
|
+
: typeof value === 'object'
|
|
68
|
+
? JSON.stringify(value, null, 2)
|
|
69
|
+
: String(value)
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
/** Coerce the typed text back to the schema's declared type where it can. */
|
|
73
|
+
function commit(text: string) {
|
|
74
|
+
if (declared === 'number' || declared === 'integer') {
|
|
75
|
+
const parsed = text.trim() === '' ? undefined : Number(text);
|
|
76
|
+
onchange?.(parsed !== undefined && Number.isFinite(parsed) ? parsed : text);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (declared === 'boolean') {
|
|
80
|
+
if (text === 'true' || text === 'false') return onchange?.(text === 'true');
|
|
81
|
+
}
|
|
82
|
+
onchange?.(text);
|
|
83
|
+
}
|
|
84
|
+
</script>
|
|
85
|
+
|
|
86
|
+
<div
|
|
87
|
+
role="group"
|
|
88
|
+
aria-label="Unrecognised field: {label}"
|
|
89
|
+
data-schema-form-unknown=""
|
|
90
|
+
data-unknown-reason={reason}
|
|
91
|
+
data-path={path}
|
|
92
|
+
class="border-status-warning/50 bg-status-warning/5 grid gap-2 rounded-lg border border-dashed p-3"
|
|
93
|
+
>
|
|
94
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
95
|
+
<TriangleAlert class="text-status-warning size-4 shrink-0" aria-hidden="true" />
|
|
96
|
+
<span class="text-body font-semibold">{label}</span>
|
|
97
|
+
<StatusBadge status="warning" label="Unrecognised control" />
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<p class="text-muted-foreground text-xs">
|
|
101
|
+
{detail}. Rendered raw and unstyled so the value is not lost, at
|
|
102
|
+
<code class="font-mono">{scope}</code>
|
|
103
|
+
</p>
|
|
104
|
+
|
|
105
|
+
{#if editable}
|
|
106
|
+
<Label for={id} class="text-2xs text-muted-foreground uppercase">Raw value</Label>
|
|
107
|
+
<Input
|
|
108
|
+
{id}
|
|
109
|
+
{disabled}
|
|
110
|
+
value={raw}
|
|
111
|
+
class="font-mono"
|
|
112
|
+
oninput={(event) => commit((event.currentTarget as HTMLInputElement).value)}
|
|
113
|
+
/>
|
|
114
|
+
{:else}
|
|
115
|
+
<pre
|
|
116
|
+
data-schema-form-unknown-readonly=""
|
|
117
|
+
class="bg-surface-1 border-border text-muted-foreground max-h-48 overflow-auto rounded-md border p-2 font-mono text-xs">{raw ||
|
|
118
|
+
'(no value)'}</pre>
|
|
119
|
+
<p class="text-muted-foreground text-xs">
|
|
120
|
+
Structured value: shown, not edited, so nothing is silently rewritten.
|
|
121
|
+
</p>
|
|
122
|
+
{/if}
|
|
123
|
+
</div>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { JsonSchema, UnknownReason } from '../types.js';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
label: string;
|
|
4
|
+
/** JSON Pointer this control addressed, or the property's own pointer. */
|
|
5
|
+
scope: string;
|
|
6
|
+
/** Dot path into the value. */
|
|
7
|
+
path: string;
|
|
8
|
+
reason: UnknownReason;
|
|
9
|
+
/** What specifically could not be done, in one sentence. */
|
|
10
|
+
detail: string;
|
|
11
|
+
value: unknown;
|
|
12
|
+
schema?: JsonSchema;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
id: string;
|
|
15
|
+
/** Emitted when the raw editor commits a primitive. */
|
|
16
|
+
onchange?: (next: unknown) => void;
|
|
17
|
+
};
|
|
18
|
+
declare const UnknownField: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
19
|
+
type UnknownField = ReturnType<typeof UnknownField>;
|
|
20
|
+
export default UnknownField;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SearchResults — a ranked retrieval answer: for each hit, the document's
|
|
4
|
+
* title, the passage that matched (with the matching spans emphasised), a
|
|
5
|
+
* source chip, consumer-mapped state, and a relevance figure.
|
|
6
|
+
*
|
|
7
|
+
* Designed, not extracted (#30): no in-repo view existed to generalise
|
|
8
|
+
* from. The shape follows the documentation-search results pattern — title
|
|
9
|
+
* over highlighted snippet over source — rendered in this package's own
|
|
10
|
+
* vocabulary: the highlight is a tint of the app's accent so every
|
|
11
|
+
* consumer's results wear its own palette; the score is a mono, tabular
|
|
12
|
+
* figure per the machine-value rule; state chips are StatusBadge.
|
|
13
|
+
*
|
|
14
|
+
* Fixed props over plain data: the page owns the query and the fetch, and
|
|
15
|
+
* this only renders the answer. `<ol>` because rank IS the meaning.
|
|
16
|
+
*/
|
|
17
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
18
|
+
import { cn, type WithElementRef } from '../../../utils.js';
|
|
19
|
+
import SearchIcon from '@lucide/svelte/icons/search';
|
|
20
|
+
import Button from '../button/button.svelte';
|
|
21
|
+
import StatusBadge from '../status-badge/status-badge.svelte';
|
|
22
|
+
import EmptyState from '../empty-state/empty-state.svelte';
|
|
23
|
+
import ErrorState from '../error-state/error-state.svelte';
|
|
24
|
+
import LoadingState from '../loading-state/loading-state.svelte';
|
|
25
|
+
import type { LibrarySearchResult, SearchSnippetSegment } from '../library-browse/types.js';
|
|
26
|
+
|
|
27
|
+
let {
|
|
28
|
+
results,
|
|
29
|
+
query = '',
|
|
30
|
+
total,
|
|
31
|
+
loading = false,
|
|
32
|
+
error = null,
|
|
33
|
+
promptTitle = 'Search the catalogue',
|
|
34
|
+
promptDescription = 'Results appear here as you search.',
|
|
35
|
+
noResultsTitle,
|
|
36
|
+
noResultsDescription = 'Try a broader term, or search a different collection.',
|
|
37
|
+
resultHref,
|
|
38
|
+
onOpen,
|
|
39
|
+
onRetry,
|
|
40
|
+
ref = $bindable(null),
|
|
41
|
+
class: className,
|
|
42
|
+
...restProps
|
|
43
|
+
}: WithElementRef<Omit<HTMLAttributes<HTMLDivElement>, 'results'>> & {
|
|
44
|
+
/** `Omit` above: svelte/elements declares a legacy `results` HTML
|
|
45
|
+
* attribute (number) on every element, and without the omission this
|
|
46
|
+
* prop's type silently intersects with it — callers then cannot pass
|
|
47
|
+
* an array at all. Caught by CI's svelte-check on the test call sites. */
|
|
48
|
+
results: LibrarySearchResult[];
|
|
49
|
+
/** The term the page searched for; shown in the count line and empty copy. */
|
|
50
|
+
query?: string;
|
|
51
|
+
/** Total hits when the page truncates; defaults to the rows given. */
|
|
52
|
+
total?: number;
|
|
53
|
+
loading?: boolean;
|
|
54
|
+
error?: string | null;
|
|
55
|
+
/** Copy for the surface before any search has been made. */
|
|
56
|
+
promptTitle?: string;
|
|
57
|
+
promptDescription?: string;
|
|
58
|
+
/** Copy when a search matched nothing; defaults name the query. */
|
|
59
|
+
noResultsTitle?: string;
|
|
60
|
+
noResultsDescription?: string;
|
|
61
|
+
/** The app's own routed link per hit; this package has no router. */
|
|
62
|
+
resultHref?: (result: LibrarySearchResult) => string;
|
|
63
|
+
onOpen?: (result: LibrarySearchResult) => void;
|
|
64
|
+
onRetry?: () => void;
|
|
65
|
+
} = $props();
|
|
66
|
+
|
|
67
|
+
const shownTotal = $derived(total ?? results.length);
|
|
68
|
+
|
|
69
|
+
/** Normalise a snippet: plain text becomes one unhighlighted segment. */
|
|
70
|
+
function segments(snippet: string | SearchSnippetSegment[]): SearchSnippetSegment[] {
|
|
71
|
+
return typeof snippet === 'string' ? [{ text: snippet }] : snippet;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Relevance as a whole percentage; the wire value is [0, 1]. */
|
|
75
|
+
function pct(score: number): number {
|
|
76
|
+
return Math.round(Math.min(1, Math.max(0, score)) * 100);
|
|
77
|
+
}
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<div bind:this={ref} class={cn('flex flex-col gap-3', className)} {...restProps}>
|
|
81
|
+
{#if loading}
|
|
82
|
+
<LoadingState message="Searching…" />
|
|
83
|
+
{:else if error}
|
|
84
|
+
<ErrorState message={error}>
|
|
85
|
+
{#snippet action()}
|
|
86
|
+
{#if onRetry}
|
|
87
|
+
<Button variant="outline" onclick={onRetry}>Retry</Button>
|
|
88
|
+
{/if}
|
|
89
|
+
{/snippet}
|
|
90
|
+
</ErrorState>
|
|
91
|
+
{:else if results.length === 0}
|
|
92
|
+
{#if query}
|
|
93
|
+
<EmptyState
|
|
94
|
+
icon={SearchIcon}
|
|
95
|
+
title={noResultsTitle ?? `No results for “${query}”`}
|
|
96
|
+
description={noResultsDescription}
|
|
97
|
+
/>
|
|
98
|
+
{:else}
|
|
99
|
+
<EmptyState icon={SearchIcon} title={promptTitle} description={promptDescription} />
|
|
100
|
+
{/if}
|
|
101
|
+
{:else}
|
|
102
|
+
<p class="text-muted-foreground text-sm">
|
|
103
|
+
<span class="font-mono tabular-nums">{shownTotal}</span>
|
|
104
|
+
result{shownTotal === 1 ? '' : 's'}{query ? ` for “${query}”` : ''}
|
|
105
|
+
</p>
|
|
106
|
+
|
|
107
|
+
<ol class="divide-border border-border ds-edge bg-card divide-y rounded-lg border">
|
|
108
|
+
{#each results as result (result.id)}
|
|
109
|
+
<li>
|
|
110
|
+
<article class="flex items-start gap-4 px-4 py-3.5">
|
|
111
|
+
<div class="min-w-0 flex-1">
|
|
112
|
+
{#if resultHref}
|
|
113
|
+
<a
|
|
114
|
+
href={resultHref(result)}
|
|
115
|
+
class="font-medium hover:underline"
|
|
116
|
+
onclick={() => onOpen?.(result)}
|
|
117
|
+
>
|
|
118
|
+
{result.title}
|
|
119
|
+
</a>
|
|
120
|
+
{:else if onOpen}
|
|
121
|
+
<button
|
|
122
|
+
type="button"
|
|
123
|
+
class="text-left font-medium hover:underline"
|
|
124
|
+
onclick={() => onOpen(result)}
|
|
125
|
+
>
|
|
126
|
+
{result.title}
|
|
127
|
+
</button>
|
|
128
|
+
{:else}
|
|
129
|
+
<span class="font-medium">{result.title}</span>
|
|
130
|
+
{/if}
|
|
131
|
+
|
|
132
|
+
{#if result.source || result.meta}
|
|
133
|
+
<div class="text-muted-foreground mt-0.5 flex flex-wrap items-center gap-2 text-xs">
|
|
134
|
+
{#if result.source}
|
|
135
|
+
<span class="bg-surface-2 rounded px-1.5 py-0.5 font-medium">
|
|
136
|
+
{result.source}
|
|
137
|
+
</span>
|
|
138
|
+
{/if}
|
|
139
|
+
{#if result.meta}
|
|
140
|
+
<span>{result.meta}</span>
|
|
141
|
+
{/if}
|
|
142
|
+
</div>
|
|
143
|
+
{/if}
|
|
144
|
+
|
|
145
|
+
{#if result.snippet}
|
|
146
|
+
<!-- The matched passage. <mark> carries the semantics; the UA's
|
|
147
|
+
black-on-yellow default is replaced with a tint of the
|
|
148
|
+
app's own accent, so the highlight is skinned by the
|
|
149
|
+
consumer's palette like everything else. -->
|
|
150
|
+
<p class="text-muted-foreground mt-1.5 line-clamp-3 text-sm">
|
|
151
|
+
{#each segments(result.snippet) as segment, i (i)}
|
|
152
|
+
{#if segment.highlight}
|
|
153
|
+
<mark class="bg-primary/15 text-foreground rounded-sm px-0.5 font-medium">
|
|
154
|
+
{segment.text}
|
|
155
|
+
</mark>
|
|
156
|
+
{:else}
|
|
157
|
+
{segment.text}
|
|
158
|
+
{/if}
|
|
159
|
+
{/each}
|
|
160
|
+
</p>
|
|
161
|
+
{/if}
|
|
162
|
+
|
|
163
|
+
{#if result.badges?.length}
|
|
164
|
+
<div class="mt-1.5 flex flex-wrap gap-1">
|
|
165
|
+
<!-- Keyed by index: LibraryBadge carries no id, and two states
|
|
166
|
+
can legitimately share a label. -->
|
|
167
|
+
{#each result.badges as badge, i (i)}
|
|
168
|
+
<StatusBadge status={badge.status} label={badge.label} />
|
|
169
|
+
{/each}
|
|
170
|
+
</div>
|
|
171
|
+
{/if}
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
{#if result.score !== undefined}
|
|
175
|
+
<span
|
|
176
|
+
class="text-muted-foreground mt-0.5 flex-none font-mono text-xs tabular-nums"
|
|
177
|
+
aria-label="Relevance {pct(result.score)} percent"
|
|
178
|
+
>
|
|
179
|
+
{pct(result.score)}%
|
|
180
|
+
</span>
|
|
181
|
+
{/if}
|
|
182
|
+
</article>
|
|
183
|
+
</li>
|
|
184
|
+
{/each}
|
|
185
|
+
</ol>
|
|
186
|
+
{/if}
|
|
187
|
+
</div>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SearchResults — a ranked retrieval answer: for each hit, the document's
|
|
3
|
+
* title, the passage that matched (with the matching spans emphasised), a
|
|
4
|
+
* source chip, consumer-mapped state, and a relevance figure.
|
|
5
|
+
*
|
|
6
|
+
* Designed, not extracted (#30): no in-repo view existed to generalise
|
|
7
|
+
* from. The shape follows the documentation-search results pattern — title
|
|
8
|
+
* over highlighted snippet over source — rendered in this package's own
|
|
9
|
+
* vocabulary: the highlight is a tint of the app's accent so every
|
|
10
|
+
* consumer's results wear its own palette; the score is a mono, tabular
|
|
11
|
+
* figure per the machine-value rule; state chips are StatusBadge.
|
|
12
|
+
*
|
|
13
|
+
* Fixed props over plain data: the page owns the query and the fetch, and
|
|
14
|
+
* this only renders the answer. `<ol>` because rank IS the meaning.
|
|
15
|
+
*/
|
|
16
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
17
|
+
import { type WithElementRef } from '../../../utils.js';
|
|
18
|
+
import type { LibrarySearchResult } from '../library-browse/types.js';
|
|
19
|
+
type $$ComponentProps = WithElementRef<Omit<HTMLAttributes<HTMLDivElement>, 'results'>> & {
|
|
20
|
+
/** `Omit` above: svelte/elements declares a legacy `results` HTML
|
|
21
|
+
* attribute (number) on every element, and without the omission this
|
|
22
|
+
* prop's type silently intersects with it — callers then cannot pass
|
|
23
|
+
* an array at all. Caught by CI's svelte-check on the test call sites. */
|
|
24
|
+
results: LibrarySearchResult[];
|
|
25
|
+
/** The term the page searched for; shown in the count line and empty copy. */
|
|
26
|
+
query?: string;
|
|
27
|
+
/** Total hits when the page truncates; defaults to the rows given. */
|
|
28
|
+
total?: number;
|
|
29
|
+
loading?: boolean;
|
|
30
|
+
error?: string | null;
|
|
31
|
+
/** Copy for the surface before any search has been made. */
|
|
32
|
+
promptTitle?: string;
|
|
33
|
+
promptDescription?: string;
|
|
34
|
+
/** Copy when a search matched nothing; defaults name the query. */
|
|
35
|
+
noResultsTitle?: string;
|
|
36
|
+
noResultsDescription?: string;
|
|
37
|
+
/** The app's own routed link per hit; this package has no router. */
|
|
38
|
+
resultHref?: (result: LibrarySearchResult) => string;
|
|
39
|
+
onOpen?: (result: LibrarySearchResult) => void;
|
|
40
|
+
onRetry?: () => void;
|
|
41
|
+
};
|
|
42
|
+
declare const SearchResults: import("svelte").Component<$$ComponentProps, {}, "ref">;
|
|
43
|
+
type SearchResults = ReturnType<typeof SearchResults>;
|
|
44
|
+
export default SearchResults;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poodle64/ui",
|
|
3
|
-
"version": "2026.8.
|
|
3
|
+
"version": "2026.8.13",
|
|
4
4
|
"description": "Household shared component layer: shadcn-svelte primitives (bits-ui) plus the composed page chrome every app builds its routes from, restyled by each app's @poodle64/design-tokens alias layer. One fix reaches every app.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
+
"@jsonforms/core": "^3.8.0",
|
|
43
44
|
"@lucide/svelte": "^1.25.0",
|
|
44
45
|
"@tanstack/table-core": "^8.21.3",
|
|
45
46
|
"clsx": "^2.1.1",
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"meta": {
|
|
3
3
|
"package": "@poodle64/ui",
|
|
4
|
-
"version": "2026.8.
|
|
4
|
+
"version": "2026.8.13",
|
|
5
5
|
"generatedBy": "scripts/generate-registry.mjs",
|
|
6
6
|
"source": "scripts/situations.json + package source (DO NOT EDIT the outputs by hand)",
|
|
7
|
-
"componentCount":
|
|
8
|
-
"situationCount":
|
|
7
|
+
"componentCount": 54,
|
|
8
|
+
"situationCount": 14
|
|
9
9
|
},
|
|
10
10
|
"situations": [
|
|
11
11
|
{
|
|
@@ -155,6 +155,55 @@
|
|
|
155
155
|
}
|
|
156
156
|
]
|
|
157
157
|
},
|
|
158
|
+
{
|
|
159
|
+
"key": "server-described-form",
|
|
160
|
+
"title": "A form the server described",
|
|
161
|
+
"description": "A config object whose shape arrives at runtime as a JSON Schema plus a JSON Forms UI Schema — the renderer walks both and dispatches to this package's widgets. Anything it cannot dispatch renders flagged, never blank.",
|
|
162
|
+
"components": [
|
|
163
|
+
{
|
|
164
|
+
"name": "SchemaForm",
|
|
165
|
+
"dir": "schema-form",
|
|
166
|
+
"import": "@poodle64/ui/schema-form",
|
|
167
|
+
"props": "schema, value, onChange, uischema?, disabled?, idPrefix?",
|
|
168
|
+
"insteadOf": "a hand-written renderer over a home-grown hint vocabulary, which is how a field goes missing in silence"
|
|
169
|
+
}
|
|
170
|
+
]
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"key": "library-data",
|
|
174
|
+
"title": "Library data the app fetched",
|
|
175
|
+
"description": "The estate's shared document-library surfaces — a faceted catalogue, one collection's detail, one document's detail, a ranked search answer — rendered from plain props the app maps its own API responses into. The package never fetches and never routes: the app owns the query, the offset and every link. Configuration is the schema-driven half (SchemaForm); library data is fixed components because its shape is stable.",
|
|
176
|
+
"components": [
|
|
177
|
+
{
|
|
178
|
+
"name": "LibraryBrowse",
|
|
179
|
+
"dir": "library-browse",
|
|
180
|
+
"import": "@poodle64/ui/library-browse",
|
|
181
|
+
"props": "documents, facets?, query?, total?, offset?, limit?, loading?, error?, onQueryChange?, onFacetChange?, onPageChange?, documentHref?, onOpenDocument?, toolbar?",
|
|
182
|
+
"insteadOf": "a hand-built catalogue page: search box, facet rail, filter chips, results table and pager re-derived per app"
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"name": "CollectionDetail",
|
|
186
|
+
"dir": "collection-detail",
|
|
187
|
+
"import": "@poodle64/ui/collection-detail",
|
|
188
|
+
"props": "collection, stats?, documents?, documentsTotal?, loading?, error?, documentsLoading?, documentsError?, onPageChange?, documentHref?, onOpenDocument?, actions?, children?",
|
|
189
|
+
"insteadOf": "a bespoke collection page assembled from raw Card, a hand-built table and its own stat rows"
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"name": "DocumentDetail",
|
|
193
|
+
"dir": "document-detail",
|
|
194
|
+
"import": "@poodle64/ui/document-detail",
|
|
195
|
+
"props": "document, loading?, error?, collectionHref?, onOpenCollection?, actions?, children?",
|
|
196
|
+
"insteadOf": "a bespoke document page: identity dl, locations, tags and memberships hand-laid-out per app"
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"name": "SearchResults",
|
|
200
|
+
"dir": "search-results",
|
|
201
|
+
"import": "@poodle64/ui/search-results",
|
|
202
|
+
"props": "results, query?, total?, loading?, error?, resultHref?, onOpen?, onRetry?",
|
|
203
|
+
"insteadOf": "a hand-built ranked hit list minting its own highlight and relevance styling"
|
|
204
|
+
}
|
|
205
|
+
]
|
|
206
|
+
},
|
|
158
207
|
{
|
|
159
208
|
"key": "page-frame",
|
|
160
209
|
"title": "The frame every route shares",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @poodle64/ui — situation → component map
|
|
2
2
|
|
|
3
3
|
<!-- GENERATED by scripts/generate-registry.mjs from scripts/situations.json + package source. DO NOT EDIT. -->
|
|
4
|
-
Generated from `@poodle64/ui@2026.8.
|
|
4
|
+
Generated from `@poodle64/ui@2026.8.13`. 54 components, 14 situations.
|
|
5
5
|
|
|
6
6
|
**Read this before writing a `<div>`.** Find the SITUATION you are in below, then compose the component named for it — do not hand-build it from raw `Card` or utility classes. Import is `import { Name } from '<import path>'`. Props marked `?` are optional. This map is the retrieval step the [`frontend-design` skill] makes mandatory; the [CHI 2026 study] measured composing-from-a-registry at 95% design-system compliance against 71% for writing the CSS from a prose style guide.
|
|
7
7
|
|
|
@@ -83,6 +83,25 @@ _The generic titled card every route reaches for — an optional header with ico
|
|
|
83
83
|
| --- | --- | --- | --- |
|
|
84
84
|
| `Panel` | `@poodle64/ui/panel` | `children, title?, subtitle?, icon?, action?, pad?` | raw Card + CardHeader + CardTitle assembled by hand on every route |
|
|
85
85
|
|
|
86
|
+
## A form the server described
|
|
87
|
+
|
|
88
|
+
_A config object whose shape arrives at runtime as a JSON Schema plus a JSON Forms UI Schema — the renderer walks both and dispatches to this package's widgets. Anything it cannot dispatch renders flagged, never blank._
|
|
89
|
+
|
|
90
|
+
| Component | Import | Key props | Reach for it instead of |
|
|
91
|
+
| --- | --- | --- | --- |
|
|
92
|
+
| `SchemaForm` | `@poodle64/ui/schema-form` | `schema, value, onChange, uischema?, disabled?, idPrefix?` | a hand-written renderer over a home-grown hint vocabulary, which is how a field goes missing in silence |
|
|
93
|
+
|
|
94
|
+
## Library data the app fetched
|
|
95
|
+
|
|
96
|
+
_The estate's shared document-library surfaces — a faceted catalogue, one collection's detail, one document's detail, a ranked search answer — rendered from plain props the app maps its own API responses into. The package never fetches and never routes: the app owns the query, the offset and every link. Configuration is the schema-driven half (SchemaForm); library data is fixed components because its shape is stable._
|
|
97
|
+
|
|
98
|
+
| Component | Import | Key props | Reach for it instead of |
|
|
99
|
+
| --- | --- | --- | --- |
|
|
100
|
+
| `LibraryBrowse` | `@poodle64/ui/library-browse` | `documents, facets?, query?, total?, offset?, limit?, loading?, error?, onQueryChange?, onFacetChange?, onPageChange?, documentHref?, onOpenDocument?, toolbar?` | a hand-built catalogue page: search box, facet rail, filter chips, results table and pager re-derived per app |
|
|
101
|
+
| `CollectionDetail` | `@poodle64/ui/collection-detail` | `collection, stats?, documents?, documentsTotal?, loading?, error?, documentsLoading?, documentsError?, onPageChange?, documentHref?, onOpenDocument?, actions?, children?` | a bespoke collection page assembled from raw Card, a hand-built table and its own stat rows |
|
|
102
|
+
| `DocumentDetail` | `@poodle64/ui/document-detail` | `document, loading?, error?, collectionHref?, onOpenCollection?, actions?, children?` | a bespoke document page: identity dl, locations, tags and memberships hand-laid-out per app |
|
|
103
|
+
| `SearchResults` | `@poodle64/ui/search-results` | `results, query?, total?, loading?, error?, resultHref?, onOpen?, onRetry?` | a hand-built ranked hit list minting its own highlight and relevance styling |
|
|
104
|
+
|
|
86
105
|
## The frame every route shares
|
|
87
106
|
|
|
88
107
|
_The standing chrome — the app shell and its nav, the one page-title treatment, the persistent right-hand context column._
|