@cosmicdrift/kumiko-renderer 0.373.0 → 0.377.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 (466) hide show
  1. package/README.md +2 -2
  2. package/dist/app/action-form-support.d.ts +58 -0
  3. package/dist/app/action-form-support.js +96 -0
  4. package/dist/app/client-actions.d.ts +46 -0
  5. package/dist/app/client-actions.js +60 -0
  6. package/dist/app/column-renderers.js +1 -1
  7. package/dist/app/config-edit-shim.js +1 -1
  8. package/dist/app/content-preview.js +1 -6
  9. package/dist/app/custom-screens.js +1 -1
  10. package/dist/app/dashboard-body.d.ts +1 -1
  11. package/dist/app/entity-list-expanded-row.d.ts +1 -1
  12. package/dist/app/entity-list-expanded-row.js +4 -0
  13. package/dist/app/form-schema.d.ts +1 -1
  14. package/dist/app/form-schema.js +2 -2
  15. package/dist/app/header-actions-bar.d.ts +19 -0
  16. package/dist/app/header-actions-bar.js +30 -0
  17. package/dist/app/kumiko-screen.d.ts +1 -1
  18. package/dist/app/kumiko-screen.js +134 -66
  19. package/dist/app/list-empty-content.d.ts +2 -2
  20. package/dist/app/list-empty-content.js +3 -2
  21. package/dist/app/list-facets.d.ts +1 -1
  22. package/dist/app/nav.js +2 -2
  23. package/dist/app/query-state-banners.d.ts +1 -1
  24. package/dist/app/reference-link.d.ts +1 -1
  25. package/dist/app/row-actions.d.ts +10 -2
  26. package/dist/app/row-actions.js +85 -6
  27. package/dist/app/secret-mint-body.d.ts +1 -1
  28. package/dist/app/secrets-edit-body.d.ts +1 -1
  29. package/dist/app/use-record-actions.d.ts +12 -0
  30. package/dist/app/use-record-actions.js +30 -0
  31. package/dist/app/write-failed-error.d.ts +1 -1
  32. package/dist/components/availability-options-select.d.ts +1 -1
  33. package/dist/components/embedded-list-field.d.ts +1 -1
  34. package/dist/components/embedded-list-field.js +1 -1
  35. package/dist/components/file-list-field.d.ts +1 -1
  36. package/dist/components/grid-cell-for-field.d.ts +4 -2
  37. package/dist/components/grid-cell-for-field.js +2 -2
  38. package/dist/components/multi-select-checkboxes.d.ts +1 -1
  39. package/dist/components/multi-select-checkboxes.js +1 -1
  40. package/dist/components/query-options-select.d.ts +1 -1
  41. package/dist/components/related-list-section.d.ts +1 -1
  42. package/dist/components/related-list-section.js +16 -3
  43. package/dist/components/render-edit-logic.d.ts +1 -1
  44. package/dist/components/render-edit-types.d.ts +2 -2
  45. package/dist/components/render-edit.d.ts +1 -1
  46. package/dist/components/render-edit.js +5 -3
  47. package/dist/components/render-field.d.ts +5 -2
  48. package/dist/components/render-field.js +103 -9
  49. package/dist/components/render-list.d.ts +1 -1
  50. package/dist/components/render-list.js +20 -4
  51. package/dist/components/write-form-section.d.ts +1 -1
  52. package/dist/components/write-form-section.js +1 -1
  53. package/dist/context/dispatcher-context.d.ts +1 -1
  54. package/dist/format-when.js +1 -1
  55. package/dist/headless/apex/css.d.ts +3 -0
  56. package/dist/headless/apex/css.js +298 -0
  57. package/dist/headless/apex/index.d.ts +236 -0
  58. package/dist/headless/apex/index.js +377 -0
  59. package/dist/headless/apex/lightbox.d.ts +4 -0
  60. package/dist/headless/apex/lightbox.js +81 -0
  61. package/dist/headless/contracts/asset.d.ts +15 -0
  62. package/dist/headless/contracts/asset.js +17 -0
  63. package/dist/headless/contracts/index.d.ts +3 -0
  64. package/dist/headless/contracts/index.js +1 -0
  65. package/dist/headless/contracts/locale.d.ts +7 -0
  66. package/dist/headless/contracts/locale.js +11 -0
  67. package/dist/headless/contracts/primitives.d.ts +93 -0
  68. package/dist/headless/contracts/primitives.js +21 -0
  69. package/dist/headless/dispatcher/index.d.ts +2 -0
  70. package/dist/headless/dispatcher/index.js +1 -0
  71. package/dist/headless/dispatcher/types.d.ts +86 -0
  72. package/dist/headless/dispatcher/types.js +34 -0
  73. package/dist/headless/form/form-controller.d.ts +2 -0
  74. package/dist/headless/form/form-controller.js +409 -0
  75. package/dist/headless/form/index.d.ts +3 -0
  76. package/dist/headless/form/index.js +2 -0
  77. package/dist/headless/form/types.d.ts +76 -0
  78. package/dist/headless/form/types.js +1 -0
  79. package/dist/headless/form/zod-bridge.d.ts +10 -0
  80. package/dist/headless/form/zod-bridge.js +103 -0
  81. package/dist/headless/format/document-values.d.ts +12 -0
  82. package/dist/headless/format/document-values.js +70 -0
  83. package/dist/headless/format/escape.d.ts +5 -0
  84. package/dist/headless/format/escape.js +75 -0
  85. package/dist/headless/format/html-template.d.ts +9 -0
  86. package/dist/headless/format/html-template.js +39 -0
  87. package/dist/headless/format/index.d.ts +12 -0
  88. package/dist/headless/format/index.js +237 -0
  89. package/dist/headless/format/money.d.ts +3 -0
  90. package/dist/headless/format/money.js +39 -0
  91. package/dist/headless/index.d.ts +14 -0
  92. package/dist/headless/index.js +9 -0
  93. package/dist/headless/locale-routing/index.d.ts +41 -0
  94. package/dist/headless/locale-routing/index.js +145 -0
  95. package/dist/headless/nav/index.d.ts +2 -0
  96. package/dist/headless/nav/index.js +1 -0
  97. package/dist/headless/nav/resolve.d.ts +2 -0
  98. package/dist/headless/nav/resolve.js +71 -0
  99. package/dist/headless/nav/types.d.ts +26 -0
  100. package/dist/headless/nav/types.js +1 -0
  101. package/dist/headless/store/create-store.d.ts +2 -0
  102. package/dist/headless/store/create-store.js +43 -0
  103. package/dist/headless/store/equality.d.ts +1 -0
  104. package/dist/headless/store/equality.js +34 -0
  105. package/dist/headless/store/index.d.ts +3 -0
  106. package/dist/headless/store/index.js +2 -0
  107. package/dist/headless/store/types.d.ts +7 -0
  108. package/dist/headless/store/types.js +10 -0
  109. package/dist/headless/view-model/edit.d.ts +18 -0
  110. package/dist/headless/view-model/edit.js +501 -0
  111. package/dist/headless/view-model/embedded-list.d.ts +27 -0
  112. package/dist/headless/view-model/embedded-list.js +63 -0
  113. package/dist/headless/view-model/index.d.ts +7 -0
  114. package/dist/headless/view-model/index.js +3 -0
  115. package/dist/headless/view-model/list-file-display.d.ts +3 -0
  116. package/dist/headless/view-model/list-file-display.js +27 -0
  117. package/dist/headless/view-model/list.d.ts +16 -0
  118. package/dist/headless/view-model/list.js +229 -0
  119. package/dist/headless/view-model/types.d.ts +382 -0
  120. package/dist/headless/view-model/types.js +1 -0
  121. package/dist/hooks/use-ai-text.d.ts +1 -1
  122. package/dist/hooks/use-form.d.ts +1 -1
  123. package/dist/hooks/use-form.js +1 -1
  124. package/dist/hooks/use-mutation.d.ts +1 -1
  125. package/dist/hooks/use-query.d.ts +1 -1
  126. package/dist/hooks/use-store.d.ts +1 -1
  127. package/dist/hooks/use-store.js +7 -7
  128. package/dist/hooks/use-stream-handler.d.ts +1 -1
  129. package/dist/i18n-defaults.js +7 -0
  130. package/dist/i18n.d.ts +1 -1
  131. package/dist/i18n.js +1 -1
  132. package/dist/index.d.ts +5 -1
  133. package/dist/index.js +7 -4
  134. package/dist/live/csrf.d.ts +2 -0
  135. package/dist/live/csrf.js +60 -0
  136. package/dist/live/dispatcher-live.d.ts +7 -0
  137. package/dist/live/dispatcher-live.js +286 -0
  138. package/dist/live/envelopes.d.ts +101 -0
  139. package/dist/live/envelopes.js +35 -0
  140. package/dist/live/error-mapping.d.ts +16 -0
  141. package/dist/live/error-mapping.js +82 -0
  142. package/dist/live/index.d.ts +7 -0
  143. package/dist/live/index.js +5 -0
  144. package/dist/live/locale.d.ts +2 -0
  145. package/dist/live/locale.js +25 -0
  146. package/dist/live/sse-stream.d.ts +14 -0
  147. package/dist/live/sse-stream.js +128 -0
  148. package/dist/primitives.d.ts +53 -36
  149. package/dist/primitives.js +1 -1
  150. package/dist/session/session-ended.d.ts +1 -1
  151. package/dist/tokens.d.ts +3 -4
  152. package/dist/tokens.js +4 -5
  153. package/dist/web/app/app-schema-boot.d.ts +19 -0
  154. package/dist/web/app/app-schema-boot.js +124 -0
  155. package/dist/web/app/browser-locale.d.ts +22 -0
  156. package/dist/web/app/browser-locale.js +78 -0
  157. package/dist/web/app/client-plugin.d.ts +106 -0
  158. package/dist/web/app/client-plugin.js +12 -0
  159. package/dist/web/app/create-app.d.ts +104 -0
  160. package/dist/web/app/create-app.js +498 -0
  161. package/dist/web/app/create-public-surface.d.ts +36 -0
  162. package/dist/web/app/create-public-surface.js +40 -0
  163. package/dist/web/app/dashboard-body.d.ts +3 -0
  164. package/dist/web/app/dashboard-body.js +865 -0
  165. package/dist/web/app/dashboard-refresh.d.ts +6 -0
  166. package/dist/web/app/dashboard-refresh.js +15 -0
  167. package/dist/web/app/document-lang-sync.d.ts +4 -0
  168. package/dist/web/app/document-lang-sync.js +27 -0
  169. package/dist/web/app/draft-storage.d.ts +11 -0
  170. package/dist/web/app/draft-storage.js +53 -0
  171. package/dist/web/app/nav-providers-context.d.ts +13 -0
  172. package/dist/web/app/nav-providers-context.js +18 -0
  173. package/dist/web/app/nav.d.ts +40 -0
  174. package/dist/web/app/nav.js +231 -0
  175. package/dist/web/app/plain-content-editor.d.ts +3 -0
  176. package/dist/web/app/plain-content-editor.js +30 -0
  177. package/dist/web/app/relative-time.d.ts +10 -0
  178. package/dist/web/app/relative-time.js +56 -0
  179. package/dist/web/app/resolvers-context.d.ts +12 -0
  180. package/dist/web/app/resolvers-context.js +10 -0
  181. package/dist/web/app/rich-content-editor.d.ts +3 -0
  182. package/dist/web/app/rich-content-editor.js +15 -0
  183. package/dist/web/app/root-error-fallback.d.ts +2 -0
  184. package/dist/web/app/root-error-fallback.js +21 -0
  185. package/dist/web/app/screen-error-fallback.d.ts +6 -0
  186. package/dist/web/app/screen-error-fallback.js +17 -0
  187. package/dist/web/app/tiptap-editor.d.ts +3 -0
  188. package/dist/web/app/tiptap-editor.js +120 -0
  189. package/dist/web/app/toast-action-notifier.d.ts +5 -0
  190. package/dist/web/app/toast-action-notifier.js +17 -0
  191. package/dist/web/components/config-cascade.d.ts +13 -0
  192. package/dist/web/components/config-cascade.js +155 -0
  193. package/dist/web/components/config-source-badge.d.ts +6 -0
  194. package/dist/web/components/config-source-badge.js +55 -0
  195. package/dist/web/default-color-scheme.d.ts +2 -0
  196. package/dist/web/default-color-scheme.js +3 -0
  197. package/dist/web/icons.d.ts +94 -0
  198. package/dist/web/icons.js +100 -0
  199. package/dist/web/index.d.ts +78 -0
  200. package/dist/web/index.js +53 -0
  201. package/dist/web/layout/app-layout.d.ts +21 -0
  202. package/dist/web/layout/app-layout.js +8 -0
  203. package/dist/web/layout/avatar.d.ts +12 -0
  204. package/dist/web/layout/avatar.js +63 -0
  205. package/dist/web/layout/default-app-shell.d.ts +41 -0
  206. package/dist/web/layout/default-app-shell.js +16 -0
  207. package/dist/web/layout/editor-panel.d.ts +10 -0
  208. package/dist/web/layout/editor-panel.js +29 -0
  209. package/dist/web/layout/fill-classes.d.ts +9 -0
  210. package/dist/web/layout/fill-classes.js +15 -0
  211. package/dist/web/layout/filter-app-schema-navs.d.ts +7 -0
  212. package/dist/web/layout/filter-app-schema-navs.js +68 -0
  213. package/dist/web/layout/header-action-group.d.ts +6 -0
  214. package/dist/web/layout/header-action-group.js +8 -0
  215. package/dist/web/layout/header-overflow-menu.d.ts +4 -0
  216. package/dist/web/layout/header-overflow-menu.js +8 -0
  217. package/dist/web/layout/language-menu-items.d.ts +8 -0
  218. package/dist/web/layout/language-menu-items.js +16 -0
  219. package/dist/web/layout/language-switcher.d.ts +25 -0
  220. package/dist/web/layout/language-switcher.js +46 -0
  221. package/dist/web/layout/nav-tree.d.ts +19 -0
  222. package/dist/web/layout/nav-tree.js +536 -0
  223. package/dist/web/layout/page-header-slot.d.ts +25 -0
  224. package/dist/web/layout/page-header-slot.js +50 -0
  225. package/dist/web/layout/profile-menu.d.ts +13 -0
  226. package/dist/web/layout/profile-menu.js +6 -0
  227. package/dist/web/layout/shell-breadcrumb.d.ts +11 -0
  228. package/dist/web/layout/shell-breadcrumb.js +34 -0
  229. package/dist/web/layout/shell-header.d.ts +12 -0
  230. package/dist/web/layout/shell-header.js +159 -0
  231. package/dist/web/layout/sidebar-brand.d.ts +15 -0
  232. package/dist/web/layout/sidebar-brand.js +9 -0
  233. package/dist/web/layout/sidebar-panel.d.ts +41 -0
  234. package/dist/web/layout/sidebar-panel.js +107 -0
  235. package/dist/web/layout/sidebar-user.d.ts +8 -0
  236. package/dist/web/layout/sidebar-user.js +17 -0
  237. package/dist/web/layout/sidebar.d.ts +17 -0
  238. package/dist/web/layout/sidebar.js +11 -0
  239. package/dist/web/layout/target-resolver-stub.d.ts +13 -0
  240. package/dist/web/layout/target-resolver-stub.js +61 -0
  241. package/dist/web/layout/target-url.d.ts +12 -0
  242. package/dist/web/layout/target-url.js +77 -0
  243. package/dist/web/layout/theme-label.d.ts +15 -0
  244. package/dist/web/layout/theme-label.js +35 -0
  245. package/dist/web/layout/theme-menu-item.d.ts +12 -0
  246. package/dist/web/layout/theme-menu-item.js +16 -0
  247. package/dist/web/layout/theme-toggle.d.ts +17 -0
  248. package/dist/web/layout/theme-toggle.js +22 -0
  249. package/dist/web/layout/topbar.d.ts +8 -0
  250. package/dist/web/layout/topbar.js +4 -0
  251. package/dist/web/layout/workspace-shell.d.ts +63 -0
  252. package/dist/web/layout/workspace-shell.js +220 -0
  253. package/dist/web/layout/workspace-switcher.d.ts +15 -0
  254. package/dist/web/layout/workspace-switcher.js +36 -0
  255. package/dist/web/lib/accept-attr.d.ts +1 -0
  256. package/dist/web/lib/accept-attr.js +6 -0
  257. package/dist/web/lib/clamp.d.ts +1 -0
  258. package/dist/web/lib/clamp.js +3 -0
  259. package/dist/web/lib/cn.d.ts +6 -0
  260. package/dist/web/lib/cn.js +9 -0
  261. package/dist/web/lib/download.d.ts +2 -0
  262. package/dist/web/lib/download.js +24 -0
  263. package/dist/web/lib/file-ref-download.d.ts +15 -0
  264. package/dist/web/lib/file-ref-download.js +62 -0
  265. package/dist/web/lib/file-upload-form-data.d.ts +7 -0
  266. package/dist/web/lib/file-upload-form-data.js +11 -0
  267. package/dist/web/lib/resize-image.d.ts +1 -0
  268. package/dist/web/lib/resize-image.js +66 -0
  269. package/dist/web/lib/upload-file-ref.d.ts +9 -0
  270. package/dist/web/lib/upload-file-ref.js +29 -0
  271. package/dist/web/primitives/action-menu.d.ts +34 -0
  272. package/dist/web/primitives/action-menu.js +30 -0
  273. package/dist/web/primitives/calendar-popover.d.ts +13 -0
  274. package/dist/web/primitives/calendar-popover.js +76 -0
  275. package/dist/web/primitives/combobox.d.ts +56 -0
  276. package/dist/web/primitives/combobox.js +127 -0
  277. package/dist/web/primitives/date-field.d.ts +18 -0
  278. package/dist/web/primitives/date-field.js +74 -0
  279. package/dist/web/primitives/date-input.d.ts +1 -0
  280. package/dist/web/primitives/date-input.js +5 -0
  281. package/dist/web/primitives/date-parse.d.ts +11 -0
  282. package/dist/web/primitives/date-parse.js +159 -0
  283. package/dist/web/primitives/dialog.d.ts +3 -0
  284. package/dist/web/primitives/dialog.js +42 -0
  285. package/dist/web/primitives/drawer.d.ts +3 -0
  286. package/dist/web/primitives/drawer.js +17 -0
  287. package/dist/web/primitives/dropdown-menu.d.ts +15 -0
  288. package/dist/web/primitives/dropdown-menu.js +56 -0
  289. package/dist/web/primitives/embedded-list-input.d.ts +9 -0
  290. package/dist/web/primitives/embedded-list-input.js +382 -0
  291. package/dist/web/primitives/file-thumbnail.d.ts +25 -0
  292. package/dist/web/primitives/file-thumbnail.js +90 -0
  293. package/dist/web/primitives/file-upload.d.ts +21 -0
  294. package/dist/web/primitives/file-upload.js +56 -0
  295. package/dist/web/primitives/index.d.ts +34 -0
  296. package/dist/web/primitives/index.js +2021 -0
  297. package/dist/web/primitives/json-view.d.ts +7 -0
  298. package/dist/web/primitives/json-view.js +84 -0
  299. package/dist/web/primitives/layout.d.ts +27 -0
  300. package/dist/web/primitives/layout.js +30 -0
  301. package/dist/web/primitives/lightbox.d.ts +3 -0
  302. package/dist/web/primitives/lightbox.js +36 -0
  303. package/dist/web/primitives/locale-number.d.ts +2 -0
  304. package/dist/web/primitives/locale-number.js +54 -0
  305. package/dist/web/primitives/located-timestamp-input.d.ts +22 -0
  306. package/dist/web/primitives/located-timestamp-input.js +22 -0
  307. package/dist/web/primitives/metric.d.ts +5 -0
  308. package/dist/web/primitives/metric.js +18 -0
  309. package/dist/web/primitives/modal-shell.d.ts +17 -0
  310. package/dist/web/primitives/modal-shell.js +9 -0
  311. package/dist/web/primitives/modal.d.ts +3 -0
  312. package/dist/web/primitives/modal.js +13 -0
  313. package/dist/web/primitives/money-input.d.ts +18 -0
  314. package/dist/web/primitives/money-input.js +122 -0
  315. package/dist/web/primitives/narrow-pinned-footer.d.ts +12 -0
  316. package/dist/web/primitives/narrow-pinned-footer.js +153 -0
  317. package/dist/web/primitives/number-input.d.ts +18 -0
  318. package/dist/web/primitives/number-input.js +53 -0
  319. package/dist/web/primitives/option-groups.d.ts +11 -0
  320. package/dist/web/primitives/option-groups.js +27 -0
  321. package/dist/web/primitives/page-header.d.ts +3 -0
  322. package/dist/web/primitives/page-header.js +52 -0
  323. package/dist/web/primitives/promo-panel.d.ts +3 -0
  324. package/dist/web/primitives/promo-panel.js +11 -0
  325. package/dist/web/primitives/segmented-select.d.ts +21 -0
  326. package/dist/web/primitives/segmented-select.js +62 -0
  327. package/dist/web/primitives/share-actions.d.ts +4 -0
  328. package/dist/web/primitives/share-actions.js +56 -0
  329. package/dist/web/primitives/status-badge.d.ts +3 -0
  330. package/dist/web/primitives/status-badge.js +5 -0
  331. package/dist/web/primitives/sticky-action-bar.d.ts +5 -0
  332. package/dist/web/primitives/sticky-action-bar.js +15 -0
  333. package/dist/web/primitives/tabs.d.ts +3 -0
  334. package/dist/web/primitives/tabs.js +107 -0
  335. package/dist/web/primitives/timestamp-input.d.ts +27 -0
  336. package/dist/web/primitives/timestamp-input.js +87 -0
  337. package/dist/web/primitives/toast.d.ts +19 -0
  338. package/dist/web/primitives/toast.js +63 -0
  339. package/dist/web/primitives/tz-input.d.ts +11 -0
  340. package/dist/web/primitives/tz-input.js +8 -0
  341. package/dist/web/primitives/tz-options.d.ts +4 -0
  342. package/dist/web/primitives/tz-options.js +17 -0
  343. package/dist/web/primitives/use-keyboard-inset.d.ts +1 -0
  344. package/dist/web/primitives/use-keyboard-inset.js +28 -0
  345. package/dist/web/primitives/use-narrow-viewport.d.ts +4 -0
  346. package/dist/web/primitives/use-narrow-viewport.js +46 -0
  347. package/dist/web/react-css-custom-properties.d.ts +6 -0
  348. package/dist/web/react-css-custom-properties.js +1 -0
  349. package/dist/web/sse/live-events.d.ts +15 -0
  350. package/dist/web/sse/live-events.js +148 -0
  351. package/dist/web/theme-plugin.d.ts +41 -0
  352. package/dist/web/theme-plugin.js +123 -0
  353. package/dist/web/tokens.d.ts +24 -0
  354. package/dist/web/tokens.js +203 -0
  355. package/dist/web/ui/avatar.d.ts +11 -0
  356. package/dist/web/ui/avatar.js +25 -0
  357. package/dist/web/ui/badge.d.ts +9 -0
  358. package/dist/web/ui/badge.js +26 -0
  359. package/dist/web/ui/breadcrumb.d.ts +11 -0
  360. package/dist/web/ui/breadcrumb.js +29 -0
  361. package/dist/web/ui/button.d.ts +10 -0
  362. package/dist/web/ui/button.js +37 -0
  363. package/dist/web/ui/checkbox.d.ts +4 -0
  364. package/dist/web/ui/checkbox.js +11 -0
  365. package/dist/web/ui/input.d.ts +3 -0
  366. package/dist/web/ui/input.js +8 -0
  367. package/dist/web/ui/label.d.ts +4 -0
  368. package/dist/web/ui/label.js +10 -0
  369. package/dist/web/ui/resizable.d.ts +7 -0
  370. package/dist/web/ui/resizable.js +16 -0
  371. package/dist/web/ui/separator.d.ts +4 -0
  372. package/dist/web/ui/separator.js +10 -0
  373. package/dist/web/ui/sheet.d.ts +14 -0
  374. package/dist/web/ui/sheet.js +42 -0
  375. package/dist/web/ui/sidebar.d.ts +73 -0
  376. package/dist/web/ui/sidebar.js +211 -0
  377. package/dist/web/ui/skeleton.d.ts +2 -0
  378. package/dist/web/ui/skeleton.js +7 -0
  379. package/dist/web/ui/switch.d.ts +6 -0
  380. package/dist/web/ui/switch.js +10 -0
  381. package/dist/web/ui/table.d.ts +10 -0
  382. package/dist/web/ui/table.js +30 -0
  383. package/dist/web/ui/tabs.d.ts +11 -0
  384. package/dist/web/ui/tabs.js +31 -0
  385. package/dist/web/ui/textarea.d.ts +3 -0
  386. package/dist/web/ui/textarea.js +8 -0
  387. package/dist/web/ui/tooltip.d.ts +7 -0
  388. package/dist/web/ui/tooltip.js +19 -0
  389. package/dist/web/ui/use-mobile.d.ts +1 -0
  390. package/dist/web/ui/use-mobile.js +16 -0
  391. package/dist/web/version/update-checker.d.ts +9 -0
  392. package/dist/web/version/update-checker.js +88 -0
  393. package/dist/web/widgets/ai-text-field.d.ts +36 -0
  394. package/dist/web/widgets/ai-text-field.js +137 -0
  395. package/dist/web/widgets/chart-window.d.ts +58 -0
  396. package/dist/web/widgets/chart-window.js +190 -0
  397. package/dist/web/widgets/charts.d.ts +208 -0
  398. package/dist/web/widgets/charts.js +685 -0
  399. package/dist/web/widgets/collapsible-section.d.ts +10 -0
  400. package/dist/web/widgets/collapsible-section.js +17 -0
  401. package/dist/web/widgets/dashboard-list.d.ts +21 -0
  402. package/dist/web/widgets/dashboard-list.js +45 -0
  403. package/dist/web/widgets/detail-list.d.ts +15 -0
  404. package/dist/web/widgets/detail-list.js +8 -0
  405. package/dist/web/widgets/drawer.d.ts +65 -0
  406. package/dist/web/widgets/drawer.js +142 -0
  407. package/dist/web/widgets/feed-list.d.ts +13 -0
  408. package/dist/web/widgets/feed-list.js +9 -0
  409. package/dist/web/widgets/floating-panel.d.ts +30 -0
  410. package/dist/web/widgets/floating-panel.js +242 -0
  411. package/dist/web/widgets/form-fields.d.ts +93 -0
  412. package/dist/web/widgets/form-fields.js +50 -0
  413. package/dist/web/widgets/index.d.ts +27 -0
  414. package/dist/web/widgets/index.js +30 -0
  415. package/dist/web/widgets/infinity-list.d.ts +46 -0
  416. package/dist/web/widgets/infinity-list.js +211 -0
  417. package/dist/web/widgets/mode-switch.d.ts +21 -0
  418. package/dist/web/widgets/mode-switch.js +25 -0
  419. package/dist/web/widgets/photo-slots.d.ts +29 -0
  420. package/dist/web/widgets/photo-slots.js +52 -0
  421. package/dist/web/widgets/plan-card.d.ts +36 -0
  422. package/dist/web/widgets/plan-card.js +13 -0
  423. package/dist/web/widgets/progress-bar.d.ts +19 -0
  424. package/dist/web/widgets/progress-bar.js +24 -0
  425. package/dist/web/widgets/progress-list.d.ts +19 -0
  426. package/dist/web/widgets/progress-list.js +11 -0
  427. package/dist/web/widgets/public-shell.d.ts +22 -0
  428. package/dist/web/widgets/public-shell.js +15 -0
  429. package/dist/web/widgets/query-table.d.ts +29 -0
  430. package/dist/web/widgets/query-table.js +30 -0
  431. package/dist/web/widgets/result-panel.d.ts +69 -0
  432. package/dist/web/widgets/result-panel.js +30 -0
  433. package/dist/web/widgets/section-card.d.ts +12 -0
  434. package/dist/web/widgets/section-card.js +9 -0
  435. package/dist/web/widgets/sheet-parts.d.ts +9 -0
  436. package/dist/web/widgets/sheet-parts.js +11 -0
  437. package/dist/web/widgets/side-by-side-table.d.ts +31 -0
  438. package/dist/web/widgets/side-by-side-table.js +11 -0
  439. package/dist/web/widgets/stat.d.ts +54 -0
  440. package/dist/web/widgets/stat.js +65 -0
  441. package/dist/web/widgets/states.d.ts +23 -0
  442. package/dist/web/widgets/states.js +20 -0
  443. package/dist/web/widgets/status-badge.d.ts +15 -0
  444. package/dist/web/widgets/status-badge.js +24 -0
  445. package/dist/web/widgets/step-bar.d.ts +35 -0
  446. package/dist/web/widgets/step-bar.js +60 -0
  447. package/dist/web/widgets/upload-zone.d.ts +24 -0
  448. package/dist/web/widgets/upload-zone.js +108 -0
  449. package/dist/web/widgets/use-draft.d.ts +20 -0
  450. package/dist/web/widgets/use-draft.js +21 -0
  451. package/dist/web/widgets/use-pointer-drag.d.ts +21 -0
  452. package/dist/web/widgets/use-pointer-drag.js +53 -0
  453. package/package.json +105 -7
  454. package/src/changes.json +77 -0
  455. package/src/web/fonts/OFL.txt +93 -0
  456. package/src/web/fonts/ibm-plex-mono-latin-400.woff2 +0 -0
  457. package/src/web/fonts/ibm-plex-mono-latin-500.woff2 +0 -0
  458. package/src/web/fonts/ibm-plex-mono-latin-ext-400.woff2 +0 -0
  459. package/src/web/fonts/ibm-plex-mono-latin-ext-500.woff2 +0 -0
  460. package/src/web/fonts/ibm-plex-sans-latin-400.woff2 +0 -0
  461. package/src/web/fonts/ibm-plex-sans-latin-500.woff2 +0 -0
  462. package/src/web/fonts/ibm-plex-sans-latin-600.woff2 +0 -0
  463. package/src/web/fonts/ibm-plex-sans-latin-ext-400.woff2 +0 -0
  464. package/src/web/fonts/ibm-plex-sans-latin-ext-500.woff2 +0 -0
  465. package/src/web/fonts/ibm-plex-sans-latin-ext-600.woff2 +0 -0
  466. package/src/web/styles.css +401 -0
