@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.
Files changed (222) hide show
  1. package/LICENSE +21 -57
  2. package/dist/app/action-form-body.d.ts +2 -2
  3. package/dist/app/action-form-body.js +12 -4
  4. package/dist/app/action-form-shim.d.ts +8 -8
  5. package/dist/app/action-form-shim.js +20 -22
  6. package/dist/app/action-form-support.d.ts +1 -0
  7. package/dist/app/action-form-support.js +1 -1
  8. package/dist/app/app-features-context.js +4 -4
  9. package/dist/app/column-renderers.d.ts +7 -9
  10. package/dist/app/column-renderers.js +15 -18
  11. package/dist/app/config-edit-body.js +7 -3
  12. package/dist/app/config-edit-shim.d.ts +7 -8
  13. package/dist/app/config-edit-shim.js +18 -20
  14. package/dist/app/custom-screens.d.ts +3 -3
  15. package/dist/app/custom-screens.js +8 -8
  16. package/dist/app/dashboard-body.d.ts +2 -2
  17. package/dist/app/dashboard-body.js +2 -2
  18. package/dist/app/entity-edit-prefill.d.ts +11 -0
  19. package/dist/app/entity-edit-prefill.js +18 -13
  20. package/dist/app/entity-edit-screen.js +4 -4
  21. package/dist/app/entity-list-screen.js +3 -3
  22. package/dist/app/extension-form-submit.d.ts +1 -1
  23. package/dist/app/extension-form-submit.js +14 -15
  24. package/dist/app/extension-sections.d.ts +29 -28
  25. package/dist/app/extension-sections.js +10 -10
  26. package/dist/app/feature-schema.js +11 -11
  27. package/dist/app/form-schema.js +2 -2
  28. package/dist/app/header-actions-bar.js +2 -2
  29. package/dist/app/kumiko-screen.js +8 -10
  30. package/dist/app/layout-fields.js +1 -1
  31. package/dist/app/list-facets.js +3 -3
  32. package/dist/app/nav.d.ts +10 -10
  33. package/dist/app/nav.js +13 -13
  34. package/dist/app/projection-detail-body.js +4 -4
  35. package/dist/app/projection-detail-shim.d.ts +6 -5
  36. package/dist/app/projection-detail-shim.js +23 -22
  37. package/dist/app/projection-list-body.js +10 -10
  38. package/dist/app/projection-list-shim.d.ts +5 -5
  39. package/dist/app/projection-list-shim.js +17 -18
  40. package/dist/app/qn.js +10 -12
  41. package/dist/app/qualify-screen-id.js +1 -1
  42. package/dist/app/screen-access.js +2 -2
  43. package/dist/app/screen-shared.js +1 -1
  44. package/dist/app/secret-mint-body.js +4 -4
  45. package/dist/app/write-failed-error.js +8 -7
  46. package/dist/components/edit-mounts.d.ts +2 -2
  47. package/dist/components/embedded-list-field.js +5 -5
  48. package/dist/components/grid-cell-for-field.d.ts +1 -1
  49. package/dist/components/reference-create-dialog.js +3 -3
  50. package/dist/components/related-list-section.d.ts +1 -1
  51. package/dist/components/related-list-section.js +15 -2
  52. package/dist/components/render-edit-action-button.d.ts +1 -3
  53. package/dist/components/render-edit-action-button.js +2 -2
  54. package/dist/components/render-edit-logic.js +1 -1
  55. package/dist/components/render-edit-types.d.ts +6 -6
  56. package/dist/components/render-edit.js +74 -73
  57. package/dist/components/render-field.d.ts +7 -7
  58. package/dist/components/render-field.js +62 -61
  59. package/dist/components/render-list.d.ts +42 -43
  60. package/dist/components/render-list.js +93 -84
  61. package/dist/components/use-wizard-form-draft.js +10 -10
  62. package/dist/context/dispatcher-context.js +5 -5
  63. package/dist/context/user-roles-context.js +2 -2
  64. package/dist/flow-diagram/render.js +9 -1
  65. package/dist/headless/apex/index.d.ts +1 -2
  66. package/dist/headless/apex/index.js +5 -6
  67. package/dist/headless/form/form-controller.js +14 -6
  68. package/dist/headless/form/types.d.ts +1 -0
  69. package/dist/headless/form/zod-bridge.js +2 -2
  70. package/dist/headless/format/index.js +89 -64
  71. package/dist/headless/format/money.js +1 -1
  72. package/dist/headless/index.js +2 -2
  73. package/dist/headless/view-model/edit.js +4 -2
  74. package/dist/headless/view-model/list.js +184 -150
  75. package/dist/headless/view-model/types.d.ts +6 -3
  76. package/dist/hooks/live-refetch-scheduler.js +4 -0
  77. package/dist/hooks/reference-limits.js +11 -12
  78. package/dist/hooks/use-list-url-state.d.ts +13 -13
  79. package/dist/hooks/use-list-url-state.js +21 -22
  80. package/dist/hooks/use-query.js +3 -3
  81. package/dist/hooks/use-reference-lookup.d.ts +9 -1
  82. package/dist/hooks/use-reference-lookup.js +32 -19
  83. package/dist/hooks/use-store.js +18 -22
  84. package/dist/hooks/use-stream-handler.js +4 -2
  85. package/dist/i18n-defaults.js +6 -8
  86. package/dist/i18n.d.ts +10 -10
  87. package/dist/i18n.js +25 -25
  88. package/dist/index.d.ts +1 -0
  89. package/dist/index.js +6 -4
  90. package/dist/live/dispatcher-live.js +2 -2
  91. package/dist/primitives.d.ts +244 -221
  92. package/dist/primitives.js +1 -1
  93. package/dist/sse/live-events.d.ts +5 -5
  94. package/dist/sse/live-events.js +17 -17
  95. package/dist/tokens.d.ts +8 -8
  96. package/dist/tokens.js +16 -17
  97. package/dist/web/app/browser-locale.d.ts +9 -9
  98. package/dist/web/app/browser-locale.js +19 -19
  99. package/dist/web/app/client-plugin.d.ts +44 -52
  100. package/dist/web/app/client-plugin.js +4 -5
  101. package/dist/web/app/create-app.d.ts +28 -31
  102. package/dist/web/app/create-app.js +29 -25
  103. package/dist/web/app/dashboard-body.js +17 -1
  104. package/dist/web/app/dashboard-panels.js +12 -2
  105. package/dist/web/app/document-lang-sync.js +1 -1
  106. package/dist/web/app/draft-storage.js +1 -1
  107. package/dist/web/app/nav-providers-context.d.ts +3 -3
  108. package/dist/web/app/nav-providers-context.js +3 -3
  109. package/dist/web/app/nav.d.ts +18 -17
  110. package/dist/web/app/nav.js +66 -64
  111. package/dist/web/app/rich-content-editor.js +1 -1
  112. package/dist/web/components/config-cascade.js +17 -18
  113. package/dist/web/components/config-source-badge.js +4 -4
  114. package/dist/web/index.d.ts +1 -1
  115. package/dist/web/index.js +1 -1
  116. package/dist/web/layout/app-layout.d.ts +9 -9
  117. package/dist/web/layout/app-layout.js +2 -2
  118. package/dist/web/layout/avatar.d.ts +4 -3
  119. package/dist/web/layout/avatar.js +15 -17
  120. package/dist/web/layout/default-app-shell.d.ts +22 -23
  121. package/dist/web/layout/editor-panel.js +3 -3
  122. package/dist/web/layout/nav-tree.js +86 -84
  123. package/dist/web/layout/profile-menu.d.ts +1 -1
  124. package/dist/web/layout/shell-header.js +2 -1
  125. package/dist/web/layout/sidebar-brand.d.ts +7 -7
  126. package/dist/web/layout/sidebar-user.d.ts +1 -1
  127. package/dist/web/layout/sidebar-user.js +4 -4
  128. package/dist/web/layout/sidebar.d.ts +8 -8
  129. package/dist/web/layout/sidebar.js +7 -7
  130. package/dist/web/layout/target-resolver-stub.d.ts +5 -5
  131. package/dist/web/layout/target-resolver-stub.js +20 -20
  132. package/dist/web/layout/target-url.d.ts +7 -7
  133. package/dist/web/layout/target-url.js +22 -22
  134. package/dist/web/layout/theme-toggle.js +4 -5
  135. package/dist/web/layout/uniform-bar-controls.d.ts +1 -0
  136. package/dist/web/layout/uniform-bar-controls.js +4 -0
  137. package/dist/web/layout/workspace-shell.d.ts +21 -21
  138. package/dist/web/layout/workspace-shell.js +29 -30
  139. package/dist/web/lib/cn.d.ts +4 -4
  140. package/dist/web/lib/cn.js +4 -4
  141. package/dist/web/lib/file-ref-download.js +4 -1
  142. package/dist/web/primitives/action-menu.d.ts +8 -8
  143. package/dist/web/primitives/action-menu.js +14 -13
  144. package/dist/web/primitives/calendar-popover.d.ts +1 -1
  145. package/dist/web/primitives/calendar-popover.js +10 -10
  146. package/dist/web/primitives/card-styles.js +1 -1
  147. package/dist/web/primitives/card.js +2 -2
  148. package/dist/web/primitives/code-input.d.ts +6 -0
  149. package/dist/web/primitives/code-input.js +31 -0
  150. package/dist/web/primitives/combobox.d.ts +12 -12
  151. package/dist/web/primitives/combobox.js +34 -35
  152. package/dist/web/primitives/data-table-cell.js +2 -2
  153. package/dist/web/primitives/data-table.js +6 -6
  154. package/dist/web/primitives/date-field.js +2 -2
  155. package/dist/web/primitives/date-input.js +4 -4
  156. package/dist/web/primitives/default-form.js +2 -2
  157. package/dist/web/primitives/dropdown-menu.js +14 -15
  158. package/dist/web/primitives/embedded-list-input.js +3 -3
  159. package/dist/web/primitives/facet-filter.js +2 -2
  160. package/dist/web/primitives/field.js +9 -8
  161. package/dist/web/primitives/file-thumbnail.d.ts +3 -0
  162. package/dist/web/primitives/file-thumbnail.js +5 -0
  163. package/dist/web/primitives/form.d.ts +1 -1
  164. package/dist/web/primitives/form.js +6 -6
  165. package/dist/web/primitives/input.js +8 -3
  166. package/dist/web/primitives/layout-elements.js +7 -5
  167. package/dist/web/primitives/layout.js +2 -2
  168. package/dist/web/primitives/located-timestamp-input.js +4 -4
  169. package/dist/web/primitives/money-input.js +13 -17
  170. package/dist/web/primitives/page-header.js +3 -1
  171. package/dist/web/primitives/pager.js +1 -1
  172. package/dist/web/primitives/row-actions.js +6 -7
  173. package/dist/web/primitives/secret-reveal.js +1 -1
  174. package/dist/web/primitives/timestamp-input.d.ts +9 -9
  175. package/dist/web/primitives/timestamp-input.js +21 -21
  176. package/dist/web/primitives/toast.js +12 -13
  177. package/dist/web/primitives/use-focus-on-mount.d.ts +2 -0
  178. package/dist/web/primitives/use-focus-on-mount.js +9 -0
  179. package/dist/web/sse/live-events.d.ts +6 -6
  180. package/dist/web/sse/live-events.js +3 -3
  181. package/dist/web/tokens.d.ts +11 -12
  182. package/dist/web/tokens.js +35 -37
  183. package/dist/web/version/update-checker.js +14 -13
  184. package/dist/web/widgets/ai-text-field.js +1 -1
  185. package/dist/web/widgets/bounded-load-cache.d.ts +1 -1
  186. package/dist/web/widgets/bounded-load-cache.js +10 -4
  187. package/dist/web/widgets/charts.d.ts +10 -10
  188. package/dist/web/widgets/charts.js +8 -8
  189. package/dist/web/widgets/choice-cards.d.ts +17 -0
  190. package/dist/web/widgets/choice-cards.js +12 -0
  191. package/dist/web/widgets/collapsible-section.d.ts +3 -3
  192. package/dist/web/widgets/collapsible-section.js +7 -7
  193. package/dist/web/widgets/detail-list.d.ts +5 -3
  194. package/dist/web/widgets/detail-list.js +5 -3
  195. package/dist/web/widgets/feed-list.d.ts +2 -2
  196. package/dist/web/widgets/feed-list.js +2 -2
  197. package/dist/web/widgets/form-fields.d.ts +14 -14
  198. package/dist/web/widgets/form-fields.js +14 -13
  199. package/dist/web/widgets/index.d.ts +1 -0
  200. package/dist/web/widgets/index.js +5 -4
  201. package/dist/web/widgets/infinity-list.js +3 -3
  202. package/dist/web/widgets/mode-switch.d.ts +2 -2
  203. package/dist/web/widgets/mode-switch.js +2 -2
  204. package/dist/web/widgets/pdf-preview.js +1 -1
  205. package/dist/web/widgets/plan-card.d.ts +2 -0
  206. package/dist/web/widgets/plan-card.js +1 -1
  207. package/dist/web/widgets/progress-list.d.ts +1 -2
  208. package/dist/web/widgets/progress-list.js +1 -2
  209. package/dist/web/widgets/query-table.d.ts +6 -6
  210. package/dist/web/widgets/result-panel.d.ts +16 -16
  211. package/dist/web/widgets/result-panel.js +10 -10
  212. package/dist/web/widgets/section-card.d.ts +3 -3
  213. package/dist/web/widgets/section-card.js +3 -3
  214. package/dist/web/widgets/stat.d.ts +4 -4
  215. package/dist/web/widgets/stat.js +6 -6
  216. package/dist/web/widgets/states.d.ts +4 -4
  217. package/dist/web/widgets/states.js +4 -4
  218. package/dist/web/widgets/upload-zone.js +13 -5
  219. package/dist/web/widgets/use-draft.d.ts +7 -7
  220. package/dist/web/widgets/use-draft.js +7 -7
  221. package/package.json +6 -6
  222. package/src/changes.json +49 -0
