@aiaiai-pt/design-system 0.32.0 → 0.34.0
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/components/SectionNavigation.svelte +185 -0
- package/components/SectionNavigation.svelte.d.ts +60 -0
- package/components/ServiceNavigation.svelte +38 -14
- package/components/SiteHeader.svelte +260 -11
- package/components/index.js +1 -0
- package/components/renderer/ResultsChartWidget.svelte +77 -0
- package/components/renderer/ResultsChartWidget.svelte.d.ts +4 -0
- package/components/renderer/StatGridWidget.svelte +33 -0
- package/components/renderer/StatGridWidget.svelte.d.ts +4 -0
- package/components/renderer/aggregate.d.ts +34 -0
- package/components/renderer/aggregate.ts +72 -0
- package/components/renderer/data-provider.d.ts +82 -0
- package/components/renderer/data-provider.ts +162 -0
- package/components/renderer/dispatch.d.ts +63 -0
- package/components/renderer/dispatch.ts +99 -0
- package/components/renderer/registry.d.ts +48 -0
- package/components/renderer/registry.ts +99 -0
- package/components/renderer/resolve-data.d.ts +109 -0
- package/components/renderer/resolve-data.ts +619 -0
- package/components/renderer/types.d.ts +164 -0
- package/components/renderer/types.ts +250 -0
- package/components/renderer/vote.d.ts +121 -0
- package/components/renderer/vote.ts +253 -0
- package/package.json +15 -3
- package/tokens/components.css +29 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DS renderer widget registry — the extensible dispatcher layer (#492).
|
|
3
|
+
*
|
|
4
|
+
* Ships a BASE registry with the two clean DS widgets (stat-grid + results-chart)
|
|
5
|
+
* and exposes a `registerWidget` hook so host apps (the portal, the admin) can
|
|
6
|
+
* append their own coupled widgets at startup without modifying this module
|
|
7
|
+
* (open/closed principle, JSONForms model).
|
|
8
|
+
*
|
|
9
|
+
* `dispatch.ts` (the tester/priority core) is the single dispatcher both the DS
|
|
10
|
+
* base registry and host-extended registries share; the registry is generic over
|
|
11
|
+
* the payload so `selectEntry` stays component-free and unit-testable.
|
|
12
|
+
*
|
|
13
|
+
* TH-08 preserved: `resolveWidget` returns the matched entry's known-good `key`
|
|
14
|
+
* literal — the operator's raw block `type`/`binding.kind` is never the key.
|
|
15
|
+
*/
|
|
16
|
+
import type { Component } from "svelte";
|
|
17
|
+
import { type Match, type RegistryEntry } from "./dispatch";
|
|
18
|
+
import type { Block, OntologySchema, WidgetKind, WidgetProps } from "./types";
|
|
19
|
+
export type WidgetComponent = Component<WidgetProps>;
|
|
20
|
+
/**
|
|
21
|
+
* A TYPE-specific widget over a shared kind (JSONForms model): the authoring
|
|
22
|
+
* `type` hint ranks it ABOVE the kind-generic widget (score 20 > 10) for the
|
|
23
|
+
* same binding. `type` is UNTRUSTED — it only influences ranking; the resolved
|
|
24
|
+
* key is the entry literal (TH-08).
|
|
25
|
+
*/
|
|
26
|
+
export declare function byTypeOnKind(key: string, kind: WidgetKind, component: unknown): RegistryEntry<WidgetComponent>;
|
|
27
|
+
/** A widget whose tester is the generic "binding.kind === K" check (score 10). */
|
|
28
|
+
export declare function byKind(key: string, kind: WidgetKind, component: unknown): RegistryEntry<WidgetComponent>;
|
|
29
|
+
/**
|
|
30
|
+
* Register a widget in the host-extensible registry. Call at app startup
|
|
31
|
+
* (before any `resolveWidget` calls) to append host-specific widgets. Each
|
|
32
|
+
* entry is appended in call order; ties within the same score resolve to the
|
|
33
|
+
* first-registered entry (stable, deterministic — base entries win ties).
|
|
34
|
+
*
|
|
35
|
+
* Signature mirrors `byKind` / `byTypeOnKind` so the caller writes:
|
|
36
|
+
* `registerWidget(byKind("entity-list", "list", EntityListWidget))`
|
|
37
|
+
*
|
|
38
|
+
* @param entry - A registry entry built with `byKind`, `byTypeOnKind`, or a
|
|
39
|
+
* hand-crafted `{ key, payload, tester }` for a custom tester.
|
|
40
|
+
*/
|
|
41
|
+
export declare function registerWidget(entry: RegistryEntry<WidgetComponent>): void;
|
|
42
|
+
/**
|
|
43
|
+
* Resolve a block to its widget via the tester/priority dispatcher. Searches
|
|
44
|
+
* the full registry (base DS entries + host-registered). Returns `null` when
|
|
45
|
+
* no widget applies — the caller renders per blast radius (`decideRender`),
|
|
46
|
+
* never interpolating the block's raw `type`/`kind`.
|
|
47
|
+
*/
|
|
48
|
+
export declare function resolveWidget(block: Block, schema?: OntologySchema | null): Match<WidgetComponent> | null;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DS renderer widget registry — the extensible dispatcher layer (#492).
|
|
3
|
+
*
|
|
4
|
+
* Ships a BASE registry with the two clean DS widgets (stat-grid + results-chart)
|
|
5
|
+
* and exposes a `registerWidget` hook so host apps (the portal, the admin) can
|
|
6
|
+
* append their own coupled widgets at startup without modifying this module
|
|
7
|
+
* (open/closed principle, JSONForms model).
|
|
8
|
+
*
|
|
9
|
+
* `dispatch.ts` (the tester/priority core) is the single dispatcher both the DS
|
|
10
|
+
* base registry and host-extended registries share; the registry is generic over
|
|
11
|
+
* the payload so `selectEntry` stays component-free and unit-testable.
|
|
12
|
+
*
|
|
13
|
+
* TH-08 preserved: `resolveWidget` returns the matched entry's known-good `key`
|
|
14
|
+
* literal — the operator's raw block `type`/`binding.kind` is never the key.
|
|
15
|
+
*/
|
|
16
|
+
import type { Component } from "svelte";
|
|
17
|
+
import {
|
|
18
|
+
NOT_APPLICABLE,
|
|
19
|
+
selectEntry,
|
|
20
|
+
type Match,
|
|
21
|
+
type RegistryEntry,
|
|
22
|
+
} from "./dispatch";
|
|
23
|
+
import type { Block, OntologySchema, WidgetKind, WidgetProps } from "./types";
|
|
24
|
+
|
|
25
|
+
import ResultsChartWidget from "./ResultsChartWidget.svelte";
|
|
26
|
+
import StatGridWidget from "./StatGridWidget.svelte";
|
|
27
|
+
|
|
28
|
+
export type WidgetComponent = Component<WidgetProps>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A TYPE-specific widget over a shared kind (JSONForms model): the authoring
|
|
32
|
+
* `type` hint ranks it ABOVE the kind-generic widget (score 20 > 10) for the
|
|
33
|
+
* same binding. `type` is UNTRUSTED — it only influences ranking; the resolved
|
|
34
|
+
* key is the entry literal (TH-08).
|
|
35
|
+
*/
|
|
36
|
+
export function byTypeOnKind(
|
|
37
|
+
key: string,
|
|
38
|
+
kind: WidgetKind,
|
|
39
|
+
component: unknown,
|
|
40
|
+
): RegistryEntry<WidgetComponent> {
|
|
41
|
+
return {
|
|
42
|
+
key,
|
|
43
|
+
payload: component as WidgetComponent,
|
|
44
|
+
tester: (binding, _schema, type) =>
|
|
45
|
+
binding.kind === kind && type === key ? 20 : NOT_APPLICABLE,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** A widget whose tester is the generic "binding.kind === K" check (score 10). */
|
|
50
|
+
export function byKind(
|
|
51
|
+
key: string,
|
|
52
|
+
kind: WidgetKind,
|
|
53
|
+
component: unknown,
|
|
54
|
+
): RegistryEntry<WidgetComponent> {
|
|
55
|
+
return {
|
|
56
|
+
key,
|
|
57
|
+
payload: component as WidgetComponent,
|
|
58
|
+
tester: (binding) => (binding.kind === kind ? 10 : NOT_APPLICABLE),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Mutable internal registry — base DS entries first, host-registered entries
|
|
64
|
+
* appended in call order. Exported (readonly view) for introspection / testing.
|
|
65
|
+
*/
|
|
66
|
+
const _entries: RegistryEntry<WidgetComponent>[] = [
|
|
67
|
+
// DS base: the two clean widgets shipped by this package.
|
|
68
|
+
byKind("stat-grid", "kpi", StatGridWidget),
|
|
69
|
+
byTypeOnKind("results-chart", "aggregate", ResultsChartWidget),
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Register a widget in the host-extensible registry. Call at app startup
|
|
74
|
+
* (before any `resolveWidget` calls) to append host-specific widgets. Each
|
|
75
|
+
* entry is appended in call order; ties within the same score resolve to the
|
|
76
|
+
* first-registered entry (stable, deterministic — base entries win ties).
|
|
77
|
+
*
|
|
78
|
+
* Signature mirrors `byKind` / `byTypeOnKind` so the caller writes:
|
|
79
|
+
* `registerWidget(byKind("entity-list", "list", EntityListWidget))`
|
|
80
|
+
*
|
|
81
|
+
* @param entry - A registry entry built with `byKind`, `byTypeOnKind`, or a
|
|
82
|
+
* hand-crafted `{ key, payload, tester }` for a custom tester.
|
|
83
|
+
*/
|
|
84
|
+
export function registerWidget(entry: RegistryEntry<WidgetComponent>): void {
|
|
85
|
+
_entries.push(entry);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Resolve a block to its widget via the tester/priority dispatcher. Searches
|
|
90
|
+
* the full registry (base DS entries + host-registered). Returns `null` when
|
|
91
|
+
* no widget applies — the caller renders per blast radius (`decideRender`),
|
|
92
|
+
* never interpolating the block's raw `type`/`kind`.
|
|
93
|
+
*/
|
|
94
|
+
export function resolveWidget(
|
|
95
|
+
block: Block,
|
|
96
|
+
schema: OntologySchema | null = null,
|
|
97
|
+
): Match<WidgetComponent> | null {
|
|
98
|
+
return selectEntry(_entries, block.binding, schema, block.type);
|
|
99
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `resolveData(block)` — the single data path between a block's pointer and a
|
|
3
|
+
* widget (#73, spec §5). Reads the `binding`, fetches through the host-injected
|
|
4
|
+
* `DataProvider` (#492 P0), and returns normalised `{ schema, data, actionDef }`.
|
|
5
|
+
* The block stores ONLY the pointer — rows are never baked in.
|
|
6
|
+
*
|
|
7
|
+
* Pure + provider-injectable so the security-critical decisions (which paths are
|
|
8
|
+
* reachable, fail-closed on misconfig/upstream error) are unit/mutation-
|
|
9
|
+
* testable without SvelteKit. The route loader is a thin wrapper that passes a
|
|
10
|
+
* `createPublicDataProvider` wrapping its server `fetch` (which carries the
|
|
11
|
+
* tenant + graduated principal).
|
|
12
|
+
*
|
|
13
|
+
* Fail-closed (§14.8): an unmappable binding never fetches; a non-OK upstream
|
|
14
|
+
* yields `{ ok: false }` and the caller renders per blast radius (see
|
|
15
|
+
* `decideRender` in `./dispatch.ts`).
|
|
16
|
+
*/
|
|
17
|
+
import type { Binding, Block, FeedView, OntologySchema, WidgetKind } from "./types";
|
|
18
|
+
import type { DataProvider } from "./data-provider";
|
|
19
|
+
/**
|
|
20
|
+
* Presentational kinds (#75 M5 hero/lookup + #105 Phase 7 landing/content) that
|
|
21
|
+
* render PURELY from their block props — no data path, no fetch. resolveData
|
|
22
|
+
* short-circuits them with `{ data: null }`, which still satisfies decideRender's
|
|
23
|
+
* "matched + dataOk" contract (the widget owns its own props). Keeping them in
|
|
24
|
+
* one set means a new content section never accidentally tries to fetch.
|
|
25
|
+
*/
|
|
26
|
+
export declare const PRESENTATIONAL_KINDS: ReadonlySet<WidgetKind>;
|
|
27
|
+
export interface ResolvedData {
|
|
28
|
+
data: unknown;
|
|
29
|
+
/** Ontology schema for the bound entity — wired in 73d. */
|
|
30
|
+
schema: OntologySchema | null;
|
|
31
|
+
/** Action definition for form bindings — wired in 73f. */
|
|
32
|
+
actionDef: unknown | null;
|
|
33
|
+
/** The BFF path the data came from (#75 M5 slice 4) — widgets that page
|
|
34
|
+
* (list "load more") re-fetch it client-side with `?after=`. */
|
|
35
|
+
dataPath?: string;
|
|
36
|
+
}
|
|
37
|
+
export type ResolveDataResult = {
|
|
38
|
+
ok: true;
|
|
39
|
+
value: ResolvedData;
|
|
40
|
+
} | {
|
|
41
|
+
ok: false;
|
|
42
|
+
status: number;
|
|
43
|
+
reason: string;
|
|
44
|
+
};
|
|
45
|
+
/** The minimal fetch shape we depend on (injectable; SvelteKit `fetch` fits). */
|
|
46
|
+
export type FetchLike = (path: string) => Promise<{
|
|
47
|
+
ok: boolean;
|
|
48
|
+
status: number;
|
|
49
|
+
json: () => Promise<unknown>;
|
|
50
|
+
}>;
|
|
51
|
+
export interface ResolveDataDeps {
|
|
52
|
+
/** Host-injected data transport seam (#492 P0). */
|
|
53
|
+
provider: DataProvider;
|
|
54
|
+
/**
|
|
55
|
+
* "Now", injected by the route loader (`new Date()`), used to compute the
|
|
56
|
+
* calendar's required `start`/`end` window (73e). Optional — only the
|
|
57
|
+
* `calendar` kind reads it. Injected (not read here) so the path stays
|
|
58
|
+
* deterministic in tests.
|
|
59
|
+
*/
|
|
60
|
+
now?: Date;
|
|
61
|
+
/**
|
|
62
|
+
* The active feed view (`?view=` — #308), used only by the `feed` kind to
|
|
63
|
+
* pick which underlying feed (list/map) to resolve. Absent → the block's
|
|
64
|
+
* first configured view.
|
|
65
|
+
*/
|
|
66
|
+
view?: string;
|
|
67
|
+
/**
|
|
68
|
+
* The URL's facet/search params (#308), forwarded to a feed read so the SSR
|
|
69
|
+
* feed is server-FILTERED (and paging rides `dataPath`). The BFF drops any
|
|
70
|
+
* param it doesn't declare (its security contract), so the whole set is safe
|
|
71
|
+
* to pass. Reserved keys (`view`, `reference`) are stripped by the loader.
|
|
72
|
+
*/
|
|
73
|
+
filterParams?: Record<string, string>;
|
|
74
|
+
}
|
|
75
|
+
export declare function feedViews(props: Record<string, unknown> | undefined): FeedView[];
|
|
76
|
+
/** The active view = the requested one when it's an available view, else the
|
|
77
|
+
* default (first). Keeps `?view=` shareable + an unknown value harmless. */
|
|
78
|
+
export declare function activeFeedView(views: FeedView[], requested: string | undefined): FeedView;
|
|
79
|
+
/** Merge facet/search params into a BFF path (handles an existing `?`). Empty
|
|
80
|
+
* values drop; an empty param set returns the path unchanged. */
|
|
81
|
+
export declare function appendQuery(path: string, params: Record<string, string> | undefined): string;
|
|
82
|
+
/**
|
|
83
|
+
* Pure mapping: pointer → BFF public path (#70 surface, app-first). Returns
|
|
84
|
+
* `null` for any binding that doesn't map to a wired read endpoint — the
|
|
85
|
+
* caller fails closed (no fetch, no DOM). `binding.filter` carries the
|
|
86
|
+
* per-route selector (ref/key/id) for the single-resource kinds.
|
|
87
|
+
*/
|
|
88
|
+
export declare function bffPathForBinding(binding: Binding, app: string): string | null;
|
|
89
|
+
/**
|
|
90
|
+
* Pure mapping: pointer → BFF public *schema* path (#73 / 73d). Only the
|
|
91
|
+
* schema-driven kinds (`list`, `detail`) have one; the entity's field shape
|
|
92
|
+
* (key/type/label) lets EntityList/DetailView render columns/fields without a
|
|
93
|
+
* hardcoded entity-type union (§14.6). Returns `null` for every other kind, and
|
|
94
|
+
* for list/detail without an entity. `binding.filter` (the detail row id) is
|
|
95
|
+
* NOT part of the schema path — the schema is per *type*, not per row.
|
|
96
|
+
*/
|
|
97
|
+
export declare function schemaPathForBinding(binding: Binding, app: string): string | null;
|
|
98
|
+
/**
|
|
99
|
+
* The date window the `/public/calendar` fetch requires (73e). The endpoint
|
|
100
|
+
* 422s without `start`/`end`, so the calendar block fetches a window computed
|
|
101
|
+
* from the injected "now": the current month through two months out (covering
|
|
102
|
+
* the default month view plus a buffer for next-month navigation). Pure given
|
|
103
|
+
* `now`, computed in UTC so it doesn't drift with the server timezone.
|
|
104
|
+
*/
|
|
105
|
+
export declare function calendarWindow(now: Date): {
|
|
106
|
+
start: string;
|
|
107
|
+
end: string;
|
|
108
|
+
};
|
|
109
|
+
export declare function resolveData(block: Block, deps: ResolveDataDeps): Promise<ResolveDataResult>;
|