@cosmicdrift/kumiko-renderer 0.389.0 → 0.390.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 (230) hide show
  1. package/dist/app/projection-detail-body.js +59 -394
  2. package/dist/app/projection-detail-header-model.d.ts +11 -0
  3. package/dist/app/projection-detail-header-model.js +33 -0
  4. package/dist/app/projection-detail-header-region.d.ts +28 -0
  5. package/dist/app/projection-detail-header-region.js +81 -0
  6. package/dist/app/projection-detail-header.d.ts +29 -0
  7. package/dist/app/projection-detail-header.js +84 -0
  8. package/dist/app/projection-detail-navigation.d.ts +13 -0
  9. package/dist/app/projection-detail-navigation.js +91 -0
  10. package/dist/app/projection-detail-states.d.ts +22 -0
  11. package/dist/app/projection-detail-states.js +26 -0
  12. package/dist/app/projection-drawer-host.d.ts +12 -0
  13. package/dist/app/projection-drawer-host.js +10 -0
  14. package/dist/app/projection-list-body.d.ts +1 -1
  15. package/dist/app/projection-list-body.js +69 -189
  16. package/dist/app/projection-list-render-props.d.ts +56 -0
  17. package/dist/app/projection-list-render-props.js +47 -0
  18. package/dist/app/projection-list-render.d.ts +26 -0
  19. package/dist/app/projection-list-render.js +20 -0
  20. package/dist/app/projection-list-row-click.d.ts +12 -0
  21. package/dist/app/projection-list-row-click.js +14 -0
  22. package/dist/app/use-projection-detail-actions.d.ts +35 -0
  23. package/dist/app/use-projection-detail-actions.js +156 -0
  24. package/dist/app/use-projection-detail-data.d.ts +19 -0
  25. package/dist/app/use-projection-detail-data.js +47 -0
  26. package/dist/app/use-projection-detail-layout.d.ts +17 -0
  27. package/dist/app/use-projection-detail-layout.js +23 -0
  28. package/dist/app/use-projection-list-actions.d.ts +31 -0
  29. package/dist/app/use-projection-list-actions.js +73 -0
  30. package/dist/app/use-projection-list-data.d.ts +20 -0
  31. package/dist/app/use-projection-list-data.js +26 -0
  32. package/dist/app/use-projection-list-definitions.d.ts +11 -0
  33. package/dist/app/use-projection-list-definitions.js +20 -0
  34. package/dist/app/use-projection-list-query.d.ts +28 -0
  35. package/dist/app/use-projection-list-query.js +83 -0
  36. package/dist/components/render-edit-banners.d.ts +12 -0
  37. package/dist/components/render-edit-banners.js +90 -0
  38. package/dist/components/render-edit-draft-save-helpers.d.ts +19 -0
  39. package/dist/components/render-edit-draft-save-helpers.js +42 -0
  40. package/dist/components/render-edit-field-grid.d.ts +4 -0
  41. package/dist/components/render-edit-field-grid.js +40 -0
  42. package/dist/components/render-edit-fields-section.d.ts +4 -0
  43. package/dist/components/render-edit-fields-section.js +76 -0
  44. package/dist/components/render-edit-form-actions.d.ts +4 -0
  45. package/dist/components/render-edit-form-actions.js +66 -0
  46. package/dist/components/render-edit-header-actions.d.ts +11 -0
  47. package/dist/components/render-edit-header-actions.js +120 -0
  48. package/dist/components/render-edit-layout-flags.d.ts +29 -0
  49. package/dist/components/render-edit-layout-flags.js +106 -0
  50. package/dist/components/render-edit-layout.d.ts +4 -0
  51. package/dist/components/render-edit-layout.js +79 -0
  52. package/dist/components/render-edit-render-model.d.ts +63 -0
  53. package/dist/components/render-edit-render-model.js +14 -0
  54. package/dist/components/render-edit-section-kinds.d.ts +4 -0
  55. package/dist/components/render-edit-section-kinds.js +115 -0
  56. package/dist/components/render-edit-submit-result.d.ts +8 -0
  57. package/dist/components/render-edit-submit-result.js +77 -0
  58. package/dist/components/render-edit-submit.d.ts +7 -0
  59. package/dist/components/render-edit-submit.js +113 -0
  60. package/dist/components/render-edit-titles.d.ts +17 -0
  61. package/dist/components/render-edit-titles.js +99 -0
  62. package/dist/components/render-edit-wizard-navigation.d.ts +11 -0
  63. package/dist/components/render-edit-wizard-navigation.js +115 -0
  64. package/dist/components/render-edit.d.ts +2 -2
  65. package/dist/components/render-edit.js +63 -1128
  66. package/dist/components/render-field-choice-inputs.d.ts +7 -0
  67. package/dist/components/render-field-choice-inputs.js +52 -0
  68. package/dist/components/render-field-control.d.ts +12 -0
  69. package/dist/components/render-field-control.js +93 -0
  70. package/dist/components/render-field-display-text.d.ts +4 -0
  71. package/dist/components/render-field-display-text.js +48 -0
  72. package/dist/components/render-field-fill-action.d.ts +13 -0
  73. package/dist/components/render-field-fill-action.js +63 -0
  74. package/dist/components/render-field-input-shared.d.ts +39 -0
  75. package/dist/components/render-field-input-shared.js +20 -0
  76. package/dist/components/render-field-input.d.ts +3 -0
  77. package/dist/components/render-field-input.js +34 -0
  78. package/dist/components/render-field-meta.d.ts +6 -0
  79. package/dist/components/render-field-meta.js +51 -0
  80. package/dist/components/render-field-read-only.d.ts +11 -0
  81. package/dist/components/render-field-read-only.js +75 -0
  82. package/dist/components/render-field-reference.d.ts +15 -0
  83. package/dist/components/render-field-reference.js +103 -0
  84. package/dist/components/render-field-scalar-inputs.d.ts +10 -0
  85. package/dist/components/render-field-scalar-inputs.js +35 -0
  86. package/dist/components/render-field-text-inputs.d.ts +3 -0
  87. package/dist/components/render-field-text-inputs.js +34 -0
  88. package/dist/components/render-field-values.d.ts +18 -0
  89. package/dist/components/render-field-values.js +111 -0
  90. package/dist/components/render-field-write-only.d.ts +9 -0
  91. package/dist/components/render-field-write-only.js +29 -0
  92. package/dist/components/render-field.d.ts +2 -2
  93. package/dist/components/render-field.js +39 -670
  94. package/dist/components/render-list-header-slot.d.ts +5 -0
  95. package/dist/components/render-list-header-slot.js +30 -0
  96. package/dist/components/render-list-reference-bridge.d.ts +3 -0
  97. package/dist/components/render-list-reference-bridge.js +25 -0
  98. package/dist/components/render-list-reference-columns.d.ts +25 -0
  99. package/dist/components/render-list-reference-columns.js +137 -0
  100. package/dist/components/render-list-table-props.d.ts +52 -0
  101. package/dist/components/render-list-table-props.js +53 -0
  102. package/dist/components/render-list-toolbar-model.d.ts +42 -0
  103. package/dist/components/render-list-toolbar-model.js +82 -0
  104. package/dist/components/render-list-toolbar.d.ts +38 -0
  105. package/dist/components/render-list-toolbar.js +40 -0
  106. package/dist/components/render-list-types.d.ts +113 -0
  107. package/dist/components/render-list-types.js +1 -0
  108. package/dist/components/render-list.d.ts +4 -113
  109. package/dist/components/render-list.js +91 -307
  110. package/dist/components/use-reference-input-state.d.ts +20 -0
  111. package/dist/components/use-reference-input-state.js +85 -0
  112. package/dist/components/use-render-edit-controlled-sync.d.ts +3 -0
  113. package/dist/components/use-render-edit-controlled-sync.js +26 -0
  114. package/dist/components/use-render-edit-draft-save.d.ts +29 -0
  115. package/dist/components/use-render-edit-draft-save.js +141 -0
  116. package/dist/components/use-render-edit-form.d.ts +10 -0
  117. package/dist/components/use-render-edit-form.js +101 -0
  118. package/dist/components/use-render-edit-sections.d.ts +15 -0
  119. package/dist/components/use-render-edit-sections.js +100 -0
  120. package/dist/components/use-render-edit-state.d.ts +65 -0
  121. package/dist/components/use-render-edit-state.js +158 -0
  122. package/dist/components/use-render-edit-steps.d.ts +13 -0
  123. package/dist/components/use-render-edit-steps.js +52 -0
  124. package/dist/components/use-render-list-columns.d.ts +18 -0
  125. package/dist/components/use-render-list-columns.js +41 -0
  126. package/dist/components/use-render-list-search.d.ts +4 -0
  127. package/dist/components/use-render-list-search.js +50 -0
  128. package/dist/primitives.d.ts +1 -1
  129. package/dist/web/app/dashboard-body.d.ts +1 -1
  130. package/dist/web/app/dashboard-body.js +42 -246
  131. package/dist/web/app/dashboard-header.d.ts +34 -0
  132. package/dist/web/app/dashboard-header.js +58 -0
  133. package/dist/web/app/dashboard-screen-controls.d.ts +11 -0
  134. package/dist/web/app/dashboard-screen-controls.js +42 -0
  135. package/dist/web/app/dashboard-screen-tiles.d.ts +8 -0
  136. package/dist/web/app/dashboard-screen-tiles.js +42 -0
  137. package/dist/web/app/dashboard-visibility.d.ts +42 -0
  138. package/dist/web/app/dashboard-visibility.js +102 -0
  139. package/dist/web/app/use-dashboard-screen-state.d.ts +35 -0
  140. package/dist/web/app/use-dashboard-screen-state.js +69 -0
  141. package/dist/web/primitives/data-table/actions-column.js +6 -5
  142. package/dist/web/primitives/data-table/card-body.d.ts +6 -0
  143. package/dist/web/primitives/data-table/card-body.js +32 -0
  144. package/dist/web/primitives/data-table/card-row.d.ts +5 -0
  145. package/dist/web/primitives/data-table/card-row.js +31 -0
  146. package/dist/web/primitives/data-table/card-view.d.ts +3 -0
  147. package/dist/web/primitives/data-table/card-view.js +13 -0
  148. package/dist/web/primitives/data-table/column-resize-handle.js +3 -1
  149. package/dist/web/primitives/data-table/data-cell-props.d.ts +7 -0
  150. package/dist/web/primitives/data-table/data-cell-props.js +8 -0
  151. package/dist/web/primitives/data-table/row-render-parts.d.ts +8 -0
  152. package/dist/web/primitives/data-table/row-render-parts.js +35 -0
  153. package/dist/web/primitives/data-table/table-body.d.ts +3 -0
  154. package/dist/web/primitives/data-table/table-body.js +32 -0
  155. package/dist/web/primitives/data-table/table-content.d.ts +3 -0
  156. package/dist/web/primitives/data-table/table-content.js +41 -0
  157. package/dist/web/primitives/data-table/table-data-cell.d.ts +4 -0
  158. package/dist/web/primitives/data-table/table-data-cell.js +25 -0
  159. package/dist/web/primitives/data-table/table-header-row.d.ts +3 -0
  160. package/dist/web/primitives/data-table/table-header-row.js +21 -0
  161. package/dist/web/primitives/data-table/table-view.d.ts +3 -0
  162. package/dist/web/primitives/data-table/table-view.js +25 -0
  163. package/dist/web/primitives/data-table/toolbar.d.ts +6 -0
  164. package/dist/web/primitives/data-table/toolbar.js +53 -0
  165. package/dist/web/primitives/data-table/use-data-table-state.d.ts +44 -0
  166. package/dist/web/primitives/data-table/use-data-table-state.js +123 -0
  167. package/dist/web/primitives/data-table.d.ts +3 -4
  168. package/dist/web/primitives/data-table.js +32 -252
  169. package/dist/web/primitives/file-value-list.js +1 -1
  170. package/dist/web/primitives/form-footer-parts.d.ts +25 -0
  171. package/dist/web/primitives/form-footer-parts.js +55 -0
  172. package/dist/web/primitives/form.js +33 -43
  173. package/dist/web/primitives/input-choice-kinds.d.ts +4 -0
  174. package/dist/web/primitives/input-choice-kinds.js +67 -0
  175. package/dist/web/primitives/input-file-kinds.d.ts +3 -0
  176. package/dist/web/primitives/input-file-kinds.js +6 -0
  177. package/dist/web/primitives/input-number-kinds.d.ts +4 -0
  178. package/dist/web/primitives/input-number-kinds.js +16 -0
  179. package/dist/web/primitives/input-optional-props.d.ts +20 -0
  180. package/dist/web/primitives/input-optional-props.js +24 -0
  181. package/dist/web/primitives/input-select-presentation.d.ts +13 -0
  182. package/dist/web/primitives/input-select-presentation.js +19 -0
  183. package/dist/web/primitives/input-shared.d.ts +22 -0
  184. package/dist/web/primitives/input-shared.js +10 -0
  185. package/dist/web/primitives/input-temporal-kinds.d.ts +5 -0
  186. package/dist/web/primitives/input-temporal-kinds.js +10 -0
  187. package/dist/web/primitives/input-text-kinds.d.ts +7 -0
  188. package/dist/web/primitives/input-text-kinds.js +33 -0
  189. package/dist/web/primitives/input.js +35 -161
  190. package/dist/web/widgets/chart-data.d.ts +35 -0
  191. package/dist/web/widgets/chart-data.js +37 -0
  192. package/dist/web/widgets/chart-markers.d.ts +26 -0
  193. package/dist/web/widgets/chart-markers.js +64 -0
  194. package/dist/web/widgets/chart-parts.d.ts +20 -0
  195. package/dist/web/widgets/chart-parts.js +15 -0
  196. package/dist/web/widgets/charts.d.ts +5 -78
  197. package/dist/web/widgets/charts.js +7 -485
  198. package/dist/web/widgets/drawer-panel-class.d.ts +3 -0
  199. package/dist/web/widgets/drawer-panel-class.js +40 -0
  200. package/dist/web/widgets/drawer-resize-controls.d.ts +11 -0
  201. package/dist/web/widgets/drawer-resize-controls.js +12 -0
  202. package/dist/web/widgets/drawer-sizing.d.ts +19 -0
  203. package/dist/web/widgets/drawer-sizing.js +60 -0
  204. package/dist/web/widgets/drawer.js +17 -116
  205. package/dist/web/widgets/stacked-area-brush.d.ts +13 -0
  206. package/dist/web/widgets/stacked-area-brush.js +91 -0
  207. package/dist/web/widgets/stacked-area-chart.d.ts +6 -0
  208. package/dist/web/widgets/stacked-area-chart.js +45 -0
  209. package/dist/web/widgets/stacked-area-chrome.d.ts +7 -0
  210. package/dist/web/widgets/stacked-area-chrome.js +77 -0
  211. package/dist/web/widgets/stacked-area-geometry.d.ts +54 -0
  212. package/dist/web/widgets/stacked-area-geometry.js +83 -0
  213. package/dist/web/widgets/stacked-area-model.d.ts +11 -0
  214. package/dist/web/widgets/stacked-area-model.js +1 -0
  215. package/dist/web/widgets/stacked-area-plot.d.ts +3 -0
  216. package/dist/web/widgets/stacked-area-plot.js +51 -0
  217. package/dist/web/widgets/stacked-area-props.d.ts +83 -0
  218. package/dist/web/widgets/stacked-area-props.js +16 -0
  219. package/dist/web/widgets/stacked-area-svg.d.ts +3 -0
  220. package/dist/web/widgets/stacked-area-svg.js +38 -0
  221. package/dist/web/widgets/stacked-area-tooltip.d.ts +3 -0
  222. package/dist/web/widgets/stacked-area-tooltip.js +86 -0
  223. package/dist/web/widgets/use-drawer-resize.d.ts +22 -0
  224. package/dist/web/widgets/use-drawer-resize.js +52 -0
  225. package/dist/web/widgets/use-stacked-area-tooltip.d.ts +9 -0
  226. package/dist/web/widgets/use-stacked-area-tooltip.js +34 -0
  227. package/dist/web/widgets/use-stacked-area-window.d.ts +20 -0
  228. package/dist/web/widgets/use-stacked-area-window.js +76 -0
  229. package/package.json +9 -9
  230. package/src/changes.json +54 -0
