@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,30 +1,30 @@
|
|
|
1
1
|
// @runtime client
|
|
2
|
-
// URL
|
|
3
|
-
//
|
|
2
|
+
// URL bridge for visual-tree targets. Serializes TargetRef into
|
|
3
|
+
// search params and parses it back.
|
|
4
4
|
//
|
|
5
|
-
// **URL
|
|
5
|
+
// **URL shape**:
|
|
6
6
|
// ?t=<featureId>:<action>&a_<argKey1>=<value1>&a_<argKey2>=<value2>...
|
|
7
7
|
//
|
|
8
|
-
//
|
|
8
|
+
// Example: text-content edit "imprint/de"
|
|
9
9
|
// ?t=text-content:edit&a_slug=imprint&a_lang=de
|
|
10
10
|
//
|
|
11
|
-
// **
|
|
12
|
-
//
|
|
13
|
-
// (`t`)
|
|
11
|
+
// **Why the `a_` prefix**: avoids naming conflicts with other query
|
|
12
|
+
// params (pagination, sorting, filtering) and clearly separates target
|
|
13
|
+
// metadata (`t`) from action arguments (`a_*`).
|
|
14
14
|
//
|
|
15
|
-
// **
|
|
16
|
-
// JSON-base64
|
|
17
|
-
// values
|
|
18
|
-
//
|
|
15
|
+
// **Why not JSON-encoded**: keeps the URL readable and bookmarkable.
|
|
16
|
+
// JSON-base64 would be robust for arbitrary shapes but unreadable. Argument
|
|
17
|
+
// values are currently only primitive strings (text-content: slug, lang) —
|
|
18
|
+
// nested arguments could switch to JSON if they create a real need.
|
|
19
19
|
const TARGET_PARAM = "t";
|
|
20
20
|
const ARG_PREFIX = "a_";
|
|
21
|
-
/**
|
|
22
|
-
* existing `a_*` keys plus `t`
|
|
23
|
-
*
|
|
21
|
+
/** Builds a search-params update for `setSearchParams`. Clears all
|
|
22
|
+
* existing `a_*` keys plus `t` so switching targets cannot leave stale
|
|
23
|
+
* arguments behind. Returns null-valued entries to clear, plus the new
|
|
24
24
|
* target entries. */
|
|
25
25
|
export function serializeTarget(target, currentParams) {
|
|
26
26
|
const updates = {};
|
|
27
|
-
// Clear
|
|
27
|
+
// Clear previous argument keys so switching targets cannot leave stale arguments.
|
|
28
28
|
for (const key of Object.keys(currentParams)) {
|
|
29
29
|
if (key.startsWith(ARG_PREFIX))
|
|
30
30
|
updates[key] = null;
|
|
@@ -32,9 +32,9 @@ export function serializeTarget(target, currentParams) {
|
|
|
32
32
|
updates[TARGET_PARAM] = `${target.featureId}:${target.action}`;
|
|
33
33
|
if (target.args !== undefined) {
|
|
34
34
|
for (const [k, v] of Object.entries(target.args)) {
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
// JSON-encoded URL
|
|
35
|
+
// Only string arguments are supported. Numbers and booleans are coerced via String();
|
|
36
|
+
// nested objects and arrays are unsupported because they would require a
|
|
37
|
+
// JSON-encoded URL.
|
|
38
38
|
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
|
|
39
39
|
updates[`${ARG_PREFIX}${k}`] = String(v);
|
|
40
40
|
}
|
|
@@ -42,8 +42,8 @@ export function serializeTarget(target, currentParams) {
|
|
|
42
42
|
}
|
|
43
43
|
return updates;
|
|
44
44
|
}
|
|
45
|
-
/**
|
|
46
|
-
* Returns undefined
|
|
45
|
+
/** Parses the current search parameters back into a TargetRef.
|
|
46
|
+
* Returns undefined when the `t` key is missing, indicating no active target. */
|
|
47
47
|
export function parseTargetFromSearchParams(params) {
|
|
48
48
|
const t = params[TARGET_PARAM];
|
|
49
49
|
if (t === undefined || t === "")
|
|
@@ -65,8 +65,8 @@ export function parseTargetFromSearchParams(params) {
|
|
|
65
65
|
}
|
|
66
66
|
return hasArgs ? { featureId, action, args } : { featureId, action };
|
|
67
67
|
}
|
|
68
|
-
/** Returns null-valued updates that clear target
|
|
69
|
-
*
|
|
68
|
+
/** Returns null-valued updates that clear the target and all arguments. Used by
|
|
69
|
+
* the close button and on unmount. */
|
|
70
70
|
export function clearTargetSearchParams(currentParams) {
|
|
71
71
|
const updates = { [TARGET_PARAM]: null };
|
|
72
72
|
for (const key of Object.keys(currentParams)) {
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
// ThemeToggle — Button
|
|
3
|
-
// Icon
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// reinreichen.
|
|
2
|
+
// ThemeToggle — Button that cycles through the theme choices (light → dark → auto).
|
|
3
|
+
// Icon slots are props so renderer/web does not pull in an icon library as a hard
|
|
4
|
+
// dependency. Defaults: Unicode glyphs (☀ / ☾) — they work in every browser, and
|
|
5
|
+
// each app can pass lucide/heroicons/custom SVGs through the props.
|
|
7
6
|
import { useContext } from "react";
|
|
8
7
|
import { HeaderOverflowMenuContext, headerOverflowMenuItemClass } from "./header-overflow-menu.js";
|
|
9
8
|
import { useThemeToggleLabel } from "./theme-label.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const UNIFORM_BAR_CONTROLS_CLASSES = "[&_[data-slot=button]:not([data-size^=icon])]:h-9 [&_[data-slot=input]]:h-9";
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Header and toolbar controls share one height whatever size a screen, app or
|
|
2
|
+
// primitive asked for: the descendant selector outranks the button's own
|
|
3
|
+
// size utilities. Icon-only buttons keep their square size.
|
|
4
|
+
export const UNIFORM_BAR_CONTROLS_CLASSES = "[&_[data-slot=button]:not([data-size^=icon])]:h-9 [&_[data-slot=input]]:h-9";
|
|
@@ -7,11 +7,11 @@ export type WorkspaceShellUser = {
|
|
|
7
7
|
export type WorkspaceShellProps = {
|
|
8
8
|
/** Branding / logo on the left side of the topbar. */
|
|
9
9
|
readonly brand: ReactNode;
|
|
10
|
-
/** Schema
|
|
11
|
-
* registry.getAllWorkspaces() + getWorkspaceNavs().
|
|
12
|
-
* AppSchema (multi-feature)
|
|
13
|
-
* feature, workspaces inline).
|
|
14
|
-
* plain NavTree
|
|
10
|
+
/** Schema with `workspaces` populated — the engine builds this from
|
|
11
|
+
* registry.getAllWorkspaces() + getWorkspaceNavs(). Accepts
|
|
12
|
+
* AppSchema (multi-feature) or the legacy FeatureSchema (single-
|
|
13
|
+
* feature, workspaces inline). Without workspaces, the shell falls
|
|
14
|
+
* back to a plain NavTree without a switcher. */
|
|
15
15
|
readonly schema: AppSchema | FeatureSchema;
|
|
16
16
|
/** Optional topbar end-slot — TenantSwitcher / ThemeToggle / UserMenu. */
|
|
17
17
|
readonly topbarActions?: ReactNode;
|
|
@@ -21,9 +21,9 @@ export type WorkspaceShellProps = {
|
|
|
21
21
|
* workspace from schema, otherwise first accessible. Useful for SSR
|
|
22
22
|
* and tests to pre-seed without hitting the URL. */
|
|
23
23
|
readonly initialWorkspaceId?: string;
|
|
24
|
-
/** Footer
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
/** Footer slot at the bottom of the sidebar — profile row, help link,
|
|
25
|
+
* build information. It stays at the bottom via `mt-auto` (see
|
|
26
|
+
* Sidebar.footer). Symmetric with DefaultAppShell.sidebarFooter. */
|
|
27
27
|
readonly sidebarFooter?: ReactNode;
|
|
28
28
|
/** Runtime badge slot per nav leaf (bare nav id), same as DefaultAppShell. */
|
|
29
29
|
readonly navBadges?: ReadonlyMap<string, ReactNode>;
|
|
@@ -37,27 +37,27 @@ export type WorkspaceShellProps = {
|
|
|
37
37
|
export declare function WorkspaceShell({ brand, schema, topbarActions, user, initialWorkspaceId, sidebarFooter, navBadges, fill, children, }: WorkspaceShellProps): ReactNode;
|
|
38
38
|
export declare function filterByAccess(workspaces: readonly WorkspaceSchema[], userRoles: readonly string[] | undefined): readonly WorkspaceSchema[];
|
|
39
39
|
/** Returns the short screen-id of the first nav-member that points at
|
|
40
|
-
* a screen. Walks the workspace's nav-list in order and skips section
|
|
41
|
-
* headers (NavDefinitions
|
|
42
|
-
*
|
|
40
|
+
* a screen. Walks the workspace's nav-list in order and skips section
|
|
41
|
+
* headers (NavDefinitions without `screen`) so a workspace whose first
|
|
42
|
+
* member is a header still resolves to a renderable screen.
|
|
43
43
|
*
|
|
44
44
|
* Returns "" when:
|
|
45
45
|
* - navMembers is undefined or empty
|
|
46
|
-
* - all members are section
|
|
47
|
-
* - all members are unknown to the schema (drift
|
|
48
|
-
* r.workspace.nav
|
|
49
|
-
*
|
|
46
|
+
* - all members are section headers (no `screen` property)
|
|
47
|
+
* - all members are unknown to the schema (drift between
|
|
48
|
+
* r.workspace.nav and the registered navs — the boot validator catches
|
|
49
|
+
* this server-side; this is only a graceful fallback)
|
|
50
50
|
*
|
|
51
|
-
* The caller MUST loop-guard on "": rendering with screenId=""
|
|
52
|
-
* KumikoScreen
|
|
53
|
-
*
|
|
54
|
-
*
|
|
51
|
+
* The caller MUST loop-guard on "": rendering with screenId="" makes
|
|
52
|
+
* KumikoScreen show the not-found banner — and nav.replace with an empty
|
|
53
|
+
* screenId would fire the useLayoutEffect in WorkspaceShell on every
|
|
54
|
+
* re-render (see the matching guard there).
|
|
55
55
|
*
|
|
56
|
-
* Earlier code used `lastSegment(navQn)
|
|
56
|
+
* Earlier code used `lastSegment(navQn)`, which only worked when the
|
|
57
57
|
* feature followed the convention nav.id === screen.id (samples/apps/
|
|
58
58
|
* workspaces does this). Apps with distinct ids (publicstatus: nav
|
|
59
59
|
* "components" → screen "component-list") got "Screen not found".
|
|
60
60
|
*
|
|
61
|
-
* Exported
|
|
61
|
+
* Exported for edge-case tests. */
|
|
62
62
|
export declare function firstNavScreenId(app: AppSchema, navMembers: readonly string[] | undefined): string;
|
|
63
63
|
export declare function resolveDefaultId(visible: readonly WorkspaceSchema[], preferredShortId: string | undefined): string | undefined;
|
|
@@ -63,7 +63,7 @@ export function WorkspaceShell({ brand, schema, topbarActions, user, initialWork
|
|
|
63
63
|
const handleSelect = useCallback((id) => {
|
|
64
64
|
// Pick the first nav-member that actually points at a screen so
|
|
65
65
|
// the URL lands on something renderable instead of `/<workspace>`.
|
|
66
|
-
// Section
|
|
66
|
+
// Section headers (nav entries without `screen`) are skipped.
|
|
67
67
|
const target = visible.find((ws) => ws.definition.id === id);
|
|
68
68
|
const screenId = firstNavScreenId(app, target?.navMembers);
|
|
69
69
|
nav.navigate({ workspaceId: id, screenId });
|
|
@@ -104,12 +104,11 @@ export function WorkspaceShell({ brand, schema, topbarActions, user, initialWork
|
|
|
104
104
|
return;
|
|
105
105
|
}
|
|
106
106
|
const screenId = firstNavScreenId(app, navMembers);
|
|
107
|
-
// Loop
|
|
108
|
-
// (
|
|
109
|
-
// screenId=""
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
// schicken.
|
|
107
|
+
// Loop guard: if none of the nav-members has a screen property
|
|
108
|
+
// (all are section headers or unknown QNs), nav.replace with
|
|
109
|
+
// screenId="" would fire the effect again on every re-render and pin
|
|
110
|
+
// the user to "/<workspace>/". Do not replace in that case — the
|
|
111
|
+
// workspace selector can then send the user to a leaf nav manually.
|
|
113
112
|
if (screenId === "")
|
|
114
113
|
return;
|
|
115
114
|
nav.replace({ workspaceId: activeId, screenId });
|
|
@@ -132,13 +131,13 @@ export function WorkspaceShell({ brand, schema, topbarActions, user, initialWork
|
|
|
132
131
|
return new Set(activeWorkspace.navMembers);
|
|
133
132
|
}, [app.workspaces, activeWorkspace]);
|
|
134
133
|
const switcher = activeId !== undefined && (_jsx(WorkspaceSwitcher, { workspaces: visible, activeId: activeId, onSelect: handleSelect }));
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
// RoutedScreen
|
|
141
|
-
// (WorkspaceDefinition.navigation
|
|
134
|
+
// One Nav: always NavTree. Dynamic nodes (r.nav({ provider: true }))
|
|
135
|
+
// load their children client-side from the registered navProviders —
|
|
136
|
+
// the former navigation:"tree"/VisualTree branch is folded in here.
|
|
137
|
+
// In the content area, an active target (from nav.searchParams, set
|
|
138
|
+
// when a target node is clicked) renders EditorPanel instead of
|
|
139
|
+
// RoutedScreen; otherwise it renders the regular screen children.
|
|
140
|
+
// (WorkspaceDefinition.navigation has been a no-op since then; removal deferred.)
|
|
142
141
|
const resolvers = useResolvers();
|
|
143
142
|
const activeTarget = parseTargetFromSearchParams(nav.searchParams);
|
|
144
143
|
const sidebarContent = (_jsx(NavTree, { schema: app, ...(user !== undefined && { user }), ...(allowedNavQns !== undefined && { allowedNavQns }), ...(navBadges !== undefined && { navBadges }) }));
|
|
@@ -148,8 +147,8 @@ export function WorkspaceShell({ brand, schema, topbarActions, user, initialWork
|
|
|
148
147
|
// above the content. topbarActions render right inside the header row
|
|
149
148
|
// instead of a separate topbar — one header line, not two.
|
|
150
149
|
const fillCls = fillClasses(fill);
|
|
151
|
-
//
|
|
152
|
-
//
|
|
150
|
+
// The second column appears only when a screen fills it (sidebar-09 pattern);
|
|
151
|
+
// otherwise every other screen would have an empty strip beside the navigation.
|
|
153
152
|
const panel = useSidebarPanelHost();
|
|
154
153
|
return (_jsx(SidebarProvider, { ...fillCls.provider, children: _jsxs(SidebarPanelProvider, { value: panel.value, children: [_jsxs(Sidebar, { collapsible: "icon", mobileTitle: t("kumiko.sidebar.title"), mobileDescription: t("kumiko.sidebar.description"), children: [_jsxs(SidebarHeader, { "data-kumiko-layout": "sidebar-header", children: [brand, switcher] }), _jsx(SidebarContent, { "data-kumiko-layout": "sidebar-nav", children: sidebarContent }), sidebarFooter !== undefined && (_jsx(SidebarFooter, { "data-kumiko-layout": "sidebar-footer", className: "border-t border-sidebar-border", children: sidebarFooter })), _jsx(SidebarRail, { "aria-label": t("kumiko.sidebar.toggle"), title: t("kumiko.sidebar.toggle") })] }), _jsx("div", { ref: panel.ref, "data-kumiko-layout": "sidebar-panel", className: panel.occupied ? "flex h-svh shrink-0 flex-col" : "hidden" }), _jsx(SidebarInset, { className: fillCls.inset, children: _jsxs(PageHeaderSlotProvider, { children: [_jsx(ShellHeader, { schema: app, ...(user !== undefined && { user }), ...(topbarActions !== undefined && { headerActions: topbarActions }) }), _jsx("main", { className: fillCls.main, children: activeTarget !== undefined ? (_jsx(EditorPanel, { resolvers: resolvers })) : (_jsx(UserRolesProvider, { roles: user?.roles, children: children })) })] }) })] }) }));
|
|
155
154
|
}
|
|
@@ -166,28 +165,28 @@ function byOrderThenInsertion(a, b) {
|
|
|
166
165
|
return ao - bo;
|
|
167
166
|
}
|
|
168
167
|
/** Returns the short screen-id of the first nav-member that points at
|
|
169
|
-
* a screen. Walks the workspace's nav-list in order and skips section
|
|
170
|
-
* headers (NavDefinitions
|
|
171
|
-
*
|
|
168
|
+
* a screen. Walks the workspace's nav-list in order and skips section
|
|
169
|
+
* headers (NavDefinitions without `screen`) so a workspace whose first
|
|
170
|
+
* member is a header still resolves to a renderable screen.
|
|
172
171
|
*
|
|
173
172
|
* Returns "" when:
|
|
174
173
|
* - navMembers is undefined or empty
|
|
175
|
-
* - all members are section
|
|
176
|
-
* - all members are unknown to the schema (drift
|
|
177
|
-
* r.workspace.nav
|
|
178
|
-
*
|
|
174
|
+
* - all members are section headers (no `screen` property)
|
|
175
|
+
* - all members are unknown to the schema (drift between
|
|
176
|
+
* r.workspace.nav and the registered navs — the boot validator catches
|
|
177
|
+
* this server-side; this is only a graceful fallback)
|
|
179
178
|
*
|
|
180
|
-
* The caller MUST loop-guard on "": rendering with screenId=""
|
|
181
|
-
* KumikoScreen
|
|
182
|
-
*
|
|
183
|
-
*
|
|
179
|
+
* The caller MUST loop-guard on "": rendering with screenId="" makes
|
|
180
|
+
* KumikoScreen show the not-found banner — and nav.replace with an empty
|
|
181
|
+
* screenId would fire the useLayoutEffect in WorkspaceShell on every
|
|
182
|
+
* re-render (see the matching guard there).
|
|
184
183
|
*
|
|
185
|
-
* Earlier code used `lastSegment(navQn)
|
|
184
|
+
* Earlier code used `lastSegment(navQn)`, which only worked when the
|
|
186
185
|
* feature followed the convention nav.id === screen.id (samples/apps/
|
|
187
186
|
* workspaces does this). Apps with distinct ids (publicstatus: nav
|
|
188
187
|
* "components" → screen "component-list") got "Screen not found".
|
|
189
188
|
*
|
|
190
|
-
* Exported
|
|
189
|
+
* Exported for edge-case tests. */
|
|
191
190
|
export function firstNavScreenId(app, navMembers) {
|
|
192
191
|
if (navMembers === undefined || navMembers.length === 0)
|
|
193
192
|
return "";
|
|
@@ -197,7 +196,7 @@ export function firstNavScreenId(app, navMembers) {
|
|
|
197
196
|
if (qualifyNavId(feature.featureName, nav.id) !== navQn)
|
|
198
197
|
continue;
|
|
199
198
|
if (nav.screen === undefined)
|
|
200
|
-
break; // section-header →
|
|
199
|
+
break; // section-header → next member
|
|
201
200
|
return lastSegment(nav.screen);
|
|
202
201
|
}
|
|
203
202
|
}
|
package/dist/web/lib/cn.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ClassValue } from "clsx";
|
|
2
|
-
/** shadcn
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
/** Standard shadcn helper: `clsx` combines conditional classes,
|
|
3
|
+
* while `tailwind-merge` resolves conflicts (for example,
|
|
4
|
+
* `px-2 px-4` → `px-4`). Primitives use this to merge consumer
|
|
5
|
+
* classes with default classes without duplicates. */
|
|
6
6
|
export declare function cn(...inputs: ClassValue[]): string;
|
package/dist/web/lib/cn.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { clsx } from "clsx";
|
|
2
2
|
import { twMerge } from "tailwind-merge";
|
|
3
|
-
/** shadcn
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
/** Standard shadcn helper: `clsx` combines conditional classes,
|
|
4
|
+
* while `tailwind-merge` resolves conflicts (for example,
|
|
5
|
+
* `px-2 px-4` → `px-4`). Primitives use this to merge consumer
|
|
6
|
+
* classes with default classes without duplicates. */
|
|
7
7
|
export function cn(...inputs) {
|
|
8
8
|
return twMerge(clsx(inputs));
|
|
9
9
|
}
|
|
@@ -37,6 +37,9 @@ export async function fetchFileRefBlob(fileRefId) {
|
|
|
37
37
|
fileName: fileNameFromContentDisposition(res.headers.get("content-disposition")),
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
|
+
// The browser starts the download after click() returns; Safari and some
|
|
41
|
+
// Firefox versions fail it when the blob URL is already revoked.
|
|
42
|
+
const OBJECT_URL_REVOKE_DELAY_MS = 40_000;
|
|
40
43
|
export function saveBlobAsFile(fileName, blob, options) {
|
|
41
44
|
const payload = options?.bom === true ? new Blob([UTF8_BOM, blob], { type: blob.type }) : blob;
|
|
42
45
|
const url = URL.createObjectURL(payload);
|
|
@@ -53,7 +56,7 @@ export function saveBlobAsFile(fileName, blob, options) {
|
|
|
53
56
|
}
|
|
54
57
|
}
|
|
55
58
|
finally {
|
|
56
|
-
URL.revokeObjectURL(url);
|
|
59
|
+
setTimeout(() => URL.revokeObjectURL(url), OBJECT_URL_REVOKE_DELAY_MS);
|
|
57
60
|
}
|
|
58
61
|
}
|
|
59
62
|
export async function downloadFileRef(fileRefId, options) {
|
|
@@ -3,15 +3,15 @@ export type MenuItemDef = {
|
|
|
3
3
|
readonly kind: "item";
|
|
4
4
|
readonly id: string;
|
|
5
5
|
readonly label: string;
|
|
6
|
-
/** Optional
|
|
6
|
+
/** Optional icon displayed before the label (16px). */
|
|
7
7
|
readonly icon?: ReactNode;
|
|
8
|
-
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
/** Display-only shortcut hint on the right (monospace, muted).
|
|
9
|
+
* The caller registers the shortcut separately; Linear-style
|
|
10
|
+
* strings include "O then M", "Alt + Q", and "⌘K". */
|
|
11
11
|
readonly shortcut?: string;
|
|
12
12
|
readonly onSelect: () => void;
|
|
13
13
|
readonly disabled?: boolean;
|
|
14
|
-
/** "danger"
|
|
14
|
+
/** "danger" renders the label in red, typically for sign-out or delete actions. */
|
|
15
15
|
readonly variant?: "default" | "danger";
|
|
16
16
|
} | {
|
|
17
17
|
readonly kind: "separator";
|
|
@@ -22,12 +22,12 @@ export type MenuItemDef = {
|
|
|
22
22
|
export type ActionMenuProps = {
|
|
23
23
|
/** Free-form Trigger-Component — Avatar, IconButton, Pill, Custom. */
|
|
24
24
|
readonly trigger: ReactNode;
|
|
25
|
-
/** aria-label
|
|
25
|
+
/** aria-label for the trigger wrapper. */
|
|
26
26
|
readonly triggerLabel?: string;
|
|
27
27
|
readonly items: readonly MenuItemDef[];
|
|
28
|
-
/**
|
|
28
|
+
/** Where to anchor the popup relative to the trigger. Default: end. */
|
|
29
29
|
readonly align?: "start" | "center" | "end";
|
|
30
|
-
/**
|
|
30
|
+
/** Minimum width for the content. Default: 14rem. */
|
|
31
31
|
readonly minWidth?: string;
|
|
32
32
|
readonly testId?: string;
|
|
33
33
|
};
|
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
// Generic
|
|
3
|
-
// menu
|
|
4
|
-
// Header-Menu (
|
|
5
|
-
//
|
|
2
|
+
// Generic action menu built on @radix-ui/react-dropdown-
|
|
3
|
+
// menu. Used for ProfileMenu (topbar avatar), Edit-
|
|
4
|
+
// Header-Menu (three dots), and list-row kebab menus; only the
|
|
5
|
+
// trigger differs between uses.
|
|
6
6
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
7
|
+
// Unlike RowActionsCell (see primitives/index.tsx), this is a
|
|
8
|
+
// stable, schema-free layer callable from app code and the schema
|
|
9
|
+
// renderer (KumikoApp), without the rowActions schema or per-row
|
|
10
|
+
// visibility logic.
|
|
11
11
|
//
|
|
12
|
-
// Items
|
|
13
|
-
//
|
|
14
|
-
// "O then M")
|
|
12
|
+
// Items form a discriminated union (item / separator / label). Items
|
|
13
|
+
// can optionally display a keyboard-shortcut hint on the right
|
|
14
|
+
// (Linear-style strings such as "O then M"), an icon on the left,
|
|
15
|
+
// and a variant for danger styling.
|
|
15
16
|
import { useState } from "react";
|
|
16
17
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "./dropdown-menu.js";
|
|
17
18
|
export function ActionMenu({ trigger, triggerLabel, items, align = "end", minWidth = "14rem", testId, }) {
|
|
18
19
|
const [open, setOpen] = useState(false);
|
|
19
20
|
return (_jsxs(DropdownMenu, { open: open, onOpenChange: setOpen, children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx("button", { type: "button", "data-testid": testId ?? "action-menu-trigger", ...(triggerLabel !== undefined && { "aria-label": triggerLabel }), className: "rounded outline-none focus-visible:ring-1 focus-visible:ring-ring", children: trigger }) }), _jsx(DropdownMenuContent, { align: align, style: { minWidth }, children: items.map((item, idx) => {
|
|
20
21
|
if (item.kind === "separator") {
|
|
21
|
-
// biome-ignore lint/suspicious/noArrayIndexKey: Separators
|
|
22
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: Separators have no ID, so their position provides identity
|
|
22
23
|
return _jsx(DropdownMenuSeparator, {}, `sep-${idx}`);
|
|
23
24
|
}
|
|
24
25
|
if (item.kind === "label") {
|
|
25
|
-
// biome-ignore lint/suspicious/noArrayIndexKey:
|
|
26
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: labels have no ID either, so their position provides identity
|
|
26
27
|
return _jsx(DropdownMenuLabel, { children: item.label }, `label-${idx}`);
|
|
27
28
|
}
|
|
28
29
|
return (_jsxs(DropdownMenuItem, { onSelect: item.onSelect, disabled: item.disabled === true, "data-testid": `action-menu-item-${item.id}`, className: item.variant === "danger" ? "text-destructive focus:text-destructive" : undefined, children: [item.icon !== undefined && _jsx("span", { className: "size-4 shrink-0", children: item.icon }), _jsx("span", { className: "flex-1", children: item.label }), item.shortcut !== undefined && (_jsx("span", { className: "ml-auto text-xs text-muted-foreground tracking-wider font-mono", children: item.shortcut }))] }, item.id));
|
|
@@ -6,7 +6,7 @@ export type CalendarPopoverProps = {
|
|
|
6
6
|
readonly max?: Date;
|
|
7
7
|
readonly disabled?: boolean;
|
|
8
8
|
readonly hasError?: boolean;
|
|
9
|
-
/** a11y
|
|
9
|
+
/** a11y label for the icon button (no visible text). */
|
|
10
10
|
readonly triggerLabel: string;
|
|
11
11
|
};
|
|
12
12
|
export declare const inFieldIconButtonClass: string;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
// CalendarPopover —
|
|
3
|
-
// TimestampInput.
|
|
2
|
+
// CalendarPopover — shared calendar trigger for DateInput and
|
|
3
|
+
// TimestampInput. An icon button opens a Radix popover with
|
|
4
4
|
// react-day-picker (v9). captionLayout="dropdown" + startMonth/endMonth
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
// provide month AND year dropdowns instead of only month navigation —
|
|
6
|
+
// going back 10 years would otherwise take ~120 clicks. min/max gray out
|
|
7
|
+
// days outside the allowed range.
|
|
8
8
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
9
|
+
// The text input (typing) intentionally lives in each Primitive, not here:
|
|
10
|
+
// date has one field, while timestamp has two (date + time). This lets both
|
|
11
|
+
// share the calendar mechanics without sharing their input layout.
|
|
12
12
|
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
13
13
|
import { Calendar as CalendarIcon } from "lucide-react";
|
|
14
14
|
import { lazy, Suspense } from "react";
|
|
@@ -24,8 +24,8 @@ const popoverClass = "z-50 rounded-md border bg-popover p-3 text-popover-foregro
|
|
|
24
24
|
"data-[state=open]:animate-in data-[state=closed]:animate-out " +
|
|
25
25
|
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 " +
|
|
26
26
|
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95";
|
|
27
|
-
// Default
|
|
28
|
-
//
|
|
27
|
+
// Default range when the field does not set min/max; otherwise the year
|
|
28
|
+
// dropdown would be empty or unusable. Computed at render time.
|
|
29
29
|
function defaultRange() {
|
|
30
30
|
const thisYear = new Date().getFullYear();
|
|
31
31
|
return { start: new Date(1900, 0, 1), end: new Date(thisYear + 10, 11, 31) };
|
|
@@ -9,7 +9,7 @@ export const cardSurface = cva("flex flex-col border bg-card text-card-foregroun
|
|
|
9
9
|
variants: { radius: { xl: "rounded-[var(--card-radius)]", lg: "rounded-lg" } },
|
|
10
10
|
defaultVariants: { radius: "xl" },
|
|
11
11
|
});
|
|
12
|
-
// Wraps instead of running off-screen when the row outgrows its container
|
|
12
|
+
// Wraps instead of running off-screen when the row outgrows its container.
|
|
13
13
|
export const cardFooter = "flex flex-wrap items-center justify-end gap-2 py-4";
|
|
14
14
|
export const cardFooterInset = "px-[var(--card-padding)]";
|
|
15
15
|
// /30 read as nearly invisible against the light-theme card (white card,
|
|
@@ -26,8 +26,8 @@ export function DefaultCard({ slots, options, className, testId, dataAttributes,
|
|
|
26
26
|
// An unframed card has no radius to clip; overflow-hidden would also
|
|
27
27
|
// zero its flex-item min-height and cut off content inside a scroller.
|
|
28
28
|
(framed || fillHeight) && "overflow-hidden",
|
|
29
|
-
// Same "no flex-1" reasoning as DefaultForm's own fillHeight card
|
|
30
|
-
//
|
|
29
|
+
// Same "no flex-1" reasoning as DefaultForm's own fillHeight card:
|
|
30
|
+
// sizes to content and only shrinks (min-h-0) once
|
|
31
31
|
// an ancestor is itself height-constrained.
|
|
32
32
|
fillHeight && "min-h-0 flex flex-col", className), children: [header, children != null && (_jsx("div", { className: cn("grow", screenBody && "flex flex-col gap-6", padded &&
|
|
33
33
|
(hasHeader
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useContext } from "react";
|
|
3
|
+
import { cn } from "../lib/cn.js";
|
|
4
|
+
import { FieldDescriptionContext } from "./contexts.js";
|
|
5
|
+
import { useFocusOnMount } from "./use-focus-on-mount.js";
|
|
6
|
+
const MIN_LENGTH_FOR_SEPARATOR = 6;
|
|
7
|
+
export function CodeInput(props) {
|
|
8
|
+
const { length, value, hasError, disabled } = props;
|
|
9
|
+
const describedBy = useContext(FieldDescriptionContext);
|
|
10
|
+
const inputRef = useFocusOnMount(props.autoFocus);
|
|
11
|
+
const activeIndex = Math.min(value.length, length - 1);
|
|
12
|
+
const hasSeparator = length >= MIN_LENGTH_FOR_SEPARATOR && length % 2 === 0;
|
|
13
|
+
const handleChange = (e) => {
|
|
14
|
+
const next = e.target.value.replace(/\D/g, "").slice(0, length);
|
|
15
|
+
props.onChange(next);
|
|
16
|
+
if (next.length === length && next !== value)
|
|
17
|
+
props.onComplete?.(next);
|
|
18
|
+
};
|
|
19
|
+
const cells = [];
|
|
20
|
+
for (let index = 0; index < length; index++) {
|
|
21
|
+
const isActive = index === activeIndex;
|
|
22
|
+
const char = value[index];
|
|
23
|
+
if (hasSeparator && index === length / 2) {
|
|
24
|
+
cells.push(_jsx("span", { "aria-hidden": "true", className: "shrink-0 text-muted-foreground", children: "\u2013" }, "separator"));
|
|
25
|
+
}
|
|
26
|
+
cells.push(_jsxs("div", { "data-active": isActive, "aria-hidden": "true", className: cn("flex h-12 min-w-0 flex-1 items-center justify-center rounded-md border border-input bg-transparent text-xl font-semibold tabular-nums shadow-xs transition-[color,box-shadow] dark:bg-input/30", hasError === true && "border-destructive", "data-[active=true]:group-focus-within:ring-[3px]", hasError === true
|
|
27
|
+
? "data-[active=true]:group-focus-within:border-destructive data-[active=true]:group-focus-within:ring-destructive/20 dark:data-[active=true]:group-focus-within:ring-destructive/40"
|
|
28
|
+
: "data-[active=true]:group-focus-within:border-ring data-[active=true]:group-focus-within:ring-ring/50"), children: [char, isActive && char === undefined ? (_jsx("span", { className: "hidden h-6 w-px animate-pulse bg-foreground group-focus-within:block" })) : null] }, index));
|
|
29
|
+
}
|
|
30
|
+
return (_jsxs("div", { className: cn("group relative flex w-full items-center gap-2", disabled === true && "cursor-not-allowed opacity-50"), children: [cells, _jsx("input", { ref: inputRef, type: "text", id: props.id, name: props.name, value: value, onChange: handleChange, disabled: disabled, inputMode: "numeric", autoComplete: props.autoComplete ?? "one-time-code", spellCheck: false, "data-testid": props.testId, "aria-describedby": describedBy, "aria-required": props.required, "aria-invalid": hasError === true ? true : undefined, className: "absolute inset-0 h-full w-full bg-transparent text-transparent caret-transparent outline-none selection:bg-transparent" })] }));
|
|
31
|
+
}
|
|
@@ -20,27 +20,27 @@ type ComboboxBaseProps = {
|
|
|
20
20
|
readonly placeholder?: string;
|
|
21
21
|
readonly searchPlaceholder?: string;
|
|
22
22
|
readonly emptyText?: string;
|
|
23
|
-
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
|
|
23
|
+
/** When set, enables remote search. cmdk's client-side filter is disabled
|
|
24
|
+
* because the server has already filtered the results, and the search input
|
|
25
|
+
* invokes onSearchChange with a 300ms debounce. Without this callback,
|
|
26
|
+
* filtering remains local using cmdk's fuzzy matching on the loaded options.
|
|
27
|
+
*/
|
|
28
28
|
readonly onSearchChange?: (q: string) => void;
|
|
29
|
-
/**
|
|
29
|
+
/** Shows a spinner in the trigger and popover footer while remote search is
|
|
30
|
+
* in progress. */
|
|
30
31
|
readonly loading?: boolean;
|
|
31
32
|
/** Fixed footer row below the option list ("+ createLabel"). Rendered
|
|
32
33
|
* unfiltered — stays visible even when the search matches nothing,
|
|
33
34
|
* which is the most common case for reference-lookups on a freshly
|
|
34
|
-
* seeded system
|
|
35
|
+
* seeded system. Omit for the plain
|
|
35
36
|
* select-existing combobox. */
|
|
36
37
|
readonly onCreate?: () => void;
|
|
37
38
|
/** Label for the create-footer row. Default: i18n `kumiko.actions.create`. */
|
|
38
39
|
readonly createLabel?: string;
|
|
39
|
-
/** Test
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* den Default (false). */
|
|
40
|
+
/** Test hook that controls the popover's initial open state. In jsdom with
|
|
41
|
+
* Radix Popover, userEvent.click does not reliably trigger PointerEvents;
|
|
42
|
+
* tests set defaultOpen=true so the popover is rendered immediately.
|
|
43
|
+
* Production code leaves the default as false. */
|
|
44
44
|
readonly defaultOpen?: boolean;
|
|
45
45
|
};
|
|
46
46
|
export type ComboboxInputProps = ComboboxBaseProps & ({
|