@cosmicdrift/kumiko-renderer 0.386.0 → 0.388.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 -57
- package/dist/app/action-form-body.d.ts +2 -2
- package/dist/app/action-form-body.js +12 -4
- package/dist/app/action-form-shim.d.ts +8 -8
- package/dist/app/action-form-shim.js +20 -22
- package/dist/app/action-form-support.d.ts +1 -0
- package/dist/app/action-form-support.js +1 -1
- package/dist/app/app-features-context.js +4 -4
- package/dist/app/column-renderers.d.ts +7 -9
- package/dist/app/column-renderers.js +15 -18
- package/dist/app/config-edit-body.js +7 -3
- package/dist/app/config-edit-shim.d.ts +7 -8
- package/dist/app/config-edit-shim.js +18 -20
- package/dist/app/custom-screens.d.ts +3 -3
- package/dist/app/custom-screens.js +8 -8
- package/dist/app/dashboard-body.d.ts +2 -2
- package/dist/app/dashboard-body.js +2 -2
- package/dist/app/entity-edit-prefill.d.ts +11 -0
- package/dist/app/entity-edit-prefill.js +18 -13
- package/dist/app/entity-edit-screen.js +4 -4
- package/dist/app/entity-list-screen.js +3 -3
- package/dist/app/extension-form-submit.d.ts +1 -1
- package/dist/app/extension-form-submit.js +14 -15
- package/dist/app/extension-sections.d.ts +29 -28
- package/dist/app/extension-sections.js +10 -10
- package/dist/app/feature-schema.js +11 -11
- package/dist/app/form-schema.js +2 -2
- package/dist/app/header-actions-bar.js +2 -2
- package/dist/app/kumiko-screen.js +8 -10
- package/dist/app/layout-fields.js +1 -1
- package/dist/app/list-facets.js +3 -3
- package/dist/app/nav.d.ts +10 -10
- package/dist/app/nav.js +13 -13
- package/dist/app/projection-detail-body.js +4 -4
- package/dist/app/projection-detail-shim.d.ts +6 -5
- package/dist/app/projection-detail-shim.js +23 -22
- package/dist/app/projection-list-body.js +10 -10
- package/dist/app/projection-list-shim.d.ts +5 -5
- package/dist/app/projection-list-shim.js +17 -18
- package/dist/app/qn.js +10 -12
- package/dist/app/qualify-screen-id.js +1 -1
- package/dist/app/screen-access.js +2 -2
- package/dist/app/screen-shared.js +1 -1
- package/dist/app/secret-mint-body.js +4 -4
- package/dist/app/write-failed-error.js +8 -7
- package/dist/components/edit-mounts.d.ts +2 -2
- package/dist/components/embedded-list-field.js +5 -5
- package/dist/components/grid-cell-for-field.d.ts +1 -1
- package/dist/components/reference-create-dialog.js +3 -3
- package/dist/components/related-list-section.d.ts +1 -1
- package/dist/components/related-list-section.js +15 -2
- package/dist/components/render-edit-action-button.d.ts +1 -3
- package/dist/components/render-edit-action-button.js +2 -2
- package/dist/components/render-edit-logic.js +1 -1
- package/dist/components/render-edit-types.d.ts +6 -6
- package/dist/components/render-edit.js +74 -73
- package/dist/components/render-field.d.ts +7 -7
- package/dist/components/render-field.js +62 -61
- package/dist/components/render-list.d.ts +42 -43
- package/dist/components/render-list.js +93 -84
- package/dist/components/use-wizard-form-draft.js +10 -10
- package/dist/context/dispatcher-context.js +5 -5
- package/dist/context/user-roles-context.js +2 -2
- package/dist/flow-diagram/render.js +9 -1
- package/dist/headless/apex/index.d.ts +1 -2
- package/dist/headless/apex/index.js +5 -6
- package/dist/headless/form/form-controller.js +14 -6
- package/dist/headless/form/types.d.ts +1 -0
- package/dist/headless/form/zod-bridge.js +2 -2
- package/dist/headless/format/index.js +89 -64
- package/dist/headless/format/money.js +1 -1
- package/dist/headless/index.js +2 -2
- package/dist/headless/view-model/edit.js +4 -2
- package/dist/headless/view-model/list.js +184 -150
- package/dist/headless/view-model/types.d.ts +6 -3
- package/dist/hooks/live-refetch-scheduler.js +4 -0
- package/dist/hooks/reference-limits.js +11 -12
- package/dist/hooks/use-list-url-state.d.ts +13 -13
- package/dist/hooks/use-list-url-state.js +21 -22
- package/dist/hooks/use-query.js +3 -3
- package/dist/hooks/use-reference-lookup.d.ts +9 -1
- package/dist/hooks/use-reference-lookup.js +32 -19
- package/dist/hooks/use-store.js +18 -22
- package/dist/hooks/use-stream-handler.js +4 -2
- package/dist/i18n-defaults.js +6 -8
- package/dist/i18n.d.ts +10 -10
- package/dist/i18n.js +25 -25
- package/dist/index.d.ts +1 -0
- package/dist/index.js +6 -4
- package/dist/live/dispatcher-live.js +2 -2
- package/dist/primitives.d.ts +244 -221
- package/dist/primitives.js +1 -1
- package/dist/sse/live-events.d.ts +5 -5
- package/dist/sse/live-events.js +17 -17
- package/dist/tokens.d.ts +8 -8
- package/dist/tokens.js +16 -17
- package/dist/web/app/browser-locale.d.ts +9 -9
- package/dist/web/app/browser-locale.js +19 -19
- package/dist/web/app/client-plugin.d.ts +44 -52
- package/dist/web/app/client-plugin.js +4 -5
- package/dist/web/app/create-app.d.ts +28 -31
- package/dist/web/app/create-app.js +29 -25
- package/dist/web/app/dashboard-body.js +17 -1
- package/dist/web/app/dashboard-panels.js +12 -2
- package/dist/web/app/document-lang-sync.js +1 -1
- package/dist/web/app/draft-storage.js +1 -1
- package/dist/web/app/nav-providers-context.d.ts +3 -3
- package/dist/web/app/nav-providers-context.js +3 -3
- package/dist/web/app/nav.d.ts +18 -17
- package/dist/web/app/nav.js +66 -64
- package/dist/web/app/rich-content-editor.js +1 -1
- package/dist/web/components/config-cascade.js +17 -18
- package/dist/web/components/config-source-badge.js +4 -4
- package/dist/web/index.d.ts +1 -1
- package/dist/web/index.js +1 -1
- package/dist/web/layout/app-layout.d.ts +9 -9
- package/dist/web/layout/app-layout.js +2 -2
- package/dist/web/layout/avatar.d.ts +4 -3
- package/dist/web/layout/avatar.js +15 -17
- package/dist/web/layout/default-app-shell.d.ts +22 -23
- package/dist/web/layout/editor-panel.js +3 -3
- package/dist/web/layout/nav-tree.js +86 -84
- package/dist/web/layout/profile-menu.d.ts +1 -1
- package/dist/web/layout/shell-header.js +2 -1
- package/dist/web/layout/sidebar-brand.d.ts +7 -7
- package/dist/web/layout/sidebar-user.d.ts +1 -1
- package/dist/web/layout/sidebar-user.js +4 -4
- package/dist/web/layout/sidebar.d.ts +8 -8
- package/dist/web/layout/sidebar.js +7 -7
- package/dist/web/layout/target-resolver-stub.d.ts +5 -5
- package/dist/web/layout/target-resolver-stub.js +20 -20
- package/dist/web/layout/target-url.d.ts +7 -7
- package/dist/web/layout/target-url.js +22 -22
- package/dist/web/layout/theme-toggle.js +4 -5
- package/dist/web/layout/uniform-bar-controls.d.ts +1 -0
- package/dist/web/layout/uniform-bar-controls.js +4 -0
- package/dist/web/layout/workspace-shell.d.ts +21 -21
- package/dist/web/layout/workspace-shell.js +29 -30
- package/dist/web/lib/cn.d.ts +4 -4
- package/dist/web/lib/cn.js +4 -4
- package/dist/web/lib/file-ref-download.js +4 -1
- package/dist/web/primitives/action-menu.d.ts +8 -8
- package/dist/web/primitives/action-menu.js +14 -13
- package/dist/web/primitives/calendar-popover.d.ts +1 -1
- package/dist/web/primitives/calendar-popover.js +10 -10
- package/dist/web/primitives/card-styles.js +1 -1
- package/dist/web/primitives/card.js +2 -2
- package/dist/web/primitives/code-input.d.ts +6 -0
- package/dist/web/primitives/code-input.js +31 -0
- package/dist/web/primitives/combobox.d.ts +12 -12
- package/dist/web/primitives/combobox.js +34 -35
- package/dist/web/primitives/data-table-cell.js +2 -2
- package/dist/web/primitives/data-table.js +6 -6
- package/dist/web/primitives/date-field.js +2 -2
- package/dist/web/primitives/date-input.js +4 -4
- package/dist/web/primitives/default-form.js +2 -2
- package/dist/web/primitives/dropdown-menu.js +14 -15
- package/dist/web/primitives/embedded-list-input.js +3 -3
- package/dist/web/primitives/facet-filter.js +2 -2
- package/dist/web/primitives/field.js +9 -8
- package/dist/web/primitives/file-thumbnail.d.ts +3 -0
- package/dist/web/primitives/file-thumbnail.js +5 -0
- package/dist/web/primitives/form.d.ts +1 -1
- package/dist/web/primitives/form.js +6 -6
- package/dist/web/primitives/input.js +8 -3
- package/dist/web/primitives/layout-elements.js +7 -5
- package/dist/web/primitives/layout.js +2 -2
- package/dist/web/primitives/located-timestamp-input.js +4 -4
- package/dist/web/primitives/money-input.js +13 -17
- package/dist/web/primitives/page-header.js +3 -1
- package/dist/web/primitives/pager.js +1 -1
- package/dist/web/primitives/row-actions.js +6 -7
- package/dist/web/primitives/secret-reveal.js +1 -1
- package/dist/web/primitives/timestamp-input.d.ts +9 -9
- package/dist/web/primitives/timestamp-input.js +21 -21
- package/dist/web/primitives/toast.js +12 -13
- package/dist/web/primitives/use-focus-on-mount.d.ts +2 -0
- package/dist/web/primitives/use-focus-on-mount.js +9 -0
- package/dist/web/sse/live-events.d.ts +6 -6
- package/dist/web/sse/live-events.js +3 -3
- package/dist/web/tokens.d.ts +11 -12
- package/dist/web/tokens.js +35 -37
- package/dist/web/version/update-checker.js +14 -13
- package/dist/web/widgets/ai-text-field.js +1 -1
- package/dist/web/widgets/bounded-load-cache.d.ts +1 -1
- package/dist/web/widgets/bounded-load-cache.js +10 -4
- package/dist/web/widgets/charts.d.ts +10 -10
- package/dist/web/widgets/charts.js +8 -8
- package/dist/web/widgets/choice-cards.d.ts +17 -0
- package/dist/web/widgets/choice-cards.js +12 -0
- package/dist/web/widgets/collapsible-section.d.ts +3 -3
- package/dist/web/widgets/collapsible-section.js +7 -7
- package/dist/web/widgets/detail-list.d.ts +5 -3
- package/dist/web/widgets/detail-list.js +5 -3
- package/dist/web/widgets/feed-list.d.ts +2 -2
- package/dist/web/widgets/feed-list.js +2 -2
- package/dist/web/widgets/form-fields.d.ts +14 -14
- package/dist/web/widgets/form-fields.js +14 -13
- package/dist/web/widgets/index.d.ts +1 -0
- package/dist/web/widgets/index.js +5 -4
- package/dist/web/widgets/infinity-list.js +3 -3
- package/dist/web/widgets/mode-switch.d.ts +2 -2
- package/dist/web/widgets/mode-switch.js +2 -2
- package/dist/web/widgets/pdf-preview.js +1 -1
- package/dist/web/widgets/plan-card.d.ts +2 -0
- package/dist/web/widgets/plan-card.js +1 -1
- package/dist/web/widgets/progress-list.d.ts +1 -2
- package/dist/web/widgets/progress-list.js +1 -2
- package/dist/web/widgets/query-table.d.ts +6 -6
- package/dist/web/widgets/result-panel.d.ts +16 -16
- package/dist/web/widgets/result-panel.js +10 -10
- package/dist/web/widgets/section-card.d.ts +3 -3
- package/dist/web/widgets/section-card.js +3 -3
- package/dist/web/widgets/stat.d.ts +4 -4
- package/dist/web/widgets/stat.js +6 -6
- package/dist/web/widgets/states.d.ts +4 -4
- package/dist/web/widgets/states.js +4 -4
- package/dist/web/widgets/upload-zone.js +13 -5
- package/dist/web/widgets/use-draft.d.ts +7 -7
- package/dist/web/widgets/use-draft.js +7 -7
- package/package.json +6 -6
- package/src/changes.json +49 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
/** Result section for a calculator: SectionCard with an empty state (Banner)
|
|
3
|
+
* or a metric list (DetailList) plus optional extras (table, notes).
|
|
4
|
+
* Replaces hand-built `<dl>`/banner blocks in custom screens. */
|
|
5
5
|
export declare function ResultPanel({ title, subtitle, empty, emptyText, rows, footer, children, testId, }: {
|
|
6
6
|
readonly title: string;
|
|
7
7
|
readonly subtitle?: string;
|
|
@@ -12,7 +12,7 @@ export declare function ResultPanel({ title, subtitle, empty, emptyText, rows, f
|
|
|
12
12
|
readonly value: ReactNode;
|
|
13
13
|
readonly emphasize?: boolean;
|
|
14
14
|
}[];
|
|
15
|
-
/** Action
|
|
15
|
+
/** Action slot at the bottom of the card (e.g. "Move to financing"). */
|
|
16
16
|
readonly footer?: ReactNode;
|
|
17
17
|
readonly children?: ReactNode;
|
|
18
18
|
readonly testId?: string;
|
|
@@ -27,18 +27,18 @@ export interface ResultTableFooterRow {
|
|
|
27
27
|
readonly value: ReactNode;
|
|
28
28
|
readonly emphasize?: boolean;
|
|
29
29
|
}
|
|
30
|
-
/**
|
|
31
|
-
*
|
|
32
|
-
* ponytail:
|
|
33
|
-
*
|
|
30
|
+
/** Static, prop-driven result table for calculated rows (tranches, scenarios).
|
|
31
|
+
* Avoids repeating the `<table>`+tabular-nums pattern in each screen.
|
|
32
|
+
* ponytail: intentionally minimal and read-only—for sorting, paging, or facets,
|
|
33
|
+
* use usePrimitives().DataTable or QueryTable instead. */
|
|
34
34
|
export declare function ResultTable<Row>({ columns, rows, rowKey, testId, className, card, footer, }: {
|
|
35
35
|
readonly columns: readonly ResultColumn<Row>[];
|
|
36
36
|
readonly rows: readonly Row[];
|
|
37
37
|
readonly rowKey: (row: Row, index: number) => string;
|
|
38
38
|
readonly testId?: string;
|
|
39
39
|
readonly className?: string;
|
|
40
|
-
/**
|
|
41
|
-
* bg-muted
|
|
40
|
+
/** Framed appearance like the CRUD list (DataTable): rounded border container +
|
|
41
|
+
* bg-muted header band. Default = bare (table only, without a container). */
|
|
42
42
|
readonly card?: boolean;
|
|
43
43
|
/** Summary rows (Subtotal/Tax/Total) rendered as real `<tr>`s in the same
|
|
44
44
|
* `<table>` instead of a separate flex-div next to it — guarantees
|
|
@@ -49,12 +49,12 @@ export declare function ResultTable<Row>({ columns, rows, rowKey, testId, classN
|
|
|
49
49
|
export interface ComparisonMetric<Col> {
|
|
50
50
|
readonly label: string;
|
|
51
51
|
readonly value: (col: Col, index: number) => ReactNode;
|
|
52
|
-
/** Index
|
|
52
|
+
/** Index of the best column for this row (highlighted); -1 = none. */
|
|
53
53
|
readonly bestIndex?: (cols: readonly Col[]) => number;
|
|
54
54
|
}
|
|
55
|
-
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
55
|
+
/** Transposed comparison table: row = metric, column = variant, with the best
|
|
56
|
+
* column optionally highlighted for each metric. For scenario/offer comparisons
|
|
57
|
+
* where ResultTable (row = record) is not suitable. */
|
|
58
58
|
export declare function ComparisonTable<Col>({ columns, columnHeader, columnKey, metrics, metricLabel, testId, className, card, }: {
|
|
59
59
|
readonly columns: readonly Col[];
|
|
60
60
|
readonly columnHeader: (col: Col, index: number) => string;
|
|
@@ -63,7 +63,7 @@ export declare function ComparisonTable<Col>({ columns, columnHeader, columnKey,
|
|
|
63
63
|
readonly metricLabel: string;
|
|
64
64
|
readonly testId?: string;
|
|
65
65
|
readonly className?: string;
|
|
66
|
-
/**
|
|
67
|
-
* bg-muted
|
|
66
|
+
/** Framed appearance like the CRUD list (DataTable): rounded border container +
|
|
67
|
+
* bg-muted header band. Default = bare. */
|
|
68
68
|
readonly card?: boolean;
|
|
69
69
|
}): ReactNode;
|
|
@@ -3,24 +3,24 @@ import { usePrimitives } from "../../index.js";
|
|
|
3
3
|
import { cn } from "../lib/cn.js";
|
|
4
4
|
import { DetailList } from "./detail-list.js";
|
|
5
5
|
import { SectionCard } from "./section-card.js";
|
|
6
|
-
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
/** Result section for a calculator: SectionCard with an empty state (Banner)
|
|
7
|
+
* or a metric list (DetailList) plus optional extras (table, notes).
|
|
8
|
+
* Replaces hand-built `<dl>`/banner blocks in custom screens. */
|
|
9
9
|
export function ResultPanel({ title, subtitle, empty, emptyText, rows, footer, children, testId, }) {
|
|
10
10
|
const { Banner } = usePrimitives();
|
|
11
11
|
return (_jsx(SectionCard, { title: title, subtitle: subtitle, footer: footer, testId: testId, children: empty === true ? (_jsx(Banner, { variant: "info", padded: true, children: emptyText })) : (_jsxs("div", { className: "flex flex-col gap-4", children: [rows !== undefined && rows.length > 0 && _jsx(DetailList, { rows: rows }), children] })) }));
|
|
12
12
|
}
|
|
13
|
-
/**
|
|
14
|
-
*
|
|
15
|
-
* ponytail:
|
|
16
|
-
*
|
|
13
|
+
/** Static, prop-driven result table for calculated rows (tranches, scenarios).
|
|
14
|
+
* Avoids repeating the `<table>`+tabular-nums pattern in each screen.
|
|
15
|
+
* ponytail: intentionally minimal and read-only—for sorting, paging, or facets,
|
|
16
|
+
* use usePrimitives().DataTable or QueryTable instead. */
|
|
17
17
|
export function ResultTable({ columns, rows, rowKey, testId, className, card, footer, }) {
|
|
18
18
|
const table = (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { "data-testid": testId, className: cn("w-full text-sm", className), children: [_jsx("thead", { className: cn(card === true && "bg-muted"), children: _jsx("tr", { className: "border-b text-left text-muted-foreground", children: columns.map((col) => (_jsx("th", { className: cn("py-1.5 font-medium", card === true && "px-3", col.align === "right" && "text-right"), children: col.header }, col.header))) }) }), _jsxs("tbody", { children: [rows.map((row, i) => (_jsx("tr", { className: "border-b last:border-0", children: columns.map((col) => (_jsx("td", { className: cn("py-1.5", card === true && "px-3", col.align === "right" && "text-right tabular-nums"), children: col.cell(row) }, col.header))) }, rowKey(row, i)))), footer?.map((row, i) => (_jsxs("tr", { className: cn(i === 0 && "border-t", row.emphasize === true && "font-semibold"), children: [_jsxs("td", { colSpan: columns.length > 1 ? Math.max(columns.length - 1, 1) : undefined, className: cn("py-1.5 text-muted-foreground", card === true && "px-3", row.emphasize === true && "text-foreground", columns.length === 1 && "flex items-center justify-between gap-4"), children: [row.label, columns.length === 1 && (_jsx("span", { className: cn("text-right tabular-nums", row.emphasize === true && "text-foreground"), children: row.value }))] }), columns.length > 1 && (_jsx("td", { className: cn("py-1.5 text-right tabular-nums", card === true && "px-3"), children: row.value }))] }, row.label)))] })] }) }));
|
|
19
19
|
return card === true ? (_jsx("div", { className: "overflow-hidden rounded-lg border bg-card", children: table })) : (table);
|
|
20
20
|
}
|
|
21
|
-
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
/** Transposed comparison table: row = metric, column = variant, with the best
|
|
22
|
+
* column optionally highlighted for each metric. For scenario/offer comparisons
|
|
23
|
+
* where ResultTable (row = record) is not suitable. */
|
|
24
24
|
export function ComparisonTable({ columns, columnHeader, columnKey, metrics, metricLabel, testId, className, card, }) {
|
|
25
25
|
const table = (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { "data-testid": testId, className: cn("w-full min-w-[24rem] text-sm", className), children: [_jsx("thead", { className: cn(card === true && "bg-muted"), children: _jsxs("tr", { className: "border-b text-left text-muted-foreground", children: [_jsx("th", { className: cn("py-1.5 font-medium", card === true && "px-3"), children: metricLabel }), columns.map((col, i) => (_jsx("th", { className: cn("py-1.5 text-right font-medium", card === true && "px-3"), children: columnHeader(col, i) }, columnKey(col, i))))] }) }), _jsx("tbody", { children: metrics.map((metric) => {
|
|
26
26
|
const best = metric.bestIndex !== undefined ? metric.bestIndex(columns) : -1;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
/** Section with a title and optional action slot (range switcher, filter) —
|
|
3
|
+
* replaces repeated `<section className={CARD}>…<h2>` blocks in
|
|
4
|
+
* custom screens. Thin wrapper around the Card primitive. */
|
|
5
5
|
export declare function SectionCard({ title, subtitle, action, footer, children, testId, }: {
|
|
6
6
|
readonly title: string;
|
|
7
7
|
readonly subtitle?: string;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { usePrimitives } from "../../index.js";
|
|
3
|
-
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
/** Section with a title and optional action slot (range switcher, filter) —
|
|
4
|
+
* replaces repeated `<section className={CARD}>…<h2>` blocks in
|
|
5
|
+
* custom screens. Thin wrapper around the Card primitive. */
|
|
6
6
|
export function SectionCard({ title, subtitle, action, footer, children, testId, }) {
|
|
7
7
|
const { Card } = usePrimitives();
|
|
8
8
|
return (_jsx(Card, { slots: { title, subtitle, headerActions: action, footer }, testId: testId, children: _jsx("div", { className: "flex h-full flex-col gap-4", children: children }) }));
|
|
@@ -10,8 +10,8 @@ export declare function Sparkline({ points, className, }: {
|
|
|
10
10
|
readonly points: readonly number[];
|
|
11
11
|
readonly className?: string;
|
|
12
12
|
}): ReactNode;
|
|
13
|
-
/** Icon-
|
|
14
|
-
*
|
|
13
|
+
/** Icon-bearing metric tile with an optional delta chip, trend line,
|
|
14
|
+
* and sparkline. `icon` is a ready-made node (the app supplies its SVG). */
|
|
15
15
|
export declare function StatCard({ icon, label, value, sub, tone, accentColor, delta, trend, spark, testId, children, }: {
|
|
16
16
|
readonly icon?: ReactNode;
|
|
17
17
|
readonly label: string;
|
|
@@ -41,8 +41,8 @@ export declare function StatStripCell({ icon, label, value, sub, tone, accentCol
|
|
|
41
41
|
readonly spark?: readonly number[];
|
|
42
42
|
readonly testId?: string;
|
|
43
43
|
}): ReactNode;
|
|
44
|
-
/**
|
|
45
|
-
* StatCard
|
|
44
|
+
/** Compact metric tile (without an icon) — for dense KPI grids.
|
|
45
|
+
* StatCard is the icon-bearing variant. */
|
|
46
46
|
export declare function MiniStat({ label, value, tone, emphasize, testId, labelTestId, valueTestId, }: {
|
|
47
47
|
readonly label: string;
|
|
48
48
|
readonly value: string;
|
package/dist/web/widgets/stat.js
CHANGED
|
@@ -15,8 +15,8 @@ const TONE_VALUE = {
|
|
|
15
15
|
warn: "text-destructive",
|
|
16
16
|
negative: "text-status-bad",
|
|
17
17
|
};
|
|
18
|
-
// Inline
|
|
19
|
-
//
|
|
18
|
+
// Inline SVG sparkline (no chart dependency for a 28px curve): line +
|
|
19
|
+
// subtle area gradient, colored via currentColor (caller sets text-tone).
|
|
20
20
|
export function Sparkline({ points, className, }) {
|
|
21
21
|
if (points.length < 2)
|
|
22
22
|
return null;
|
|
@@ -42,8 +42,8 @@ function StatIconChip({ icon, tone, accentColor, }) {
|
|
|
42
42
|
backgroundColor: `color-mix(in srgb, ${accentColor} 12%, transparent)`,
|
|
43
43
|
}, children: icon }));
|
|
44
44
|
}
|
|
45
|
-
/** Icon-
|
|
46
|
-
*
|
|
45
|
+
/** Icon-bearing metric tile with an optional delta chip, trend line,
|
|
46
|
+
* and sparkline. `icon` is a ready-made node (the app supplies its SVG). */
|
|
47
47
|
export function StatCard({ icon, label, value, sub, tone = "default", accentColor, delta, trend, spark, testId, children, }) {
|
|
48
48
|
const { Card } = usePrimitives();
|
|
49
49
|
// StatCard is a reusable small-tile widget (a metrics dashboard grid, not
|
|
@@ -57,8 +57,8 @@ export function StatCard({ icon, label, value, sub, tone = "default", accentColo
|
|
|
57
57
|
export function StatStripCell({ icon, label, value, sub, tone = "default", accentColor, delta, spark, testId, }) {
|
|
58
58
|
return (_jsxs("div", { "data-testid": testId, className: "flex min-w-0 flex-col gap-1 px-4 py-3", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-x-2 gap-y-1", children: [_jsxs("span", { className: "flex min-w-0 items-center gap-2 text-xs font-medium text-muted-foreground", children: [icon !== undefined && _jsx(StatIconChip, { icon: icon, tone: tone, accentColor: accentColor }), _jsx("span", { children: label })] }), delta !== undefined && (_jsxs("span", { className: cn("inline-flex shrink-0 items-center gap-0.5 whitespace-nowrap rounded-full px-1.5 py-0.5 text-xs font-medium tabular-nums", TONE_CHIP[delta.tone ?? tone]), children: [delta.direction === "up" ? "↑" : "↓", delta.value] }))] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: cn("text-xl font-semibold tabular-nums", TONE_VALUE[tone]), children: value }), spark !== undefined && (_jsx(Sparkline, { points: spark, className: cn("h-7 w-20 shrink-0", TONE_VALUE[tone]) }))] }), sub !== undefined && _jsx("div", { className: "text-xs text-muted-foreground", children: sub })] }));
|
|
59
59
|
}
|
|
60
|
-
/**
|
|
61
|
-
* StatCard
|
|
60
|
+
/** Compact metric tile (without an icon) — for dense KPI grids.
|
|
61
|
+
* StatCard is the icon-bearing variant. */
|
|
62
62
|
export function MiniStat({ label, value, tone = "default", emphasize = false, testId, labelTestId, valueTestId, }) {
|
|
63
63
|
const { Card } = usePrimitives();
|
|
64
64
|
return (_jsxs(Card, { options: { padded: false }, className: cn("p-3", emphasize && "ring-1 ring-primary/30"), testId: testId, children: [_jsx("div", { className: "text-xs text-muted-foreground", "data-testid": labelTestId, children: label }), _jsx("div", { className: cn("mt-0.5 font-semibold tabular-nums", TONE_VALUE[tone], emphasize ? "text-lg" : "text-sm"), "data-testid": valueTestId, children: value })] }));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import type { DispatcherError } from "../../headless/index.ts";
|
|
3
|
-
/**
|
|
4
|
-
* entityList
|
|
3
|
+
/** Empty state with an optional icon and CTA, using the dashed-box style of
|
|
4
|
+
* entityList empty states as a standalone widget for custom screens. */
|
|
5
5
|
export declare function EmptyState({ icon, title, description, action, testId, }: {
|
|
6
6
|
readonly icon?: ReactNode;
|
|
7
7
|
readonly title: string;
|
|
@@ -9,13 +9,13 @@ export declare function EmptyState({ icon, title, description, action, testId, }
|
|
|
9
9
|
readonly action?: ReactNode;
|
|
10
10
|
readonly testId?: string;
|
|
11
11
|
}): ReactNode;
|
|
12
|
-
/**
|
|
12
|
+
/** Loading state with skeleton rows styled like cards; `rows` controls the height. */
|
|
13
13
|
export declare function LoadingState({ rows, className, testId, }: {
|
|
14
14
|
readonly rows?: number;
|
|
15
15
|
readonly className?: string;
|
|
16
16
|
readonly testId?: string;
|
|
17
17
|
}): ReactNode;
|
|
18
|
-
/**
|
|
18
|
+
/** Error state based on the Banner primitive, with a retry button. */
|
|
19
19
|
export declare function ErrorState({ error, onRetry, testId, }: {
|
|
20
20
|
readonly error: DispatcherError;
|
|
21
21
|
readonly onRetry?: () => void;
|
|
@@ -2,17 +2,17 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { dispatcherErrorText, usePrimitives, useTranslation } from "../../index.js";
|
|
3
3
|
import { cn } from "../lib/cn.js";
|
|
4
4
|
import { Skeleton } from "../ui/skeleton.js";
|
|
5
|
-
/**
|
|
6
|
-
* entityList
|
|
5
|
+
/** Empty state with an optional icon and CTA, using the dashed-box style of
|
|
6
|
+
* entityList empty states as a standalone widget for custom screens. */
|
|
7
7
|
export function EmptyState({ icon, title, description, action, testId, }) {
|
|
8
8
|
return (_jsxs("div", { "data-testid": testId, className: "flex flex-col items-center justify-center gap-2 rounded-lg border border-dashed px-6 py-10 text-center", children: [icon !== undefined && _jsx("div", { className: "text-muted-foreground", children: icon }), _jsx("div", { className: "text-sm font-medium", children: title }), description !== undefined && (_jsx("div", { className: "text-sm text-muted-foreground", children: description })), action !== undefined && _jsx("div", { className: "mt-2", children: action })] }));
|
|
9
9
|
}
|
|
10
|
-
/**
|
|
10
|
+
/** Loading state with skeleton rows styled like cards; `rows` controls the height. */
|
|
11
11
|
export function LoadingState({ rows = 3, className, testId, }) {
|
|
12
12
|
const t = useTranslation();
|
|
13
13
|
return (_jsx("output", { "data-testid": testId, "aria-label": t("kumiko.widget.loading"), className: cn("flex w-full flex-col gap-2", className), children: Array.from({ length: rows }, (_, i) => (_jsx(Skeleton, { className: "h-9 w-full" }, i))) }));
|
|
14
14
|
}
|
|
15
|
-
/**
|
|
15
|
+
/** Error state based on the Banner primitive, with a retry button. */
|
|
16
16
|
export function ErrorState({ error, onRetry, testId, }) {
|
|
17
17
|
const { Banner, Button } = usePrimitives();
|
|
18
18
|
const t = useTranslation();
|
|
@@ -114,6 +114,18 @@ export function UploadZone({ onUpload, title, hint, accept, multiple = true, cap
|
|
|
114
114
|
if (row.value !== undefined)
|
|
115
115
|
onRemove?.(row.value);
|
|
116
116
|
}
|
|
117
|
+
// Values the owner already held before any upload of this zone (editing a record with files).
|
|
118
|
+
// Hidden while an upload runs: its value reaches `doneValues` one render before its row is done.
|
|
119
|
+
const uploading = rows.some((row) => row.status === "uploading");
|
|
120
|
+
const existingRows = doneValues === undefined || uploading
|
|
121
|
+
? []
|
|
122
|
+
: doneValues
|
|
123
|
+
.filter((value) => !rows.some((row) => row.value === value))
|
|
124
|
+
.map((value) => ({ id: `existing:${value}`, fileName: value, status: "done", value }));
|
|
125
|
+
const visibleRows = [
|
|
126
|
+
...existingRows,
|
|
127
|
+
...rows.filter((row) => row.value === undefined || doneValues === undefined || doneValues.includes(row.value)),
|
|
128
|
+
];
|
|
117
129
|
const acceptAttr = toAcceptAttr(accept);
|
|
118
130
|
return (_jsxs("div", { "data-testid": testId, className: "flex flex-col gap-4", children: [_jsxs("label", { htmlFor: inputId, onDragOver: (e) => {
|
|
119
131
|
e.preventDefault();
|
|
@@ -123,9 +135,5 @@ export function UploadZone({ onUpload, title, hint, accept, multiple = true, cap
|
|
|
123
135
|
if (!(e.relatedTarget instanceof Node) || !e.currentTarget.contains(e.relatedTarget)) {
|
|
124
136
|
setDragOver(false);
|
|
125
137
|
}
|
|
126
|
-
}, onDrop: handleDrop, "data-testid": testId !== undefined ? `${testId}-dropzone` : undefined, className: cn("flex w-full cursor-pointer flex-col items-center gap-3 rounded-lg border-2 border-dashed p-10 text-center transition-colors has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-ring has-[:focus-visible]:ring-offset-2", dragOver ? "border-primary bg-muted/40" : "border-border", disabled === true && "cursor-not-allowed opacity-50"), children: [_jsx(Upload, { className: "size-8 text-muted-foreground", "aria-hidden": "true" }), _jsx("span", { className: "text-sm", children: title }), hint !== undefined && _jsx("span", { className: "text-sm text-muted-foreground", children: hint }), _jsx("input", { ref: inputRef, id: inputId, type: "file", multiple: multiple, className: "sr-only", "data-testid": testId !== undefined ? `${testId}-input` : undefined, disabled: disabled, ...(acceptAttr !== undefined && { accept: acceptAttr }), ...(capture !== undefined && { capture }), onChange: (e) => void uploadFiles(e.target.files) })] }),
|
|
127
|
-
.filter((row) => row.value === undefined ||
|
|
128
|
-
doneValues === undefined ||
|
|
129
|
-
doneValues.includes(row.value))
|
|
130
|
-
.map((row) => (_jsxs("li", { "data-testid": testId !== undefined ? `${testId}-row` : undefined, className: "flex items-center justify-between gap-3 rounded-md border border-border px-4 py-2", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx(FileUp, { className: "size-4 shrink-0 text-muted-foreground", "aria-hidden": "true" }), _jsx("span", { className: "truncate text-sm", children: row.fileName })] }), _jsxs("output", { className: "flex shrink-0 items-center gap-1.5 text-xs text-muted-foreground", children: [STATUS_ICON[row.status], _jsx("span", { className: "sr-only", children: t(STATUS_LABEL_KEY[row.status]) }), row.status === "error" && row.error !== undefined && _jsx("span", { children: row.error }), row.value !== undefined && onRemove !== undefined && disabled !== true && (_jsx("button", { type: "button", className: "rounded-sm p-0.5 text-muted-foreground hover:text-foreground", "aria-label": t("kumiko.widget.upload.remove"), "data-testid": testId !== undefined ? `${testId}-remove` : undefined, onClick: () => removeRow(row), children: _jsx(X, { className: "size-3.5", "aria-hidden": "true" }) }))] })] }, row.id))) }))] }));
|
|
138
|
+
}, onDrop: handleDrop, "data-testid": testId !== undefined ? `${testId}-dropzone` : undefined, className: cn("flex w-full cursor-pointer flex-col items-center gap-3 rounded-lg border-2 border-dashed p-10 text-center transition-colors has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-ring has-[:focus-visible]:ring-offset-2", dragOver ? "border-primary bg-muted/40" : "border-border", disabled === true && "cursor-not-allowed opacity-50"), children: [_jsx(Upload, { className: "size-8 text-muted-foreground", "aria-hidden": "true" }), _jsx("span", { className: "text-sm", children: title }), hint !== undefined && _jsx("span", { className: "text-sm text-muted-foreground", children: hint }), _jsx("input", { ref: inputRef, id: inputId, type: "file", multiple: multiple, className: "sr-only", "data-testid": testId !== undefined ? `${testId}-input` : undefined, disabled: disabled, ...(acceptAttr !== undefined && { accept: acceptAttr }), ...(capture !== undefined && { capture }), onChange: (e) => void uploadFiles(e.target.files) })] }), visibleRows.length > 0 && (_jsx("ul", { className: "flex flex-col gap-2", children: visibleRows.map((row) => (_jsxs("li", { "data-testid": testId !== undefined ? `${testId}-row` : undefined, className: "flex items-center justify-between gap-3 rounded-md border border-border px-4 py-2", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx(FileUp, { className: "size-4 shrink-0 text-muted-foreground", "aria-hidden": "true" }), _jsx("span", { className: "truncate text-sm", children: row.fileName })] }), _jsxs("output", { className: "flex shrink-0 items-center gap-1.5 text-xs text-muted-foreground", children: [STATUS_ICON[row.status], _jsx("span", { className: "sr-only", children: t(STATUS_LABEL_KEY[row.status]) }), row.status === "error" && row.error !== undefined && _jsx("span", { children: row.error }), row.value !== undefined && onRemove !== undefined && disabled !== true && (_jsx("button", { type: "button", className: "rounded-sm p-0.5 text-muted-foreground hover:text-foreground", "aria-label": t("kumiko.widget.upload.remove"), "data-testid": testId !== undefined ? `${testId}-remove` : undefined, onClick: () => removeRow(row), children: _jsx(X, { className: "size-3.5", "aria-hidden": "true" }) }))] })] }, row.id))) }))] }));
|
|
131
139
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* `idPrefix`
|
|
6
|
-
*
|
|
7
|
-
*
|
|
1
|
+
/** Form state for calculator screens: a draft object plus `patch` for
|
|
2
|
+
* partial updates, and `field(name)` that provides `{ id, name, value, onChange }`
|
|
3
|
+
* props ready for field widgets (NumberField/MoneyField/PercentField).
|
|
4
|
+
* Replaces the repeated draft interface and patch helper in each screen.
|
|
5
|
+
* `idPrefix` prevents duplicate DOM `id`s when multiple `useDraft` forms with
|
|
6
|
+
* same-named fields are mounted on the same page; `name` remains the plain key
|
|
7
|
+
* for the form payload. */
|
|
8
8
|
export declare function useDraft<T extends object>(defaults: T, options?: {
|
|
9
9
|
readonly idPrefix?: string;
|
|
10
10
|
}): {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { useCallback, useState } from "react";
|
|
2
|
-
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `idPrefix`
|
|
7
|
-
*
|
|
8
|
-
*
|
|
2
|
+
/** Form state for calculator screens: a draft object plus `patch` for
|
|
3
|
+
* partial updates, and `field(name)` that provides `{ id, name, value, onChange }`
|
|
4
|
+
* props ready for field widgets (NumberField/MoneyField/PercentField).
|
|
5
|
+
* Replaces the repeated draft interface and patch helper in each screen.
|
|
6
|
+
* `idPrefix` prevents duplicate DOM `id`s when multiple `useDraft` forms with
|
|
7
|
+
* same-named fields are mounted on the same page; `name` remains the plain key
|
|
8
|
+
* for the form payload. */
|
|
9
9
|
export function useDraft(defaults, options) {
|
|
10
10
|
const idPrefix = options?.idPrefix;
|
|
11
11
|
const [draft, setDraft] = useState(defaults);
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-renderer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.388.0",
|
|
4
4
|
"description": "React renderer for Kumiko screens. The root export holds the platform-agnostic logic (primitives contract, hooks, KumikoScreen, navigation and SSE abstractions); ./headless the DOM-free UI logic, ./live the HTTP dispatcher, ./web the browser bindings (HTML primitives, history navigation, EventSource live events, createKumikoApp).",
|
|
5
|
-
"license": "
|
|
5
|
+
"license": "MIT",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"kumiko": {
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
}
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
52
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
51
|
+
"@cosmicdrift/kumiko-framework": "0.388.0",
|
|
52
|
+
"@cosmicdrift/kumiko-types": "0.388.0",
|
|
53
53
|
"@radix-ui/react-dialog": "^1.1.15",
|
|
54
54
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
55
55
|
"@radix-ui/react-label": "^2.1.8",
|
|
@@ -92,8 +92,8 @@
|
|
|
92
92
|
}
|
|
93
93
|
},
|
|
94
94
|
"devDependencies": {
|
|
95
|
-
"@cosmicdrift/kumiko-locale-de": "0.
|
|
96
|
-
"@cosmicdrift/kumiko-testing": "0.
|
|
95
|
+
"@cosmicdrift/kumiko-locale-de": "0.388.0",
|
|
96
|
+
"@cosmicdrift/kumiko-testing": "0.388.0",
|
|
97
97
|
"@tailwindcss/cli": "^4.3.0",
|
|
98
98
|
"@testing-library/react": "^16.3.2",
|
|
99
99
|
"@testing-library/user-event": "^14.6.1",
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,53 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.388.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "List view model and cell formats as tables",
|
|
6
|
+
"detail": "computeListViewModel resolves each column through an ordered list of resolvers and applyFormatSpec dispatches through a table of cell formatters. Output is unchanged; a format named like an Object.prototype member such as \"toString\" still takes the unknown-format path."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.388.0",
|
|
10
|
+
"type": "fix",
|
|
11
|
+
"title": "Single-line row actions, stable form columns and select width, system-scope configEdit",
|
|
12
|
+
"detail": "Row actions never wrap onto a second line; the template overview lets long titles break and cuts the slug earlier so the table keeps room at 1280 px. In screen forms a section with explicitly declared columns (two or more) lays out as that many columns, and a select cell is as wide as a text cell instead of growing with its help text. A configEdit screen with scope system now reads the system level, so a SystemAdmin no longer sees the value of their own tenant row."
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"version": "0.388.0",
|
|
16
|
+
"type": "fix",
|
|
17
|
+
"title": "Uniform control height in page headers and list toolbars, spacing for projectionList descriptions",
|
|
18
|
+
"detail": "Buttons in the page header (create, toolbarActions, detail actions, form actions and app-added buttons such as the AI button) and in list toolbars (search, filters, secondary actions) now share one height, independent of the size a screen or app requested. A toolbarAction promoted into the page header no longer renders smaller than the standard create button. A projectionList without a toolbar no longer glues its description to the page header. The header of a CollapsibleSection (\"Erweitert\") has the same height as the form controls next to it."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"version": "0.388.0",
|
|
22
|
+
"type": "breaking",
|
|
23
|
+
"title": "Input gets kind \"code\" and autoFocus on kind \"text\"",
|
|
24
|
+
"detail": "Input has a new kind \"code\" (length, onComplete, autoFocus, autoComplete defaulting to one-time-code), a single real input shown as separate cells; onComplete fires once when input, paste or autofill reaches full length and the value holds digits only. kind \"text\" gains autoFocus.",
|
|
25
|
+
"migration": "custom Input implementations (PrimitivesProvider override) with an exhaustive switch over props.kind need a case \"code\"; delegating to DefaultInput from @cosmicdrift/kumiko-renderer/web is enough"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"version": "0.388.0",
|
|
29
|
+
"type": "fix",
|
|
30
|
+
"title": "Renderer low-severity robustness fixes",
|
|
31
|
+
"detail": "The list search echo handling no longer mutates state during render and no longer re-sends an already sent value. A failing live refetch no longer drops a queued follow-up run. Downloaded blob URLs are revoked after a delay, failed PDF loads are retried on the next mount, the file meta cache is cleared when the app gate unmounts, and renderFlow rejects duplicate node ids and unknown icons. The es/fr/it titles of the reset-to-system action now say the tenant template is reset to the system template."
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"version": "0.388.0",
|
|
35
|
+
"type": "fix",
|
|
36
|
+
"title": "Renderer review fixes for lookups, dropzone, markSaved, actionForm reset and dashboard gates",
|
|
37
|
+
"detail": "Reference labels show \"Unknown\" only when the lookup page was complete; a related-list column keeps sortable on fields without a sortable key; a files dropzone lists and can remove files already in the value; extension markSaved accepts the written field names, no longer clears other sections' dirty state; an actionForm with load and resetOnSuccess reloads the record before resetting; fallback panels no longer flash before the visibility gate answers; identical text blocks get unique keys; the missing-screen warning names the route id."
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"version": "0.388.0",
|
|
41
|
+
"type": "improvement",
|
|
42
|
+
"title": "useTenantCurrency hook",
|
|
43
|
+
"detail": "The tenant currency resolution of the entity edit prefill is exported as a hook with an explicit fallback for errors and missing values and no query when no currency is needed; the mail preview uses it only for templates with a money variable."
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"version": "0.388.0",
|
|
47
|
+
"type": "improvement",
|
|
48
|
+
"title": "ChoiceCards widget and PlanCard price prefix",
|
|
49
|
+
"detail": "ChoiceCards is a card-styled radio group (title, description, aside, native radios, keyboard operable) for exclusive choices that need more than a segment label. PlanCardPrice takes an optional prefix shown before the amount, for example \"from\"."
|
|
50
|
+
},
|
|
2
51
|
{
|
|
3
52
|
"version": "0.385.0",
|
|
4
53
|
"type": "improvement",
|