@@ -0,0 +1,25 @@
1
+ import type { CSSProperties, ReactNode } from "react";
2
+ import type { useTranslation } from "../../index.ts";
3
+ type Translate = ReturnType<typeof useTranslation>;
4
+ export type FormFooterModel = {
5
+ readonly t: Translate;
6
+ readonly testId: string | undefined;
7
+ readonly chromeless: boolean;
8
+ readonly stickyActions: boolean | undefined;
9
+ readonly fillHeight: boolean | undefined;
10
+ readonly unsavedCount: number | undefined;
11
+ readonly railed: boolean;
12
+ readonly pinned: boolean;
13
+ readonly secondaryActions: ReactNode;
14
+ readonly renderedSecondary: ReactNode;
15
+ readonly renderedActions: ReactNode;
16
+ readonly hasNonPrimaryOverflow: boolean;
17
+ readonly fixedFooterStyle: CSSProperties | undefined;
18
+ };
19
+ export declare function unsavedStatusText(t: Translate, unsavedCount: number): string;
20
+ export declare function renderNarrowPinnedFooter(model: Pick<FormFooterModel, "t" | "testId" | "unsavedCount">, nodes: readonly ReactNode[], keyboardInset: number): ReactNode;
21
+ export declare function footerRowClassName(model: FormFooterModel): string;
22
+ export declare function renderUnsavedStatus(model: FormFooterModel, unsavedCount: number): ReactNode;
23
+ export declare function renderSecondaryActionsGroup(model: FormFooterModel): ReactNode;
24
+ export declare function renderPrimaryActionsGroup(model: FormFooterModel): ReactNode;
25
+ export {};
@@ -0,0 +1,55 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../lib/cn.js";
3
+ import { cardFooterBorder } from "./card-styles.js";
4
+ import { NarrowPinnedFooter } from "./narrow-pinned-footer.js";
5
+ import { STICKY_FOOTER_SAFE_AREA_CLASS } from "./sticky-action-bar.js";
6
+ export function unsavedStatusText(t, unsavedCount) {
7
+ if (unsavedCount === 0)
8
+ return t("kumiko.form.noUnsavedChanges");
9
+ if (unsavedCount === 1)
10
+ return t("kumiko.form.unsaved.one");
11
+ return t("kumiko.form.unsaved.other", { count: unsavedCount });
12
+ }
13
+ export function renderNarrowPinnedFooter(model, nodes, keyboardInset) {
14
+ const { t, testId, unsavedCount } = model;
15
+ return (_jsx(NarrowPinnedFooter, { nodes: nodes, overflowLabel: unsavedCount !== undefined && unsavedCount > 0
16
+ ? `${t("kumiko.list.row-actions.more")}, ${unsavedStatusText(t, unsavedCount)}`
17
+ : t("kumiko.list.row-actions.more"), unsavedCount: unsavedCount, unsavedStatus: unsavedCount !== undefined && unsavedCount > 0
18
+ ? unsavedStatusText(t, unsavedCount)
19
+ : undefined, testId: testId, keyboardInset: keyboardInset }));
20
+ }
21
+ export function footerRowClassName(model) {
22
+ const { pinned, chromeless, fillHeight, railed } = model;
23
+ return cn(pinned
24
+ ? cn("flex h-14 shrink-0 items-center justify-end gap-2 border-t border-border bg-card px-6", !railed && "md:pl-10")
25
+ : "flex flex-col-reverse gap-3 py-3 sm:flex-row sm:items-center sm:justify-between sm:py-4", !pinned && !chromeless && "px-[var(--card-padding)]", !pinned && !chromeless && cardFooterBorder, !pinned && fillHeight === true && "shrink-0");
26
+ }
27
+ export function renderUnsavedStatus(model, unsavedCount) {
28
+ const { t, testId, pinned } = model;
29
+ return (_jsxs("div", { "data-testid": testId !== undefined ? `${testId}-unsaved` : undefined, className: cn("mr-auto flex min-w-0 items-center gap-2 text-[13px]",
30
+ // Only the stacked (flex-col-reverse) footer moves the status above the
31
+ // actions; the pinned single-row footer keeps it on the left.
32
+ !pinned && "max-sm:order-last", unsavedCount > 0 ? "text-foreground-secondary" : "text-muted-foreground"), children: [unsavedCount > 0 && (_jsx("span", { className: "size-1.5 shrink-0 rounded-full bg-status-active", "aria-hidden": "true" })), _jsx("span", { className: "truncate", children: unsavedStatusText(t, unsavedCount) })] }));
33
+ }
34
+ export function renderSecondaryActionsGroup(model) {
35
+ const { testId, pinned, hasNonPrimaryOverflow, renderedSecondary } = model;
36
+ return (_jsx("div", { "data-testid": testId !== undefined ? `${testId}-actions-secondary` : undefined, className: cn(
37
+ // min-h-11: link-variant buttons (Copy link) are h-auto and would fall
38
+ // below the 24px touch-target minimum on narrow viewports.
39
+ "flex flex-wrap items-center gap-2 max-sm:[&_button]:min-h-11 max-sm:[&_button]:px-2 max-sm:[&_button]:text-xs", pinned && hasNonPrimaryOverflow && "mr-auto"), children: renderedSecondary }));
40
+ }
41
+ export function renderPrimaryActionsGroup(model) {
42
+ const { testId, pinned, stickyActions, fixedFooterStyle, renderedActions } = model;
43
+ return (_jsx("div", { "data-testid": testId !== undefined ? `${testId}-actions` : undefined, style: fixedFooterStyle, className: cn("flex items-center gap-2", pinned
44
+ ? "shrink-0 max-md:[&>button]:min-h-11"
45
+ : "flex-wrap max-sm:w-full max-sm:[&>button]:flex-1 max-sm:[&>button]:min-h-11 sm:ml-auto",
46
+ // Below sm (640px): pin only the primary action to the viewport
47
+ // bottom instead of normal flow, so a virtual keyboard shrinking
48
+ // the viewport can't push it out of reach. `fixed`
49
+ // escapes the card's `overflow-hidden` (only transform/filter/
50
+ // contain ancestors trap it, confirmed against
51
+ // AppLayout/SidebarInset — neither sets those).
52
+ stickyActions === true &&
53
+ !pinned &&
54
+ cn("max-sm:fixed max-sm:inset-x-0 max-sm:bottom-0 max-sm:z-20 max-sm:bg-background max-sm:p-4 max-sm:shadow-[0_-4px_12px_-4px_rgb(0_0_0_/_0.15)]", STICKY_FOOTER_SAFE_AREA_CLASS)), children: renderedActions }));
55
+ }
@@ -2,13 +2,13 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
2
2
  import { Children, Fragment, isValidElement, useContext, useEffect, useRef, useState, } from "react";
