rich-react-component 0.1.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/LICENSE +21 -0
- package/README.md +118 -0
- package/dist/base/Accordion.d.ts +17 -0
- package/dist/base/Alert.d.ts +15 -0
- package/dist/base/AutoComplete.d.ts +20 -0
- package/dist/base/Avatar.d.ts +8 -0
- package/dist/base/Badge.d.ts +9 -0
- package/dist/base/Breadcrumb.d.ts +10 -0
- package/dist/base/Button.d.ts +13 -0
- package/dist/base/Card.d.ts +9 -0
- package/dist/base/CheckBox.d.ts +17 -0
- package/dist/base/Col.d.ts +12 -0
- package/dist/base/ComboBox.d.ts +17 -0
- package/dist/base/Confirm.d.ts +25 -0
- package/dist/base/Container.d.ts +7 -0
- package/dist/base/DataGrid.d.ts +43 -0
- package/dist/base/DatePicker.d.ts +23 -0
- package/dist/base/DateTimePicker.d.ts +17 -0
- package/dist/base/Divider.d.ts +8 -0
- package/dist/base/FileInput.d.ts +19 -0
- package/dist/base/FormField.d.ts +12 -0
- package/dist/base/Icon.d.ts +15 -0
- package/dist/base/Input.d.ts +30 -0
- package/dist/base/Menu.d.ts +21 -0
- package/dist/base/Modal.d.ts +19 -0
- package/dist/base/MultiSelect.d.ts +25 -0
- package/dist/base/Navbar.d.ts +16 -0
- package/dist/base/NumberInput.d.ts +17 -0
- package/dist/base/PageHeader.d.ts +12 -0
- package/dist/base/Pagination.d.ts +15 -0
- package/dist/base/PasswordInput.d.ts +10 -0
- package/dist/base/Popover.d.ts +10 -0
- package/dist/base/ProgressBar.d.ts +10 -0
- package/dist/base/RadioButton.d.ts +18 -0
- package/dist/base/RadioGroup.d.ts +14 -0
- package/dist/base/Rating.d.ts +9 -0
- package/dist/base/Row.d.ts +6 -0
- package/dist/base/Select.d.ts +24 -0
- package/dist/base/Sidebar.d.ts +31 -0
- package/dist/base/Skeleton.d.ts +8 -0
- package/dist/base/Slider.d.ts +10 -0
- package/dist/base/Spinner.d.ts +7 -0
- package/dist/base/Stack.d.ts +12 -0
- package/dist/base/Stepper.d.ts +12 -0
- package/dist/base/Switch.d.ts +16 -0
- package/dist/base/Tabs.d.ts +16 -0
- package/dist/base/Tag.d.ts +14 -0
- package/dist/base/TextArea.d.ts +11 -0
- package/dist/base/TimePicker.d.ts +20 -0
- package/dist/base/Toast.d.ts +27 -0
- package/dist/base/Tooltip.d.ts +21 -0
- package/dist/base/index.d.ts +56 -0
- package/dist/base/shared/BooleanFieldControl.d.ts +22 -0
- package/dist/base/shared/FieldShell.d.ts +22 -0
- package/dist/base/shared/Label.d.ts +9 -0
- package/dist/base/shared/Popup.d.ts +25 -0
- package/dist/base/shared/ValidationMessage.d.ts +11 -0
- package/dist/base/shared/classNames.d.ts +1 -0
- package/dist/base/shared/fieldTypes.d.ts +26 -0
- package/dist/base/shared/index.d.ts +9 -0
- package/dist/base/shared/useControllableState.d.ts +16 -0
- package/dist/base/shared/useEscapeKey.d.ts +2 -0
- package/dist/base/shared/useListNavigation.d.ts +18 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +2092 -0
- package/dist/index.js.map +1 -0
- package/dist/remote/RemoteAutoComplete.d.ts +22 -0
- package/dist/remote/RemoteComboBox.d.ts +21 -0
- package/dist/remote/RemoteDataGrid.d.ts +24 -0
- package/dist/remote/RemoteMultiSelect.d.ts +13 -0
- package/dist/remote/RemoteSelect.d.ts +17 -0
- package/dist/remote/hooks/useDebouncedValue.d.ts +1 -0
- package/dist/remote/hooks/useRemoteData.d.ts +31 -0
- package/dist/remote/http/HttpClient.d.ts +31 -0
- package/dist/remote/index.d.ts +9 -0
- package/dist/remote/mapping.d.ts +8 -0
- package/dist/smart/SmartAction.d.ts +14 -0
- package/dist/smart/SmartActions.d.ts +14 -0
- package/dist/smart/SmartField.d.ts +18 -0
- package/dist/smart/SmartForm.d.ts +19 -0
- package/dist/smart/actionRegistry.d.ts +11 -0
- package/dist/smart/componentRegistry.d.ts +24 -0
- package/dist/smart/dataSourceRegistry.d.ts +17 -0
- package/dist/smart/index.d.ts +26 -0
- package/dist/smart/resolvers/comboBoxResolver.d.ts +10 -0
- package/dist/smart/resolvers/dataGridResolver.d.ts +11 -0
- package/dist/smart/resolvers/datePickerResolver.d.ts +3 -0
- package/dist/smart/resolvers/index.d.ts +13 -0
- package/dist/smart/resolvers/inputResolver.d.ts +8 -0
- package/dist/smart/runtime.d.ts +26 -0
- package/dist/smart/types.d.ts +114 -0
- package/dist/smart/useSmartAction.d.ts +36 -0
- package/dist/smart/useSmartDependencies.d.ts +13 -0
- package/dist/smart/useSmartField.d.ts +47 -0
- package/package.json +68 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ComboBoxProps } from '../base';
|
|
2
|
+
import { HttpClient } from './http/HttpClient';
|
|
3
|
+
export interface RemoteComboBoxProps<TValue extends string | number = string, TResponse = unknown> extends Omit<ComboBoxProps<TValue>, "options" | "loading"> {
|
|
4
|
+
url: string;
|
|
5
|
+
params?: Record<string, unknown>;
|
|
6
|
+
/** Key read off each raw remote item to produce the option's `value` (doc section 4). */
|
|
7
|
+
valueMember: string;
|
|
8
|
+
/** Key read off each raw remote item to produce the option's `label`. */
|
|
9
|
+
displayMember: string;
|
|
10
|
+
/** Escape hatch for response envelopes beyond bare array / `{ items: [...] }` (doc section 11). */
|
|
11
|
+
mapResponse?: (response: TResponse) => unknown[];
|
|
12
|
+
client?: HttpClient;
|
|
13
|
+
enabled?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Adds remote-loading behavior to Base ComboBox by composing it — this is
|
|
17
|
+
* NOT a second independent ComboBox implementation (doc section 20). All
|
|
18
|
+
* selection/keyboard/popup UI behavior is Base ComboBox's; this component
|
|
19
|
+
* only owns fetch lifecycle + raw-item -> SelectOption mapping.
|
|
20
|
+
*/
|
|
21
|
+
export declare function RemoteComboBox<TValue extends string | number = string, TResponse = unknown>({ url, params, valueMember, displayMember, mapResponse, client, enabled, ...comboBoxProps }: RemoteComboBoxProps<TValue, TResponse>): import("react").JSX.Element;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { DataGridProps } from '../base';
|
|
2
|
+
import { HttpClient } from './http/HttpClient';
|
|
3
|
+
export interface RemoteDataGridProps<TRow, TResponse = unknown> extends Omit<DataGridProps<TRow>, "rows" | "loading" | "error" | "page" | "pageSize" | "totalCount" | "onPageChange" | "sort" | "onSortChange"> {
|
|
4
|
+
url: string;
|
|
5
|
+
/** Additional filter params (e.g. `{ countryId }`) — page/pageSize/sort are managed internally. */
|
|
6
|
+
params?: Record<string, unknown>;
|
|
7
|
+
mapResponse?: (response: TResponse) => TRow[];
|
|
8
|
+
/**
|
|
9
|
+
* Extension point: read a total row count out of the raw response, when
|
|
10
|
+
* the backend provides one. Strongly recommended — without it, pagination
|
|
11
|
+
* falls back to a has-more heuristic (a full page implies "at least one
|
|
12
|
+
* more page exists") which cannot show an exact page count.
|
|
13
|
+
*/
|
|
14
|
+
getTotalCount?: (response: TResponse) => number | undefined;
|
|
15
|
+
pageSize?: number;
|
|
16
|
+
client?: HttpClient;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Adds paging/sorting-against-a-server-endpoint behavior to Base DataGrid
|
|
20
|
+
* by composing it. All table rendering, selection, and pagination/sort UI
|
|
21
|
+
* remain Base DataGrid's — this component only owns request params
|
|
22
|
+
* (page/pageSize/sort/filters) and response mapping (doc section 10).
|
|
23
|
+
*/
|
|
24
|
+
export declare function RemoteDataGrid<TRow, TResponse = unknown>({ url, params, mapResponse, getTotalCount, pageSize, client, ...dataGridProps }: RemoteDataGridProps<TRow, TResponse>): import("react").JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { MultiSelectProps } from '../base';
|
|
2
|
+
import { HttpClient } from './http/HttpClient';
|
|
3
|
+
export interface RemoteMultiSelectProps<TValue extends string | number = string, TResponse = unknown> extends Omit<MultiSelectProps<TValue>, "options" | "loading"> {
|
|
4
|
+
url: string;
|
|
5
|
+
params?: Record<string, unknown>;
|
|
6
|
+
valueMember: string;
|
|
7
|
+
displayMember: string;
|
|
8
|
+
mapResponse?: (response: TResponse) => unknown[];
|
|
9
|
+
client?: HttpClient;
|
|
10
|
+
enabled?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** Same reuse pattern as RemoteComboBox/RemoteSelect, composing Base MultiSelect. */
|
|
13
|
+
export declare function RemoteMultiSelect<TValue extends string | number = string, TResponse = unknown>({ url, params, valueMember, displayMember, mapResponse, client, enabled, ...multiSelectProps }: RemoteMultiSelectProps<TValue, TResponse>): import("react").JSX.Element;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { SelectProps } from '../base';
|
|
2
|
+
import { HttpClient } from './http/HttpClient';
|
|
3
|
+
export interface RemoteSelectProps<TValue extends string | number = string, TResponse = unknown> extends Omit<SelectProps<TValue>, "options" | "loading"> {
|
|
4
|
+
url: string;
|
|
5
|
+
params?: Record<string, unknown>;
|
|
6
|
+
valueMember: string;
|
|
7
|
+
displayMember: string;
|
|
8
|
+
mapResponse?: (response: TResponse) => unknown[];
|
|
9
|
+
client?: HttpClient;
|
|
10
|
+
enabled?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Same reuse pattern as RemoteComboBox: composes Base Select instead of
|
|
14
|
+
* reimplementing it (doc section 20). Thin — only fetch lifecycle +
|
|
15
|
+
* valueMember/displayMember mapping belong here.
|
|
16
|
+
*/
|
|
17
|
+
export declare function RemoteSelect<TValue extends string | number = string, TResponse = unknown>({ url, params, valueMember, displayMember, mapResponse, client, enabled, ...selectProps }: RemoteSelectProps<TValue, TResponse>): import("react").JSX.Element;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function useDebouncedValue<T>(value: T, delayMs: number): T;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { HttpClient } from '../http/HttpClient';
|
|
2
|
+
export type RemoteState = "idle" | "loading" | "success" | "empty" | "error";
|
|
3
|
+
export interface UseRemoteDataOptions<TResponse, TItem> {
|
|
4
|
+
client?: HttpClient;
|
|
5
|
+
/** `null`/`undefined` disables fetching entirely (e.g. RemoteAutoComplete below minSearchLength). */
|
|
6
|
+
url: string | null | undefined;
|
|
7
|
+
params?: Record<string, unknown>;
|
|
8
|
+
mapResponse: (response: TResponse) => TItem[];
|
|
9
|
+
enabled?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface UseRemoteDataResult<TItem> {
|
|
12
|
+
state: RemoteState;
|
|
13
|
+
items: TItem[];
|
|
14
|
+
error: Error | null;
|
|
15
|
+
reload: () => void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The one place remote/API data fetching lifecycle logic lives. Every
|
|
19
|
+
* Remote Component (RemoteComboBox, RemoteAutoComplete, RemoteDataGrid)
|
|
20
|
+
* composes this instead of writing its own fetch effect (doc section 6).
|
|
21
|
+
*
|
|
22
|
+
* Handles, per doc sections 5/7/8:
|
|
23
|
+
* - stable request identity: params are compared by serialized *value*,
|
|
24
|
+
* not object reference, so a new `{ ...params }` literal each render
|
|
25
|
+
* does not cause an infinite request loop.
|
|
26
|
+
* - request cancellation via AbortController.
|
|
27
|
+
* - stale-response protection via a monotonically increasing request id,
|
|
28
|
+
* which still guards correctness even if abort doesn't win the race.
|
|
29
|
+
* - a consistent idle/loading/success/empty/error state model.
|
|
30
|
+
*/
|
|
31
|
+
export declare function useRemoteData<TResponse, TItem>({ client, url, params, mapResponse, enabled, }: UseRemoteDataOptions<TResponse, TItem>): UseRemoteDataResult<TItem>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface HttpRequestOptions {
|
|
2
|
+
params?: Record<string, unknown>;
|
|
3
|
+
signal?: AbortSignal;
|
|
4
|
+
}
|
|
5
|
+
export interface HttpClient {
|
|
6
|
+
get<TResponse>(url: string, options?: HttpRequestOptions): Promise<TResponse>;
|
|
7
|
+
}
|
|
8
|
+
export declare class HttpError extends Error {
|
|
9
|
+
status: number;
|
|
10
|
+
statusText: string;
|
|
11
|
+
url: string;
|
|
12
|
+
constructor(status: number, statusText: string, url: string);
|
|
13
|
+
}
|
|
14
|
+
export interface CreateHttpClientOptions {
|
|
15
|
+
baseUrl?: string;
|
|
16
|
+
/** Injectable fetch implementation — used by tests, could also route through a wrapped/instrumented fetch. */
|
|
17
|
+
fetcher?: typeof fetch;
|
|
18
|
+
/**
|
|
19
|
+
* Extension point for cross-cutting concerns the doc explicitly defers
|
|
20
|
+
* (authorization, tenant headers, interceptors) without building them now
|
|
21
|
+
* (doc section 6: only establish extension points where justified).
|
|
22
|
+
*/
|
|
23
|
+
buildRequestInit?: (url: string, init: RequestInit) => RequestInit;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The single place Remote Components go through to talk to a backend.
|
|
27
|
+
* Base Components never import this (enforced by dependency direction, not
|
|
28
|
+
* just convention — Base has no dependency on `remote/*` at all).
|
|
29
|
+
*/
|
|
30
|
+
export declare function createHttpClient(options?: CreateHttpClientOptions): HttpClient;
|
|
31
|
+
export declare const defaultHttpClient: HttpClient;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './http/HttpClient';
|
|
2
|
+
export * from './hooks/useRemoteData';
|
|
3
|
+
export * from './hooks/useDebouncedValue';
|
|
4
|
+
export * from './mapping';
|
|
5
|
+
export * from './RemoteComboBox';
|
|
6
|
+
export * from './RemoteAutoComplete';
|
|
7
|
+
export * from './RemoteDataGrid';
|
|
8
|
+
export * from './RemoteSelect';
|
|
9
|
+
export * from './RemoteMultiSelect';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { SelectOption } from '../base';
|
|
2
|
+
/**
|
|
3
|
+
* Handles the two common response envelopes (bare array, `{ items: [...] }`)
|
|
4
|
+
* explicitly (doc section 11). Anything else must be handled via an
|
|
5
|
+
* explicit `mapResponse` — no guessing beyond these two conventions.
|
|
6
|
+
*/
|
|
7
|
+
export declare function defaultArrayNormalization(response: unknown): unknown[];
|
|
8
|
+
export declare function toSelectOption<TValue extends string | number>(raw: unknown, valueMember: string, displayMember: string): SelectOption<TValue>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { SmartRuntimeContext } from './runtime';
|
|
2
|
+
import { SmartActionMetadata } from './types';
|
|
3
|
+
export interface SmartActionProps {
|
|
4
|
+
metadata: SmartActionMetadata;
|
|
5
|
+
ctx: Pick<SmartRuntimeContext, "actions" | "translate">;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Composes Base Button, not a second button implementation (doc §16/§20) —
|
|
9
|
+
* the only component-shaped thing action metadata currently resolves to.
|
|
10
|
+
* Unlike field metadata, an action's `component: "Button"` never needs a
|
|
11
|
+
* registry lookup: there is exactly one action UI shape, so there is
|
|
12
|
+
* nothing to resolve between.
|
|
13
|
+
*/
|
|
14
|
+
export declare function SmartAction({ metadata, ctx }: SmartActionProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { SmartRuntimeContext } from './runtime';
|
|
2
|
+
import { SmartFormMetadata } from './types';
|
|
3
|
+
export interface SmartActionsProps {
|
|
4
|
+
metadata: SmartFormMetadata;
|
|
5
|
+
ctx: Pick<SmartRuntimeContext, "actions" | "translate">;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* A thin iteration helper over SmartAction — the action-side counterpart
|
|
9
|
+
* of SmartForm (doc §09/§15). Maps `metadata.actions` to SmartActions and
|
|
10
|
+
* nothing else: no layout, no spacing, no handler logic of its own. The
|
|
11
|
+
* consuming screen wraps this in whatever layout (Stack, a form footer,
|
|
12
|
+
* a toolbar) it needs.
|
|
13
|
+
*/
|
|
14
|
+
export declare function SmartActions({ metadata, ctx }: SmartActionsProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ReactElement } from 'react';
|
|
2
|
+
import { SmartRuntimeContext } from './runtime';
|
|
3
|
+
import { SmartFieldMetadata } from './types';
|
|
4
|
+
export interface SmartFieldProps {
|
|
5
|
+
metadata: SmartFieldMetadata;
|
|
6
|
+
ctx: SmartRuntimeContext;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Given one field's metadata, resolves it into whatever Base/Remote
|
|
10
|
+
* component its `component` discriminant maps to (doc §09/§14) — it owns
|
|
11
|
+
* no UI of its own, only delegates to `ctx.registry`.
|
|
12
|
+
*
|
|
13
|
+
* Also wires the mechanical dependency tracking every metadata-driven
|
|
14
|
+
* field needs (doc §06): a consuming screen no longer calls
|
|
15
|
+
* useSmartDependencies by hand per field, SmartField does it here and
|
|
16
|
+
* reports which field needed the refresh, not just which value changed.
|
|
17
|
+
*/
|
|
18
|
+
export declare function SmartField({ metadata, ctx }: SmartFieldProps): ReactElement | null;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { SmartRuntimeContext } from './runtime';
|
|
2
|
+
import { SmartFormMetadata } from './types';
|
|
3
|
+
export interface SmartFormProps {
|
|
4
|
+
metadata: SmartFormMetadata;
|
|
5
|
+
ctx: SmartRuntimeContext;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* A thin iteration helper over SmartField (doc §09/§15/§18 decision 4) —
|
|
9
|
+
* it owns no field values, evaluates no business rule, and decides no
|
|
10
|
+
* layout. Its only two responsibilities: fail the whole form closed on an
|
|
11
|
+
* unsupported schema version instead of rendering a partially-understood
|
|
12
|
+
* form (doc §12, reusing Alert rather than a bespoke error UI), and map
|
|
13
|
+
* each field's metadata to a SmartField.
|
|
14
|
+
*
|
|
15
|
+
* A SmartField used standalone (outside SmartForm) is responsible for its
|
|
16
|
+
* own schemaVersion check — this component is the only place that check
|
|
17
|
+
* happens automatically.
|
|
18
|
+
*/
|
|
19
|
+
export declare function SmartForm({ metadata, ctx }: SmartFormProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type SmartActionHandler = () => void;
|
|
2
|
+
export interface SmartActionRegistry {
|
|
3
|
+
register(id: string, handler: SmartActionHandler): void;
|
|
4
|
+
resolve(id: string): SmartActionHandler | undefined;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Metadata carries an action id, never a function (doc §08/§11 — no
|
|
8
|
+
* executable code from the server, ever). The application registers the
|
|
9
|
+
* real handler once; Smart only looks it up by id.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createSmartActionRegistry(): SmartActionRegistry;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ReactElement } from 'react';
|
|
2
|
+
import { SmartFieldMetadata } from './types';
|
|
3
|
+
import { SmartRuntimeContext } from './runtime';
|
|
4
|
+
export type SmartResolver<M extends SmartFieldMetadata = SmartFieldMetadata> = (metadata: M, ctx: SmartRuntimeContext) => ReactElement | null;
|
|
5
|
+
export interface SmartComponentRegistry {
|
|
6
|
+
register<M extends SmartFieldMetadata>(component: M["component"], resolver: SmartResolver<M>): void;
|
|
7
|
+
resolve(metadata: SmartFieldMetadata, ctx: SmartRuntimeContext): ReactElement | null;
|
|
8
|
+
has(component: string): boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Keyed by the `component` discriminant — not a switch, not a plugin
|
|
12
|
+
* framework (doc §04). Type safety lives at the register()/resolve()
|
|
13
|
+
* generic boundary; internal storage type-erases to `SmartResolver`, the
|
|
14
|
+
* same escape hatch any typed registry needs, isolated to this one
|
|
15
|
+
* function. No component type is privileged: Phase 2's built-in Input/
|
|
16
|
+
* ComboBox/DatePicker/DataGrid resolvers will register through this exact
|
|
17
|
+
* same call an application uses for a custom component.
|
|
18
|
+
*
|
|
19
|
+
* An unresolved `component` value never falls back to an unrelated
|
|
20
|
+
* component (doc §12) — resolve() returns null and warns once per unknown
|
|
21
|
+
* component name, so a field silently disappears from the form instead of
|
|
22
|
+
* rendering the wrong thing.
|
|
23
|
+
*/
|
|
24
|
+
export declare function createSmartComponentRegistry(): SmartComponentRegistry;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { HttpClient } from '../remote';
|
|
2
|
+
export interface SmartDataSourceDefinition {
|
|
3
|
+
url: string;
|
|
4
|
+
/** Defaults to Remote's own defaultHttpClient when omitted, same as RemoteComboBox etc. already do. */
|
|
5
|
+
client?: HttpClient;
|
|
6
|
+
}
|
|
7
|
+
export interface SmartDataSourceRegistry {
|
|
8
|
+
register(id: string, definition: SmartDataSourceDefinition): void;
|
|
9
|
+
resolve(id: string): SmartDataSourceDefinition | undefined;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Backs the id-based indirection decided in doc §09/§18 (decision 2):
|
|
13
|
+
* metadata never carries a raw URL, only an id an application registers
|
|
14
|
+
* once. The application owns environment/hostname/auth wiring in one
|
|
15
|
+
* place — Smart never needs to know it exists.
|
|
16
|
+
*/
|
|
17
|
+
export declare function createSmartDataSourceRegistry(initial?: Record<string, SmartDataSourceDefinition>): SmartDataSourceRegistry;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart layer — Phases 1-5 complete (approved architecture, see the
|
|
3
|
+
* "Smart Components" design doc):
|
|
4
|
+
* Phase 1 — metadata types, the three registries, the headless
|
|
5
|
+
* useSmartField/useSmartDependencies engine.
|
|
6
|
+
* Phase 2 — built-in Input/ComboBox/DatePicker resolvers + SmartField.
|
|
7
|
+
* Phase 3 — SmartForm, a thin iteration helper over SmartField.
|
|
8
|
+
* Phase 4 — built-in DataGrid resolver + declarative column metadata.
|
|
9
|
+
* Phase 5 — useSmartAction/SmartAction/SmartActions (the action-side
|
|
10
|
+
* counterpart of useSmartField/SmartField/SmartForm), and
|
|
11
|
+
* localized boolean-column formatting in the DataGrid
|
|
12
|
+
* resolver.
|
|
13
|
+
*/
|
|
14
|
+
export * from './types';
|
|
15
|
+
export * from './componentRegistry';
|
|
16
|
+
export * from './dataSourceRegistry';
|
|
17
|
+
export * from './actionRegistry';
|
|
18
|
+
export * from './runtime';
|
|
19
|
+
export * from './useSmartField';
|
|
20
|
+
export * from './useSmartDependencies';
|
|
21
|
+
export * from './useSmartAction';
|
|
22
|
+
export * from './resolvers';
|
|
23
|
+
export * from './SmartField';
|
|
24
|
+
export * from './SmartForm';
|
|
25
|
+
export * from './SmartAction';
|
|
26
|
+
export * from './SmartActions';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { SmartResolver } from '../componentRegistry';
|
|
2
|
+
import { ComboBoxMetadata } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Composes RemoteComboBox, not ComboBox directly (doc §04 — Base vs. Remote
|
|
5
|
+
* is inferred from the metadata shape: ComboBoxMetadata always carries a
|
|
6
|
+
* `dataSource`, so this field is always remote-backed). RemoteComboBox in
|
|
7
|
+
* turn composes Base ComboBox, so no selection/keyboard/popup behavior is
|
|
8
|
+
* duplicated anywhere in this chain.
|
|
9
|
+
*/
|
|
10
|
+
export declare const comboBoxResolver: SmartResolver<ComboBoxMetadata>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { SmartResolver } from '../componentRegistry';
|
|
2
|
+
import { DataGridMetadata } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Composes RemoteDataGrid, not Base DataGrid directly (doc §04 — inferred
|
|
5
|
+
* from `dataSource` the same way ComboBoxMetadata is). Column metadata is
|
|
6
|
+
* translated into a real `DataGridColumn<TRow>[]` and handed straight to
|
|
7
|
+
* the existing grid — no second grid engine, no filtering/grouping DSL
|
|
8
|
+
* (doc §10, matching the original library's own DataGrid scope
|
|
9
|
+
* restraint).
|
|
10
|
+
*/
|
|
11
|
+
export declare const dataGridResolver: SmartResolver<DataGridMetadata>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { SmartComponentRegistry } from '../componentRegistry';
|
|
2
|
+
import { comboBoxResolver } from './comboBoxResolver';
|
|
3
|
+
import { dataGridResolver } from './dataGridResolver';
|
|
4
|
+
import { datePickerResolver } from './datePickerResolver';
|
|
5
|
+
import { inputResolver } from './inputResolver';
|
|
6
|
+
export { comboBoxResolver, dataGridResolver, datePickerResolver, inputResolver };
|
|
7
|
+
/**
|
|
8
|
+
* Registers Input/ComboBox/DatePicker/DataGrid through the exact same
|
|
9
|
+
* `register()` call an application uses for a custom component (doc §04 —
|
|
10
|
+
* no privileged core; a custom "PatientSelector" resolver is registered
|
|
11
|
+
* the same way).
|
|
12
|
+
*/
|
|
13
|
+
export declare function registerBuiltInResolvers(registry: SmartComponentRegistry): void;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { SmartResolver } from '../componentRegistry';
|
|
2
|
+
import { InputMetadata } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Composes Base Input (doc §03/§20 — reuse, not a second text-field
|
|
5
|
+
* implementation). The only thing this file owns is translating resolved
|
|
6
|
+
* metadata into Input's existing prop contract.
|
|
7
|
+
*/
|
|
8
|
+
export declare const inputResolver: SmartResolver<InputMetadata>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { SmartActionRegistry } from './actionRegistry';
|
|
2
|
+
import { SmartComponentRegistry } from './componentRegistry';
|
|
3
|
+
import { SmartDataSourceRegistry } from './dataSourceRegistry';
|
|
4
|
+
/**
|
|
5
|
+
* What a resolver/SmartField needs to turn one piece of metadata into
|
|
6
|
+
* component props. Deliberately owns no canonical field values of its own
|
|
7
|
+
* (doc §05/§18 decision 3) — `values` is always a read-only snapshot the
|
|
8
|
+
* caller supplies, the same controlled shape any Base field already uses
|
|
9
|
+
* via `value`/`onChange`.
|
|
10
|
+
*/
|
|
11
|
+
export interface SmartRuntimeContext {
|
|
12
|
+
values: Record<string, unknown>;
|
|
13
|
+
onFieldChange: (name: string, value: unknown) => void;
|
|
14
|
+
registry: SmartComponentRegistry;
|
|
15
|
+
dataSources: SmartDataSourceRegistry;
|
|
16
|
+
actions: SmartActionRegistry;
|
|
17
|
+
/** Absent = fall back to metadata's raw `label`/`helpText`/etc. strings (doc §07). */
|
|
18
|
+
translate?: (key: string) => string;
|
|
19
|
+
/**
|
|
20
|
+
* Mechanical signal only — "`fieldName`'s metadata needs refreshing
|
|
21
|
+
* because `changedField` changed." Never evaluates why; SmartField wires
|
|
22
|
+
* this to useSmartDependencies automatically for every field, so a
|
|
23
|
+
* consuming screen no longer calls the hook by hand.
|
|
24
|
+
*/
|
|
25
|
+
onMetadataRefreshNeeded?: (fieldName: string, changedField: string) => void;
|
|
26
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metadata contracts for the Smart layer (architecture proposal, approved —
|
|
3
|
+
* see the "Smart Components" design doc). A discriminated union, not one
|
|
4
|
+
* universal object — the same split Base already uses between
|
|
5
|
+
* FieldIdentity/FieldState/FieldPresentation, one layer up. Components
|
|
6
|
+
* compose only the mixins that apply to them.
|
|
7
|
+
*
|
|
8
|
+
* This module is pure data shape. It renders nothing and calls nothing —
|
|
9
|
+
* resolvers (src/smart/resolvers) are the only files that turn it into UI.
|
|
10
|
+
*/
|
|
11
|
+
export interface SmartFieldIdentity {
|
|
12
|
+
/** Binding key — looked up against a runtime `values` record, never owned by Smart itself. */
|
|
13
|
+
name: string;
|
|
14
|
+
/**
|
|
15
|
+
* Field names whose *value* this field's configuration depends on.
|
|
16
|
+
* Mechanical only: it tells Smart when to ask the backend for fresh
|
|
17
|
+
* metadata, never what the backend's answer should be (doc §06 — business
|
|
18
|
+
* rules stay server-owned).
|
|
19
|
+
*/
|
|
20
|
+
dependsOn?: string[];
|
|
21
|
+
}
|
|
22
|
+
export interface SmartFieldState {
|
|
23
|
+
visible?: boolean;
|
|
24
|
+
readOnly?: boolean;
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
required?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface SmartPresentation {
|
|
29
|
+
label?: string;
|
|
30
|
+
/** Semantic key, resolved via a caller-supplied `translate`. Wins over `label` when both are present (doc §07). */
|
|
31
|
+
labelKey?: string;
|
|
32
|
+
placeholder?: string;
|
|
33
|
+
helpText?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface SmartValidation {
|
|
36
|
+
validationMessage?: string;
|
|
37
|
+
/** Semantic key, resolved via a caller-supplied `translate`. Wins over `validationMessage` when both are present. */
|
|
38
|
+
validationMessageKey?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A data-source request parameter is either a literal or a reference to
|
|
42
|
+
* another field's current value — never a magic string like "$CountryId"
|
|
43
|
+
* (doc §06/§09).
|
|
44
|
+
*/
|
|
45
|
+
export type SmartParamValue = {
|
|
46
|
+
kind: "static";
|
|
47
|
+
value: unknown;
|
|
48
|
+
} | {
|
|
49
|
+
kind: "field";
|
|
50
|
+
field: string;
|
|
51
|
+
};
|
|
52
|
+
export interface SmartDataSourceRef {
|
|
53
|
+
/** Id registered in a SmartDataSourceRegistry — never a raw URL (doc §09/§11/§18 decision 2). */
|
|
54
|
+
id: string;
|
|
55
|
+
params?: Record<string, SmartParamValue>;
|
|
56
|
+
}
|
|
57
|
+
export interface InputMetadata extends SmartFieldIdentity, SmartFieldState, SmartPresentation, SmartValidation {
|
|
58
|
+
component: "Input";
|
|
59
|
+
maxLength?: number;
|
|
60
|
+
}
|
|
61
|
+
export interface ComboBoxMetadata extends SmartFieldIdentity, SmartFieldState, SmartPresentation, SmartValidation {
|
|
62
|
+
component: "ComboBox";
|
|
63
|
+
valueMember: string;
|
|
64
|
+
displayMember: string;
|
|
65
|
+
dataSource: SmartDataSourceRef;
|
|
66
|
+
}
|
|
67
|
+
export interface DatePickerMetadata extends SmartFieldIdentity, SmartFieldState, SmartPresentation, SmartValidation {
|
|
68
|
+
component: "DatePicker";
|
|
69
|
+
minDate?: string;
|
|
70
|
+
maxDate?: string;
|
|
71
|
+
}
|
|
72
|
+
export interface SmartDataGridColumn {
|
|
73
|
+
key: string;
|
|
74
|
+
header?: string;
|
|
75
|
+
headerKey?: string;
|
|
76
|
+
sortable?: boolean;
|
|
77
|
+
visible?: boolean;
|
|
78
|
+
format?: "text" | "date" | "currency" | "boolean";
|
|
79
|
+
}
|
|
80
|
+
export interface DataGridMetadata extends SmartFieldIdentity, SmartFieldState, SmartPresentation, SmartValidation {
|
|
81
|
+
component: "DataGrid";
|
|
82
|
+
columns: SmartDataGridColumn[];
|
|
83
|
+
dataSource: SmartDataSourceRef;
|
|
84
|
+
/** Row field each row's unique identity is read from — DataGrid always needs a stable key. */
|
|
85
|
+
rowKey: string;
|
|
86
|
+
/**
|
|
87
|
+
* Top-level key on the raw response holding a total row count (e.g.
|
|
88
|
+
* "total" for a `{ items, total }` envelope) — a declarative field-name
|
|
89
|
+
* reference, the same pattern as `valueMember`/`displayMember` on
|
|
90
|
+
* ComboBoxMetadata, never a function (doc §07/§24). Omit to fall back to
|
|
91
|
+
* RemoteDataGrid's has-more heuristic.
|
|
92
|
+
*/
|
|
93
|
+
totalCountField?: string;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Add a new component type by adding a member here, not by widening an
|
|
97
|
+
* existing one (doc §03 — the trade for type safety is that a new component
|
|
98
|
+
* type needs a client-side union member before the backend can use it).
|
|
99
|
+
*/
|
|
100
|
+
export type SmartFieldMetadata = InputMetadata | ComboBoxMetadata | DatePickerMetadata | DataGridMetadata;
|
|
101
|
+
export interface SmartActionMetadata {
|
|
102
|
+
id: string;
|
|
103
|
+
component: "Button";
|
|
104
|
+
visible?: boolean;
|
|
105
|
+
enabled?: boolean;
|
|
106
|
+
label?: string;
|
|
107
|
+
labelKey?: string;
|
|
108
|
+
}
|
|
109
|
+
export interface SmartFormMetadata {
|
|
110
|
+
/** Mandatory. An unsupported version fails the whole form closed (doc §12) — never a partial render. */
|
|
111
|
+
schemaVersion: string;
|
|
112
|
+
fields: SmartFieldMetadata[];
|
|
113
|
+
actions?: SmartActionMetadata[];
|
|
114
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { SmartActionRegistry, SmartActionHandler } from './actionRegistry';
|
|
2
|
+
import { SmartActionMetadata, SmartFormMetadata } from './types';
|
|
3
|
+
export interface UseSmartActionOptions {
|
|
4
|
+
actions: SmartActionRegistry;
|
|
5
|
+
/** Absent = fall back to metadata's raw `label` string (doc §07). */
|
|
6
|
+
translate?: (key: string) => string;
|
|
7
|
+
}
|
|
8
|
+
export interface ResolvedSmartAction {
|
|
9
|
+
metadata: SmartActionMetadata;
|
|
10
|
+
visible: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* `metadata.enabled` ANDed with "a handler actually exists" — metadata
|
|
13
|
+
* saying `enabled: true` cannot make an action clickable if the
|
|
14
|
+
* application never registered a handler for its id (doc §12 fail-safe:
|
|
15
|
+
* an unregistered action renders disabled, never a no-op that looks
|
|
16
|
+
* clickable).
|
|
17
|
+
*/
|
|
18
|
+
enabled: boolean;
|
|
19
|
+
label?: string;
|
|
20
|
+
handler?: SmartActionHandler;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Resolves one already-known action's metadata. Pure function, mirrors
|
|
24
|
+
* `resolveSmartFieldMetadata` — action metadata carries an id, never a
|
|
25
|
+
* function; the real handler comes from the application-owned
|
|
26
|
+
* `SmartActionRegistry` (doc §08/§16), looked up here by that id.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveSmartActionMetadata(metadata: SmartActionMetadata, options: UseSmartActionOptions): ResolvedSmartAction;
|
|
29
|
+
/**
|
|
30
|
+
* Looks an action up by id in `formMetadata.actions` and resolves it via
|
|
31
|
+
* `resolveSmartActionMetadata` — the action-side counterpart of
|
|
32
|
+
* `useSmartField`. Returns `undefined` when no action with that id is
|
|
33
|
+
* declared, the same "not present = left alone" convention `useSmartField`
|
|
34
|
+
* uses for fields.
|
|
35
|
+
*/
|
|
36
|
+
export declare function useSmartAction(id: string, formMetadata: SmartFormMetadata | undefined, options: UseSmartActionOptions): ResolvedSmartAction | undefined;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mechanical dependency tracking only (doc §06/§10): watches the fields a
|
|
3
|
+
* piece of metadata declared via `dependsOn` and calls
|
|
4
|
+
* `onMetadataRefreshNeeded` once per field whose value actually changed
|
|
5
|
+
* since the last render. It never evaluates *why* a change matters — that
|
|
6
|
+
* stays server-owned; this hook only decides *when to ask again*.
|
|
7
|
+
*
|
|
8
|
+
* Uses the same "stable value-based identity" strategy as useRemoteData's
|
|
9
|
+
* `paramsKey` (serialize, compare by value, not by object/array reference)
|
|
10
|
+
* to avoid the exact infinite-loop failure mode the original architecture
|
|
11
|
+
* doc warns against for Remote's own params.
|
|
12
|
+
*/
|
|
13
|
+
export declare function useSmartDependencies(dependsOn: string[] | undefined, values: Record<string, unknown>, onMetadataRefreshNeeded?: (changedField: string) => void): void;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { SmartFieldMetadata, SmartFormMetadata } from './types';
|
|
2
|
+
export interface UseSmartFieldOptions {
|
|
3
|
+
values: Record<string, unknown>;
|
|
4
|
+
/** Absent = fall back to metadata's raw `label`/`helpText`/etc. strings. */
|
|
5
|
+
translate?: (key: string) => string;
|
|
6
|
+
}
|
|
7
|
+
export interface ResolvedSmartField<M extends SmartFieldMetadata = SmartFieldMetadata> {
|
|
8
|
+
metadata: M;
|
|
9
|
+
visible: boolean;
|
|
10
|
+
readOnly: boolean;
|
|
11
|
+
disabled: boolean;
|
|
12
|
+
required: boolean;
|
|
13
|
+
label?: string;
|
|
14
|
+
placeholder?: string;
|
|
15
|
+
helpText?: string;
|
|
16
|
+
validationMessage?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Only present for metadata carrying a `dataSource` (ComboBox/DataGrid).
|
|
19
|
+
* Resolved from `dataSource.params` + the current `values` snapshot —
|
|
20
|
+
* ready to pass straight through as a Remote component's `params` prop
|
|
21
|
+
* (doc §06). Field-name lookups that miss simply resolve to `undefined`,
|
|
22
|
+
* same as reading a missing key off any plain object.
|
|
23
|
+
*/
|
|
24
|
+
params?: Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolves one already-known metadata object. Pure function of its
|
|
28
|
+
* arguments — no React state, no effect, no fetch. Exported separately from
|
|
29
|
+
* `useSmartField` so a component-registry resolver that already holds a
|
|
30
|
+
* specific field's metadata (as `SmartResolver` hands it) can reuse this
|
|
31
|
+
* exact translate/params logic instead of re-deriving it (doc §04's
|
|
32
|
+
* resolvers and `useSmartField`'s by-name lookup are two entry points onto
|
|
33
|
+
* the same resolution, not two implementations).
|
|
34
|
+
*/
|
|
35
|
+
export declare function resolveSmartFieldMetadata<M extends SmartFieldMetadata = SmartFieldMetadata>(metadata: M, options: UseSmartFieldOptions): ResolvedSmartField<M>;
|
|
36
|
+
/**
|
|
37
|
+
* The headless engine every Smart component composes (doc §05/§18 —
|
|
38
|
+
* Smart Runtime owns no canonical state). Looks a field up by name in
|
|
39
|
+
* `formMetadata` and resolves it via `resolveSmartFieldMetadata`. Anything
|
|
40
|
+
* not present in the metadata map is left alone: a field with no matching
|
|
41
|
+
* metadata entry returns `undefined`, meaning "not driven by metadata," not
|
|
42
|
+
* "hidden."
|
|
43
|
+
*
|
|
44
|
+
* A future SmartField component will call this directly, the same way
|
|
45
|
+
* RemoteComboBox composes useRemoteData instead of writing its own effect.
|
|
46
|
+
*/
|
|
47
|
+
export declare function useSmartField<M extends SmartFieldMetadata = SmartFieldMetadata>(name: string, formMetadata: SmartFormMetadata | undefined, options: UseSmartFieldOptions): ResolvedSmartField<M> | undefined;
|