@cosmicdrift/kumiko-renderer 0.334.0 → 0.336.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/dist/app/content-preview.js +7 -9
- package/dist/app/extension-sections.d.ts +6 -0
- package/dist/app/form-schema.d.ts +2 -1
- package/dist/app/form-schema.js +11 -2
- package/dist/app/kumiko-screen.d.ts +5 -0
- package/dist/app/kumiko-screen.js +196 -65
- package/dist/app/nav.d.ts +5 -0
- package/dist/app/nav.js +17 -4
- package/dist/app/reference-facet-bridge.js +8 -1
- package/dist/app/row-actions.d.ts +12 -0
- package/dist/app/row-actions.js +24 -7
- package/dist/app/screen-access.js +13 -9
- package/dist/app/secret-mint-body.js +40 -6
- package/dist/app/secrets-edit-body.js +71 -44
- package/dist/components/embedded-list-field.js +2 -4
- package/dist/components/multi-select-checkboxes.js +4 -1
- package/dist/components/reference-create-dialog.js +9 -4
- package/dist/components/related-list-section.js +7 -5
- package/dist/components/render-edit-action-button.d.ts +1 -1
- package/dist/components/render-edit-action-button.js +5 -1
- package/dist/components/render-edit-logic.d.ts +2 -1
- package/dist/components/render-edit-logic.js +8 -5
- package/dist/components/render-edit-types.d.ts +10 -1
- package/dist/components/render-edit.js +189 -54
- package/dist/components/render-field.js +10 -13
- package/dist/components/render-list.js +6 -6
- package/dist/components/write-form-section.js +12 -20
- package/dist/hooks/use-reference-lookup.d.ts +7 -0
- package/dist/hooks/use-reference-lookup.js +10 -0
- package/dist/hooks/use-report-step-complete.d.ts +2 -0
- package/dist/hooks/use-report-step-complete.js +13 -0
- package/dist/i18n.js +8 -4
- package/dist/index.d.ts +6 -2
- package/dist/index.js +4 -1
- package/dist/primitives.d.ts +30 -8
- package/dist/primitives.js +5 -0
- package/dist/session/session-ended.d.ts +16 -0
- package/dist/session/session-ended.js +73 -0
- package/dist/sort-by-accessor.d.ts +6 -2
- package/dist/sort-by-accessor.js +13 -3
- package/dist/sse/live-events.d.ts +1 -1
- package/package.json +5 -5
- package/src/changes.json +54 -0
|
@@ -6,6 +6,13 @@ export declare function referenceLookupSource(refFeature: string, refEntity: str
|
|
|
6
6
|
readonly queryQn: string;
|
|
7
7
|
readonly labelKey: string;
|
|
8
8
|
};
|
|
9
|
+
export declare function referenceOptionSource(field: {
|
|
10
|
+
readonly refLabelField?: string;
|
|
11
|
+
readonly refOptionsQuery?: string;
|
|
12
|
+
}, refFeature: string, refEntity: string): {
|
|
13
|
+
readonly queryQn: string;
|
|
14
|
+
readonly labelKey: string;
|
|
15
|
+
};
|
|
9
16
|
/** Empty while the query loads (or fails), so callers fall back to the raw id.
|
|
10
17
|
* `featureName` is the reference's target feature, not the screen's. */
|
|
11
18
|
export declare function useReferenceLookup(featureName: string, refEntity: string, labelField: string): {
|
|
@@ -34,6 +34,16 @@ export function referenceLookupSource(refFeature, refEntity, labelField) {
|
|
|
34
34
|
labelKey: labelField,
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
|
+
// Where a reference field's option rows come from, and which of their keys
|
|
38
|
+
// holds the display text — the entity's own list handler plus `refLabelField`
|
|
39
|
+
// by default, or an author-supplied handler whose rows carry `label`
|
|
40
|
+
// (fw#2780), for targets with no readable column of their own.
|
|
41
|
+
export function referenceOptionSource(field, refFeature, refEntity) {
|
|
42
|
+
if (field.refOptionsQuery !== undefined) {
|
|
43
|
+
return { queryQn: field.refOptionsQuery, labelKey: "label" };
|
|
44
|
+
}
|
|
45
|
+
return referenceLookupSource(refFeature, refEntity, field.refLabelField ?? "id");
|
|
46
|
+
}
|
|
37
47
|
/** Empty while the query loads (or fails), so callers fall back to the raw id.
|
|
38
48
|
* `featureName` is the reference's target feature, not the screen's. */
|
|
39
49
|
export function useReferenceLookup(featureName, refEntity, labelField) {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
// Lets an extension wizard step report whether it already holds its data
|
|
3
|
+
// without a raw useEffect in app code (the no-raw-hooks guard forbids those).
|
|
4
|
+
// `complete === null` means the step's data is still loading: nothing is
|
|
5
|
+
// reported, so a loading step never flashes "not done" over a passed-via-Next
|
|
6
|
+
// state. Outside update-mode wizards `reportStepComplete` is undefined and the
|
|
7
|
+
// hook does nothing.
|
|
8
|
+
export function useReportStepComplete(reportStepComplete, complete) {
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (reportStepComplete !== undefined && complete !== null)
|
|
11
|
+
reportStepComplete(complete);
|
|
12
|
+
}, [reportStepComplete, complete]);
|
|
13
|
+
}
|
package/dist/i18n.js
CHANGED
|
@@ -65,7 +65,8 @@ export function useLocale() {
|
|
|
65
65
|
}
|
|
66
66
|
// Subscribe + current locale-snapshot. Wir brauchen den Rückgabewert
|
|
67
67
|
// selbst nicht — wichtig ist nur der re-render-trigger.
|
|
68
|
-
|
|
68
|
+
const getLocaleSnapshot = () => ctx.resolver.locale();
|
|
69
|
+
useSyncExternalStore(ctx.resolver.subscribe, getLocaleSnapshot, getLocaleSnapshot);
|
|
69
70
|
return ctx.resolver;
|
|
70
71
|
}
|
|
71
72
|
/** `t("key", params)` resolves in this order:
|
|
@@ -83,7 +84,8 @@ export function useTranslation() {
|
|
|
83
84
|
// Re-Render bei Sprach-Wechsel. `ctx.resolver.subscribe` ist bereits
|
|
84
85
|
// eine stable-reference aus dem Resolver, daher hier keine eigene
|
|
85
86
|
// Memoization der Subscribe-Callback nötig.
|
|
86
|
-
const
|
|
87
|
+
const getLocaleSnapshot = () => ctx.resolver.locale();
|
|
88
|
+
const locale = useSyncExternalStore(ctx.resolver.subscribe, getLocaleSnapshot, getLocaleSnapshot);
|
|
87
89
|
// `t` MUSS referenz-stabil sein solange sich Resolver/Bundles/Locale
|
|
88
90
|
// nicht ändern — Consumer nutzen `t` regelmäßig in useEffect-Deps
|
|
89
91
|
// (z.B. um Queries neu zu laden wenn sich die Sprache ändert). Ein neu
|
|
@@ -132,7 +134,8 @@ function bundleLocaleTiers(locale, fallbackLocale, formality) {
|
|
|
132
134
|
* Used by DataTable FormatCell so plain tables without a provider do not crash. */
|
|
133
135
|
export function useOptionalLocale() {
|
|
134
136
|
const ctx = useContext(LocaleContext);
|
|
135
|
-
|
|
137
|
+
const getLocaleSnapshot = () => (ctx ? ctx.resolver.locale() : "en");
|
|
138
|
+
useSyncExternalStore((onStoreChange) => (ctx ? ctx.resolver.subscribe(onStoreChange) : () => { }), getLocaleSnapshot, getLocaleSnapshot);
|
|
136
139
|
return ctx === undefined ? undefined : ctx.resolver.locale();
|
|
137
140
|
}
|
|
138
141
|
/** Non-throwing time-zone read — "UTC" outside LocaleProvider, matching
|
|
@@ -144,7 +147,8 @@ export function useOptionalTimeZone() {
|
|
|
144
147
|
/** Non-throwing translate — undefined outside LocaleProvider. */
|
|
145
148
|
export function useOptionalTranslation() {
|
|
146
149
|
const ctx = useContext(LocaleContext);
|
|
147
|
-
const
|
|
150
|
+
const getLocaleSnapshot = () => (ctx ? ctx.resolver.locale() : "en");
|
|
151
|
+
const locale = useSyncExternalStore((onStoreChange) => (ctx ? ctx.resolver.subscribe(onStoreChange) : () => { }), getLocaleSnapshot, getLocaleSnapshot);
|
|
148
152
|
const formality = useContext(FormalityContext);
|
|
149
153
|
const t = useCallback((key, params) => ctx === undefined ? key : translateWithFallbacks(ctx, locale, formality, key, params), [ctx, locale, formality]);
|
|
150
154
|
return ctx === undefined ? undefined : t;
|
package/dist/index.d.ts
CHANGED
|
@@ -35,6 +35,7 @@ export { dispatcherErrorText, WriteFailedError } from "./app/write-failed-error.
|
|
|
35
35
|
export { RelatedListSection } from "./components/related-list-section.js";
|
|
36
36
|
export type { RenderEditAction, RenderEditChangeState, RenderEditControls, RenderEditProps, } from "./components/render-edit.js";
|
|
37
37
|
export { RenderEdit } from "./components/render-edit.js";
|
|
38
|
+
export { needsActionConfirm } from "./components/render-edit-action-button.js";
|
|
38
39
|
export type { RenderFieldProps } from "./components/render-field.js";
|
|
39
40
|
export { RenderField } from "./components/render-field.js";
|
|
40
41
|
export type { RenderListProps } from "./components/render-list.js";
|
|
@@ -60,6 +61,7 @@ export type { UseMutationResult } from "./hooks/use-mutation.js";
|
|
|
60
61
|
export { useMutation } from "./hooks/use-mutation.js";
|
|
61
62
|
export type { UseQueryOptions, UseQueryResult } from "./hooks/use-query.js";
|
|
62
63
|
export { entityFromQueryType, useQuery } from "./hooks/use-query.js";
|
|
64
|
+
export { useReportStepComplete } from "./hooks/use-report-step-complete.js";
|
|
63
65
|
export { useStore, useStoreSelector } from "./hooks/use-store.js";
|
|
64
66
|
export type { StreamStatus, UseStreamHandlerOptions, UseStreamHandlerResult, } from "./hooks/use-stream-handler.js";
|
|
65
67
|
export { useStreamHandler } from "./hooks/use-stream-handler.js";
|
|
@@ -68,8 +70,10 @@ export { createStaticLocaleResolver, FormalityProvider, LocaleProvider, mergeTra
|
|
|
68
70
|
export { kumikoDefaultTranslations } from "./i18n-defaults.js";
|
|
69
71
|
export { InsideDrawerProvider, useInsideDrawer } from "./inside-drawer.js";
|
|
70
72
|
export { PageHeaderCompactProvider, PageHeaderSlotAvailableProvider, usePageHeaderCompact, usePageHeaderSlotAvailable, } from "./page-header-slot.js";
|
|
71
|
-
export type { ActionMenuItemSpec, ActionOverflowMenuProps, AppPrimitives, BannerProps, ButtonProps, CardOptions, CardProps, CardSlots, CopyButtonProps, CorePrimitives, DataTableDateRangeFacet, DataTableFacet, DataTableFacetChip, DataTableProps, DataTableRowAction, DataTableRowActionMode, DataTableRowGrouping, DataTableSort, DataTableSortDir, DialogProps, DrawerProps, EmbeddedListCellType, EmbeddedListColumn, EmbeddedListInputProps, EmbeddedListTotal, FieldCellWidth, FieldProps, FillContainerProps, FormProps, FormSectionNavItem, FormWidth, GridCellProps, GridProps, HeadingProps, InputProps, JsonViewProps, LightboxProps, LinkProps, MetricBandProps, MetricProps, ModalProps, PageHeaderProps, PrimitivesProviderProps, PrimitivesRegistry, ProgressProps, ProgressTone, PromoPanelAction, PromoPanelProps, RuntimeRenderer, SecretRevealProps, SecretRevealValue, SectionProps, ShareButtonProps, StatusBadgeProps, StatusTone, StepBarProps, StickyActionBarProps, StickyPrimaryActionMarker, TabsProps, TextProps, TrailingInputAction, WizardStepGroupProps, } from "./primitives.js";
|
|
72
|
-
export { PrimitivesProvider, STICKY_PRIMARY_ACTION_PROP, shouldRenderActionsIconOnly, statusToneForOptionTone, statusToneForValue, usePrimitives, } from "./primitives.js";
|
|
73
|
+
export type { ActionMenuItemSpec, ActionOverflowMenuProps, AppPrimitives, BannerProps, ButtonProps, CardOptions, CardProps, CardSlots, CopyButtonProps, CorePrimitives, DataTableDateRangeFacet, DataTableFacet, DataTableFacetChip, DataTableProps, DataTableRowAction, DataTableRowActionMode, DataTableRowGrouping, DataTableSort, DataTableSortDir, DialogProps, DrawerProps, EmbeddedListCellType, EmbeddedListColumn, EmbeddedListInputProps, EmbeddedListTotal, FieldCellWidth, FieldProps, FillContainerProps, FooterActionMarker, FooterActionRole, FormProps, FormSectionNavItem, FormWidth, GridCellProps, GridProps, HeadingProps, InputProps, JsonViewProps, LightboxProps, LinkProps, MetricBandProps, MetricProps, ModalProps, PageHeaderProps, PrimitivesProviderProps, PrimitivesRegistry, ProgressProps, ProgressTone, PromoPanelAction, PromoPanelProps, RuntimeRenderer, SecretRevealProps, SecretRevealValue, SectionProps, ShareButtonProps, StatusBadgeProps, StatusTone, StepBarProps, StickyActionBarProps, StickyPrimaryActionMarker, TabsProps, TextProps, TrailingInputAction, WizardStepGroupProps, } from "./primitives.js";
|
|
74
|
+
export { FOOTER_ACTION_ROLE_PROP, NARROW_LABEL_PROP, PrimitivesProvider, STICKY_PRIMARY_ACTION_PROP, shouldRenderActionsIconOnly, statusToneForOptionTone, statusToneForValue, usePrimitives, } from "./primitives.js";
|
|
75
|
+
export type { SessionEndedSignal, SessionEndedSignalProviderProps, } from "./session/session-ended.js";
|
|
76
|
+
export { createSessionEndedSignal, isSessionEndedError, SESSION_ENDED_ERROR_CODES, SessionEndedSignalProvider, useSessionEndedSignal, withSessionEndedDetection, } from "./session/session-ended.js";
|
|
73
77
|
export { buildWhatsAppShareUrl } from "./share-links.js";
|
|
74
78
|
export { sortByAccessor } from "./sort-by-accessor.js";
|
|
75
79
|
export type { LiveEvent, LiveEventSubscriber, LiveEventsProviderProps } from "./sse/live-events.js";
|
package/dist/index.js
CHANGED
|
@@ -29,6 +29,7 @@ export { VariableChips } from "./app/variable-chips.js";
|
|
|
29
29
|
export { dispatcherErrorText, WriteFailedError } from "./app/write-failed-error.js";
|
|
30
30
|
export { RelatedListSection } from "./components/related-list-section.js";
|
|
31
31
|
export { RenderEdit } from "./components/render-edit.js";
|
|
32
|
+
export { needsActionConfirm } from "./components/render-edit-action-button.js";
|
|
32
33
|
export { RenderField } from "./components/render-field.js";
|
|
33
34
|
export { RenderList } from "./components/render-list.js";
|
|
34
35
|
export { DispatcherProvider, useDispatcher, useDispatcherStatus, useOptionalDispatcher, } from "./context/dispatcher-context.js";
|
|
@@ -43,13 +44,15 @@ export { useForm } from "./hooks/use-form.js";
|
|
|
43
44
|
export { useListUrlState } from "./hooks/use-list-url-state.js";
|
|
44
45
|
export { useMutation } from "./hooks/use-mutation.js";
|
|
45
46
|
export { entityFromQueryType, useQuery } from "./hooks/use-query.js";
|
|
47
|
+
export { useReportStepComplete } from "./hooks/use-report-step-complete.js";
|
|
46
48
|
export { useStore, useStoreSelector } from "./hooks/use-store.js";
|
|
47
49
|
export { useStreamHandler } from "./hooks/use-stream-handler.js";
|
|
48
50
|
export { createStaticLocaleResolver, FormalityProvider, LocaleProvider, mergeTranslations, translationsByLocaleFromKeys, useFormality, useLocale, useOptionalLocale, useOptionalTranslation, useTranslation, } from "./i18n.js";
|
|
49
51
|
export { kumikoDefaultTranslations } from "./i18n-defaults.js";
|
|
50
52
|
export { InsideDrawerProvider, useInsideDrawer } from "./inside-drawer.js";
|
|
51
53
|
export { PageHeaderCompactProvider, PageHeaderSlotAvailableProvider, usePageHeaderCompact, usePageHeaderSlotAvailable, } from "./page-header-slot.js";
|
|
52
|
-
export { PrimitivesProvider, STICKY_PRIMARY_ACTION_PROP, shouldRenderActionsIconOnly, statusToneForOptionTone, statusToneForValue, usePrimitives, } from "./primitives.js";
|
|
54
|
+
export { FOOTER_ACTION_ROLE_PROP, NARROW_LABEL_PROP, PrimitivesProvider, STICKY_PRIMARY_ACTION_PROP, shouldRenderActionsIconOnly, statusToneForOptionTone, statusToneForValue, usePrimitives, } from "./primitives.js";
|
|
55
|
+
export { createSessionEndedSignal, isSessionEndedError, SESSION_ENDED_ERROR_CODES, SessionEndedSignalProvider, useSessionEndedSignal, withSessionEndedDetection, } from "./session/session-ended.js";
|
|
53
56
|
export { buildWhatsAppShareUrl } from "./share-links.js";
|
|
54
57
|
export { sortByAccessor } from "./sort-by-accessor.js";
|
|
55
58
|
export { LiveEventsProvider, useLiveEvents } from "./sse/live-events.js";
|
package/dist/primitives.d.ts
CHANGED
|
@@ -14,6 +14,16 @@ export type DataAttributes = Readonly<Record<`data-${string}`, string>>;
|
|
|
14
14
|
* split can't inspect (see `ScreenSlots.footerPrimary`). */
|
|
15
15
|
export declare const STICKY_PRIMARY_ACTION_PROP: "kumikoStickyPrimaryAction";
|
|
16
16
|
export type StickyPrimaryActionMarker = Partial<Record<typeof STICKY_PRIMARY_ACTION_PROP, boolean>>;
|
|
17
|
+
export type FooterActionRole = "back" | "primary";
|
|
18
|
+
/** Tells the pinned phone footer which node is Back and which is the primary
|
|
19
|
+
* action, for buttons that are not `type="submit"` (e.g. the wizard Back). */
|
|
20
|
+
export declare const FOOTER_ACTION_ROLE_PROP: "kumikoFooterActionRole";
|
|
21
|
+
/** Short label a footer action uses instead of its children below `sm`. */
|
|
22
|
+
export declare const NARROW_LABEL_PROP: "kumikoNarrowLabel";
|
|
23
|
+
export type FooterActionMarker = {
|
|
24
|
+
readonly [FOOTER_ACTION_ROLE_PROP]?: FooterActionRole;
|
|
25
|
+
readonly [NARROW_LABEL_PROP]?: string;
|
|
26
|
+
};
|
|
17
27
|
/** Standard-Button. `loading` zeigt einen Spinner statt der Children
|
|
18
28
|
* und sollte mit `disabled` kombiniert werden, wenn die Action wirklich
|
|
19
29
|
* blockiert bis das Loading durch ist (z.B. async submit). Native-
|
|
@@ -957,6 +967,10 @@ export type GridProps = {
|
|
|
957
967
|
/** One full-width row per child, separated by hairlines — the field rows of
|
|
958
968
|
* a `settings-list` section. Wins over `columns`/`flow`. */
|
|
959
969
|
readonly list?: boolean;
|
|
970
|
+
/** Id of the element naming this grid (a Field label). Turns the grid into
|
|
971
|
+
* a `role="group"` so a Field whose control is a set of inputs (checkbox
|
|
972
|
+
* group) still gives screen readers a group name. */
|
|
973
|
+
readonly ariaLabelledBy?: string;
|
|
960
974
|
};
|
|
961
975
|
/** Width class of a field cell inside a `flow` grid: sized by what the field holds. */
|
|
962
976
|
export type FieldCellWidth = "text" | "number" | "money" | "date" | "timestamp" | "select" | "full" | "toggle";
|
|
@@ -1095,9 +1109,9 @@ export type CardSlots = {
|
|
|
1095
1109
|
readonly header?: ReactNode;
|
|
1096
1110
|
readonly title?: ReactNode;
|
|
1097
1111
|
readonly subtitle?: ReactNode;
|
|
1098
|
-
/** Free-form block content
|
|
1099
|
-
*
|
|
1100
|
-
*
|
|
1112
|
+
/** Free-form block content in its own full-width row below the
|
|
1113
|
+
* title/subtitle/`headerActions` row, within the same header block — for a
|
|
1114
|
+
* host-supplied header (projectionDetail `slots.header`). */
|
|
1101
1115
|
readonly headerContent?: ReactNode;
|
|
1102
1116
|
/** Rechtsbündiger Header-Slot (Range-Umschalter, Filter, Badge …). */
|
|
1103
1117
|
readonly headerActions?: ReactNode;
|
|
@@ -1149,12 +1163,13 @@ export type ProgressProps = {
|
|
|
1149
1163
|
readonly testId?: string;
|
|
1150
1164
|
};
|
|
1151
1165
|
/** Wizard step overview — numbered chips, one per step label, with the
|
|
1152
|
-
* active step highlighted. With `onStepSelect`, completed steps
|
|
1153
|
-
*
|
|
1154
|
-
*
|
|
1155
|
-
* (kumiko-framework#1966). Implementations render a
|
|
1166
|
+
* active step highlighted. With `onStepSelect`, completed steps become
|
|
1167
|
+
* buttons (every non-current step with `selectableSteps: "all"`); the caller
|
|
1168
|
+
* owns the validation gate for forward jumps. Implementations render a
|
|
1156
1169
|
* narrow-viewport fallback showing `compactLabel` instead (caller
|
|
1157
|
-
* supplies it pre-translated, e.g. "Step 2 of 5 · Industry")
|
|
1170
|
+
* supplies it pre-translated, e.g. "Step 2 of 5 · Industry"). With
|
|
1171
|
+
* `onStepSelect` and `selectableSteps: "all"` that fallback is a dropdown
|
|
1172
|
+
* listing all steps so narrow viewports can jump too — which of
|
|
1158
1173
|
* the two is visible is a responsive layout choice owned by the
|
|
1159
1174
|
* implementation, not this contract. */
|
|
1160
1175
|
export type StepBarProps = {
|
|
@@ -1162,6 +1177,13 @@ export type StepBarProps = {
|
|
|
1162
1177
|
readonly currentIndex: number;
|
|
1163
1178
|
readonly compactLabel: string;
|
|
1164
1179
|
readonly onStepSelect?: (index: number) => void;
|
|
1180
|
+
/** Explicit done state per step index. Absent: a step is done when its
|
|
1181
|
+
* index is below `currentIndex`. The current step is never done. */
|
|
1182
|
+
readonly doneSteps?: readonly boolean[];
|
|
1183
|
+
/** "done" (default): only done steps are jump targets. "all": every
|
|
1184
|
+
* non-current step is, when `onStepSelect` is set; the compact label then
|
|
1185
|
+
* becomes a step picker. */
|
|
1186
|
+
readonly selectableSteps?: "done" | "all";
|
|
1165
1187
|
/** "label" (default) swaps the step row for `compactLabel` on narrow
|
|
1166
1188
|
* viewports; "steps" keeps the row there too — for short wizards whose
|
|
1167
1189
|
* labels fit a phone and whose done steps must stay tappable. */
|
package/dist/primitives.js
CHANGED
|
@@ -6,6 +6,11 @@ import { createContext, useContext, } from "react";
|
|
|
6
6
|
* footer-slot mount, whose actual rendered button is opaque app code the
|
|
7
7
|
* split can't inspect (see `ScreenSlots.footerPrimary`). */
|
|
8
8
|
export const STICKY_PRIMARY_ACTION_PROP = "kumikoStickyPrimaryAction";
|
|
9
|
+
/** Tells the pinned phone footer which node is Back and which is the primary
|
|
10
|
+
* action, for buttons that are not `type="submit"` (e.g. the wizard Back). */
|
|
11
|
+
export const FOOTER_ACTION_ROLE_PROP = "kumikoFooterActionRole";
|
|
12
|
+
/** Short label a footer action uses instead of its children below `sm`. */
|
|
13
|
+
export const NARROW_LABEL_PROP = "kumikoNarrowLabel";
|
|
9
14
|
/** Teil-C action-icon collapse rule: a group of more than two actions where
|
|
10
15
|
* every member carries an icon renders icon-only instead of wall-to-wall
|
|
11
16
|
* text buttons; any icon-less member keeps the whole group on text so it
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Dispatcher, DispatcherError } from "@cosmicdrift/kumiko-headless";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
export declare const SESSION_ENDED_ERROR_CODES: readonly ["session_invalid", "invalid_token", "missing_token", "unauthenticated"];
|
|
4
|
+
export declare function isSessionEndedError(error: Pick<DispatcherError, "code" | "httpStatus">): boolean;
|
|
5
|
+
export type SessionEndedSignal = {
|
|
6
|
+
notify(): void;
|
|
7
|
+
subscribe(listener: () => void): () => void;
|
|
8
|
+
};
|
|
9
|
+
export declare function createSessionEndedSignal(): SessionEndedSignal;
|
|
10
|
+
export declare function withSessionEndedDetection(dispatcher: Dispatcher, signal: SessionEndedSignal): Dispatcher;
|
|
11
|
+
export type SessionEndedSignalProviderProps = {
|
|
12
|
+
readonly signal: SessionEndedSignal;
|
|
13
|
+
readonly children: ReactNode;
|
|
14
|
+
};
|
|
15
|
+
export declare function SessionEndedSignalProvider({ signal, children, }: SessionEndedSignalProviderProps): ReactNode;
|
|
16
|
+
export declare function useSessionEndedSignal(): SessionEndedSignal | undefined;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
// Only auth-layer 401s end a session. Handlers never throw 401 otherwise and
|
|
4
|
+
// 403 means "authenticated but not allowed", which must not log the user out.
|
|
5
|
+
export const SESSION_ENDED_ERROR_CODES = [
|
|
6
|
+
"session_invalid",
|
|
7
|
+
"invalid_token",
|
|
8
|
+
"missing_token",
|
|
9
|
+
"unauthenticated",
|
|
10
|
+
];
|
|
11
|
+
export function isSessionEndedError(error) {
|
|
12
|
+
return (error.httpStatus === 401 &&
|
|
13
|
+
SESSION_ENDED_ERROR_CODES.some((sessionEndedCode) => sessionEndedCode === error.code));
|
|
14
|
+
}
|
|
15
|
+
function isSessionEndedThrow(thrown) {
|
|
16
|
+
if (typeof thrown !== "object" || thrown === null)
|
|
17
|
+
return false;
|
|
18
|
+
if (!("code" in thrown) || !("httpStatus" in thrown))
|
|
19
|
+
return false;
|
|
20
|
+
const { code, httpStatus } = thrown;
|
|
21
|
+
return typeof code === "string" && typeof httpStatus === "number"
|
|
22
|
+
? isSessionEndedError({ code, httpStatus })
|
|
23
|
+
: false;
|
|
24
|
+
}
|
|
25
|
+
export function createSessionEndedSignal() {
|
|
26
|
+
const listeners = new Set();
|
|
27
|
+
return {
|
|
28
|
+
notify() {
|
|
29
|
+
for (const listener of [...listeners])
|
|
30
|
+
listener();
|
|
31
|
+
},
|
|
32
|
+
subscribe(listener) {
|
|
33
|
+
listeners.add(listener);
|
|
34
|
+
return () => {
|
|
35
|
+
listeners.delete(listener);
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export function withSessionEndedDetection(dispatcher, signal) {
|
|
41
|
+
async function detect(pending) {
|
|
42
|
+
const result = await pending;
|
|
43
|
+
if (!result.isSuccess && result.error !== undefined && isSessionEndedError(result.error)) {
|
|
44
|
+
signal.notify();
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
write: (type, payload, opts) => detect(dispatcher.write(type, payload, opts)),
|
|
50
|
+
query: (type, payload, opts) => detect(dispatcher.query(type, payload, opts)),
|
|
51
|
+
batch: (commands, opts) => detect(dispatcher.batch(commands, opts)),
|
|
52
|
+
async *stream(type, payload, opts) {
|
|
53
|
+
try {
|
|
54
|
+
yield* dispatcher.stream(type, payload, opts);
|
|
55
|
+
}
|
|
56
|
+
catch (thrown) {
|
|
57
|
+
if (isSessionEndedThrow(thrown))
|
|
58
|
+
signal.notify();
|
|
59
|
+
throw thrown;
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
statusStore: dispatcher.statusStore,
|
|
63
|
+
pendingWrites: () => dispatcher.pendingWrites(),
|
|
64
|
+
pendingFiles: () => dispatcher.pendingFiles(),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const SessionEndedSignalContext = createContext(undefined);
|
|
68
|
+
export function SessionEndedSignalProvider({ signal, children, }) {
|
|
69
|
+
return _jsx(SessionEndedSignalContext, { value: signal, children: children });
|
|
70
|
+
}
|
|
71
|
+
export function useSessionEndedSignal() {
|
|
72
|
+
return useContext(SessionEndedSignalContext);
|
|
73
|
+
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { DataTableSort } from "./primitives.js";
|
|
2
|
+
type SortValue = string | number | null;
|
|
2
3
|
/** Sorts `rows` by a `DataTableSort` against a field->accessor map. Unknown
|
|
3
4
|
* field or `sort === null` returns `rows` unchanged (no-op, not an error —
|
|
4
|
-
* callers pass whatever the DataTable reports).
|
|
5
|
-
|
|
5
|
+
* callers pass whatever the DataTable reports). `null` values sort last in
|
|
6
|
+
* both directions; strings compare locale-aware with numeric collation, so
|
|
7
|
+
* numeric strings (Postgres numeric/bigint) order by value. */
|
|
8
|
+
export declare function sortByAccessor<TRow>(rows: readonly TRow[], sort: DataTableSort | null, accessors: Readonly<Record<string, (row: TRow) => SortValue>>, locale?: string): readonly TRow[];
|
|
9
|
+
export {};
|
package/dist/sort-by-accessor.js
CHANGED
|
@@ -1,16 +1,26 @@
|
|
|
1
|
+
function compareNonNull(a, b, collator) {
|
|
2
|
+
if (typeof a === "number" && typeof b === "number")
|
|
3
|
+
return a - b;
|
|
4
|
+
return collator.compare(String(a), String(b));
|
|
5
|
+
}
|
|
1
6
|
/** Sorts `rows` by a `DataTableSort` against a field->accessor map. Unknown
|
|
2
7
|
* field or `sort === null` returns `rows` unchanged (no-op, not an error —
|
|
3
|
-
* callers pass whatever the DataTable reports).
|
|
4
|
-
|
|
8
|
+
* callers pass whatever the DataTable reports). `null` values sort last in
|
|
9
|
+
* both directions; strings compare locale-aware with numeric collation, so
|
|
10
|
+
* numeric strings (Postgres numeric/bigint) order by value. */
|
|
11
|
+
export function sortByAccessor(rows, sort, accessors, locale) {
|
|
5
12
|
if (sort === null)
|
|
6
13
|
return rows;
|
|
7
14
|
const accessor = accessors[sort.field];
|
|
8
15
|
if (accessor === undefined)
|
|
9
16
|
return rows;
|
|
10
17
|
const factor = sort.dir === "asc" ? 1 : -1;
|
|
18
|
+
const collator = new Intl.Collator(locale, { numeric: true, sensitivity: "base" });
|
|
11
19
|
return [...rows].sort((a, b) => {
|
|
12
20
|
const av = accessor(a);
|
|
13
21
|
const bv = accessor(b);
|
|
14
|
-
|
|
22
|
+
if (av === null || bv === null)
|
|
23
|
+
return av === bv ? 0 : av === null ? 1 : -1;
|
|
24
|
+
return factor * compareNonNull(av, bv, collator);
|
|
15
25
|
});
|
|
16
26
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-renderer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.336.0",
|
|
4
4
|
"description": "Platform-agnostic React renderer for Kumiko screens. Contains the shared logic — primitives-contract, hooks, KumikoScreen, navigation & SSE abstractions — that any platform-specific renderer (web, native) composes. No DOM, no EventSource, no react-dom.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
19
|
-
"@cosmicdrift/kumiko-headless": "0.
|
|
20
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
18
|
+
"@cosmicdrift/kumiko-framework": "0.336.0",
|
|
19
|
+
"@cosmicdrift/kumiko-headless": "0.336.0",
|
|
20
|
+
"@cosmicdrift/kumiko-types": "0.336.0",
|
|
21
21
|
"react": "^19.2.6",
|
|
22
22
|
"temporal-polyfill": "^0.3.2",
|
|
23
23
|
"zod": "^4.4.3"
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"@types/react-dom": "^19.2.3",
|
|
29
29
|
"jsdom": "^29.1.1",
|
|
30
30
|
"react-dom": "^19.2.6",
|
|
31
|
-
"@cosmicdrift/kumiko-locale-de": "0.
|
|
31
|
+
"@cosmicdrift/kumiko-locale-de": "0.336.0"
|
|
32
32
|
},
|
|
33
33
|
"repository": {
|
|
34
34
|
"type": "git",
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,58 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.336.0",
|
|
4
|
+
"type": "breaking",
|
|
5
|
+
"title": "LiveEvent data has no payload, it carries eventType",
|
|
6
|
+
"detail": "`LiveEvent.data` is now `{ id, aggregateType, eventType, version, createdAt }`. The server sends signals only, so consumers refetch through a query.",
|
|
7
|
+
"migration": "Replace reads of `event.data.payload` with a query refetch. `useQuery({ live: true })` already does this and needs no change."
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"version": "0.336.0",
|
|
11
|
+
"type": "improvement",
|
|
12
|
+
"title": "useReportStepComplete reports an extension wizard step's completeness without a raw useEffect",
|
|
13
|
+
"detail": "`useReportStepComplete(props.reportStepComplete, complete)` with `complete: boolean | null`. `null` reports nothing (data loading), a boolean is reported whenever it changes. Outside update-mode wizards `reportStepComplete` is undefined and the hook does nothing.",
|
|
14
|
+
"migration": "Extension steps that call `reportStepComplete` from a `useEffect` should switch to `useReportStepComplete(reportStepComplete, complete)`; app repos need this to pass the no-raw-hooks guard."
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"version": "0.335.0",
|
|
18
|
+
"type": "fix",
|
|
19
|
+
"title": "secretMint reveal and confirm render as one screen form with the translated screen title; new RenderEdit props leadContent and i18nScreenId"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"version": "0.335.0",
|
|
23
|
+
"type": "fix",
|
|
24
|
+
"title": "Router paths encode and decode each segment, so entity ids with slashes or spaces round-trip"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"version": "0.335.0",
|
|
28
|
+
"type": "fix",
|
|
29
|
+
"title": "Icon-only list and edit action buttons set a hover title"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"version": "0.335.0",
|
|
33
|
+
"type": "fix",
|
|
34
|
+
"title": "Secret delete confirmation, grouped-field conditions, scoped valid flag, tenant currency in reference-create dialog",
|
|
35
|
+
"detail": "Renderer review fixes. Deleting a secret on a secretsEdit screen now asks for confirmation first (new `config.secrets.deleteConfirm` key) and the delete button is disabled while a save or delete is running. The multiSelect checkbox group is exposed as a labelled group (`GridProps.ariaLabelledBy`). The inline reference-create dialog seeds `currency: { kind: \"tenant\" }` money fields from the tenant currency. Fields declared only inside a section `groups` entry now get their `visible`/`readOnly`/`required` conditions registered. `onChange`'s `valid` ignores issues on hidden fields and outside the `fields` scope, like submit does (shared `relevantFieldIssues` helper). Copy-link in the form footer keeps a 44px touch target on narrow viewports. A free-text sibling-field number unit longer than 8 characters is no longer rendered as a suffix."
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"version": "0.335.0",
|
|
39
|
+
"type": "fix",
|
|
40
|
+
"title": "Scoped validation matches exact paths, root refine issues are visible, tabs keep the active tab by id, update redirects use the saved FK, related list sorting handles numeric strings, nulls and locales",
|
|
41
|
+
"detail": "Form and list fixes. A scoped `validate()` entry such as `address.city` no longer reports sibling issues like `address.zip`. Root-level `.refine()` issues now show as a banner in `RenderEdit` instead of blocking submit silently. In tabs mode the active tab follows its id when a field change hides an earlier tab, and an explicit subtitle override is kept. An entityEdit update redirect with `idFrom` reads the saved projection, so a changed parent FK redirects to the new parent. A row-extractor key missing from the row no longer wipes the target field's default. `relatedList` client-side sorting orders numeric strings by value, puts empty values last and compares text locale-aware. A reference list facet warns in dev when the lookup hits its row cap."
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"version": "0.335.0",
|
|
45
|
+
"type": "fix",
|
|
46
|
+
"title": "writeForm section shows the error banner for issues no rendered field can display",
|
|
47
|
+
"detail": "A writeForm section keeps its error banner when the server reports an issue no rendered field can show (`version`, `id`, a root-level refine, a hidden field), instead of failing the save without any visible message."
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"version": "0.335.0",
|
|
51
|
+
"type": "breaking",
|
|
52
|
+
"title": "Wizard editing an existing record shows data-based done state and allows jumping to any step; delete record actions leave the screen",
|
|
53
|
+
"detail": "Update-mode wizards compute done per step from the record (valid, non-empty fields, or passed via Next) and make every non-current step a jump target; forward jumps run the current step's validate gate. `ExtensionSectionProps.reportStepComplete` lets extension steps report completeness. `StepBar` gets `doneSteps` and `selectableSteps`. A writeHandler record action with `redirect`, or a delete of the shown record, navigates away (returnTo, redirect, listScreenId, entity list) instead of refetching.",
|
|
54
|
+
"migration": "Extension wizard steps in an entityEdit that edits an existing record should call `reportStepComplete(true)` (usually from an effect) once they hold their data, otherwise the step bar shows them as not done until the user passes them with Next. This also applies to singleton wizards (e.g. a company-setup wizard), whose steps now show done by data and are all jump targets; tests that assumed back-only chips must be updated. A delete writeHandler action on a projectionDetail or entityEdit now navigates away after success (returnTo, else listScreenId or the entity's list) instead of refetching into \"record not found\"; set `redirect` to choose another target. List row actions are unchanged."
|
|
55
|
+
},
|
|
2
56
|
{
|
|
3
57
|
"version": "0.333.0",
|
|
4
58
|
"type": "improvement",
|