3
3
  import { STICKY_PRIMARY_ACTION_PROP, useTranslation, } from "../../index.js";
4
4
  import { cn } from "../lib/cn.js";
5
+ import { footerRowClassName, renderNarrowPinnedFooter, renderPrimaryActionsGroup, renderSecondaryActionsGroup, renderUnsavedStatus, } from "./form-footer-parts.js";
5
6
  import { screenPaddingClassName, screenWidthClassName } from "./layout.js";
6
- import { NarrowPinnedFooter } from "./narrow-pinned-footer.js";
7
- import { STICKY_FOOTER_SAFE_AREA_CLASS, STICKY_FOOTER_SPACER_CLASS } from "./sticky-action-bar.js";
7
+ import { STICKY_FOOTER_SPACER_CLASS } from "./sticky-action-bar.js";
8
8
  import { useKeyboardInset } from "./use-keyboard-inset.js";
9
9
  import { useIsBelowSmViewport } from "./use-narrow-viewport.js";
10
10
  import "../react-css-custom-properties.js";
11
- import { cardFooterBorder, cardHeaderBorder } from "./card-styles.js";
11
+ import { cardHeaderBorder } from "./card-styles.js";
12
12
  import { InsideFormContext, ScreenFormContext, ScreenWidthContext } from "./contexts.js";
