@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,21 +1,18 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
// MoneyInput
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// Symbol (€/$/¥) UND Tausender-Trenner UND korrekte Decimals in einem
|
|
7
|
-
// Aufruf.
|
|
2
|
+
// MoneyInput uses locale-aware formatting while preserving minor units (cents) as the canonical value.
|
|
3
|
+
// On focus, it shows a raw editable decimal string; on blur, Intl.NumberFormat with
|
|
4
|
+
// `style: "currency"` supplies the currency symbol (€/$/¥), grouping separators, and
|
|
5
|
+
// correct decimal places in one call.
|
|
8
6
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// trotzdem.
|
|
7
|
+
// `type=number` cannot accept formatted strings such as ("1.234,56 €") or locale-specific
|
|
8
|
+
// decimal separators such as commas. `inputMode="decimal"` still provides a mobile numeric
|
|
9
|
+
// keypad.
|
|
13
10
|
import { useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
14
11
|
import { currencyDecimals, formatMoney as formatMoneyForLocale } from "../../headless/index.js";
|
|
15
12
|
import { cn } from "../lib/cn.js";
|
|
16
13
|
import { parseLocaleNumber, resolveSafeLocale } from "./locale-number.js";
|
|
17
14
|
// Re-exported for backward compat — callers used to import this from here
|
|
18
|
-
// before it moved to headless (shared with RenderField
|
|
15
|
+
// before it moved to headless (shared with RenderField).
|
|
19
16
|
export { currencyDecimals, parseLocaleNumber };
|
|
20
17
|
// The border and focus ring live on the wrapper so the currency symbol can be
|
|
21
18
|
// a flex sibling of the input: a symbol of any width ("€", "CHF", "R$") takes
|
|
@@ -24,22 +21,21 @@ const wrapperClass = "flex h-9 w-full items-center rounded-md border border-inpu
|
|
|
24
21
|
"transition-colors focus-within:ring-1 focus-within:ring-ring";
|
|
25
22
|
const inputClass = "h-full min-w-0 flex-1 bg-transparent px-3 placeholder:text-muted-foreground " +
|
|
26
23
|
"focus-visible:outline-none disabled:cursor-not-allowed " +
|
|
27
|
-
//
|
|
28
|
-
//
|
|
24
|
+
// Right-aligns the value like a native type=number input so amounts align at the thousands
|
|
25
|
+
// separators in list columns.
|
|
29
26
|
"text-right tabular-nums";
|
|
30
27
|
export function MoneyInput({ id, name, value, onChange, currency, locale, disabled, required, hasError, ariaLabel, }) {
|
|
31
28
|
const decimals = currencyDecimals(currency);
|
|
32
29
|
const factor = 10 ** decimals;
|
|
33
30
|
const resolvedLocale = resolveSafeLocale(locale);
|
|
34
31
|
const [focused, setFocused] = useState(false);
|
|
35
|
-
//
|
|
36
|
-
// Math.round
|
|
32
|
+
// Keeps edits raw while focused; otherwise each keystroke would round-trip through
|
|
33
|
+
// Math.round and formatting, causing the cursor to jump.
|
|
37
34
|
const [draft, setDraft] = useState("");
|
|
38
35
|
const inputRef = useRef(null);
|
|
39
36
|
const major = value === "" ? null : value / factor;
|
|
40
37
|
const { symbol, symbolPosition, formatNumber } = useMemo(() => moneyFormatParts(currency, resolvedLocale), [currency, resolvedLocale]);
|
|
41
38
|
const formatted = value === "" ? "" : formatNumber(value / factor);
|
|
42
|
-
// Edit-Mode: Decimal-String ohne Tausender-Trenner.
|
|
43
39
|
const toEditable = (m) => m === null
|
|
44
40
|
? ""
|
|
45
41
|
: m.toLocaleString(resolvedLocale, {
|
|
@@ -58,7 +54,7 @@ export function MoneyInput({ id, name, value, onChange, currency, locale, disabl
|
|
|
58
54
|
// to the *end* of the new value rather than preserving position — so
|
|
59
55
|
// without an explicit re-select, typing right after focus appends
|
|
60
56
|
// instead of replacing. That's what silently corrupted values set via
|
|
61
|
-
// Playwright's `.fill()
|
|
57
|
+
// Playwright's `.fill()`. It also matches standard
|
|
62
58
|
// money-input UX (immediate overtype on click), and sidesteps mapping a
|
|
63
59
|
// click position from the formatted view to the editable one, which has
|
|
64
60
|
// no well-defined equivalent once separators and the currency symbol
|
|
@@ -2,6 +2,8 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { useEffect } from "react";
|
|
3
3
|
import { createPortal } from "react-dom";
|
|
4
4
|
import { usePageHeaderSlot } from "../layout/page-header-slot.js";
|
|
5
|
+
import { UNIFORM_BAR_CONTROLS_CLASSES } from "../layout/uniform-bar-controls.js";
|
|
6
|
+
import { cn } from "../lib/cn.js";
|
|
5
7
|
export function DefaultPageHeader({ title, recordTitle, status, actions, overflowItems, hideBreadcrumb, breadcrumbScreenId, }) {
|
|
6
8
|
const slot = usePageHeaderSlot();
|
|
7
9
|
const setOverflowItems = slot?.setOverflowItems;
|
|
@@ -42,7 +44,7 @@ export function DefaultPageHeader({ title, recordTitle, status, actions, overflo
|
|
|
42
44
|
return () => setOverflowItems(undefined);
|
|
43
45
|
}, [setOverflowItems, overflowItems]);
|
|
44
46
|
if (slot === null) {
|
|
45
|
-
return (_jsxs("div", { "data-kumiko-layout": "page-header-inline", className: "flex items-center gap-2 px-4 py-2", children: [status, _jsx("div", { className: "ml-auto flex items-center gap-2", children: actions })] }));
|
|
47
|
+
return (_jsxs("div", { "data-kumiko-layout": "page-header-inline", className: "flex items-center gap-2 px-4 py-2", children: [status, _jsx("div", { className: cn("ml-auto flex items-center gap-2", UNIFORM_BAR_CONTROLS_CLASSES), children: actions })] }));
|
|
46
48
|
}
|
|
47
49
|
return (_jsxs(_Fragment, { children: [status !== undefined &&
|
|
48
50
|
slot.statusElement !== null &&
|
|
@@ -20,7 +20,7 @@ export const TABLE_FOOTER_BAR_CLASS = cn("flex h-15 shrink-0 items-center gap-4
|
|
|
20
20
|
// `screen.pageSize ?? 50`) rather than the real per-screen pageSize, which isn't
|
|
21
21
|
// threaded down to this component. A screen with a custom pageSize can still
|
|
22
22
|
// under- or over-show the marker; thread pageSize through KumikoScreen →
|
|
23
|
-
// RenderList → DataTable → InfiniteSentinel if that's needed
|
|
23
|
+
// RenderList → DataTable → InfiniteSentinel if that's needed.
|
|
24
24
|
export const END_LABEL_MIN_ROWS = 50;
|
|
25
25
|
export function InfiniteSentinel({ onReachEnd, loadingMore, hasMore, rowsCount, testId, }) {
|
|
26
26
|
const t = useTranslation();
|
|
@@ -10,9 +10,8 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigge
|
|
|
10
10
|
import { useToast } from "./toast.js";
|
|
11
11
|
import "../react-css-custom-properties.js";
|
|
12
12
|
import { actionIconFor, SECONDARY_BUTTON_EXTRA_CLASSES } from "./button.js";
|
|
13
|
-
// The primary row action
|
|
14
|
-
//
|
|
15
|
-
// bedienkonzept L3: "Bearbeiten steht in jeder Zeile, immer, als Text").
|
|
13
|
+
// The primary row action always remains a visible text button: `edit` when declared,
|
|
14
|
+
// otherwise the first visible action ("Edit appears in every row, always, as text").
|
|
16
15
|
function primaryRowAction(actions) {
|
|
17
16
|
return actions.find((a) => a.id === "edit") ?? actions[0];
|
|
18
17
|
}
|
|
@@ -38,19 +37,19 @@ export function RowActionsCell({ row, actions, mode = "adaptive", rowIsLink = fa
|
|
|
38
37
|
// otherwise "inline" is exactly the wall-to-wall text-button problem
|
|
39
38
|
// this feature exists to fix. An author-set `display` takes that choice over.
|
|
40
39
|
const iconOnly = !hasExplicitDisplay && shouldRenderActionsIconOnly(visible);
|
|
41
|
-
return (_jsx("div", { className: "flex w-full items-center gap-1 justify-start", children: visible.map((a) => (_jsx(RowActionButton, { row: row, action: a, iconOnly: iconOnly, compact: compact }, a.id))) }));
|
|
40
|
+
return (_jsx("div", { className: "flex w-full flex-nowrap items-center gap-1 justify-start whitespace-nowrap", children: visible.map((a) => (_jsx(RowActionButton, { row: row, action: a, iconOnly: iconOnly, compact: compact }, a.id))) }));
|
|
42
41
|
}
|
|
43
42
|
if (hasExplicitDisplay) {
|
|
44
43
|
const inline = visible.filter((a) => a.display !== undefined);
|
|
45
44
|
const rest = visible.filter((a) => a.display === undefined);
|
|
46
|
-
return (_jsxs("div", { className: "inline-flex flex-
|
|
45
|
+
return (_jsxs("div", { className: "inline-flex flex-nowrap items-center gap-1 justify-end whitespace-nowrap", children: [inline.map((a) => (_jsx(RowActionButton, { row: row, action: a, compact: compact }, a.id))), rest.length > 0 && _jsx(RowActionsKebab, { row: row, actions: rest })] }));
|
|
47
46
|
}
|
|
48
47
|
if (rowIsLink) {
|
|
49
48
|
return (_jsx("div", { className: "inline-flex items-center justify-end", children: _jsx(RowActionsKebab, { row: row, actions: visible }) }));
|
|
50
49
|
}
|
|
51
50
|
const primary = primaryRowAction(visible);
|
|
52
51
|
const rest = visible.filter((a) => a.id !== primary?.id);
|
|
53
|
-
return (_jsxs("div", { className: "inline-flex flex-
|
|
52
|
+
return (_jsxs("div", { className: "inline-flex flex-nowrap items-center gap-1 justify-end whitespace-nowrap", children: [primary !== undefined && _jsx(RowActionButton, { row: row, action: primary, asLink: true }), rest.length > 0 && _jsx(RowActionsKebab, { row: row, actions: rest })] }));
|
|
54
53
|
}
|
|
55
54
|
// Shared trigger state between inline button + kebab item: busy flag
|
|
56
55
|
// (while the async onTrigger runs) + confirm-pending action. Both
|
|
@@ -161,7 +160,7 @@ function RowActionsKebab({ row, actions, }) {
|
|
|
161
160
|
await triggerNow(action);
|
|
162
161
|
}, testId: `row-${row.id}-action-${pendingConfirm.id}-dialog` }))] }));
|
|
163
162
|
}
|
|
164
|
-
// Header/row-actions overflow menu
|
|
163
|
+
// Header/row-actions overflow menu: the trigger carries the classes of a
|
|
165
164
|
// secondary icon Button so it matches neighbouring icon buttons, including
|
|
166
165
|
// the 44 px touch size on phones. Generic ActionMenuItemSpec items instead of
|
|
167
166
|
// the DataTableRowAction schema (callers own confirm/danger handling per item).
|
|
@@ -42,7 +42,7 @@ function QrSecretValue({ value }) {
|
|
|
42
42
|
// biome-ignore lint/security/noDangerouslySetInnerHtml: qrcode-generated SVG, no user input
|
|
43
43
|
dangerouslySetInnerHTML: { __html: svg } }));
|
|
44
44
|
}
|
|
45
|
-
// One-time secret reveal (
|
|
45
|
+
// One-time secret reveal (secretMint confirm phase) — monospaced
|
|
46
46
|
// per-value display with a copy button that flips to `copiedLabel` on
|
|
47
47
|
// success. Silent-catch on clipboard error mirrors the copy-link pattern in
|
|
48
48
|
// create-app.tsx.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
/** Form
|
|
3
|
-
* UTC
|
|
4
|
-
*
|
|
2
|
+
/** Form state → datetime-local format (`yyyy-MM-ddTHH:mm`).
|
|
3
|
+
* UTC instants (with `Z`/offset) are converted to the local wall clock;
|
|
4
|
+
* offset-free values are only truncated to minutes. */
|
|
5
5
|
export declare function timestampToInputValue(value: string): string;
|
|
6
|
-
/** datetime-local
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
/** datetime-local value → wire format. wallClock=true passes the wall clock
|
|
7
|
+
* through unchanged; otherwise the local time is emitted as a UTC instant
|
|
8
|
+
* with a `Z` suffix. */
|
|
9
9
|
export declare function inputValueToTimestamp(raw: string, wallClock: boolean): string | undefined;
|
|
10
10
|
export type TimestampInputProps = {
|
|
11
11
|
readonly id: string;
|
|
@@ -18,9 +18,9 @@ export type TimestampInputProps = {
|
|
|
18
18
|
readonly hasError?: boolean;
|
|
19
19
|
readonly ariaLabel?: string;
|
|
20
20
|
readonly locale?: string;
|
|
21
|
-
/**
|
|
22
|
-
*
|
|
23
|
-
|
|
21
|
+
/** Lower and upper bounds as ISO datetimes. The calendar is constrained to
|
|
22
|
+
* day-level granularity; Zod validation enforces the exact time bounds.
|
|
23
|
+
*/
|
|
24
24
|
readonly min?: string;
|
|
25
25
|
readonly max?: string;
|
|
26
26
|
};
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
// TimestampInput (kind:"timestamp") —
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
2
|
+
// TimestampInput (kind:"timestamp") — shared date input (DateField,
|
|
3
|
+
// typing + year calendar) plus a time input. This replaced the native
|
|
4
|
+
// <input type="datetime-local">: it was typeable, but had an unstyleable
|
|
5
|
+
// browser picker without year navigation and diverged from DateInput.
|
|
6
6
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// locatedTimestamps (z.iso.datetime({ local: true })).
|
|
10
|
-
//
|
|
11
|
-
// inputValueToTimestamp
|
|
12
|
-
//
|
|
7
|
+
// Wire conversion remains unchanged: the server validates timestamp values
|
|
8
|
+
// as ISO UTC with `Z` (z.iso.datetime()) or as an offset-free wall clock for
|
|
9
|
+
// locatedTimestamps (z.iso.datetime({ local: true })). Internally, the
|
|
10
|
+
// datetime-local form `yyyy-MM-ddTHH:mm` is used; timestampToInputValue /
|
|
11
|
+
// inputValueToTimestamp encapsulate the UTC↔wall-clock conversion and are
|
|
12
|
+
// tested separately.
|
|
13
13
|
import { useState } from "react";
|
|
14
14
|
import { useTranslation } from "../../index.js";
|
|
15
15
|
import { cn } from "../lib/cn.js";
|
|
@@ -19,9 +19,9 @@ const HAS_OFFSET = /(?:Z|[+-]\d{2}:\d{2})$/;
|
|
|
19
19
|
function pad(n) {
|
|
20
20
|
return String(n).padStart(2, "0");
|
|
21
21
|
}
|
|
22
|
-
/** Form
|
|
23
|
-
* UTC
|
|
24
|
-
*
|
|
22
|
+
/** Form state → datetime-local format (`yyyy-MM-ddTHH:mm`).
|
|
23
|
+
* UTC instants (with `Z`/offset) are converted to the local wall clock;
|
|
24
|
+
* offset-free values are only truncated to minutes. */
|
|
25
25
|
export function timestampToInputValue(value) {
|
|
26
26
|
if (value === "")
|
|
27
27
|
return "";
|
|
@@ -34,16 +34,16 @@ export function timestampToInputValue(value) {
|
|
|
34
34
|
const m = LOCAL_MINUTES.exec(value);
|
|
35
35
|
return m !== null ? m[0] : "";
|
|
36
36
|
}
|
|
37
|
-
/** datetime-local
|
|
38
|
-
*
|
|
39
|
-
*
|
|
37
|
+
/** datetime-local value → wire format. wallClock=true passes the wall clock
|
|
38
|
+
* through unchanged; otherwise the local time is emitted as a UTC instant
|
|
39
|
+
* with a `Z` suffix. */
|
|
40
40
|
export function inputValueToTimestamp(raw, wallClock) {
|
|
41
41
|
if (raw === "")
|
|
42
42
|
return undefined;
|
|
43
43
|
if (wallClock)
|
|
44
44
|
return raw;
|
|
45
|
-
//
|
|
46
|
-
// (ES2020+)
|
|
45
|
+
// An offset-free datetime string is parsed by Date as LOCAL time
|
|
46
|
+
// (ES2020+), matching the semantics provided by datetime-local.
|
|
47
47
|
const d = new Date(raw);
|
|
48
48
|
if (Number.isNaN(d.getTime()))
|
|
49
49
|
return undefined;
|
|
@@ -64,9 +64,9 @@ export function TimestampInput({ id, name, value, onChange, wallClock, disabled,
|
|
|
64
64
|
const t = useTranslation();
|
|
65
65
|
const local = timestampToInputValue(value);
|
|
66
66
|
const isoDate = local !== "" ? local.slice(0, 10) : "";
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
67
|
+
// A time without a selected date would be lost from the wire value, so keep
|
|
68
|
+
// it locally until a date is selected. Once `value` contains a date, its
|
|
69
|
+
// time takes precedence.
|
|
70
70
|
const [timeText, setTimeText] = useState("");
|
|
71
71
|
const effectiveTime = local !== "" ? local.slice(11, 16) : timeText;
|
|
72
72
|
function emit(nextIsoDate, nextTime) {
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
// Toast
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// einen `toast()`-Trigger, mehr Public-API gibt's nicht.
|
|
2
|
+
// Toast primitive built on @radix-ui/react-toast: a global provider and
|
|
3
|
+
// a useToast() hook for triggering toasts throughout the app. Consumers
|
|
4
|
+
// get only the `toast()` trigger, without Context boilerplate.
|
|
6
5
|
//
|
|
7
|
-
// Variant
|
|
8
|
-
// "muted" (neutral).
|
|
9
|
-
//
|
|
6
|
+
// Variant is StatusTone, using the same color family as StatusBadge;
|
|
7
|
+
// the default is "muted" (neutral). Toasts auto-dismiss after 5s and can
|
|
8
|
+
// be closed manually with the X button. Radix provides the ARIA live region.
|
|
10
9
|
import * as Primitive from "@radix-ui/react-toast";
|
|
11
10
|
import { X } from "lucide-react";
|
|
12
11
|
import { createContext, useCallback, useContext, useId, useMemo, useRef, useState, } from "react";
|
|
@@ -17,18 +16,18 @@ const ToastContext = createContext(null);
|
|
|
17
16
|
export function useToast() {
|
|
18
17
|
const ctx = useContext(ToastContext);
|
|
19
18
|
if (ctx === null) {
|
|
20
|
-
//
|
|
21
|
-
//
|
|
19
|
+
// Do not throw: tests rendering a component without a provider must not
|
|
20
|
+
// crash. Use a no-op instead of failing the mount.
|
|
22
21
|
return { toast: () => undefined };
|
|
23
22
|
}
|
|
24
23
|
return ctx;
|
|
25
24
|
}
|
|
26
25
|
export function ToastProvider({ children }) {
|
|
27
26
|
const [entries, setEntries] = useState([]);
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
27
|
+
// A monotonically increasing useRef counter avoids a closure bug: two
|
|
28
|
+
// toast() calls in the same tick would read the same stale useState value
|
|
29
|
+
// and collide on the ID. The ref is synchronously mutable, so each call
|
|
30
|
+
// receives a guaranteed-unique ID.
|
|
32
31
|
const idPrefix = useId();
|
|
33
32
|
const counterRef = useRef(0);
|
|
34
33
|
const toast = useCallback((opts) => {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type LiveEventSubscriber } from "../../index.ts";
|
|
2
2
|
export type CreateEventSourceLiveEventsOptions = {
|
|
3
|
-
/** URL
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
/** URL of the SSE endpoint. Defaults to /api/sse, where
|
|
4
|
+
* createSseRoute mounts it on the Kumiko server. Override this when
|
|
5
|
+
* the mount path differs. */
|
|
6
6
|
readonly url?: string;
|
|
7
7
|
/** Fires once per connection when the session ended: the server refused the SSE
|
|
8
8
|
* handshake, or opened the stream with a `session-ended` frame (stale auth
|
|
@@ -22,7 +22,7 @@ export type LiveEventTimers = {
|
|
|
22
22
|
export declare const RECONNECT_BASE_DELAY_MS = 1000;
|
|
23
23
|
export declare const RECONNECT_MAX_DELAY_MS = 30000;
|
|
24
24
|
export declare function reconnectDelayMs(attempt: number, random: number): number;
|
|
25
|
-
/**
|
|
26
|
-
*
|
|
27
|
-
* `<LiveEventsProvider
|
|
25
|
+
/** Returns an EventSource-backed `LiveEventSubscriber`.
|
|
26
|
+
* Typically called once during app bootstrap and passed as a value to
|
|
27
|
+
* `<LiveEventsProvider>`; `createKumikoApp` does this. */
|
|
28
28
|
export declare function createEventSourceLiveEvents(options?: CreateEventSourceLiveEventsOptions): LiveEventSubscriber;
|
|
@@ -61,9 +61,9 @@ async function isSessionEndedRejection(url) {
|
|
|
61
61
|
controller.abort();
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
-
/**
|
|
65
|
-
*
|
|
66
|
-
* `<LiveEventsProvider
|
|
64
|
+
/** Returns an EventSource-backed `LiveEventSubscriber`.
|
|
65
|
+
* Typically called once during app bootstrap and passed as a value to
|
|
66
|
+
* `<LiveEventsProvider>`; `createKumikoApp` does this. */
|
|
67
67
|
export function createEventSourceLiveEvents(options = {}) {
|
|
68
68
|
const url = options.url ?? "/api/sse";
|
|
69
69
|
const timers = options.timers ?? defaultTimers;
|
package/dist/web/tokens.d.ts
CHANGED
|
@@ -5,20 +5,19 @@ export declare const THEME_STORAGE_KEY = "kumiko:theme";
|
|
|
5
5
|
* the host HTML (see header comment) does the same synchronously before the
|
|
6
6
|
* first paint. */
|
|
7
7
|
export declare function applyStoredThemeMode(): void;
|
|
8
|
-
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
/** Tests only: the once-per-page-load guard is a module singleton; without a reset,
|
|
9
|
+
* the mount-restore path would be structurally unreachable after the first render
|
|
10
|
+
* in the test file. */
|
|
11
11
|
export declare function __resetStoredModeAppliedForTests(): void;
|
|
12
|
-
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* `<TokensProvider value={...}>` mounten. */
|
|
12
|
+
/** Builds a TokensApi for the browser. Used by createKumikoApp; app code that needs
|
|
13
|
+
* its own token state (for example, a user preference from localStorage) can mount
|
|
14
|
+
* its own `<TokensProvider value={...}>`. */
|
|
16
15
|
export declare function useBrowserTokensApi(): TokensApi;
|
|
17
|
-
/** Default
|
|
18
|
-
* Light
|
|
16
|
+
/** Default tokens—identical to `cssVarTokens` (variable-string references).
|
|
17
|
+
* Light and dark values switch via the `.dark` class on <html>. */
|
|
19
18
|
export declare const defaultTokens: Tokens;
|
|
20
19
|
export declare const lightTokens: Tokens;
|
|
21
|
-
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
20
|
+
/** Historically wrote tokens to :root as CSS variables. Now a no-op—the CSS
|
|
21
|
+
* variables live in styles.css, not JS. Retained as an export so existing app code
|
|
22
|
+
* does not break. */
|
|
24
23
|
export declare function applyTokensToCssVars(_tokens: Tokens): void;
|
package/dist/web/tokens.js
CHANGED
|
@@ -2,26 +2,25 @@ import { useEffect, useState, useSyncExternalStore } from "react";
|
|
|
2
2
|
import { createStore } from "../headless/index.js";
|
|
3
3
|
import { cssVarTokens, } from "../index.js";
|
|
4
4
|
import { DEFAULT_COLOR_SCHEME_VARIABLE } from "./default-color-scheme.js";
|
|
5
|
-
// Web-
|
|
6
|
-
// <html>.
|
|
7
|
-
//
|
|
8
|
-
// useSyncExternalStore darüber informiert.
|
|
5
|
+
// Web-specific TokensApi implementation. The theme toggle uses the `.dark` class on
|
|
6
|
+
// <html>. The actual colors live in styles.css; this is only the JS side that triggers
|
|
7
|
+
// the class switch and informs React consumers through useSyncExternalStore.
|
|
9
8
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
9
|
+
// The DOM (`<html class="dark">`) is the source of truth; the store is only a
|
|
10
|
+
// notification bus (a tick counter) incremented by setMode/toggleMode on every class
|
|
11
|
+
// change. This keeps the DOM class as the sole authority—readCurrentMode reads it
|
|
12
|
+
// freshly on every getSnapshot.
|
|
14
13
|
//
|
|
15
|
-
// Persistence: the choice is stored in localStorage (THEME_STORAGE_KEY) and
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
// AppTheme.defaultColorScheme (CSS variable) applies, otherwise the HTML state.
|
|
14
|
+
// Persistence: the choice is stored in localStorage (THEME_STORAGE_KEY) and restored
|
|
15
|
+
// on the first hook mount; without it, the toggle would be lost on every reload.
|
|
16
|
+
// Preference: "auto" follows prefers-color-scheme live (matchMedia listener), while
|
|
17
|
+
// "light"/"dark" are explicit choices and take precedence. Without a stored choice,
|
|
18
|
+
// AppTheme.defaultColorScheme (CSS variable) applies; otherwise, the HTML state.
|
|
21
19
|
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
// csp-hashes.json, which runProdApp also
|
|
20
|
+
// To prevent FOUC, the host HTML also needs a synchronous inline script BEFORE the
|
|
21
|
+
// stylesheet link. Under a strict CSP, its hash is the sha256 in the build's
|
|
22
|
+
// csp-hashes.json, which runProdApp also passes to hostDispatch as
|
|
23
|
+
// deps.cspScriptHashes:
|
|
25
24
|
//
|
|
26
25
|
// <script>try{var s=localStorage.getItem("kumiko:theme");
|
|
27
26
|
// if(s==="dark"||(s==="auto"&&matchMedia("(prefers-color-scheme: dark)").matches))
|
|
@@ -94,8 +93,8 @@ function persistPreference(preference) {
|
|
|
94
93
|
window.localStorage.setItem(THEME_STORAGE_KEY, preference);
|
|
95
94
|
}
|
|
96
95
|
catch {
|
|
97
|
-
// skip: localStorage
|
|
98
|
-
//
|
|
96
|
+
// skip: localStorage can throw (private mode/quota); the theme then remains
|
|
97
|
+
// session-based, but the class toggle still works.
|
|
99
98
|
}
|
|
100
99
|
}
|
|
101
100
|
function applyPreference(preference) {
|
|
@@ -123,23 +122,22 @@ export function applyStoredThemeMode() {
|
|
|
123
122
|
applyPreference(declared);
|
|
124
123
|
}
|
|
125
124
|
let storedModeApplied = false;
|
|
126
|
-
/**
|
|
127
|
-
*
|
|
128
|
-
*
|
|
125
|
+
/** Tests only: the once-per-page-load guard is a module singleton; without a reset,
|
|
126
|
+
* the mount-restore path would be structurally unreachable after the first render
|
|
127
|
+
* in the test file. */
|
|
129
128
|
export function __resetStoredModeAppliedForTests() {
|
|
130
129
|
storedModeApplied = false;
|
|
131
130
|
currentPreference = null;
|
|
132
131
|
}
|
|
133
|
-
/**
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
* `<TokensProvider value={...}>` mounten. */
|
|
132
|
+
/** Builds a TokensApi for the browser. Used by createKumikoApp; app code that needs
|
|
133
|
+
* its own token state (for example, a user preference from localStorage) can mount
|
|
134
|
+
* its own `<TokensProvider value={...}>`. */
|
|
137
135
|
export function useBrowserTokensApi() {
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
// DOM
|
|
142
|
-
//
|
|
136
|
+
// Apply the stored choice once per page load. This is lazy rather than a module
|
|
137
|
+
// side effect so importing without a DOM (SSR/tests) remains safe.
|
|
138
|
+
// A useState lazy initializer is used instead of placing this directly in the
|
|
139
|
+
// render body because the DOM side effect could otherwise run during a discarded
|
|
140
|
+
// concurrent render (React may repeat or abandon render bodies).
|
|
143
141
|
useState(() => {
|
|
144
142
|
if (!storedModeApplied && typeof document !== "undefined") {
|
|
145
143
|
storedModeApplied = true;
|
|
@@ -192,13 +190,13 @@ export function useBrowserTokensApi() {
|
|
|
192
190
|
},
|
|
193
191
|
};
|
|
194
192
|
}
|
|
195
|
-
/** Default
|
|
196
|
-
* Light
|
|
193
|
+
/** Default tokens—identical to `cssVarTokens` (variable-string references).
|
|
194
|
+
* Light and dark values switch via the `.dark` class on <html>. */
|
|
197
195
|
export const defaultTokens = cssVarTokens;
|
|
198
196
|
export const lightTokens = cssVarTokens;
|
|
199
|
-
/**
|
|
200
|
-
*
|
|
201
|
-
*
|
|
197
|
+
/** Historically wrote tokens to :root as CSS variables. Now a no-op—the CSS
|
|
198
|
+
* variables live in styles.css, not JS. Retained as an export so existing app code
|
|
199
|
+
* does not break. */
|
|
202
200
|
export function applyTokensToCssVars(_tokens) {
|
|
203
|
-
//
|
|
201
|
+
// Intentionally empty—see the comment above.
|
|
204
202
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
// Update
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// build-info.json
|
|
2
|
+
// Update awareness is enabled by default, without a flag. Otherwise, an open tab
|
|
3
|
+
// never learns about a new deploy because the SPA stays loaded and does not fetch
|
|
4
|
+
// index.html again, so it requires a hard reload. Instead of a service worker—which
|
|
5
|
+
// causes the “stuck on old version” problem—this component polls the static
|
|
6
|
+
// build-info.json when the tab gains focus and shows a reload banner on drift.
|
|
7
7
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
8
|
+
// The version source is build-info.json (a hash of the asset URLs written by the
|
|
9
|
+
// production build), not /api/version (which may be empty or 404 at runtime because
|
|
10
|
+
// of environment or Dockerfile configuration). Banner strings come from
|
|
11
|
+
// kumikoDefaultTranslations (kumiko.version.*, de/en).
|
|
11
12
|
import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
12
13
|
import { useEffect, useState } from "react";
|
|
13
14
|
import { useTranslation } from "../../index.js";
|
|
@@ -21,9 +22,9 @@ export function readLoadedBuild() {
|
|
|
21
22
|
return undefined;
|
|
22
23
|
return { id, builtAt: meta?.getAttribute("data-built-at") ?? "" };
|
|
23
24
|
}
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
25
|
+
// Update detection is only valid for a real ID drift: a banner is shown only then.
|
|
26
|
+
// No loaded build (development or an old bundle), or no server build (fetch failure
|
|
27
|
+
// or invalid JSON → null), must never produce a false update banner.
|
|
27
28
|
export function shouldShowUpdate(loadedId, server) {
|
|
28
29
|
if (!loadedId || !server)
|
|
29
30
|
return false;
|
|
@@ -44,7 +45,7 @@ async function fetchServerBuild() {
|
|
|
44
45
|
return { id: raw.id, builtAt: typeof builtAt === "string" ? builtAt : "" };
|
|
45
46
|
}
|
|
46
47
|
catch {
|
|
47
|
-
//
|
|
48
|
+
// Network failure or invalid JSON → no banner; never show a false update.
|
|
48
49
|
return null;
|
|
49
50
|
}
|
|
50
51
|
}
|
|
@@ -55,7 +56,7 @@ export function UpdateChecker() {
|
|
|
55
56
|
useEffect(() => {
|
|
56
57
|
const loadedId = loaded?.id;
|
|
57
58
|
if (!loadedId)
|
|
58
|
-
return; //
|
|
59
|
+
return; // Development or an old bundle → no update awareness.
|
|
59
60
|
let cancelled = false;
|
|
60
61
|
const checking = { current: false };
|
|
61
62
|
const check = async () => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// AiTextField / AiTextArea — Drop-in replacement for TextField/TextareaField
|
|
3
|
-
// with AI-augmented actions (ai-text-primitive plan
|
|
3
|
+
// with AI-augmented actions (from the ai-text-primitive plan).
|
|
4
4
|
//
|
|
5
5
|
// Built on raw <input>/<textarea> (not the generic Input primitive) because
|
|
6
6
|
// ghost-text needs a keydown handler (Tab/Esc) and an overlay that the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function createBoundedLoadCache<T>(max: number, dispose: (value: T) => void): (key: string, load: () => Promise<T>) => Promise<T>;
|
|
1
|
+
export declare function createBoundedLoadCache<T>(max: number, dispose: (value: T) => void, shouldKeep?: (value: T) => boolean): (key: string, load: () => Promise<T>) => Promise<T>;
|