@cosmicdrift/kumiko-renderer 0.386.0 → 0.387.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 +2 -2
- package/dist/app/action-form-shim.d.ts +8 -8
- package/dist/app/action-form-shim.js +20 -22
- 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 +1 -1
- 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.js +9 -9
- 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 +24 -25
- package/dist/app/extension-sections.js +10 -10
- package/dist/app/feature-schema.js +11 -11
- package/dist/app/form-schema.js +1 -1
- package/dist/app/header-actions-bar.js +1 -1
- 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/embedded-list-field.js +4 -4
- 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 +1 -1
- 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 +63 -65
- package/dist/components/render-field.d.ts +7 -7
- package/dist/components/render-field.js +57 -59
- package/dist/components/render-list.d.ts +42 -43
- package/dist/components/render-list.js +63 -67
- 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/headless/apex/index.d.ts +1 -2
- package/dist/headless/apex/index.js +5 -6
- package/dist/headless/form/form-controller.js +6 -6
- package/dist/headless/form/zod-bridge.js +2 -2
- package/dist/headless/format/money.js +1 -1
- package/dist/headless/index.js +2 -2
- package/dist/headless/view-model/edit.js +2 -2
- package/dist/headless/view-model/list.js +3 -4
- package/dist/headless/view-model/types.d.ts +3 -3
- 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.js +16 -17
- package/dist/hooks/use-store.js +18 -22
- package/dist/hooks/use-stream-handler.js +4 -2
- package/dist/i18n-defaults.js +2 -8
- package/dist/i18n.d.ts +10 -10
- package/dist/i18n.js +25 -25
- package/dist/index.js +5 -4
- package/dist/live/dispatcher-live.js +2 -2
- package/dist/primitives.d.ts +225 -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 +21 -22
- 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/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/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/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/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/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 +2 -3
- 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 +1 -1
- package/dist/web/primitives/field.js +9 -8
- package/dist/web/primitives/form.d.ts +1 -1
- package/dist/web/primitives/form.js +6 -6
- package/dist/web/primitives/input.js +2 -2
- package/dist/web/primitives/layout-elements.js +1 -1
- 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/pager.js +1 -1
- package/dist/web/primitives/row-actions.js +3 -4
- 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/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/charts.d.ts +10 -10
- package/dist/web/widgets/charts.js +8 -8
- package/dist/web/widgets/collapsible-section.d.ts +3 -3
- package/dist/web/widgets/collapsible-section.js +6 -6
- package/dist/web/widgets/detail-list.d.ts +1 -3
- package/dist/web/widgets/detail-list.js +1 -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.js +4 -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/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/use-draft.d.ts +7 -7
- package/dist/web/widgets/use-draft.js +7 -7
- package/package.json +6 -6
|
@@ -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
|
}
|
|
@@ -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
|
|
@@ -4,10 +4,10 @@ import { type StatusTone } from "./status-badge.tsx";
|
|
|
4
4
|
export type StatusBarEntry = {
|
|
5
5
|
/** Stable key (z.B. ISO-Datum). */
|
|
6
6
|
readonly key: string;
|
|
7
|
-
/**
|
|
7
|
+
/** Bar height from 0 to 1 (e.g. operational=1, degraded=0.75, outage=0.25). */
|
|
8
8
|
readonly level: number;
|
|
9
9
|
readonly tone: StatusTone;
|
|
10
|
-
/** Tooltip
|
|
10
|
+
/** Tooltip text (<title>), supplied translated by the caller. */
|
|
11
11
|
readonly label?: string;
|
|
12
12
|
};
|
|
13
13
|
/** Status bar strip (e.g. 90-day uptime): variable-height bars with
|
|
@@ -17,7 +17,7 @@ export type StatusBarEntry = {
|
|
|
17
17
|
export declare function StatusBarChart({ entries, ariaLabel, startLabel, endLabel, highlightLast, dense, testId, }: {
|
|
18
18
|
readonly entries: readonly StatusBarEntry[];
|
|
19
19
|
readonly ariaLabel: string;
|
|
20
|
-
/**
|
|
20
|
+
/** Left and right axis labels below the chart, supplied translated. */
|
|
21
21
|
readonly startLabel?: string;
|
|
22
22
|
readonly endLabel?: string;
|
|
23
23
|
readonly highlightLast?: boolean;
|
|
@@ -25,15 +25,15 @@ export declare function StatusBarChart({ entries, ariaLabel, startLabel, endLabe
|
|
|
25
25
|
readonly dense?: boolean;
|
|
26
26
|
readonly testId?: string;
|
|
27
27
|
}): ReactNode;
|
|
28
|
-
/** Quadratic
|
|
29
|
-
*
|
|
28
|
+
/** Quadratic midpoint technique: smooths the zigzag line without overshoot
|
|
29
|
+
* (~5 lines instead of a Catmull-Rom/Bezier fit). */
|
|
30
30
|
export declare function smoothPath(pts: ReadonlyArray<{
|
|
31
31
|
readonly x: number;
|
|
32
32
|
readonly y: number;
|
|
33
33
|
}>): string;
|
|
34
34
|
export type TimeseriesPoint = {
|
|
35
35
|
readonly atMs: number;
|
|
36
|
-
/** null =
|
|
36
|
+
/** null = outage/no value → falls to the baseline for a visible drop. */
|
|
37
37
|
readonly value: number | null;
|
|
38
38
|
};
|
|
39
39
|
export type TimeseriesYAxis = {
|
|
@@ -65,16 +65,16 @@ export type TimeseriesReferenceLine = {
|
|
|
65
65
|
readonly label: string;
|
|
66
66
|
readonly tone?: StatusTone;
|
|
67
67
|
};
|
|
68
|
-
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
68
|
+
/** Time-series line chart (smoothed line with area fill). The x-axis uses
|
|
69
|
+
* TIME within windowStartMs..windowEndMs rather than the index, so 5-minute
|
|
70
|
+
* data in a 30-day window correctly appears as a narrow strip on the right. */
|
|
71
71
|
export declare function TimeseriesChart({ points, windowStartMs, windowEndMs, tone, ariaLabel, axisLabels, xAxis, yAxis, height, markers, formatMarkerTime, referenceLines, emptyContent, testId, }: {
|
|
72
72
|
readonly points: readonly TimeseriesPoint[];
|
|
73
73
|
readonly windowStartMs: number;
|
|
74
74
|
readonly windowEndMs: number;
|
|
75
75
|
readonly tone?: StatusTone;
|
|
76
76
|
readonly ariaLabel: string;
|
|
77
|
-
/**
|
|
77
|
+
/** Axis row below the chart, formatted and translated by the caller. */
|
|
78
78
|
readonly axisLabels?: {
|
|
79
79
|
readonly start: string;
|
|
80
80
|
readonly mid?: string;
|
|
@@ -5,9 +5,9 @@ import { NARROW_VIEWPORT_BREAKPOINT_PX, useIsViewportBelow, } from "../primitive
|
|
|
5
5
|
import { bucketIndexAtFraction, initialWindowSelection, nearestIndex, pointsWithin, resolveStackedAreaWindow, snapBrushWindow, snapMovedBrushWindow, stackedAreaAxisTicks, stackedAreaDateFormatFor, } from "./chart-window.js";
|
|
6
6
|
import { ModeSwitch } from "./mode-switch.js";
|
|
7
7
|
import { STATUS_TONE_TEXT } from "./status-badge.js";
|
|
8
|
-
// Inline
|
|
9
|
-
// --color-status-* / --color-foreground
|
|
10
|
-
//
|
|
8
|
+
// Inline SVG charts without a chart dependency. Colors come exclusively from the
|
|
9
|
+
// --color-status-* / --color-foreground theme tokens; axis labels are supplied
|
|
10
|
+
// translated by the caller, so the widget makes no locale assumptions.
|
|
11
11
|
const HOUR_MS = 3_600_000;
|
|
12
12
|
const DAY_MS = 24 * HOUR_MS;
|
|
13
13
|
const TONE_VAR = {
|
|
@@ -47,8 +47,8 @@ const SINGLE_POINT_RADIUS = 3;
|
|
|
47
47
|
function clampDotY(y, height) {
|
|
48
48
|
return Math.max(SINGLE_POINT_RADIUS, Math.min(height - SINGLE_POINT_RADIUS, y));
|
|
49
49
|
}
|
|
50
|
-
/** Quadratic
|
|
51
|
-
*
|
|
50
|
+
/** Quadratic midpoint technique: smooths the zigzag line without overshoot
|
|
51
|
+
* (~5 lines instead of a Catmull-Rom/Bezier fit). */
|
|
52
52
|
export function smoothPath(pts) {
|
|
53
53
|
const first = pts[0];
|
|
54
54
|
if (!first)
|
|
@@ -85,9 +85,9 @@ function yTickPosition(index, count) {
|
|
|
85
85
|
return "";
|
|
86
86
|
return "-translate-y-1/2";
|
|
87
87
|
}
|
|
88
|
-
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
88
|
+
/** Time-series line chart (smoothed line with area fill). The x-axis uses
|
|
89
|
+
* TIME within windowStartMs..windowEndMs rather than the index, so 5-minute
|
|
90
|
+
* data in a 30-day window correctly appears as a narrow strip on the right. */
|
|
91
91
|
export function TimeseriesChart({ points, windowStartMs, windowEndMs, tone = "ok", ariaLabel, axisLabels, xAxis, yAxis, height, markers, formatMarkerTime, referenceLines, emptyContent, testId, }) {
|
|
92
92
|
const gradientId = useId();
|
|
93
93
|
const descId = useId();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
/** Collapsible section using native <details> with React-controlled state; otherwise
|
|
3
|
+
* the open attribute resets to its initial value on every parent re-render.
|
|
4
|
+
* Provides consistent styling for all “Erweitert”/“Mehr” sections. */
|
|
5
5
|
export declare function CollapsibleSection({ title, defaultOpen, children, testId, }: {
|
|
6
6
|
readonly title: string;
|
|
7
7
|
readonly defaultOpen?: boolean;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { ChevronRight } from "lucide-react";
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
|
-
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
/** Collapsible section using native <details> with React-controlled state; otherwise
|
|
5
|
+
* the open attribute resets to its initial value on every parent re-render.
|
|
6
|
+
* Provides consistent styling for all “Erweitert”/“Mehr” sections. */
|
|
7
7
|
export function CollapsibleSection({ title, defaultOpen = false, children, testId, }) {
|
|
8
8
|
const [open, setOpen] = useState(defaultOpen);
|
|
9
|
-
// useState(defaultOpen)
|
|
10
|
-
//
|
|
11
|
-
//
|
|
9
|
+
// useState(defaultOpen) reads only the mount-time value; an asynchronously hydrated
|
|
10
|
+
// draft flips `defaultOpen` too late for the lazy initializer. An open-only
|
|
11
|
+
// effect never overrides the user's manual collapse.
|
|
12
12
|
useEffect(() => {
|
|
13
13
|
if (defaultOpen)
|
|
14
14
|
setOpen(true);
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
/** Read-only
|
|
3
|
-
* Wert rechts). Wert ist ReactNode → Badges/Chips möglich. `emphasize` hebt
|
|
4
|
-
* eine Zeile hervor (z.B. das Endergebnis in einem Rechner). */
|
|
2
|
+
/** Read-only key-value list for detail views (dimmed label on the left, value on the right). The value is a ReactNode, so badges and chips are supported. `emphasize` highlights a row, such as the final result in a calculator. */
|
|
5
3
|
export declare function DetailList({ rows, testId, }: {
|
|
6
4
|
readonly rows: readonly {
|
|
7
5
|
/** Optional stable key -- falls back to the label when omitted (fine as
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "../lib/cn.js";
|
|
3
|
-
/** Read-only
|
|
4
|
-
* Wert rechts). Wert ist ReactNode → Badges/Chips möglich. `emphasize` hebt
|
|
5
|
-
* eine Zeile hervor (z.B. das Endergebnis in einem Rechner). */
|
|
3
|
+
/** Read-only key-value list for detail views (dimmed label on the left, value on the right). The value is a ReactNode, so badges and chips are supported. `emphasize` highlights a row, such as the final result in a calculator. */
|
|
6
4
|
export function DetailList({ rows, testId, }) {
|
|
7
5
|
return (_jsx("dl", { "data-testid": testId, className: "@container flex flex-col divide-y", children: rows.map((row) => (_jsxs("div", { className: "grid grid-cols-1 gap-0.5 py-2.5 @md:grid-cols-[200px_1fr] @md:gap-4", children: [_jsx("dt", { className: cn("text-sm text-muted-foreground", row.emphasize === true && "font-semibold text-foreground"), children: row.label }), _jsx("dd", { className: cn("text-sm font-medium", row.emphasize === true && "font-semibold"), children: row.value })] }, row.id ?? row.label))) }));
|
|
8
6
|
}
|