13
13
  // Extension sections that render their own <form> via BareFormProvider
14
14
  // (legacy custom-form pattern, e.g. ChangeEmailSection) land
@@ -138,21 +138,7 @@ function flattenActionNodes(node) {
138
138
  }
139
139
  return Children.toArray(node);
140
140
  }
141
- function unsavedStatusText(t, unsavedCount) {
142
- if (unsavedCount === 0)
143
- return t("kumiko.form.noUnsavedChanges");
144
- if (unsavedCount === 1)
145
- return t("kumiko.form.unsaved.one");
146
- return t("kumiko.form.unsaved.other", { count: unsavedCount });
147
- }
148
- // Footer wrapper for DefaultForm's card and chromeless layouts alike — only
149
- // the card-derived horizontal padding/border differs between them.
150
- export function FormFooter({ actions, secondaryActions, testId, chromeless, stickyActions, fillHeight, unsavedCount, railed = false, }) {
151
- const t = useTranslation();
152
- const isBelowSm = useIsBelowSmViewport();
153
- const keyboardInset = useKeyboardInset();
154
- if (actions === undefined && secondaryActions === undefined)
155
- return null;
141
+ function splitStickyActions(stickyActions, actions, secondaryActions) {
156
142
  // Only split when sticky: the non-sticky (regular, non-wizard) footer must
157
143
  // reproduce the previous DOM exactly (form-action-bar.test.tsx pins
158
144
  // `-actions`/`-actions-secondary` content 1:1 to the `actions`/
@@ -170,6 +156,17 @@ export function FormFooter({ actions, secondaryActions, testId, chromeless, stic
170
156
  const renderedActions = stickyActions === true ? (hasPrimaryAction ? primaryActionNodes : actions) : actions;
171
157
  const hasNonPrimaryOverflow = nonPrimaryActionNodes !== undefined && nonPrimaryActionNodes.length > 0;
172
158
  const renderedSecondary = stickyActions === true && hasPrimaryAction ? (_jsxs(_Fragment, { children: [secondaryActions, nonPrimaryActionNodes] })) : (secondaryActions);
159
+ return { renderedActions, renderedSecondary, hasNonPrimaryOverflow };
160
+ }
161
+ // Footer wrapper for DefaultForm's card and chromeless layouts alike — only
162
+ // the card-derived horizontal padding/border differs between them.
163
+ export function FormFooter({ actions, secondaryActions, testId, chromeless, stickyActions, fillHeight, unsavedCount, railed = false, }) {
164
+ const t = useTranslation();
165
+ const isBelowSm = useIsBelowSmViewport();
166
+ const keyboardInset = useKeyboardInset();
167
+ if (actions === undefined && secondaryActions === undefined)
168
+ return null;
169
+ const { renderedActions, renderedSecondary, hasNonPrimaryOverflow } = splitStickyActions(stickyActions, actions, secondaryActions);
173
170
  const pinned = isPinnedFooter(stickyActions, fillHeight);
174
171
  const fixedFooterStyle = stickyActions === true && !pinned && isBelowSm && keyboardInset > 0
175
172
  ? { bottom: keyboardInset }
@@ -177,32 +174,25 @@ export function FormFooter({ actions, secondaryActions, testId, chromeless, stic
177
174
  // Below sm the pinned footer swaps to NarrowPinnedFooter; the subtree remounts, so a
178
175
  // confirm dialog owned by an action button closes on a breakpoint change (rotate, resize).
179
176
  if (pinned && isBelowSm) {
180
- return (_jsx(NarrowPinnedFooter, { nodes: [...flattenActionNodes(secondaryActions), ...flattenActionNodes(actions)], overflowLabel: unsavedCount !== undefined && unsavedCount > 0
181
- ? `${t("kumiko.list.row-actions.more")}, ${unsavedStatusText(t, unsavedCount)}`
182
- : t("kumiko.list.row-actions.more"), unsavedCount: unsavedCount, unsavedStatus: unsavedCount !== undefined && unsavedCount > 0
183
- ? unsavedStatusText(t, unsavedCount)
184
- : undefined, testId: testId, keyboardInset: keyboardInset }));
177
+ return renderNarrowPinnedFooter({ t, testId, unsavedCount }, [...flattenActionNodes(secondaryActions), ...flattenActionNodes(actions)], keyboardInset);
185
178
  }
186
- return (_jsxs("div", { className: cn(pinned
187
- ? cn("flex h-14 shrink-0 items-center justify-end gap-2 border-t border-border bg-card px-6", !railed && "md:pl-10")
188
- : "flex flex-col-reverse gap-3 py-3 sm:flex-row sm:items-center sm:justify-between sm:py-4", !pinned && !chromeless && "px-[var(--card-padding)]", !pinned && !chromeless && cardFooterBorder, !pinned && fillHeight === true && "shrink-0"), children: [unsavedCount !== undefined && (_jsxs("div", { "data-testid": testId !== undefined ? `${testId}-unsaved` : undefined, className: cn("mr-auto flex min-w-0 items-center gap-2 text-[13px]",
189
- // Only the stacked (flex-col-reverse) footer moves the status above the
190
- // actions; the pinned single-row footer keeps it on the left.
191
- !pinned && "max-sm:order-last", unsavedCount > 0 ? "text-foreground-secondary" : "text-muted-foreground"), children: [unsavedCount > 0 && (_jsx("span", { className: "size-1.5 shrink-0 rounded-full bg-status-active", "aria-hidden": "true" })), _jsx("span", { className: "truncate", children: unsavedStatusText(t, unsavedCount) })] })), (secondaryActions !== undefined || hasNonPrimaryOverflow) && (_jsx("div", { "data-testid": testId !== undefined ? `${testId}-actions-secondary` : undefined, className: cn(
192
- // min-h-11: link-variant buttons (Copy link) are h-auto and would fall
193
- // below the 24px touch-target minimum on narrow viewports.
194
- "flex flex-wrap items-center gap-2 max-sm:[&_button]:min-h-11 max-sm:[&_button]:px-2 max-sm:[&_button]:text-xs", pinned && hasNonPrimaryOverflow && "mr-auto"), children: renderedSecondary })), renderedActions !== undefined && (_jsx("div", { "data-testid": testId !== undefined ? `${testId}-actions` : undefined, style: fixedFooterStyle, className: cn("flex items-center gap-2", pinned
195
- ? "shrink-0 max-md:[&>button]:min-h-11"
196
- : "flex-wrap max-sm:w-full max-sm:[&>button]:flex-1 max-sm:[&>button]:min-h-11 sm:ml-auto",
197
- // Below sm (640px): pin only the primary action to the viewport
198
- // bottom instead of normal flow, so a virtual keyboard shrinking
199
- // the viewport can't push it out of reach. `fixed`
200
- // escapes the card's `overflow-hidden` (only transform/filter/
201
- // contain ancestors trap it, confirmed against
202
- // AppLayout/SidebarInset — neither sets those).
203
- stickyActions === true &&
204
- !pinned &&
205
- cn("max-sm:fixed max-sm:inset-x-0 max-sm:bottom-0 max-sm:z-20 max-sm:bg-background max-sm:p-4 max-sm:shadow-[0_-4px_12px_-4px_rgb(0_0_0_/_0.15)]", STICKY_FOOTER_SAFE_AREA_CLASS)), children: renderedActions }))] }));
179
+ const model = {
180
+ t,
181
+ testId,
182
+ chromeless,
183
+ stickyActions,
184
+ fillHeight,
185
+ unsavedCount,
186
+ railed,
187
+ pinned,
188
+ secondaryActions,
189
+ renderedSecondary,
190
+ renderedActions,
191
+ hasNonPrimaryOverflow,
192
+ fixedFooterStyle,
193
+ };
194
+ return (_jsxs("div", { className: footerRowClassName(model), children: [unsavedCount !== undefined && renderUnsavedStatus(model, unsavedCount), (secondaryActions !== undefined || hasNonPrimaryOverflow) &&
195
+ renderSecondaryActionsGroup(model), renderedActions !== undefined && renderPrimaryActionsGroup(model)] }));
206
196
  }
207
197
  export const SECTION_NAV_MIN_ITEMS = 3;
208
198
  // "On this page" navigation next to a screen form. The active entry follows
@@ -0,0 +1,4 @@
1
+ import type { KindRenderer } from "./input-shared.ts";
2
+ export declare const renderBooleanKind: KindRenderer<"boolean">;
3
+ export declare const renderSelectKind: KindRenderer<"select">;
4
+ export declare const renderComboboxKind: KindRenderer<"combobox">;
@@ -0,0 +1,67 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Checkbox } from "../ui/checkbox.js";
3
+ import { Switch } from "../ui/switch.js";
4
+ import { ComboboxInput } from "./combobox.js";
5
+ import { RadioListSelect, SegmentedSelect, withInlineDisabledHint, withoutSegmentedSelectPlaceholder, } from "./field.js";
6
+ import { stateProps } from "./input-optional-props.js";
7
+ import { resolveSelectPresentation } from "./input-select-presentation.js";
8
+ // layout="inline" (checkbox lists like MultiSelectCheckboxes, the
9
+ // BooleanField widget) keeps the checkbox optics; the standard form
10
+ // field (render-field.tsx, layout="stacked") gets the switch.
11
+ export const renderBooleanKind = (props, { booleanLayout }) => booleanLayout === "inline" ? (_jsx(Checkbox, { id: props.id, name: props.name, disabled: props.disabled, "aria-required": props.required, "aria-invalid": props.hasError === true ? true : undefined, checked: props.value, onCheckedChange: (checked) => props.onChange(checked === true) })) : (_jsx(Switch, { id: props.id, name: props.name, disabled: props.disabled, "aria-required": props.required, "aria-invalid": props.hasError === true ? true : undefined, checked: props.value, onCheckedChange: (checked) => props.onChange(checked === true), className: "aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40" }));
12
+ function normalizeSelectOptions(options) {
13
+ return options.map((o) => (typeof o === "string" ? { value: o, label: o } : o));
14
+ }
15
+ // Visual consolidation: all selects go through ComboboxInput
16
+ // (cmdk + Radix popover). Before, there were two paths: Radix
17
+ // Select for `kind:"select"` and cmdk for `kind:"combobox"`. Three
18
+ // visually different variants (single select, combobox single, combobox
19
+ // multi) became unmanageable. With the merge the combobox is the only
20
+ // implementation: the search input is present even for 4-item status
21
+ // selects, a deliberately accepted UX consequence for style consistency.
22
+ export const renderSelectKind = (props) => {
23
+ const comboOptions = normalizeSelectOptions(props.options);
24
+ // The radio group can't render the unselected placeholder as its own
25
+ // segment, so it counts and renders on the real options only — the
26
+ // dropdown below keeps comboOptions (placeholder included) since it
27
+ // has a genuine "nothing selected" row.
28
+ // An author who asks for `display: "radio"` explicitly declared the ""
29
+ // option as a real choice (e.g. "Any"), so it stays a segment.
30
+ const radioGroupOptions = props.display === "radio" ? comboOptions : withoutSegmentedSelectPlaceholder(comboOptions);
31
+ const presentation = resolveSelectPresentation(props, radioGroupOptions);
32
+ if (presentation === "radioList" && radioGroupOptions.length > 0) {
33
+ return (_jsx(RadioListSelect, { id: props.id, name: props.name, value: props.value, onChange: props.onChange, options: radioGroupOptions, ...(props.radioVariant !== undefined && { variant: props.radioVariant }), ...stateProps(props) }));
34
+ }
35
+ if (presentation === "segmented" && radioGroupOptions.length > 0) {
36
+ return (_jsx(SegmentedSelect, { id: props.id, name: props.name, value: props.value, onChange: props.onChange, options: radioGroupOptions, ...stateProps(props) }));
37
+ }
38
+ // The dropdown shows a disabled option's hint inline, so it stays readable in the trigger and search.
39
+ return (_jsx(ComboboxInput, { id: props.id, name: props.name, value: props.value, onChange: props.onChange, options: comboOptions.map(withInlineDisabledHint), ...stateProps(props) }));
40
+ };
41
+ function comboboxBaseProps(props) {
42
+ return {
43
+ id: props.id,
44
+ name: props.name,
45
+ options: props.options,
46
+ ...stateProps(props),
47
+ ...(props.placeholder !== undefined && { placeholder: props.placeholder }),
48
+ ...(props.searchPlaceholder !== undefined && {
49
+ searchPlaceholder: props.searchPlaceholder,
50
+ }),
51
+ ...(props.emptyText !== undefined && { emptyText: props.emptyText }),
52
+ ...(props.onSearchChange !== undefined && { onSearchChange: props.onSearchChange }),
53
+ ...(props.loading !== undefined && { loading: props.loading }),
54
+ ...(props.onCreate !== undefined && { onCreate: props.onCreate }),
55
+ ...(props.createLabel !== undefined && { createLabel: props.createLabel }),
56
+ };
57
+ }
58
+ // Discriminated union per `multiple`; we split
59
+ // TS-side into two branches so ComboboxInput's single/multi variants
60
+ // render with the right types.
61
+ export const renderComboboxKind = (props) => {
62
+ const baseProps = comboboxBaseProps(props);
63
+ if (props.multiple === true) {
64
+ return _jsx(ComboboxInput, { ...baseProps, multiple: true, value: props.value, onChange: props.onChange });
65
+ }
66
+ return _jsx(ComboboxInput, { ...baseProps, value: props.value, onChange: props.onChange });
67
+ };
@@ -0,0 +1,3 @@
1
+ import type { KindRenderer } from "./input-shared.ts";
2
+ export declare const renderFileKind: KindRenderer<"file" | "image">;
3
+ export declare const renderFilesDropzoneKind: KindRenderer<"files-dropzone">;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { FileUploadInput } from "./file-upload.js";
3
+ import { FilesDropzoneInput } from "./files-dropzone.js";
4
+ import { uploadTargetProps } from "./input-optional-props.js";
5
+ export const renderFileKind = (props) => (_jsx(FileUploadInput, { kind: props.kind, id: props.id, value: props.value, onChange: props.onChange, ...(props.accept !== undefined && { accept: props.accept }), ...(props.disabled !== undefined && { disabled: props.disabled }), ...uploadTargetProps(props), ...(props.imageVariant !== undefined && { imageVariant: props.imageVariant }), ...(props.capture !== undefined && { capture: props.capture }), ...(props.directory !== undefined && { directory: props.directory }), ...(props.onFilesPicked !== undefined && { onFilesPicked: props.onFilesPicked }), ...(props.pickLabel !== undefined && { pickLabel: props.pickLabel }), ...(props.testId !== undefined && { testId: props.testId }) }));
6
+ export const renderFilesDropzoneKind = (props) => (_jsx(FilesDropzoneInput, { id: props.id, value: props.value, onChange: props.onChange, ...(props.accept !== undefined && { accept: props.accept }), ...(props.maxSize !== undefined && { maxSize: props.maxSize }), ...(props.maxCount !== undefined && { maxCount: props.maxCount }), ...(props.disabled !== undefined && { disabled: props.disabled }), ...uploadTargetProps(props), ...(props.testId !== undefined && { testId: props.testId }) }));
@@ -0,0 +1,4 @@
1
+ import type { KindRenderer } from "./input-shared.ts";
2
+ export declare const renderNumberKind: KindRenderer<"number">;
3
+ export declare const renderRangeKind: KindRenderer<"range">;
4
+ export declare const renderMoneyKind: KindRenderer<"money">;
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../lib/cn.js";
3
+ import { FIELD_ICON_CLASS, fieldIconFor, unitSuffixId, withInputAddons } from "./field.js";
4
+ import { stateProps } from "./input-optional-props.js";
5
+ import { MoneyInput } from "./money-input.js";
6
+ import { NumberInput } from "./number-input.js";
7
+ export const renderNumberKind = (props) => {
8
+ const NumberIcon = fieldIconFor(props.icon);
9
+ // Icon, input and unit share this one box, and the grid cell sizes the
10
+ // box rather than the bare input: anchored to a wider wrapper, the unit
11
+ // would sit next to a narrower input instead of inside it.
12
+ const numberField = (_jsxs("div", { "data-slot": "number-field", className: "relative", children: [NumberIcon !== undefined && _jsx(NumberIcon, { "aria-hidden": "true", className: FIELD_ICON_CLASS }), _jsx(NumberInput, { id: props.id, name: props.name, value: props.value, onChange: props.onChange, disabled: props.disabled, required: props.required, hasError: props.hasError, testId: props.testId, ...(props.unit !== undefined && { ariaDescribedBy: unitSuffixId(props.id) }), ...(props.locale !== undefined && { locale: props.locale }), ...(props.grouping !== undefined && { grouping: props.grouping }), ...(props.integer !== undefined && { integer: props.integer }), ...(props.placeholder !== undefined && { placeholder: props.placeholder }), className: cn(NumberIcon !== undefined ? "pl-8" : undefined, props.unit !== undefined ? "pr-8" : undefined) }), props.unit !== undefined && (_jsx("span", { id: unitSuffixId(props.id), className: "pointer-events-none absolute right-2.5 top-1/2 max-w-[40%] -translate-y-1/2 truncate text-sm text-muted-foreground", children: props.unit }))] }));
13
+ return withInputAddons(props, numberField);
14
+ };
15
+ export const renderRangeKind = (props, { common }) => (_jsx("input", { type: "range", ...common, min: props.min, max: props.max, step: props.step ?? 1, value: props.value, onChange: (e) => props.onChange(Number(e.target.value)), className: "w-full accent-primary" }));
16
+ export const renderMoneyKind = (props) => (_jsx(MoneyInput, { id: props.id, name: props.name, value: props.value, onChange: props.onChange, currency: props.currency ?? "EUR", ...(props.locale !== undefined && { locale: props.locale }), ...stateProps(props) }));
@@ -0,0 +1,20 @@
1
+ type StateInput = {
2
+ readonly disabled?: boolean;
3
+ readonly required?: boolean;
4
+ readonly hasError?: boolean;
5
+ };
6
+ type LocaleBoundsInput = {
7
+ readonly locale?: string;
8
+ readonly min?: string;
9
+ readonly max?: string;
10
+ };
11
+ type UploadTargetInput = {
12
+ readonly entityType?: string;
13
+ readonly uploadActionForm?: string;
14
+ readonly entityId?: string;
15
+ readonly fieldName?: string;
16
+ };
17
+ export declare function stateProps({ disabled, required, hasError }: StateInput): StateInput;
18
+ export declare function localeBoundsProps({ locale, min, max }: LocaleBoundsInput): LocaleBoundsInput;
19
+ export declare function uploadTargetProps({ entityType, uploadActionForm, entityId, fieldName, }: UploadTargetInput): UploadTargetInput;
20
+ export {};
@@ -0,0 +1,24 @@
1
+ // The inputs below take optional props that must be absent, not `undefined`,
2
+ // so a primitive's own defaults still apply.
3
+ export function stateProps({ disabled, required, hasError }) {
4
+ return {
5
+ ...(disabled !== undefined && { disabled }),
6
+ ...(required !== undefined && { required }),
7
+ ...(hasError !== undefined && { hasError }),
8
+ };
9
+ }
10
+ export function localeBoundsProps({ locale, min, max }) {
11
+ return {
12
+ ...(locale !== undefined && { locale }),
13
+ ...(min !== undefined && { min }),
14
+ ...(max !== undefined && { max }),
15
+ };
16
+ }
17
+ export function uploadTargetProps({ entityType, uploadActionForm, entityId, fieldName, }) {
18
+ return {
19
+ ...(entityType !== undefined && { entityType }),
20
+ ...(uploadActionForm !== undefined && { uploadActionForm }),
21
+ ...(entityId !== undefined && { entityId }),
22
+ ...(fieldName !== undefined && { fieldName }),
23
+ };
24
+ }
@@ -0,0 +1,13 @@
1
+ import { defaultSelectPresentation } from "./field.tsx";
2
+ type SelectPresentation = ReturnType<typeof defaultSelectPresentation>;
3
+ type PresentationOption = {
4
+ readonly label: string;
5
+ readonly description?: string;
6
+ readonly group?: string;
7
+ };
8
+ type PresentationRequest = {
9
+ readonly radioVariant?: "list" | "card";
10
+ readonly display?: "radio" | "dropdown";
11
+ };
12
+ export declare function resolveSelectPresentation(request: PresentationRequest, options: readonly PresentationOption[]): SelectPresentation;
13
+ export {};
@@ -0,0 +1,19 @@
1
+ import { defaultSelectPresentation } from "./field.js";
2
+ import { hasDescriptionOrGroup } from "./option-groups.js";
3
+ // An explicit `display` is an author decision and outranks the heuristic in
4
+ // both directions — a requested radio group renders as one even when the
5
+ // options outnumber the heuristic's threshold.
6
+ function requestedSelectPresentation({ radioVariant, display }, options) {
7
+ if (radioVariant === "card")
8
+ return "radioList";
9
+ if (display === "radio")
10
+ return "segmented";
11
+ if (display === "dropdown")
12
+ return "dropdown";
13
+ return defaultSelectPresentation(options);
14
+ }
15
+ // A segmented control has no room for a description line or group headings.
16
+ export function resolveSelectPresentation(request, options) {
17
+ const requested = requestedSelectPresentation(request, options);
18
+ return requested === "segmented" && hasDescriptionOrGroup(options) ? "radioList" : requested;
19
+ }
@@ -0,0 +1,22 @@
1
+ import type { ContextType, ReactNode } from "react";
2
+ import type { InputProps } from "../../index.ts";
3
+ import type { FieldDescriptionContext, FieldLayoutContext } from "./contexts.tsx";
4
+ import type { useFocusOnMount } from "./use-focus-on-mount.ts";
5
+ export type InputKind = InputProps["kind"];
6
+ export type PropsOfKind<K extends InputKind> = InputProps extends infer P ? P extends {
7
+ readonly kind: infer PK;
8
+ } ? K extends PK ? P : never : never : never;
9
+ export declare function commonInputAttrs(props: InputProps, describedBy: ContextType<typeof FieldDescriptionContext>): {
10
+ readonly id: string;
11
+ readonly name: string;
12
+ readonly disabled: boolean | undefined;
13
+ readonly "aria-describedby": string | undefined;
14
+ readonly "aria-required": boolean | undefined;
15
+ readonly "aria-invalid": true | undefined;
16
+ };
17
+ export type DefaultInputShared = {
18
+ readonly common: ReturnType<typeof commonInputAttrs>;
19
+ readonly textInputRef: ReturnType<typeof useFocusOnMount>;
20
+ readonly booleanLayout: ContextType<typeof FieldLayoutContext>;
21
+ };
22
+ export type KindRenderer<K extends InputKind> = (props: PropsOfKind<K>, shared: DefaultInputShared) => ReactNode;
@@ -0,0 +1,10 @@
1
+ export function commonInputAttrs(props, describedBy) {
2
+ return {
3
+ id: props.id,
4
+ name: props.name,
5
+ disabled: props.disabled,
6
+ "aria-describedby": describedBy,
7
+ "aria-required": props.required,
8
+ "aria-invalid": props.hasError === true ? true : undefined,
9
+ };
10
+ }
@@ -0,0 +1,5 @@
1
+ import type { KindRenderer } from "./input-shared.ts";
2
+ export declare const renderDateKind: KindRenderer<"date">;
3
+ export declare const renderTimestampKind: KindRenderer<"timestamp">;
4
+ export declare const renderLocatedTimestampKind: KindRenderer<"locatedTimestamp">;
5
+ export declare const renderTzKind: KindRenderer<"tz">;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { DateInput } from "./date-input.js";
3
+ import { localeBoundsProps, stateProps } from "./input-optional-props.js";
4
+ import { LocatedTimestampInput } from "./located-timestamp-input.js";
5
+ import { TimestampInput } from "./timestamp-input.js";
6
+ import { TzInput } from "./tz-input.js";
7
+ export const renderDateKind = (props) => (_jsx(DateInput, { id: props.id, name: props.name, value: props.value, onChange: props.onChange, ...localeBoundsProps(props), ...stateProps(props) }));
8
+ export const renderTimestampKind = (props) => (_jsx(TimestampInput, { id: props.id, name: props.name, value: props.value, onChange: props.onChange, ...(props.wallClock !== undefined && { wallClock: props.wallClock }), ...localeBoundsProps(props), ...stateProps(props) }));
9
+ export const renderLocatedTimestampKind = (props) => (_jsx(LocatedTimestampInput, { id: props.id, name: props.name, value: props.value, onChange: props.onChange, ...localeBoundsProps(props), ...stateProps(props) }));
10
+ export const renderTzKind = (props) => (_jsx(TzInput, { id: props.id, name: props.name, value: props.value, onChange: props.onChange, ...stateProps(props) }));
@@ -0,0 +1,7 @@
1
+ import type { KindRenderer } from "./input-shared.ts";
2
+ export declare const renderTextKind: KindRenderer<"text">;
3
+ export declare const renderCodeKind: KindRenderer<"code">;
4
+ export declare const renderEmailKind: KindRenderer<"email">;
5
+ export declare const renderMonthKind: KindRenderer<"month">;
6
+ export declare const renderPasswordKind: KindRenderer<"password">;
7
+ export declare const renderTextareaKind: KindRenderer<"textarea">;
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cn } from "../lib/cn.js";
3
+ import { Input as UiInput } from "../ui/input.js";
4
+ import { Textarea } from "../ui/textarea.js";
5
+ import { CodeInput } from "./code-input.js";
6
+ import { fieldIconFor, textareaMinHeight, withFieldIcon, withInputAddons, withTrailingAction, } from "./field.js";
7
+ export const renderTextKind = (props, { common, textInputRef }) => withInputAddons(props, withFieldIcon(props.icon, _jsx(UiInput, { type: "text", ref: textInputRef, ...common, ...props.dataAttributes, "data-testid": props.testId, readOnly: props.readOnly, value: props.value, onChange: (e) => props.onChange(e.target.value), onKeyDown: props.onKeyDown, ...(props.placeholder !== undefined && { placeholder: props.placeholder }), ...(props.autoComplete !== undefined && { autoComplete: props.autoComplete }), className: cn(fieldIconFor(props.icon) !== undefined ? "pl-8" : undefined) })));
8
+ export const renderCodeKind = (props) => _jsx(CodeInput, { ...props });
9
+ export const renderEmailKind = (props, { common }) => (_jsx(UiInput, { type: "email", ...common, "data-testid": props.testId, value: props.value, onChange: (e) => props.onChange(e.target.value), ...(props.placeholder !== undefined && { placeholder: props.placeholder }), autoComplete: props.autoComplete ?? "email" }));
10
+ export const renderMonthKind = (props, { common }) => (_jsx(UiInput, { type: "month", ...common, "data-testid": props.testId, value: props.value, onChange: (e) => props.onChange(e.target.value) }));
11
+ export const renderPasswordKind = (props, { common }) => withTrailingAction(props.trailingAction, _jsx(UiInput, { type: "password", ...common, "data-testid": props.testId, value: props.value, onChange: (e) => props.onChange(e.target.value), ...(props.placeholder !== undefined && { placeholder: props.placeholder }), autoComplete: props.autoComplete ?? "current-password", className: props.trailingAction !== undefined ? "pr-9" : undefined }), true);
12
+ function textareaShortcutProps({ onKeyDown, onSubmitShortcut }) {
13
+ if (onSubmitShortcut === undefined && onKeyDown === undefined)
14
+ return {};
15
+ return {
16
+ ...(onSubmitShortcut !== undefined && {
17
+ "aria-keyshortcuts": "Control+Enter Meta+Enter",
18
+ }),
19
+ onKeyDown: (e) => {
20
+ onKeyDown?.(e);
21
+ // Lets a caller that already handles Ctrl/Cmd+Enter (e.g. an
22
+ // "Enter sends" handler that ignores only Shift) opt out of the
23
+ // shortcut instead of submitting twice.
24
+ if (e.defaultPrevented)
25
+ return;
26
+ if (onSubmitShortcut === undefined || e.key !== "Enter" || !(e.metaKey || e.ctrlKey))
27
+ return;
28
+ e.preventDefault();
29
+ onSubmitShortcut();
30
+ },
31
+ };
32
+ }
33
+ export const renderTextareaKind = (props, { common }) => (_jsx(Textarea, { ...common, ...props.dataAttributes, readOnly: props.readOnly, value: props.value, onChange: (e) => props.onChange(e.target.value), rows: props.rows ?? 4, className: "resize-y", ...(props.placeholder !== undefined && { placeholder: props.placeholder }), ...(props.rows !== undefined && { style: textareaMinHeight(props.rows) }), ...textareaShortcutProps(props) }));