@@ -1,15 +1,17 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { isFormatSpec, SYSTEM_REFERENCE_LABELS, } from "@cosmicdrift/kumiko-framework/ui-types";
3
- import { applyFormatSpec, currencyDecimals, EMPTY_VALUE_PLACEHOLDER, } from "@cosmicdrift/kumiko-headless";
4
3
  import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
5
- import { createContext, Fragment, useCallback, useContext, useMemo, useState, } from "react";
4
+ import { createContext, Fragment, useCallback, useContext, useEffect, useMemo, useRef, useState, } from "react";
6
5
  import { useAppFeatures } from "../app/app-features-context.js";
7
6
  import { useColumnRenderer } from "../app/column-renderers.js";
8
7
  import { extensionSectionName } from "../app/extension-sections.js";
9
8
  import { toKebab } from "../app/qn.js";
10
9
  import { useReferenceLinkResolver } from "../app/reference-link.js";
10
+ import { evalRowExtractor } from "../app/row-actions.js";
11
11
  import { screenAccessAllows } from "../app/screen-access.js";
12
+ import { useOptionalDispatcher } from "../context/dispatcher-context.js";
12
13
  import { useUserRoles } from "../context/user-roles-context.js";
14
+ import { applyFormatSpec, currencyDecimals, EMPTY_VALUE_PLACEHOLDER, } from "../headless/index.js";
13
15
  import { REFERENCE_COMBOBOX_LIMIT } from "../hooks/reference-limits.js";