@@ -1,30 +1,30 @@
1
1
  // @runtime client
2
- // URL-Bridge für Visual-Tree-Targets. Serialisiert TargetRef in
3
- // Search-Params + parsed zurück.
2
+ // URL bridge for visual-tree targets. Serializes TargetRef into
3
+ // search params and parses it back.
4
4
  //
5
- // **URL-Shape**:
5
+ // **URL shape**:
6
6
  // ?t=<featureId>:<action>&a_<argKey1>=<value1>&a_<argKey2>=<value2>...
7
7
  //
8
- // Beispiel: text-content edit "imprint/de"
8
+ // Example: text-content edit "imprint/de"
9
9
  // ?t=text-content:edit&a_slug=imprint&a_lang=de
10
10
  //
11
- // **Warum prefix `a_`**: vermeidet Naming-Konflikt mit anderen Query-
12
- // Params (Pagination, Sort, Filter). Plus klare Trennung target-meta
13
- // (`t`) vs. action-args (`a_*`).
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
- // **Warum nicht JSON-encoded**: URL bleibt lesbar + bookmark-fähig.
16
- // JSON-base64 wäre robust für arbitrary Shapes aber unleserlich. arg-
17
- // values sind heute nur primitive strings (text-content: slug, lang) —
18
- // V.1.5 kann auf JSON wechseln wenn nested-args echten Bedarf zeigen.
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
- /** Build a search-params update for `setSearchParams`. Clears all
22
- * existing `a_*` keys plus `t` (so wechsel target nicht alte args
23
- * liegen lässt). Returns null-value entries to clear, plus the new
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 all previous arg-keys (vermeidet stale args bei target-switch).
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
- // V.1.4b: nur string-args. Numbers/booleans werden via String()
36
- // koerziert; nested objects/arrays werden NICHT supported (würde
37
- // JSON-encoded URL brauchen, siehe Header-Comment).
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
- /** Inverse — parse the current search-params back into a TargetRef.
46
- * Returns undefined wenn der `t`-Key fehlt (kein active target). */
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 + all args. Used vom
69
- * Close-Button + onUnmount. */
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 der die Theme-Wahl weiterschaltet (hell → dunkel → auto).
3
- // Icon-Slots als Props, damit renderer/web keine Icon-Lib als Hard-
4
- // Dependency zieht. Default: Unicode-Glyphs (☀ / ☾) — funktionieren in
5
- // jedem Browser, jede App kann lucide/heroicons/eigene SVG via Props
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 mit `workspaces` populated — die engine baut das aus
11
- * registry.getAllWorkspaces() + getWorkspaceNavs(). Akzeptiert
12
- * AppSchema (multi-feature) oder die legacy FeatureSchema (single-
13
- * feature, workspaces inline). Ohne Workspaces fällt der Shell auf
14
- * plain NavTree ohne Switcher zurück. */
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-Slot unten in der Sidebar — Profile-Row, Help-Link, Build-
25
- * Info. Klebt am unteren Rand via `mt-auto` (siehe Sidebar.footer).
26
- * Symmetrisch zu DefaultAppShell.sidebarFooter. */
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 ohne `screen`) so ein workspace dessen erstes
42
- * Member ein Header ist trotzdem auf einen renderbaren Screen auflöst.
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-headers (no `screen` property)
47
- * - all members are unknown to the schema (drift zwischen
48
- * r.workspace.nav und den registrierten navs — boot-validator fängt
49
- * das server-side, hier nur graceful fallback)
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="" macht
52
- * KumikoScreen den not-found-banner zeigen — und ein nav.replace mit
53
- * leerem screenId würde den useLayoutEffect in WorkspaceShell bei
54
- * jedem Re-Render erneut feuern (siehe matching guard dort).
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)` which only worked when the
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 für Edge-Case-Tests. */
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-headers (nav-Einträge ohne `screen`) werden übersprungen.
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-guard: wenn keiner der nav-members eine screen-property hat
108
- // (alle section-headers oder unbekannte QNs), würde nav.replace mit
109
- // screenId="" den Effect bei jedem Re-Render erneut feuern und den
110
- // User auf "/<workspace>/" pinnen. Lieber gar nicht replacen — der
111
- // Workspace-Selector kann den User dann manuell auf einen leaf-nav
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
- // EINE Nav: immer NavTree. Dynamische Knoten (r.nav({ provider: true }))
136
- // ziehen ihre Children client-seitig aus den registrierten navProviders —
137
- // der frühere navigation:"tree"/VisualTree-Zweig ist hier eingefaltet.
138
- // Content-Bereich: ein aktives target (aus den nav.searchParams, gesetzt
139
- // beim Klick auf einen target-Knoten) rendert den EditorPanel statt des
140
- // RoutedScreen — sonst die normalen screen-children.
141
- // (WorkspaceDefinition.navigation ist seitdem ein No-op; deferred removal.)
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
- // Zweite Spalte nur wenn ein Screen sie fuellt (sidebar-09-Muster) — sonst
152
- // stuende auf jedem anderen Screen ein leerer Streifen neben der Navigation.
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 ohne `screen`) so ein workspace dessen erstes
171
- * Member ein Header ist trotzdem auf einen renderbaren Screen auflöst.
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-headers (no `screen` property)
176
- * - all members are unknown to the schema (drift zwischen
177
- * r.workspace.nav und den registrierten navs — boot-validator fängt
178
- * das server-side, hier nur graceful fallback)
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="" macht
181
- * KumikoScreen den not-found-banner zeigen — und ein nav.replace mit
182
- * leerem screenId würde den useLayoutEffect in WorkspaceShell bei
183
- * jedem Re-Render erneut feuern (siehe matching guard dort).
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)` which only worked when the
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 für Edge-Case-Tests. */
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 → nächstes member
199
+ break; // section-header → next member
201
200
  return lastSegment(nav.screen);