14
16
  import { useQuery } from "../hooks/use-query.js";
15
17
  import { referenceOptionSource } from "../hooks/use-reference-lookup.js";
@@ -23,7 +25,7 @@ import { QueryOptionsSelect } from "./query-options-select.js";
23
25
  import { ReferenceCreateDialog } from "./reference-create-dialog.js";
24
26
  const AllFieldsRequiredContext = createContext(false);
25
27
  export const AllFieldsRequiredProvider = AllFieldsRequiredContext.Provider;
26
- export function RenderField({ field, issues, onChange, featureName, labelAppendix, fieldAppendix, appendixPlacement, description, status, allIssues, valueDisplay = "form", row, changed, layout, accent, }) {
28
+ export function RenderField({ field, issues, onChange, featureName, labelAppendix, fieldAppendix, appendixPlacement, description, status, allIssues, valueDisplay = "form", row, changed, layout, accent, fillDisabled, }) {
27
29
  const { Field, Input, Banner, Text, JsonView } = usePrimitives();
28
30
  // App-Locale (i18n) für money/date-Inputs — sonst fielen sie auf
29
31
  // navigator.language (Browser-Sprache) zurück statt der gewählten
@@ -48,7 +50,20 @@ export function RenderField({ field, issues, onChange, featureName, labelAppendi
48
50
  // Komponente gemountet werden (nicht als pure render-Call).
49
51
  const control = field.readOnly && field.renderer !== undefined ? (_jsx(FieldRendererOutput, { field: field, renderer: field.renderer, appLocale: appLocale, ...(row !== undefined && { row }) })) : field.type === "embedded" && field.embeddedListCells !== undefined ? (_jsx(EmbeddedListField, { field: field, id: id, onChange: onChange, allIssues: allIssues ?? {}, featureName: featureName ?? "" })) : field.type === "reference" ? (readOnlyText ? (_jsx(ReadOnlyReferenceValue, { field: field, featureName: featureName ?? "" })) : (_jsx(ReferenceInput, { field: field, id: id, hasError: hasError, onChange: onChange, Input: Input, featureName: featureName ?? "" }))) : field.type === "select" && field.selectOptionsQuery !== undefined && !readOnlyText ? (_jsx(QueryOptionsSelect, { field: field, query: field.selectOptionsQuery.query, payload: field.selectOptionsQuery.payload, row: row ?? { [field.field]: field.value }, id: id, hasError: hasError, onChange: onChange })) : field.type === "select" &&
50
52
  field.selectOptionsAvailabilityQuery !== undefined &&
51
- !readOnlyText ? (_jsx(AvailabilityOptionsSelect, { field: field, query: field.selectOptionsAvailabilityQuery.query, payload: field.selectOptionsAvailabilityQuery.payload, row: row ?? { [field.field]: field.value }, id: id, hasError: hasError, onChange: onChange })) : readOnlyText && !isComplexFieldType(field.type) && !isFileFieldType(field.type) ? (_jsx(Text, { testId: `field-value-${field.field}`, children: readOnlyDisplayText(field, appLocale, t) })) : field.type === "text" && field.writeOnly === true && !readOnlyText ? (_jsx(WriteOnlyTextInput, { field: field, id: id, hasError: hasError, onChange: onChange, changed: changed === true })) : (renderInput({
53
+ !readOnlyText ? (_jsx(AvailabilityOptionsSelect, { field: field, query: field.selectOptionsAvailabilityQuery.query, payload: field.selectOptionsAvailabilityQuery.payload, row: row ?? { [field.field]: field.value }, id: id, hasError: hasError, onChange: onChange })) : readOnlyText && !isComplexFieldType(field.type) && !isFileFieldType(field.type) ? (_jsx(Text, { testId: `field-value-${field.field}`, children: readOnlyDisplayText(field, appLocale, t) })) : field.type === "text" && field.writeOnly === true && !readOnlyText ? (_jsx(WriteOnlyTextInput, { field: field, id: id, hasError: hasError, onChange: onChange, changed: changed === true })) : field.fillAction !== undefined ? (_jsx(FillActionControl, { field: field, fillAction: field.fillAction, row: row ?? { [field.field]: field.value }, onChange: onChange, disabled: fillDisabled === true, Banner: Banner, renderControl: (fill) => renderInput({
54
+ field,
55
+ id,
56
+ hasError,
57
+ onChange,
58
+ Input,
59
+ appLocale,
60
+ Banner,
61
+ Text,
62
+ JsonView,
63
+ t,
64
+ row,
65
+ fill,
66
+ }) })) : (renderInput({
52
67
  field,
53
68
  id,
54
69
  hasError,
@@ -63,6 +78,63 @@ export function RenderField({ field, issues, onChange, featureName, labelAppendi
63
78
  }));
64
79
  return (_jsx(Field, { id: id, label: field.label, required: field.required && field.type !== "boolean" && !allFieldsRequired, ...(issues !== undefined && { issues }), ...(labelAppendix !== undefined && { labelAppendix }), ...(fieldAppendix !== undefined && { fieldAppendix }), ...(appendixPlacement !== undefined && { appendixPlacement }), ...(description !== undefined && { description }), ...(status !== undefined && { status }), ...(changed === true && { changed }), ...(layout !== undefined && { layout }), ...(accent === true && { accent }), testId: `field-${field.field}`, children: control }));
65
80
  }
81
+ const NUMERIC_FIELD_TYPES = new Set(["number", "bigInt", "decimal"]);
82
+ // A text field takes a number as its string form; a numeric field takes only a
83
+ // number — a string would be sent as a wrong-typed value.
84
+ function fillValueFor(fieldType, result) {
85
+ if (NUMERIC_FIELD_TYPES.has(fieldType))
86
+ return typeof result === "number" ? result : undefined;
87
+ if (typeof result === "string")
88
+ return result;
89
+ return typeof result === "number" ? String(result) : undefined;
90
+ }
91
+ // Owns the query state of a fill button. A separate component because RenderField
92
+ // returns early for hidden fields and the dispatcher hook must not be conditional.
93
+ function FillActionControl({ field, fillAction, row, onChange, disabled, Banner, renderControl, }) {
94
+ const dispatcher = useOptionalDispatcher();
95
+ const t = useTranslation();
96
+ const [busy, setBusy] = useState(false);
97
+ const [failed, setFailed] = useState(false);
98
+ // A newer click or an unmount cancels the in-flight query so a late answer
99
+ // cannot overwrite a value the user has since typed.
100
+ const activeRun = useRef(null);
101
+ useEffect(() => () => activeRun.current?.abort(), []);
102
+ const run = async () => {
103
+ if (dispatcher === undefined) {
104
+ setFailed(true);
105
+ return;
106
+ }
107
+ activeRun.current?.abort();
108
+ const ctrl = new AbortController();
109
+ activeRun.current = ctrl;
110
+ setBusy(true);
111
+ setFailed(false);
112
+ const payload = fillAction.params === undefined ? {} : evalRowExtractor(fillAction.params, row);
113
+ const result = await dispatcher.query(fillAction.query, payload, {
114
+ signal: ctrl.signal,
115
+ });
116
+ if (ctrl.signal.aborted)
117
+ return;
118
+ setBusy(false);
119
+ if (!result.isSuccess) {
120
+ setFailed(true);
121
+ return;
122
+ }
123
+ const value = fillValueFor(field.type, propertyOf(result.data, fillAction.field));
124
+ if (value === undefined) {
125
+ setFailed(true);
126
+ return;
127
+ }
128
+ onChange(value);
129
+ };
130
+ return (_jsxs(_Fragment, { children: [renderControl({
131
+ label: fillAction.label,
132
+ onPress: () => void run(),
133
+ busy,
134
+ disabled,
135
+ testId: `field-fill-${field.field}`,
136
+ }), failed && (_jsx(Banner, { variant: "error", testId: `field-fill-error-${field.field}`, children: t("kumiko.field.fillAction.failed") }))] }));
137
+ }
66
138
  // Wire value of a writeOnly field is `true` (set) / `null` (empty), never the
67
139
  // secret. Typing sets a new value; an untouched or emptied input submits
68
140
  // nothing / "" which the server reads as "keep". `null` after a loaded `true`
@@ -330,7 +402,7 @@ function resolveFieldIcon(field) {
330
402
  // SelectFieldDef.options). Structural types without a widget (embedded,
331
403
  // jsonb, multiSelect) render read-only instead of a data-destroying text
332
404
  // fallback (#1834); truly unknown scalar types still fall back to text.
333
- function renderInput({ field, id, hasError, onChange, Input, appLocale, Banner, Text, JsonView, t, row, }) {
405
+ function renderInput({ field, id, hasError, onChange, Input, appLocale, Banner, Text, JsonView, t, row, fill, }) {
334
406
  const common = {
335
407
  id,
336
408
  name: field.field,
@@ -338,6 +410,14 @@ function renderInput({ field, id, hasError, onChange, Input, appLocale, Banner,
338
410
  required: field.required,
339
411
  hasError,
340
412
  };
413
+ const placeholderProp = field.placeholder !== undefined ? { placeholder: field.placeholder } : {};
414
+ const prefix = resolveAffix(field.prefix, row);
415
+ const suffix = resolveAffix(field.suffix, row);
416
+ const affixProps = {
417
+ ...(prefix !== undefined && { prefix }),
418
+ ...(suffix !== undefined && { suffix }),
419
+ ...(fill !== undefined && { fill }),
420
+ };
341
421
  switch (field.type) {
342
422
  // decimal/bigInt are both plain numbers on the wire (fieldToZod:
343
423
  // z.number() / z.number().int().safe()) — the number input's onChange
@@ -347,11 +427,11 @@ function renderInput({ field, id, hasError, onChange, Input, appLocale, Banner,
347
427
  case "bigInt": {
348
428
  const icon = resolveFieldIcon(field);
349
429
  const unit = resolveNumberUnit(field.unit, row);
350
- return (_jsx(Input, { kind: "number", ...common, value: numberValue(field.value), onChange: (v) => onChange(v), locale: appLocale, ...(field.grouping !== undefined && { grouping: field.grouping }), ...(field.type === "bigInt" && { integer: true }), ...(icon !== undefined && { icon }), ...(unit !== undefined && { unit }) }));
430
+ return (_jsx(Input, { kind: "number", ...common, value: numberValue(field.value), onChange: (v) => onChange(v), locale: appLocale, ...(field.grouping !== undefined && { grouping: field.grouping }), ...(field.type === "bigInt" && { integer: true }), ...placeholderProp, ...affixProps, ...(icon !== undefined && { icon }), ...(unit !== undefined && { unit }) }));
351
431
  }
352
432
  case "decimal": {
353
433
  const icon = resolveFieldIcon(field);
354
- return (_jsx(Input, { kind: "number", ...common, value: numberValue(field.value), onChange: (v) => onChange(v), locale: appLocale, ...(field.grouping !== undefined && { grouping: field.grouping }), ...(icon !== undefined && { icon }) }));
434
+ return (_jsx(Input, { kind: "number", ...common, value: numberValue(field.value), onChange: (v) => onChange(v), locale: appLocale, ...(field.grouping !== undefined && { grouping: field.grouping }), ...placeholderProp, ...affixProps, ...(icon !== undefined && { icon }) }));
355
435
  }
356
436
  case "tz":
357
437
  return (_jsx(Input, { kind: "tz", ...common, value: stringValue(field.value), onChange: (v) => onChange(v) }));
@@ -414,6 +494,9 @@ function renderInput({ field, id, hasError, onChange, Input, appLocale, Banner,
414
494
  if (field.type === "text" && field.format === "password") {
415
495
  return (_jsx(Input, { kind: "password", ...common, value: stringValue(field.value), onChange: (v) => onChange(v) }));
416
496
  }
497
+ if (field.type === "text" && field.format === "month") {
498
+ return (_jsx(Input, { kind: "month", ...common, value: stringValue(field.value), onChange: (v) => onChange(v) }));
499
+ }
417
500
  // text + unknown scalar type → text input. If TextFieldDef.multiline
418
501
  // is set (the view-model carries it), the renderer switches to
419
502
  // textarea. longText always renders a textarea — that's the point of
@@ -421,10 +504,10 @@ function renderInput({ field, id, hasError, onChange, Input, appLocale, Banner,
421
504
  // only supplies an optional `{ rows }` override for it (#1925).
422
505
  if (field.type === "longText" || (field.type === "text" && field.multiline)) {
423
506
  const rows = typeof field.multiline === "object" ? field.multiline.rows : undefined;
424
- return (_jsx(Input, { kind: "textarea", ...common, value: stringValue(field.value), onChange: (v) => onChange(v), ...(rows !== undefined && { rows }) }));
507
+ return (_jsx(Input, { kind: "textarea", ...common, value: stringValue(field.value), onChange: (v) => onChange(v), ...(rows !== undefined && { rows }), ...placeholderProp }));
425
508
  }
426
509
  const icon = resolveFieldIcon(field);
427
- return (_jsx(Input, { kind: "text", ...common, value: stringValue(field.value), onChange: (v) => onChange(v), ...(icon !== undefined && { icon }), ...(field.autoComplete !== undefined && { autoComplete: field.autoComplete }) }));
510
+ return (_jsx(Input, { kind: "text", ...common, value: stringValue(field.value), onChange: (v) => onChange(v), ...placeholderProp, ...affixProps, ...(icon !== undefined && { icon }), ...(field.autoComplete !== undefined && { autoComplete: field.autoComplete }) }));
428
511
  }
429
512
  }
430
513
  }
@@ -479,6 +562,17 @@ function resolveNumberUnit(unit, row) {
479
562
  const trimmed = sibling.trim();
480
563
  return trimmed.length > 0 && trimmed.length <= MAX_SIBLING_UNIT_LENGTH ? trimmed : undefined;
481
564
  }
565
+ // String affix → used as-is (the view model already translated it). Field
566
+ // affix → the live form value; missing, empty or non-scalar means no affix.
567
+ function resolveAffix(affix, row) {
568
+ if (affix === undefined)
569
+ return undefined;
570
+ if (typeof affix === "string")
571
+ return affix;
572
+ const value = row?.[affix.field];
573
+ const text = typeof value === "number" ? String(value) : value;
574
+ return typeof text === "string" && text.trim().length > 0 ? text.trim() : undefined;
575
+ }
482
576
  function stringArrayOf(value) {
483
577
  return Array.isArray(value)
484
578
  ? value.filter((item) => typeof item === "string")
@@ -1,6 +1,6 @@
1
1
  import type { EntityDefinition, EntityListScreenDefinition, IconKey } from "@cosmicdrift/kumiko-framework/ui-types";
2
- import type { ListRowViewModel, Translate } from "@cosmicdrift/kumiko-headless";
3
2
  import { type ReactNode } from "react";
3
+ import type { ListRowViewModel, Translate } from "../headless/index.ts";
4
4
  import type { ListSort } from "../hooks/use-list-url-state.ts";
5
5
  import { type DataTableDateRangeFacet, type DataTableFacet, type DataTableProps, type DataTableRowAction, type DataTableRowActionMode, type DataTableRowGrouping } from "../primitives.tsx";
6
6
  import { type ListEmptyStateContent } from "./list-empty-state.tsx";
@@ -1,8 +1,8 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { computeListViewModel, EMPTY_VALUE_PLACEHOLDER } from "@cosmicdrift/kumiko-headless";
3
2
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
3
  import { extensionSectionName, useExtensionSectionComponent } from "../app/extension-sections.js";
5
4
  import { useReferenceLinkResolver } from "../app/reference-link.js";
5
+ import { computeListViewModel, EMPTY_VALUE_PLACEHOLDER } from "../headless/index.js";
6
6
  import { useReferenceLookup } from "../hooks/use-reference-lookup.js";
7
7
  import { useTranslation } from "../i18n.js";
8
8
  import { usePageHeaderCompact, usePageHeaderSlotAvailable } from "../page-header-slot.js";
@@ -104,7 +104,7 @@ export function RenderList(props) {
104
104
  const labelField = col.refLabelField ?? "id";
105
105
  const refEntity = col.refEntity ?? "";
106
106
  // Only a row the user can see is linkable: resolved by the server
107
- // eagerload or by the lookup query. A multi-reference stays text.
107
+ // eagerload or by the lookup query. A multi-reference links per entry via linkItemsFor.
108
108
  const linkFor = (value, row) => {
109
109
  if (value === null || value === undefined || value === "" || Array.isArray(value)) {
110
110
  return undefined;
@@ -118,6 +118,22 @@ export function RenderList(props) {
118
118
  map?.has(id);
119
119
  return resolved === true ? resolveReferenceLink(refEntity, id) : undefined;
120
120
  };
121
+ const linkItemsFor = (value, row) => {
122
+ if (!Array.isArray(value) || value.length === 0)
123
+ return undefined;
124
+ const eagerloaded = eagerloadedRef(row, col.field);
125
+ const eagerById = new Map(Array.isArray(eagerloaded) ? eagerloaded.map((r) => [String(r["id"]), r]) : []);
126
+ return value.map((v) => {
127
+ const id = String(v);
128
+ const target = eagerById.get(id);
129
+ const label = String(target?.[labelField] ?? map?.get(id) ?? id);
130
+ // Only a row the user can see is linkable: server eagerload or lookup query.
131
+ const link = target !== undefined || map?.has(id) === true
132
+ ? resolveReferenceLink(refEntity, id)
133
+ : undefined;
134
+ return { id, label, ...(link !== undefined && { link }) };
135
+ });
136
+ };
121
137
  const renderer = (value, row) => {
122
138
  // Tier 2.7e Server-Eagerload: wenn der Server _refs mit-
123
139
  // schickt, lesen wir den Display-Wert direkt aus der
@@ -142,7 +158,7 @@ export function RenderList(props) {
142
158
  const idStr = String(value);
143
159
  return map?.get(idStr) ?? idStr;
144
160
  };
145
- return { ...col, renderer, linkFor };
161
+ return { ...col, renderer, linkFor, linkItemsFor };
146
162
  });
147
163
  }, [vm.columns, referenceColumns, referenceLookups, resolveReferenceLink]);
148
164
  const enrichedVm = useMemo(() => ({
@@ -190,7 +206,7 @@ export function RenderList(props) {
190
206
  // Wenn weder onCreate noch ein Override gegeben ist, fällt die
191
207
  // DataTable auf den DataTable-Default zurück (`kumiko.list.no-entries`).
192
208
  const composedEmptyState = emptyState ??
193
- (onCreate !== undefined || emptyContent !== undefined || narrowed ? (_jsx(ListEmptyStateBody, { content: emptyContent, narrowed: narrowed, translate: translate, createButton: onCreate !== undefined && !narrowed ? (_jsx(Button, { variant: "primary", icon: "plus", onClick: onCreate, testId: "render-list-empty-create", children: effectiveCreateLabel })) : undefined })) : undefined);
209
+ (onCreate !== undefined || emptyContent !== undefined || narrowed ? (_jsx(ListEmptyStateBody, { content: emptyContent, narrowed: narrowed, translate: translate, createButton: onCreate !== undefined && !narrowed && !createInPageHeader ? (_jsx(Button, { variant: "primary", icon: "plus", onClick: onCreate, testId: "render-list-empty-create", children: effectiveCreateLabel })) : undefined })) : undefined);
194
210
  // Title-Resolution: ein konventioneller i18n-Key `screen:<id>.title`.
195
211
  // Wenn das Bundle den Key kennt, schöner Titel; sonst kommt der
196
212
  // screenId selber raus (kein "Untitled"-Fallback — der App-Dev sieht
@@ -1,5 +1,5 @@
1
- import type { EditWriteFormSectionViewModel, Translate } from "@cosmicdrift/kumiko-headless";
2
1
  import { type ReactNode } from "react";
2
+ import type { EditWriteFormSectionViewModel, Translate } from "../headless/index.ts";
3
3
  export type WriteFormSectionProps = {
4
4
  readonly section: EditWriteFormSectionViewModel;
5
5
  readonly featureName: string;
@@ -1,9 +1,9 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { I18N_KEY_PARAM } from "@cosmicdrift/kumiko-headless";
3
2
  import { useEffect, useMemo, useRef, useState } from "react";
4
3
  import * as z from "zod";
5
4
  import { isPresent, REQUIRED_FIELD_I18N_KEY } from "../app/form-schema.js";
6
5
  import { dispatcherErrorText } from "../app/write-failed-error.js";
6
+ import { I18N_KEY_PARAM } from "../headless/index.js";
7
7
  import { useForm } from "../hooks/use-form.js";
8
8
  import { useTranslation } from "../i18n.js";
9
9
  import { STICKY_PRIMARY_ACTION_PROP, usePrimitives } from "../primitives.js";
@@ -1,5 +1,5 @@
1
- import type { Dispatcher, DispatcherStatus } from "@cosmicdrift/kumiko-headless";
2
1
  import { type ReactNode } from "react";
2
+ import type { Dispatcher, DispatcherStatus } from "../headless/index.ts";
3
3
  import { type QueryPools } from "./query-pool.ts";
4
4
  export type DispatcherProviderProps = {
5
5
  readonly dispatcher: Dispatcher;
@@ -1,4 +1,4 @@
1
- import { toInstant } from "@cosmicdrift/kumiko-headless";
1
+ import { toInstant } from "./headless/index.js";
2
2
  // Shared timestamp formatter for operator screens (audit log, job runs) —
3
3
  // falls back to the raw ISO string on an unparseable value instead of "Invalid Date".
4
4
  export function formatWhen(value) {
@@ -0,0 +1,3 @@
1
+ export declare const APEX_NAV_MENU_CSS = "\n .nav-toggle { display: none; }\n .nav-toggle__trigger { display: none; align-items: center; justify-content: center;\n width: 2.25rem; height: 2.25rem; border-radius: 0.5rem; cursor: pointer;\n color: var(--fg-muted); list-style: none; }\n .nav-toggle__trigger::-webkit-details-marker { display: none; }\n .nav-toggle__trigger:hover { color: var(--fg); background: var(--bg-muted); }\n .nav-toggle__trigger svg { width: 1.35rem; height: 1.35rem; }\n .apex-dark .nav-toggle__trigger { color: var(--on-dark-muted); }\n .apex-dark .nav-toggle__trigger:hover { color: var(--on-dark); background: rgba(255,255,255,0.08); }\n\n .nav-menu { position: relative; display: inline-flex; }\n .nav-menu__trigger { display: inline-flex; align-items: center; gap: 0.3rem;\n font: inherit; font-size: 0.9375rem; color: var(--fg-muted);\n cursor: pointer; list-style: none; }\n .nav-menu__trigger::-webkit-details-marker { display: none; }\n .nav-menu__trigger:hover { color: var(--fg); }\n .nav-menu__chev { display: inline-flex; transition: transform 0.15s; opacity: 0.7; }\n .nav-menu__chev svg { width: 0.95em; height: 0.95em; }\n .nav-menu[open] .nav-menu__chev { transform: rotate(180deg); }\n .nav-menu__panel { position: absolute; top: calc(100% + 0.5rem); left: 0; z-index: 20;\n min-width: 21rem; padding: 0.5rem; background: var(--bg-card); color: var(--fg);\n border: 1px solid var(--border); border-radius: 0.75rem; box-shadow: var(--shadow);\n flex-direction: column; gap: 0.125rem; }\n .nav-menu[open] .nav-menu__panel { display: flex; }\n .nav-menu__item { display: flex; gap: 0.75rem; align-items: flex-start;\n padding: 0.6rem 0.7rem; border-radius: 0.5rem; color: var(--fg); }\n .nav-menu__item:hover { background: var(--bg-muted); color: var(--fg); }\n .nav-menu__icon { flex: none; display: inline-flex; align-items: center; justify-content: center;\n width: 2rem; height: 2rem; border-radius: 0.5rem;\n background: color-mix(in srgb, var(--primary) 12%, transparent); color: var(--primary); }\n .nav-menu__icon svg { width: 1.1rem; height: 1.1rem; }\n .nav-menu__text { display: flex; flex-direction: column; gap: 0.1rem; }\n .nav-menu__title { font-weight: 600; font-size: 0.9rem; line-height: 1.2; }\n .nav-menu__desc { font-size: 0.8rem; color: var(--fg-muted); line-height: 1.35; }\n .nav-menu__sep { height: 1px; background: var(--border); margin: 0.375rem 0.3rem; }\n .nav-menu__more { display: inline-flex; padding: 0.4rem 0.7rem; font-size: 0.85rem; font-weight: 600; color: var(--primary); }\n .nav-menu__more:hover { color: var(--primary-hover); }\n .apex-dark .nav-menu__trigger { color: var(--on-dark-muted); }\n .apex-dark .nav-menu__trigger:hover { color: var(--on-dark); }\n /* The panel is a light popover; its links must NOT inherit the dark-chrome\n nav-link color (.apex-dark .nav-links a, specificity 0,2,1) or a consumer's\n own .nav-links a. Pin both link types with matching specificity (0,2,1) so\n titles + the footer link stay readable in the dark theme. */\n .nav-menu__panel a.nav-menu__item { color: var(--fg); }\n .nav-menu__panel a.nav-menu__item:hover { color: var(--fg); }\n .nav-menu__panel a.nav-menu__more { color: var(--primary); }\n .nav-menu__panel a.nav-menu__more:hover { color: var(--primary-hover); }\n";
2
+ export declare const APEX_NAV_TOGGLE_RESPONSIVE_CSS = "\n @media (max-width: 640px) {\n .nav { position: relative; }\n .nav .nav-toggle { display: inline-flex; }\n .nav .nav-toggle__trigger { display: inline-flex; }\n .nav-toggle[open] > .nav-links { display: flex; flex-direction: column; align-items: stretch;\n gap: 0.25rem; position: absolute; top: calc(100% + 0.5rem); left: 0; right: 0; z-index: 30;\n background: var(--bg-card); border: 1px solid var(--border); border-radius: 0.75rem;\n box-shadow: var(--shadow); padding: 0.75rem; }\n .nav-toggle[open] > .nav-links > a { color: var(--fg); padding: 0.4rem 0.25rem; }\n .nav-toggle[open] > .nav-links > a:hover { color: var(--fg); }\n .nav-toggle .nav-menu { display: block; }\n .nav-toggle .nav-menu__panel { position: static; min-width: 0;\n margin-top: 0.375rem; box-shadow: none; }\n }\n";
3
+ export declare const APEX_STRUCTURAL_CSS: string;
@@ -0,0 +1,298 @@
1
+ // Structural CSS for the Apex marketing surface — shared by every app's static
2
+ // landing. Brand TOKEN VALUES (:root custom properties) and @font-face stay
3
+ // app-side (passed as brand.tokensCss / brand.fontFaceCss); this file owns only
4
+ // the structure: layout, grids, cards, buttons, header/footer, responsive.
5
+ //
6
+ // Two themes ship together; the <body class> selects one (see renderApexPage):
7
+ // - light (default): chrome on light backgrounds — translucent blurred header.
8
+ // - .apex-dark: header/hero/final-cta/footer sit on --primary with --on-dark
9
+ // text + inverted buttons (the "dark sandwich"). Requires the app's tokens
10
+ // to define --on-dark / --on-dark-muted / --on-dark-border.
11
+ //
12
+ // Token divergence is absorbed by CSS fallbacks, not parameters:
13
+ // --font-mono unset → .price-amount stays in body font (var(--font-mono, inherit))
14
+ // --accent-hover unset → eyebrow falls back to --primary
15
+ // --hero-tagline-max unset → hero tagline runs full column (var(--hero-tagline-max, none))
16
+ // icon tint → color-mix derives a faint --primary wash, no hardcoded rgba
17
+ const BASE_LAYOUT = `
18
+ * { box-sizing: border-box; }
19
+ html, body { margin: 0; padding: 0; }
20
+ body {
21
+ font-family: var(--font-body, "Inter Variable", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif);
22
+ background: var(--bg); color: var(--fg); line-height: 1.6;
23
+ -webkit-font-smoothing: antialiased;
24
+ min-height: 100vh; display: flex; flex-direction: column;
25
+ }
26
+ main { flex: 1; }
27
+ a { color: var(--primary); text-decoration: none; }
28
+ a:hover { color: var(--primary-hover); }
29
+ h1, h2, h3 { letter-spacing: -0.02em; }
30
+ .container { max-width: 1120px; margin: 0 auto; padding: 0 1.5rem; width: 100%; }
31
+ .container-narrow { max-width: 760px; margin: 0 auto; padding: 0 1.5rem; width: 100%; }
32
+ .btn {
33
+ display: inline-block; padding: 0.6rem 1.15rem; border-radius: 0.5rem;
34
+ font-weight: 600; font-size: 0.9375rem; border: 1px solid transparent;
35
+ cursor: pointer; transition: background 0.15s, border-color 0.15s, transform 0.05s;
36
+ }
37
+ .btn:active { transform: translateY(1px); }
38
+ .btn-primary { background: var(--primary); color: var(--primary-fg); }
39
+ .btn-primary:hover { background: var(--primary-hover); color: var(--primary-fg); }
40
+ .btn-secondary { background: var(--bg-card); color: var(--fg); border-color: var(--border); }
41
+ .btn-secondary:hover { border-color: var(--fg-muted); color: var(--fg); }
42
+
43
+ section { padding: 5rem 0; }
44
+ .section-head { text-align: center; max-width: 640px; margin: 0 auto 3rem; }
45
+ .section-head h2 { font-size: clamp(1.6rem, 3.5vw, 2.25rem); margin: 0 0 0.75rem; }
46
+ .section-head p { color: var(--fg-muted); margin: 0; font-size: 1.0625rem; }
47
+ .eyebrow { display: inline-block; font-size: 0.8125rem; font-weight: 600; letter-spacing: 0.04em;
48
+ text-transform: uppercase; color: var(--accent-hover, var(--primary)); margin-bottom: 0.75rem; }
49
+ `;
50
+ const HERO = `
51
+ .hero { padding: 4.5rem 0 4rem; }
52
+ .hero-grid { display: grid; grid-template-columns: 1.05fr 1fr; gap: 3.5rem; align-items: center; }
53
+ .hero-pony { width: 88px; height: 88px; margin: 0 0 1.1rem; display: block; }
54
+ .hero h1 { font-size: clamp(2.1rem, 4.5vw, 3.4rem); line-height: 1.08; margin: 0 0 1.1rem; font-weight: 700; }
55
+ .hero .tagline { font-size: clamp(1.05rem, 1.6vw, 1.25rem); color: var(--fg-muted); margin: 0 0 1.75rem; max-width: var(--hero-tagline-max, none); }
56
+ .hero-cta { display: flex; gap: 0.75rem; flex-wrap: wrap; }
57
+ .hero-meta { margin-top: 1.5rem; font-size: 0.875rem; color: var(--fg-subtle); }
58
+ .hero-meta strong { color: var(--fg-muted); font-weight: 600; }
59
+ .shot-frame { border-radius: 0.75rem; border: 1px solid var(--border); background: var(--bg-card);
60
+ box-shadow: var(--shadow); overflow: hidden; cursor: zoom-in; }
61
+ .shot-bar { display: flex; gap: 0.4rem; padding: 0.6rem 0.85rem; border-bottom: 1px solid var(--border); background: var(--bg-muted); }
62
+ .shot-bar span { width: 0.65rem; height: 0.65rem; border-radius: 50%; background: var(--border); }
63
+ .shot-frame img { display: block; width: 100%; height: auto; }
64
+ .apex-lightbox { border: none; padding: 0; margin: auto; max-width: 95vw; max-height: 90vh;
65
+ background: transparent; overflow: visible; }
66
+ .apex-lightbox::backdrop { background: rgba(15, 23, 42, 0.72); }
67
+ .apex-lightbox__img { display: block; max-width: 90vw; max-height: 85vh; width: auto; height: auto;
68
+ border-radius: 0.75rem; border: 1px solid var(--border); box-shadow: var(--shadow); }
69
+ .apex-lightbox__close { position: fixed; top: 1rem; right: 1rem; z-index: 1; width: 2.5rem; height: 2.5rem;
70
+ border: 1px solid var(--border); border-radius: 0.5rem; background: var(--bg-card); color: var(--fg);
71
+ font-size: 1.5rem; line-height: 1; cursor: pointer; }
72
+ .apex-lightbox__close:hover { background: var(--bg-muted); }
73
+ .apex-lightbox__prev, .apex-lightbox__next { position: fixed; top: 50%; transform: translateY(-50%); z-index: 1;
74
+ width: 2.5rem; height: 2.5rem; border: 1px solid var(--border); border-radius: 0.5rem;
75
+ background: var(--bg-card); color: var(--fg); font-size: 1.75rem; line-height: 1; cursor: pointer; }
76
+ .apex-lightbox__prev { left: 1rem; }
77
+ .apex-lightbox__next { right: 1rem; }
78
+ .apex-lightbox__prev:hover, .apex-lightbox__next:hover { background: var(--bg-muted); }
79
+ .apex-lightbox__prev[hidden], .apex-lightbox__next[hidden] { display: none; }
80
+ .apex-dark .apex-lightbox__prev, .apex-dark .apex-lightbox__next { background: var(--on-dark); color: var(--primary); border-color: var(--on-dark-border); }
81
+ .apex-dark .apex-lightbox__close { background: var(--on-dark); color: var(--primary); border-color: var(--on-dark-border); }
82
+ `;
83
+ const FEATURES = `
84
+ .features { background: var(--bg-muted); }
85
+ .feature-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.5rem; }
86
+ .feature { background: var(--bg-card); border: 1px solid var(--border); border-radius: 0.75rem; padding: 1.75rem; }
87
+ .feature-icon { width: 2.5rem; height: 2.5rem; border-radius: 0.6rem; display: flex; align-items: center;
88
+ justify-content: center; background: color-mix(in srgb, var(--primary) 9%, transparent); color: var(--primary); margin-bottom: 1rem; }
89
+ .feature-icon svg { width: 1.35rem; height: 1.35rem; }
90
+ .feature h3 { font-size: 1.0625rem; margin: 0 0 0.4rem; }
91
+ .feature p { color: var(--fg-muted); margin: 0; font-size: 0.9375rem; }
92
+ `;
93
+ const PRICING = `
94
+ .price-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.25rem; align-items: stretch; }
95
+ .price-card { position: relative; background: var(--bg-card); border: 1px solid var(--border);
96
+ border-radius: 0.75rem; padding: 1.75rem 1.5rem; display: flex; flex-direction: column; gap: 0.85rem; }
97
+ .price-card--featured { border-color: var(--primary); box-shadow: var(--shadow); }
98
+ .price-badge { position: absolute; top: -0.7rem; left: 50%; transform: translateX(-50%);
99
+ background: var(--accent, var(--primary)); color: var(--accent-fg, var(--primary-fg)); font-size: 0.75rem; font-weight: 700;
100
+ padding: 0.2rem 0.7rem; border-radius: 999px; white-space: nowrap; }
101
+ .price-card h3 { margin: 0; font-size: 1.25rem; }
102
+ .price-tagline { margin: 0; color: var(--fg-subtle); font-size: 0.875rem; }
103
+ .price-amount { font-size: 2rem; font-weight: 700; font-family: var(--font-mono, inherit); }
104
+ .price-amount span { font-size: 0.9375rem; font-weight: 500; color: var(--fg-subtle); font-family: inherit; }
105
+ .price-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.55rem; flex: 1; }
106
+ .price-list li { position: relative; padding-left: 1.5rem; font-size: 0.9rem; color: var(--fg-muted); }
107
+ .price-list li::before { content: ""; position: absolute; left: 0; top: 0.45rem; width: 0.7rem; height: 0.4rem;
108
+ border-left: 2px solid var(--status-ok); border-bottom: 2px solid var(--status-ok); transform: rotate(-45deg); }
109
+ .price-list li.price-cap { font-weight: 600; color: var(--fg); }
110
+ .price-card .btn { text-align: center; margin-top: 0.5rem; }
111
+ `;
112
+ const TRUST = `
113
+ .trust-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 2rem; }
114
+ .trust-item h3 { font-size: 1.0625rem; margin: 0 0 0.4rem; }
115
+ .trust-item p { color: var(--fg-muted); margin: 0; font-size: 0.9375rem; }
116
+ `;
117
+ const FINAL_CTA = `
118
+ .final-cta { text-align: center; }
119
+ .final-cta-pony { width: 96px; height: auto; display: block; margin: 0 auto 1rem; }
120
+ .final-cta h2 { font-size: clamp(1.6rem, 3.5vw, 2.25rem); margin: 0 0 0.75rem; }
121
+ .final-cta p { color: var(--fg-muted); margin: 0 0 1.75rem; font-size: 1.0625rem; }
122
+ `;
123
+ // Light chrome (default). Header floats translucent over the page; footer is a
124
+ // muted band. Dark theme overrides both below.
125
+ const CHROME_LIGHT = `
126
+ header { position: sticky; top: 0; z-index: 10;
127
+ background: color-mix(in srgb, var(--bg) 85%, transparent);
128
+ backdrop-filter: saturate(140%) blur(8px);
129
+ border-bottom: 1px solid var(--border); }
130
+ .nav { position: relative; display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding-block: 0.85rem; }
131
+ .brand { display: flex; align-items: center; gap: 0.55rem; font-weight: 700; font-size: 1.125rem; color: var(--fg); }
132
+ .brand a { color: var(--fg); display: inline-flex; align-items: center; gap: 0.55rem; }
133
+ .brand a:hover { color: var(--fg); }
134
+ .brand img { width: 1.7rem; height: 1.7rem; border-radius: 0.4rem; }
135
+ .nav-links { display: flex; gap: 1.5rem; align-items: center; font-size: 0.9375rem; }
136
+ .nav-links a { color: var(--fg-muted); }
137
+ .nav-links a:hover { color: var(--fg); }
138
+ .nav-actions { display: flex; gap: 0.75rem; align-items: center; font-size: 0.9375rem; }
139
+ .nav-actions > a:not(.btn) { color: var(--fg-muted); }
140
+ .nav-actions > a:not(.btn):hover { color: var(--fg); }
141
+ @media (max-width: 640px) { .nav-links { display: none; } }
142
+
143
+ footer { border-top: 1px solid var(--border); color: var(--fg-subtle); padding: 3.5rem 0 2.5rem; font-size: 0.9rem; }
144
+ .footer-grid { display: grid; gap: 2rem; grid-template-columns: 1.6fr repeat(var(--footer-cols, 2), 1fr); }
145
+ .footer-brand { display: flex; align-items: center; gap: 0.55rem; font-weight: 700; color: var(--fg); margin-bottom: 0.75rem; }
146
+ .footer-brand img { width: 1.6rem; height: 1.6rem; border-radius: 0.4rem; }
147
+ .footer-tagline { color: var(--fg-subtle); max-width: 34ch; margin: 0; }
148
+ .footer-col h4 { font-size: 0.8125rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--fg-subtle); margin: 0 0 0.85rem; }
149
+ .footer-col a { display: block; color: var(--fg-muted); margin-bottom: 0.5rem; }
150
+ .footer-col a:hover { color: var(--fg); }
151
+ .footer-bottom { margin-top: 2.5rem; padding-top: 1.5rem; border-top: 1px solid var(--border);
152
+ display: flex; flex-wrap: wrap; justify-content: space-between; gap: 0.75rem; }
153
+ `;
154
+ // Dark sandwich: header/hero/final-cta/footer ride on --primary. Buttons invert
155
+ // (a --primary button on --primary would vanish) → light button, --primary text.
156
+ const CHROME_DARK = `
157
+ .apex-dark header { background: var(--primary); backdrop-filter: none; border-bottom: 1px solid var(--on-dark-border); }
158
+ .apex-dark .brand, .apex-dark .brand a, .apex-dark .brand a:hover { color: var(--on-dark); }
159
+ .apex-dark .nav-links a { color: var(--on-dark-muted); }
160
+ .apex-dark .nav-links a:hover { color: var(--on-dark); }
161
+ .apex-dark .nav-actions > a:not(.btn) { color: var(--on-dark-muted); }
162
+ .apex-dark .nav-actions > a:not(.btn):hover { color: var(--on-dark); }
163
+ .apex-dark header .btn-primary { background: var(--on-dark); color: var(--primary); }
164
+ .apex-dark header .btn-primary:hover { background: rgba(255,255,255,0.85); color: var(--primary-hover); }
165
+
166
+ .apex-dark .hero { background: var(--primary); color: var(--on-dark); }
167
+ .apex-dark .hero h1 { color: var(--on-dark); }
168
+ .apex-dark .hero .tagline { color: var(--on-dark-muted); }
169
+ .apex-dark .hero .btn-primary, .apex-dark .final-cta .btn-primary { background: var(--on-dark); color: var(--primary); }
170
+ .apex-dark .hero .btn-primary:hover, .apex-dark .final-cta .btn-primary:hover { background: rgba(255,255,255,0.85); color: var(--primary-hover); }
171
+ .apex-dark .hero .btn-secondary { background: rgba(255,255,255,0.08); color: var(--on-dark); border-color: rgba(255,255,255,0.30); }
172
+ .apex-dark .hero .btn-secondary:hover { background: rgba(255,255,255,0.16); color: var(--on-dark); border-color: rgba(255,255,255,0.5); }
173
+ .apex-dark .hero-meta { color: rgba(255,255,255,0.55); }
174
+ .apex-dark .hero-meta strong { color: var(--on-dark-muted); }
175
+
176
+ .apex-dark .final-cta { background: var(--primary); color: var(--on-dark); }
177
+ .apex-dark .final-cta h2 { color: var(--on-dark); }
178
+ .apex-dark .final-cta p { color: var(--on-dark-muted); }
179
+
180
+ .apex-dark footer { background: var(--primary); color: var(--on-dark-muted); border-top: none; }
181
+ .apex-dark .footer-brand { color: var(--on-dark); }
182
+ .apex-dark .footer-tagline { color: var(--on-dark-muted); }
183
+ .apex-dark .footer-col h4 { color: var(--on-dark-muted); }
184
+ .apex-dark .footer-col a { color: var(--on-dark-muted); }
185
+ .apex-dark .footer-col a:hover { color: var(--on-dark); }
186
+ .apex-dark .footer-bottom { border-top: 1px solid var(--on-dark-border); }
187
+ `;
188
+ const RESPONSIVE = `
189
+ @media (max-width: 900px) {
190
+ .hero-grid { grid-template-columns: 1fr; gap: 2.5rem; }
191
+ .feature-grid { grid-template-columns: repeat(2, 1fr); }
192
+ .price-grid { grid-template-columns: repeat(2, 1fr); }
193
+ .footer-grid { grid-template-columns: 1fr 1fr; }
194
+ }
195
+ @media (max-width: 640px) {
196
+ section { padding: 3.5rem 0; }
197
+ .feature-grid, .price-grid, .trust-grid, .footer-grid { grid-template-columns: 1fr; }
198
+ }
199
+ `;
200
+ // header renders navLinks TWICE (renderApexHeader): a plain always-visible
201
+ // .nav-links row, plus a copy nested in .nav-toggle for < 640px — a closed
202
+ // <details> can't be forced "always open" on desktop via CSS (Chrome keeps
203
+ // its content unpainted regardless of a child's own `display`), so two
204
+ // copies sidestep that instead of fighting it. Exported standalone so a
205
+ // consumer rendering its own header chrome can include just this. A
206
+ // consumer using renderApexHeader() outside renderApexPage also needs
207
+ // APEX_NAV_TOGGLE_RESPONSIVE_CSS below for the mobile toggle to open.
208
+ export const APEX_NAV_MENU_CSS = `
209
+ .nav-toggle { display: none; }
210
+ .nav-toggle__trigger { display: none; align-items: center; justify-content: center;
211
+ width: 2.25rem; height: 2.25rem; border-radius: 0.5rem; cursor: pointer;
212
+ color: var(--fg-muted); list-style: none; }
213
+ .nav-toggle__trigger::-webkit-details-marker { display: none; }
214
+ .nav-toggle__trigger:hover { color: var(--fg); background: var(--bg-muted); }
215
+ .nav-toggle__trigger svg { width: 1.35rem; height: 1.35rem; }
216
+ .apex-dark .nav-toggle__trigger { color: var(--on-dark-muted); }
217
+ .apex-dark .nav-toggle__trigger:hover { color: var(--on-dark); background: rgba(255,255,255,0.08); }
218
+
219
+ .nav-menu { position: relative; display: inline-flex; }
220
+ .nav-menu__trigger { display: inline-flex; align-items: center; gap: 0.3rem;
221
+ font: inherit; font-size: 0.9375rem; color: var(--fg-muted);
222
+ cursor: pointer; list-style: none; }
223
+ .nav-menu__trigger::-webkit-details-marker { display: none; }
224
+ .nav-menu__trigger:hover { color: var(--fg); }
225
+ .nav-menu__chev { display: inline-flex; transition: transform 0.15s; opacity: 0.7; }
226
+ .nav-menu__chev svg { width: 0.95em; height: 0.95em; }
227
+ .nav-menu[open] .nav-menu__chev { transform: rotate(180deg); }
228
+ .nav-menu__panel { position: absolute; top: calc(100% + 0.5rem); left: 0; z-index: 20;
229
+ min-width: 21rem; padding: 0.5rem; background: var(--bg-card); color: var(--fg);
230
+ border: 1px solid var(--border); border-radius: 0.75rem; box-shadow: var(--shadow);
231
+ flex-direction: column; gap: 0.125rem; }
232
+ .nav-menu[open] .nav-menu__panel { display: flex; }
233
+ .nav-menu__item { display: flex; gap: 0.75rem; align-items: flex-start;
234
+ padding: 0.6rem 0.7rem; border-radius: 0.5rem; color: var(--fg); }
235
+ .nav-menu__item:hover { background: var(--bg-muted); color: var(--fg); }
236
+ .nav-menu__icon { flex: none; display: inline-flex; align-items: center; justify-content: center;
237
+ width: 2rem; height: 2rem; border-radius: 0.5rem;
238
+ background: color-mix(in srgb, var(--primary) 12%, transparent); color: var(--primary); }
239
+ .nav-menu__icon svg { width: 1.1rem; height: 1.1rem; }
240
+ .nav-menu__text { display: flex; flex-direction: column; gap: 0.1rem; }
241
+ .nav-menu__title { font-weight: 600; font-size: 0.9rem; line-height: 1.2; }
242
+ .nav-menu__desc { font-size: 0.8rem; color: var(--fg-muted); line-height: 1.35; }
243
+ .nav-menu__sep { height: 1px; background: var(--border); margin: 0.375rem 0.3rem; }
244
+ .nav-menu__more { display: inline-flex; padding: 0.4rem 0.7rem; font-size: 0.85rem; font-weight: 600; color: var(--primary); }
245
+ .nav-menu__more:hover { color: var(--primary-hover); }
246
+ .apex-dark .nav-menu__trigger { color: var(--on-dark-muted); }
247
+ .apex-dark .nav-menu__trigger:hover { color: var(--on-dark); }
248
+ /* The panel is a light popover; its links must NOT inherit the dark-chrome
249
+ nav-link color (.apex-dark .nav-links a, specificity 0,2,1) or a consumer's
250
+ own .nav-links a. Pin both link types with matching specificity (0,2,1) so
251
+ titles + the footer link stay readable in the dark theme. */
252
+ .nav-menu__panel a.nav-menu__item { color: var(--fg); }
253
+ .nav-menu__panel a.nav-menu__item:hover { color: var(--fg); }
254
+ .nav-menu__panel a.nav-menu__more { color: var(--primary); }
255
+ .nav-menu__panel a.nav-menu__more:hover { color: var(--primary-hover); }
256
+ `;
257
+ // Standalone mobile-open rules for .nav-toggle, split out of RESPONSIVE so a
258
+ // consumer calling renderApexHeader() outside renderApexPage (money-horse,
259
+ // show-pony, the docs sample) can include just this alongside
260
+ // APEX_NAV_MENU_CSS, without pulling in the rest of RESPONSIVE's page-level
261
+ // grid breakpoints. The two "show" rules use the `.nav ` prefix (matching
262
+ // renderApexHeader's `<div class="container nav">` wrapper) to raise their
263
+ // specificity above APEX_NAV_MENU_CSS's bare `.nav-toggle`/`.nav-toggle__trigger`
264
+ // "display: none" defaults — that makes them win regardless of concatenation
265
+ // order, since a standalone consumer controls the order, not this file.
266
+ // Also declares `.nav { position: relative; }`: the open dropdown positions
267
+ // itself against .nav (`top/left/right` below), and renderApexPage's own
268
+ // CHROME_LIGHT already sets that, but a standalone consumer's own header CSS
269
+ // generally doesn't — without it the dropdown would anchor to the viewport
270
+ // instead of the header and open off-screen. Redeclaring it here is a no-op
271
+ // inside APEX_STRUCTURAL_CSS (same value, harmless if repeated).
272
+ export const APEX_NAV_TOGGLE_RESPONSIVE_CSS = `
273
+ @media (max-width: 640px) {
274
+ .nav { position: relative; }
275
+ .nav .nav-toggle { display: inline-flex; }
276
+ .nav .nav-toggle__trigger { display: inline-flex; }
277
+ .nav-toggle[open] > .nav-links { display: flex; flex-direction: column; align-items: stretch;
278
+ gap: 0.25rem; position: absolute; top: calc(100% + 0.5rem); left: 0; right: 0; z-index: 30;
279
+ background: var(--bg-card); border: 1px solid var(--border); border-radius: 0.75rem;
280
+ box-shadow: var(--shadow); padding: 0.75rem; }
281
+ .nav-toggle[open] > .nav-links > a { color: var(--fg); padding: 0.4rem 0.25rem; }
282
+ .nav-toggle[open] > .nav-links > a:hover { color: var(--fg); }
283
+ .nav-toggle .nav-menu { display: block; }
284
+ .nav-toggle .nav-menu__panel { position: static; min-width: 0;
285
+ margin-top: 0.375rem; box-shadow: none; }
286
+ }
287
+ `;
288
+ export const APEX_STRUCTURAL_CSS = BASE_LAYOUT +
289
+ HERO +
290
+ FEATURES +
291
+ PRICING +
292
+ TRUST +
293
+ FINAL_CTA +
294
+ CHROME_LIGHT +
295
+ CHROME_DARK +
296
+ APEX_NAV_MENU_CSS +
297
+ APEX_NAV_TOGGLE_RESPONSIVE_CSS +
298
+ RESPONSIVE;