202
201
  }
203
202
  }
@@ -1,6 +1,6 @@
1
1
  import { type ClassValue } from "clsx";
2
- /** shadcn-Standard-Helper: clsx für konditionales Zusammenstecken,
3
- * tailwind-merge für Konfliktauflösung (z.B. `px-2 px-4` → `px-4`).
4
- * Jedes Primitive nutzt das um Consumer-Klassen mit Default-Klassen
5
- * zu mergen ohne Duplikate. */
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;
@@ -1,9 +1,9 @@
1
1
  import { clsx } from "clsx";
2
2
  import { twMerge } from "tailwind-merge";
3
- /** shadcn-Standard-Helper: clsx für konditionales Zusammenstecken,
4
- * tailwind-merge für Konfliktauflösung (z.B. `px-2 px-4` → `px-4`).
5
- * Jedes Primitive nutzt das um Consumer-Klassen mit Default-Klassen
6
- * zu mergen ohne Duplikate. */
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 Icon links vor dem Label (16px). */
6
+ /** Optional icon displayed before the label (16px). */
7
7
  readonly icon?: ReactNode;
8
- /** Pure-display Shortcut-Hint rechts (monospace, gedimmt).
9
- * Caller registriert den Shortcut separat — Linear-Style
10
- * Strings wie "O then M", "Alt + Q", "⌘K". */
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" rendert Label rot — typisch Sign-out, Delete. */
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 für den Trigger-Wrapper. */
25
+ /** aria-label for the trigger wrapper. */
26
26
  readonly triggerLabel?: string;
27
27
  readonly items: readonly MenuItemDef[];
28
- /** Wo das Popup relativ zum Trigger anchorn soll. Default: end. */
28
+ /** Where to anchor the popup relative to the trigger. Default: end. */
29
29
  readonly align?: "start" | "center" | "end";
30
- /** Min-width für den Content. Default: 14rem. */
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 Action-Menu — Trigger + Items, auf @radix-ui/react-dropdown-
3
- // menu basiert. Wird benutzt für ProfileMenu (Topbar-Avatar), Edit-
4
- // Header-Menu (Three-Dots), List-Row-Kebab — die Mechanik ist überall
5
- // dieselbe, nur der Trigger unterscheidet sich.
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
- // Nicht zu verwechseln mit RowActionsCell (siehe primitives/index.tsx),
8
- // die hat die rowActions-Schema-API + per-row Visibility-Logik. Hier
9
- // ist der Layer drunter: stable, schema-frei, callable von App-Code
10
- // und vom Schema-Renderer (KumikoApp).
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 sind Discriminated Union (item / separator / label), Item kann
13
- // optional einen Keyboard-Shortcut-Hint rechts tragen (Linear-Pattern
14
- // "O then M") + Icon links + variant für danger-styling.
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 haben keine ID — Reihenfolge ist Identität
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: gleicher Grund wie separator
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-Label für den Icon-Button (kein sichtbarer Text). */
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 — gemeinsamer Kalender-Trigger für DateInput und
3
- // TimestampInput. Ein Icon-Button öffnet ein Radix-Popover mit
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
- // geben Monats- UND Jahres-Dropdown statt nur Monats-Vor/Zurück — der
6
- // Grund für #369 (10 Jahre zurück war ~120 Klicks). min/max grauen
7
- // Tage außerhalb des erlaubten Bereichs aus.
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
- // Der Text-Input (Tippen) lebt bewusst NICHT hier, sondern im jeweiligen
10
- // Primitive — date hat ein Feld, timestamp zwei (Datum + Uhrzeit). So
11
- // teilen beide dieselbe Kalender-Mechanik ohne ihre Eingabe-Form zu teilen.
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-Range wenn das Feld kein min/max setzt — sonst wäre das
28
- // Jahres-Dropdown leer/unbrauchbar. Zur Render-Zeit berechnet.
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 (fw#2528).
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
- // (fw#2722/#2778): sizes to content and only shrinks (min-h-0) once
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,6 @@
1
+ import { type ReactNode } from "react";
2
+ import type { InputProps } from "../../index.ts";
3
+ export type CodeInputProps = Extract<InputProps, {
4
+ readonly kind: "code";
5
+ }>;
6
+ export declare function CodeInput(props: CodeInputProps): ReactNode;
@@ -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
- /** Tier 2.7e Remote-Search: wenn gesetzt, wechselt der Combobox in
24
- * Remote-Mode. cmdk's client-side Filter wird deaktiviert (Server
25
- * hat schon gefiltert), und der Search-Input ruft onSearchChange
26
- * debounced (300ms). Ohne diesen Callback bleibt die Local-Filter-
27
- * Variante (cmdk fuzzy-match auf den geladenen options). */
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
- /** Spinner im Trigger + Popover-Footer wenn remote search läuft. */
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 (kumiko-framework#1681). Omit for the plain
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-Hook: forciert den initial open-state des Popovers. In
40
- * jsdom + Radix-Popover triggert userEvent.click auf den Trigger
41
- * nicht zuverlässig PointerEvents — Tests setzen defaultOpen=true,
42
- * damit der Popover sofort gerendert ist. Production-Code lässt
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 & ({