@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
@@ -0,0 +1,10 @@
1
+ import { type ReactNode } from "react";
2
+ /** Aufklappbare Sektion (native <details>, aber React-kontrolliert: sonst
3
+ * springt das open-Attribut bei jedem Parent-Re-Render auf den Initialwert
4
+ * zurück). Eine Optik für alle „Erweitert"/„Mehr"-Abschnitte. */
5
+ export declare function CollapsibleSection({ title, defaultOpen, children, testId, }: {
6
+ readonly title: string;
7
+ readonly defaultOpen?: boolean;
8
+ readonly children: ReactNode;
9
+ readonly testId?: string;
10
+ }): ReactNode;
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ChevronRight } from "lucide-react";
3
+ import { useEffect, useState } from "react";
4
+ /** Aufklappbare Sektion (native <details>, aber React-kontrolliert: sonst
5
+ * springt das open-Attribut bei jedem Parent-Re-Render auf den Initialwert
6
+ * zurück). Eine Optik für alle „Erweitert"/„Mehr"-Abschnitte. */
7
+ export function CollapsibleSection({ title, defaultOpen = false, children, testId, }) {
8
+ const [open, setOpen] = useState(defaultOpen);
9
+ // useState(defaultOpen) liest nur den Mount-Wert — ein async hydrierter
10
+ // Draft flippt `defaultOpen` zu spät für den Lazy-Initializer. Open-only
11
+ // Effect: kämpft nie gegen ein manuelles Zuklappen des Users.
12
+ useEffect(() => {
13
+ if (defaultOpen)
14
+ setOpen(true);
15
+ }, [defaultOpen]);
16
+ return (_jsxs("details", { "data-testid": testId, className: "group rounded-lg border bg-card", open: open, onToggle: (e) => setOpen(e.currentTarget.open), children: [_jsxs("summary", { className: "flex cursor-pointer select-none items-center justify-between gap-2 rounded-lg px-4 py-2.5 text-sm font-medium hover:bg-muted/50 [&::-webkit-details-marker]:hidden", children: [_jsx("span", { children: title }), _jsx(ChevronRight, { "aria-hidden": "true", className: "size-4 text-muted-foreground transition-transform group-open:rotate-90" })] }), _jsx("div", { className: "border-t px-4 py-3", children: children })] }));
17
+ }
@@ -0,0 +1,21 @@
1
+ import type { ReactNode } from "react";
2
+ export type DashboardListColumn = {
3
+ readonly field: string;
4
+ /** Translated by the caller. */
5
+ readonly label: string;
6
+ readonly display?: "bar" | "badge" | "datetime";
7
+ readonly badgeToneField?: string;
8
+ };
9
+ type Row = Readonly<Record<string, unknown>>;
10
+ /** Compact 40px-row table for dashboard lists with optional bar/badge
11
+ * columns. */
12
+ export declare function DashboardListTable({ columns, rows, formatPercent, formatNumber, formatDateTime, translate, testId, }: {
13
+ readonly columns: readonly DashboardListColumn[];
14
+ readonly rows: readonly Row[];
15
+ readonly formatPercent: (fraction: number) => string;
16
+ readonly formatNumber: (value: number) => string;
17
+ readonly formatDateTime: (atMs: number) => string;
18
+ readonly translate: (key: string) => string;
19
+ readonly testId?: string;
20
+ }): ReactNode;
21
+ export {};
@@ -0,0 +1,45 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { EMPTY_VALUE_PLACEHOLDER } from "../../headless/index.js";
3
+ import { StatusBadge } from "./status-badge.js";
4
+ const BADGE_TONE = {
5
+ positive: "ok",
6
+ warn: "warn",
7
+ negative: "bad",
8
+ neutral: "muted",
9
+ };
10
+ function fractionOf(raw) {
11
+ return typeof raw === "number" && Number.isFinite(raw) ? Math.max(0, Math.min(1, raw)) : 0;
12
+ }
13
+ // Rows may carry i18n keys (quota labels, failure reasons); translate falls back to the key itself.
14
+ const I18N_KEY_SHAPE = /^[a-z][\w-]*(?:[:.][\w-]+)+$/i;
15
+ function Cell({ column, row, formatPercent, formatNumber, formatDateTime, translate, }) {
16
+ const raw = row[column.field];
17
+ if (column.display === "bar") {
18
+ const fraction = fractionOf(raw);
19
+ return (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { role: "progressbar", "aria-valuenow": Math.round(fraction * 100), "aria-valuemin": 0, "aria-valuemax": 100, "aria-label": column.label, className: "h-1 min-w-16 flex-1 overflow-hidden rounded-full bg-border", children: _jsx("div", { className: "h-full rounded-full bg-status-active", style: { width: `${fraction * 100}%` } }) }), _jsx("span", { className: "w-10 shrink-0 text-right tabular-nums text-muted-foreground", children: formatPercent(fraction) })] }));
20
+ }
21
+ if (column.display === "badge") {
22
+ if (raw === undefined || raw === null || raw === "")
23
+ return EMPTY_VALUE_PLACEHOLDER;
24
+ const toneKey = column.badgeToneField !== undefined ? row[column.badgeToneField] : undefined;
25
+ const tone = (typeof toneKey === "string" ? BADGE_TONE[toneKey] : undefined) ?? "muted";
26
+ return _jsx(StatusBadge, { tone: tone, children: String(raw) });
27
+ }
28
+ if (column.display === "datetime") {
29
+ const atMs = typeof raw === "number" ? raw : typeof raw === "string" ? Date.parse(raw) : Number.NaN;
30
+ if (!Number.isFinite(atMs))
31
+ return EMPTY_VALUE_PLACEHOLDER;
32
+ return _jsx("span", { className: "tabular-nums", children: formatDateTime(atMs) });
33
+ }
34
+ if (typeof raw === "number")
35
+ return _jsx("span", { className: "tabular-nums", children: formatNumber(raw) });
36
+ if (raw === undefined || raw === null || raw === "")
37
+ return EMPTY_VALUE_PLACEHOLDER;
38
+ const text = String(raw);
39
+ return I18N_KEY_SHAPE.test(text) ? translate(text) : text;
40
+ }
41
+ /** Compact 40px-row table for dashboard lists with optional bar/badge
42
+ * columns. */
43
+ export function DashboardListTable({ columns, rows, formatPercent, formatNumber, formatDateTime, translate, testId, }) {
44
+ return (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { "data-testid": testId, className: "w-full text-sm", children: [_jsx("thead", { children: _jsx("tr", { className: "h-9 border-b border-border-row text-left text-xs text-muted-foreground", children: columns.map((column) => (_jsx("th", { scope: "col", className: "px-4 font-medium", children: column.label }, column.field))) }) }), _jsx("tbody", { children: rows.map((row, index) => (_jsx("tr", { className: "h-10 border-b border-border-row last:border-b-0", children: columns.map((column) => (_jsx("td", { className: "px-4", children: _jsx(Cell, { column: column, row: row, formatPercent: formatPercent, formatNumber: formatNumber, formatDateTime: formatDateTime, translate: translate }) }, column.field))) }, typeof row["id"] === "string" ? row["id"] : index))) })] }) }));
45
+ }
@@ -0,0 +1,15 @@
1
+ import type { ReactNode } from "react";
2
+ /** Read-only Schlüssel-Wert-Liste für Detail-Masken (Label links gedimmt,
3
+ * Wert rechts). Wert ist ReactNode → Badges/Chips möglich. `emphasize` hebt
4
+ * eine Zeile hervor (z.B. das Endergebnis in einem Rechner). */
5
+ export declare function DetailList({ rows, testId, }: {
6
+ readonly rows: readonly {
7
+ /** Optional stable key -- falls back to the label when omitted (fine as
8
+ * long as labels are unique; set id when they can repeat). */
9
+ readonly id?: string;
10
+ readonly label: string;
11
+ readonly value: ReactNode;
12
+ readonly emphasize?: boolean;
13
+ }[];
14
+ readonly testId?: string;
15
+ }): ReactNode;
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../lib/cn.js";
3
+ /** Read-only Schlüssel-Wert-Liste für Detail-Masken (Label links gedimmt,
4
+ * Wert rechts). Wert ist ReactNode → Badges/Chips möglich. `emphasize` hebt
5
+ * eine Zeile hervor (z.B. das Endergebnis in einem Rechner). */
6
+ export function DetailList({ rows, testId, }) {
7
+ return (_jsx("dl", { "data-testid": testId, className: "@container flex flex-col divide-y", children: rows.map((row) => (_jsxs("div", { className: "grid grid-cols-1 gap-0.5 py-2.5 @md:grid-cols-[200px_1fr] @md:gap-4", children: [_jsx("dt", { className: cn("text-sm text-muted-foreground", row.emphasize === true && "font-semibold text-foreground"), children: row.label }), _jsx("dd", { className: cn("text-sm font-medium", row.emphasize === true && "font-semibold"), children: row.value })] }, row.id ?? row.label))) }));
8
+ }
@@ -0,0 +1,65 @@
1
+ import { type ReactNode } from "react";
2
+ export type DrawerProps = {
3
+ readonly open: boolean;
4
+ readonly onOpenChange: (open: boolean) => void;
5
+ readonly side?: "left" | "right" | "top" | "bottom";
6
+ readonly title?: ReactNode;
7
+ readonly description?: ReactNode;
8
+ readonly footer?: ReactNode;
9
+ readonly children: ReactNode;
10
+ readonly testId?: string;
11
+ /** Header close (X). Default `true`. Turn off when the caller's own
12
+ * footer already has a dedicated close/cancel action, so there is only
13
+ * one way to dismiss the drawer. */
14
+ readonly showCloseButton?: boolean;
15
+ /** Panel treatment. `"flush"` (default) docks the panel against the edge —
16
+ * full extent, no radius, and a border only on the edge facing the app
17
+ * content. `"floating"` gives the detached-panel look (margin to the
18
+ * viewport edge, full corner radius). Ignored in the narrow-viewport
19
+ * fullscreen layout. */
20
+ readonly variant?: "floating" | "flush";
21
+ /** `variant="flush"` only: dock the panel below the app's ShellHeader
22
+ * (offset top by `--shell-header-height`, height shrunk to match)
23
+ * instead of covering it. Default `false` is edge-to-edge. No ShellHeader
24
+ * mounted → the variable is `0`, so this is a no-op. Ignored in the
25
+ * narrow-viewport fullscreen layout, which already takes over the whole
26
+ * screen including the header. */
27
+ readonly belowHeader?: boolean;
28
+ /** Panel width for `side="left"|"right"` (ignored for top/bottom and in
29
+ * the narrow-viewport layout). A number is pixels, a string any CSS
30
+ * length. Superseded by `resize` when that's set. Default matches the
31
+ * panel's built-in `max(600px, 37.5vw)`. */
32
+ readonly width?: number | string;
33
+ /** Opt-in drag-to-resize + maximize toggle (left/right sides only). */
34
+ readonly resize?: {
35
+ readonly defaultWidthPx?: number;
36
+ readonly minWidthPx?: number;
37
+ readonly maxWidthPx?: number;
38
+ };
39
+ /** Backdrop behind the panel. A drawer exists so the content behind it
40
+ * stays readable, so the blur is off by default. Ignored when
41
+ * `modal={false}` — there is no overlay to style then. */
42
+ readonly backdrop?: {
43
+ readonly blurPx?: number;
44
+ readonly dimPercent?: number;
45
+ };
46
+ /** `false` runs the drawer as a panel beside the page instead of over it:
47
+ * no overlay, no focus trap, and the page behind stays interactive, so
48
+ * text there can be selected and copied. Escape closes in both modes, a
49
+ * click beside the panel only in the modal one — the non-modal case
50
+ * exists to be used next to the page content, where a stray click must
51
+ * not dismiss it. With `showCloseButton={false}` and no footer action
52
+ * that leaves Escape as the only way out, so keep one of the two.
53
+ * Default `true`. */
54
+ readonly modal?: boolean;
55
+ /** Extra classes for the panel, merged over the built-in ones. Not applied
56
+ * in the narrow-viewport fullscreen layout. */
57
+ readonly panelClassName?: string;
58
+ /** The body becomes a plain flex column with no padding or scrolling of its
59
+ * own, for children that bring their own scroll area and pinned footer. */
60
+ readonly fillBody?: boolean;
61
+ };
62
+ /** Slide-in panel beside a list (e.g. mail reader next to the inbox) —
63
+ * thin wrapper over the Sheet primitive with header/body/footer slots
64
+ * so screens skip per-route Radix boilerplate. */
65
+ export declare function Drawer({ open, onOpenChange, side, title, description, footer, children, testId, showCloseButton, variant, belowHeader, width, resize, backdrop, modal, panelClassName, fillBody, }: DrawerProps): ReactNode;
@@ -0,0 +1,142 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Maximize2Icon, Minimize2Icon } from "lucide-react";
3
+ import { useState } from "react";
4
+ import { useTranslation } from "../../index.js";
5
+ import { clamp } from "../lib/clamp.js";
6
+ import { cn } from "../lib/cn.js";
7
+ import { useIsNarrowViewport } from "../primitives/use-narrow-viewport.js";
8
+ import { Sheet, SheetDescription, SheetFooter, SheetHeader, SheetTitle } from "../ui/sheet.js";
9
+ import { DrawerSheetContent } from "./sheet-parts.js";
10
+ import { arrowKeyDelta, usePointerDrag } from "./use-pointer-drag.js";
11
+ // Mirrors primitives/index.tsx's cardFooter + cardFooterBorder (row layout,
12
+ // end-justified actions, top border, panel surface instead of the card
13
+ // footer's bg-muted/30) — those two consts aren't exported, so the
14
+ // equivalent classes are inlined here rather than growing the vendored
15
+ // SheetFooter's own default className with panel-specific styling.
16
+ const DRAWER_FOOTER_CLASS = "flex-row flex-wrap items-center justify-end border-t bg-background px-[var(--card-padding)] py-4";
17
+ const MIN_WIDTH_PX = 320;
18
+ const MAX_WIDTH_PX = 1000;
19
+ // Must match the static `max(600px,37.5vw)` in floatingSideClass — Tailwind's
20
+ // JIT can't read these at runtime, so the two are kept in sync by hand.
21
+ // 600px (vs. the old 520px floor) gives a two-column field row (e.g.
22
+ // street/number, zip/city) room to breathe, while staying well under half
23
+ // of a 1280px viewport so a narrow window isn't dominated by the drawer.
24
+ // 37.5vw (vs. the old 25vw) keeps that same 600px on a 1280–1600px window
25
+ // (25vw was still under the floor there, making the ratio dead weight up
26
+ // to 2400px) but actually grows past the floor by ~1600px, so a 1920px
27
+ // window — where the two-column row is cramped just as often — lands
28
+ // around 720px instead of being stuck at 600px alongside a 1280px window.
29
+ const DEFAULT_WIDTH_MIN_PX = 600;
30
+ const DEFAULT_WIDTH_VIEWPORT_RATIO = 0.375;
31
+ const DEFAULT_BLUR_PX = 0;
32
+ const DEFAULT_DIM_PERCENT = 20;
33
+ function defaultWidthFromViewport() {
34
+ return typeof window === "undefined"
35
+ ? DEFAULT_WIDTH_MIN_PX
36
+ : Math.max(DEFAULT_WIDTH_MIN_PX, Math.round(window.innerWidth * DEFAULT_WIDTH_VIEWPORT_RATIO));
37
+ }
38
+ function normalizeWidth(width) {
39
+ return typeof width === "number" ? `${width}px` : width;
40
+ }
41
+ // Must match the static `max(600px,37.5vw)` in sidePanelClass — Tailwind's
42
+ // JIT can't read DEFAULT_WIDTH_MIN_PX/DEFAULT_WIDTH_VIEWPORT_RATIO at
43
+ // runtime, so the two are kept in sync by hand.
44
+ // Same cap as WIDTH_CLASS: an explicit `width` must not outgrow the viewport.
45
+ const MAX_VIEWPORT_WIDTH = "85vw";
46
+ const WIDTH_CLASS = "w-[max(600px,37.5vw)] max-w-[85vw] sm:max-w-[max(600px,37.5vw)]";
47
+ // "floating": 32px margin + 32px radius so the panel reads as detached from
48
+ // the viewport edge, unlike the sheet primitive's flush-edge default.
49
+ // "flush" docks it back to that flush-edge default instead, borrowing the
50
+ // vendored Sheet's per-side border (only the edge facing the app content).
51
+ // Below the narrow-viewport breakpoint neither treatment fits — the JS hook
52
+ // decides, not a `sm:` Tailwind prefix, since the resize width below already
53
+ // comes from JS and two decision sources is the bug this avoids.
54
+ function sidePanelClass(side, narrow, variant, belowHeader) {
55
+ if (narrow)
56
+ return "inset-0 h-full w-full max-w-none rounded-none border-0 overflow-hidden";
57
+ if (variant === "flush") {
58
+ switch (side) {
59
+ // h-full would pin height to 100vh despite the shifted top; h-auto lets inset-y-0 size it.
60
+ case "left":
61
+ return `inset-y-0 left-0 ${belowHeader ? "h-auto" : "h-full"} ${WIDTH_CLASS} border-r shadow-2xl overflow-hidden`;
62
+ case "top":
63
+ return `inset-x-0 ${belowHeader ? "top-(--shell-header-height)" : "top-0"} h-auto max-h-[80vh] border-b shadow-2xl overflow-hidden`;
64
+ case "bottom":
65
+ return "inset-x-0 bottom-0 h-auto max-h-[80vh] border-t shadow-2xl overflow-hidden";
66
+ default:
67
+ return `inset-y-0 right-0 ${belowHeader ? "h-auto" : "h-full"} ${WIDTH_CLASS} border-l shadow-2xl overflow-hidden`;
68
+ }
69
+ }
70
+ switch (side) {
71
+ case "left":
72
+ return `inset-y-8 left-8 h-auto ${WIDTH_CLASS} rounded-[2rem] border shadow-2xl overflow-hidden`;
73
+ case "top":
74
+ return "inset-x-8 top-8 h-auto max-h-[80vh] rounded-[2rem] border shadow-2xl overflow-hidden";
75
+ case "bottom":
76
+ return "inset-x-8 bottom-8 h-auto max-h-[80vh] rounded-[2rem] border shadow-2xl overflow-hidden";
77
+ default:
78
+ return `inset-y-8 right-8 h-auto ${WIDTH_CLASS} rounded-[2rem] border shadow-2xl overflow-hidden`;
79
+ }
80
+ }
81
+ /** Slide-in panel beside a list (e.g. mail reader next to the inbox) —
82
+ * thin wrapper over the Sheet primitive with header/body/footer slots
83
+ * so screens skip per-route Radix boilerplate. */
84
+ export function Drawer({ open, onOpenChange, side = "right", title, description, footer, children, testId, showCloseButton = true, variant = "flush", belowHeader = false, width, resize, backdrop, modal = true, panelClassName, fillBody = false, }) {
85
+ const t = useTranslation();
86
+ const narrow = useIsNarrowViewport();
87
+ const canResize = resize !== undefined && (side === "left" || side === "right");
88
+ const customWidthStyle = !canResize && !narrow && (side === "left" || side === "right") && width !== undefined
89
+ ? { width: normalizeWidth(width), maxWidth: MAX_VIEWPORT_WIDTH }
90
+ : undefined;
91
+ const minWidthPx = resize?.minWidthPx ?? MIN_WIDTH_PX;
92
+ const maxWidthPx = resize?.maxWidthPx ?? MAX_WIDTH_PX;
93
+ const effectiveMaxWidthPx = () => typeof window === "undefined"
94
+ ? maxWidthPx
95
+ : Math.min(maxWidthPx, Math.round(window.innerWidth * 0.9));
96
+ const [resizedWidthPx, setResizedWidthPx] = useState(() => clamp(resize?.defaultWidthPx ?? defaultWidthFromViewport(), minWidthPx, effectiveMaxWidthPx()));
97
+ const [maximized, setMaximized] = useState(false);
98
+ const blurPx = backdrop?.blurPx ?? DEFAULT_BLUR_PX;
99
+ const dimPercent = backdrop?.dimPercent ?? DEFAULT_DIM_PERCENT;
100
+ const overlayStyle = {
101
+ backgroundColor: `rgb(0 0 0 / ${dimPercent}%)`,
102
+ ...(blurPx > 0 ? { backdropFilter: `blur(${blurPx}px)` } : {}),
103
+ };
104
+ const effectiveWidthPx = maximized ? effectiveMaxWidthPx() : resizedWidthPx;
105
+ // Inline style because tailwind-merge doesn't dedupe `top-*`/`bottom-*` against the base `inset-y-0`;
106
+ // style wins over class regardless of generated CSS order.
107
+ const verticalOffsetStyle = belowHeader && variant === "flush" && !narrow && (side === "left" || side === "right")
108
+ ? { top: "var(--shell-header-height)", bottom: 0 }
109
+ : undefined;
110
+ const widthStyle = canResize && !narrow ? { width: effectiveWidthPx, maxWidth: "none" } : customWidthStyle;
111
+ const sheetStyle = widthStyle === undefined && verticalOffsetStyle === undefined
112
+ ? undefined
113
+ : { ...widthStyle, ...verticalOffsetStyle };
114
+ const handleDrag = usePointerDrag({
115
+ onStart: () => {
116
+ setMaximized(false);
117
+ return { startWidth: effectiveWidthPx };
118
+ },
119
+ onMove: ({ startWidth }, { dx }) => {
120
+ const signedDelta = side === "right" ? -dx : dx;
121
+ setResizedWidthPx(clamp(startWidth + signedDelta, minWidthPx, effectiveMaxWidthPx()));
122
+ },
123
+ });
124
+ const onHandleKeyDown = (event) => {
125
+ const keyDelta = arrowKeyDelta(event);
126
+ if (keyDelta === undefined || keyDelta.dx === 0)
127
+ return;
128
+ event.preventDefault();
129
+ setMaximized(false);
130
+ const delta = side === "right" ? -keyDelta.dx : keyDelta.dx;
131
+ // Seed from the currently visible width, not the stale `resizedWidthPx`
132
+ // state — while maximized, it still holds the pre-maximize value, so a
133
+ // key press would otherwise jump the drawer back to that old size
134
+ // instead of resizing relative to what's on screen.
135
+ setResizedWidthPx((current) => clamp((maximized ? effectiveWidthPx : current) + delta, minWidthPx, effectiveMaxWidthPx()));
136
+ };
137
+ return (_jsx(Sheet, { open: open, onOpenChange: onOpenChange, modal: modal, children: _jsxs(DrawerSheetContent, { side: side, "data-testid": testId, overlayStyle: overlayStyle,
138
+ // Radix already drops the overlay and the focus trap for a
139
+ // non-modal Root; only the dismiss-on-outside-interaction is left
140
+ // to suppress, so a click into the page behind keeps the panel open.
141
+ onInteractOutside: modal ? undefined : (event) => event.preventDefault(), showCloseButton: showCloseButton, closeLabel: t("kumiko.dialog.close"), className: cn(sidePanelClass(side, narrow, variant, belowHeader), fillBody && "gap-0", !narrow && panelClassName), style: sheetStyle, children: [canResize && !narrow && (_jsx("button", { type: "button", onClick: () => setMaximized((m) => !m), "aria-pressed": maximized, "aria-label": maximized ? t("kumiko.widget.drawer.restore") : t("kumiko.widget.drawer.maximize"), className: "absolute top-4 right-14 z-10 rounded-xs p-1 text-muted-foreground opacity-70 transition-opacity hover:opacity-100 hover:bg-secondary focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-hidden", children: maximized ? (_jsx(Minimize2Icon, { className: "size-4" })) : (_jsx(Maximize2Icon, { className: "size-4" })) })), (title !== undefined || description !== undefined) && (_jsxs(SheetHeader, { children: [title !== undefined && _jsx(SheetTitle, { children: title }), description !== undefined && _jsx(SheetDescription, { children: description })] })), _jsx("div", { className: fillBody ? "flex min-h-0 flex-1 flex-col" : "flex-1 overflow-y-auto px-4", children: children }), footer !== undefined && (_jsx(SheetFooter, { className: DRAWER_FOOTER_CLASS, children: footer })), canResize && !narrow && (_jsx("div", { role: "separator", "aria-orientation": "vertical", "aria-label": t("kumiko.widget.drawer.resize"), "aria-valuenow": effectiveWidthPx, "aria-valuemin": minWidthPx, "aria-valuemax": effectiveMaxWidthPx(), tabIndex: 0, ...handleDrag, onKeyDown: onHandleKeyDown, className: cn("absolute inset-y-0 z-10 w-1 cursor-col-resize touch-none after:absolute after:inset-y-0 after:left-1/2 after:w-3 after:-translate-x-1/2 hover:bg-border", side === "right" ? "left-0 -translate-x-1/2" : "right-0 translate-x-1/2") }))] }) }));
142
+ }
@@ -0,0 +1,13 @@
1
+ import type { ReactNode } from "react";
2
+ export type FeedRow = {
3
+ readonly id: string;
4
+ readonly primary: string;
5
+ readonly trailing?: string;
6
+ };
7
+ /** Nicht-tabellarische Kurzliste (z.B. "nächste Termine") — Primary-Text +
8
+ * optionaler rechtsbündiger Trailing-Wert pro Zeile. */
9
+ export declare function FeedList({ rows, emptyContent, testId, }: {
10
+ readonly rows: readonly FeedRow[];
11
+ readonly emptyContent?: ReactNode;
12
+ readonly testId?: string;
13
+ }): ReactNode;
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /** Nicht-tabellarische Kurzliste (z.B. "nächste Termine") — Primary-Text +
3
+ * optionaler rechtsbündiger Trailing-Wert pro Zeile. */
4
+ export function FeedList({ rows, emptyContent, testId, }) {
5
+ if (rows.length === 0) {
6
+ return _jsx("div", { "data-testid": testId, children: emptyContent });
7
+ }
8
+ return (_jsx("ul", { "data-testid": testId, className: "flex max-h-64 flex-col overflow-y-auto pr-1 text-sm", children: rows.map((row) => (_jsxs("li", { className: "flex justify-between gap-4 border-b py-1.5 last:border-b-0", children: [_jsx("span", { children: row.primary }), row.trailing !== undefined && (_jsx("span", { className: "tabular-nums text-muted-foreground", children: row.trailing }))] }, row.id))) }));
9
+ }
@@ -0,0 +1,30 @@
1
+ import { type ReactNode } from "react";
2
+ export type FloatingPanelGeometry = {
3
+ readonly x: number;
4
+ readonly y: number;
5
+ readonly width: number;
6
+ readonly height: number;
7
+ };
8
+ export type FloatingPanelProps = {
9
+ /** Header content; the header is the move handle. */
10
+ readonly title: ReactNode;
11
+ /** Header buttons (dock, minimize, close). Pointer-down on interactive
12
+ * elements does not start a drag. */
13
+ readonly headerActions?: ReactNode;
14
+ readonly children: ReactNode;
15
+ /** localStorage key; without it the geometry isn't persisted. */
16
+ readonly storageKey?: string;
17
+ /** Default 400x560, bottom-right with a 24px margin. */
18
+ readonly defaultGeometry?: Partial<FloatingPanelGeometry>;
19
+ readonly minWidthPx?: number;
20
+ readonly minHeightPx?: number;
21
+ /** Default: viewport width minus twice the margin. */
22
+ readonly maxWidthPx?: number;
23
+ /** Default: viewport height minus twice the margin. */
24
+ readonly maxHeightPx?: number;
25
+ readonly testId?: string;
26
+ };
27
+ /** Non-modal panel floating above the page: draggable by its header,
28
+ * resizable from all edges and corners, clamped to the viewport, geometry
29
+ * optionally remembered. On narrow viewports it becomes a full-screen sheet. */
30
+ export declare function FloatingPanel({ title, headerActions, children, storageKey, defaultGeometry, minWidthPx, minHeightPx, maxWidthPx, maxHeightPx, testId, }: FloatingPanelProps): ReactNode;
@@ -0,0 +1,242 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { GripVerticalIcon } from "lucide-react";
3
+ import { useCallback, useEffect, useId, useRef, useState, } from "react";
4
+ import { createPortal } from "react-dom";
5
+ import { useTranslation } from "../../index.js";
6
+ import { clamp } from "../lib/clamp.js";
7
+ import { cn } from "../lib/cn.js";
8
+ import { useIsNarrowViewport } from "../primitives/use-narrow-viewport.js";
9
+ import { arrowKeyDelta, usePointerDrag } from "./use-pointer-drag.js";
10
+ const MARGIN_PX = 24;
11
+ const DEFAULT_WIDTH_PX = 400;
12
+ const DEFAULT_HEIGHT_PX = 560;
13
+ const DEFAULT_MIN_WIDTH_PX = 280;
14
+ const DEFAULT_MIN_HEIGHT_PX = 200;
15
+ const EDGES = ["top", "right", "bottom", "left"];
16
+ const EDGE_HANDLE_BASE = "absolute z-10 touch-none hover:bg-border focus-visible:bg-ring";
17
+ const CORNER_HANDLE_BASE = "absolute z-20 size-3 touch-none";
18
+ const EDGE_HANDLE_CLASS = {
19
+ top: cn(EDGE_HANDLE_BASE, "inset-x-3 top-0 h-1.5 cursor-ns-resize"),
20
+ bottom: cn(EDGE_HANDLE_BASE, "inset-x-3 bottom-0 h-1.5 cursor-ns-resize"),
21
+ left: cn(EDGE_HANDLE_BASE, "inset-y-3 left-0 w-1.5 cursor-ew-resize"),
22
+ right: cn(EDGE_HANDLE_BASE, "inset-y-3 right-0 w-1.5 cursor-ew-resize"),
23
+ };
24
+ const CORNER_HANDLES = [
25
+ { edges: ["top", "left"], className: cn(CORNER_HANDLE_BASE, "top-0 left-0 cursor-nwse-resize") },
26
+ {
27
+ edges: ["top", "right"],
28
+ className: cn(CORNER_HANDLE_BASE, "top-0 right-0 cursor-nesw-resize"),
29
+ },
30
+ {
31
+ edges: ["bottom", "left"],
32
+ className: cn(CORNER_HANDLE_BASE, "bottom-0 left-0 cursor-nesw-resize"),
33
+ },
34
+ {
35
+ edges: ["bottom", "right"],
36
+ className: cn(CORNER_HANDLE_BASE, "right-0 bottom-0 cursor-nwse-resize"),
37
+ },
38
+ ];
39
+ const EDGE_LABEL_KEYS = {
40
+ top: "kumiko.widget.floatingPanel.resizeTop",
41
+ right: "kumiko.widget.floatingPanel.resizeRight",
42
+ bottom: "kumiko.widget.floatingPanel.resizeBottom",
43
+ left: "kumiko.widget.floatingPanel.resizeLeft",
44
+ };
45
+ const DRAG_HANDLE_SELECTOR = "[data-drag-handle]";
46
+ const INTERACTIVE_HEADER_TARGET = "button, a, input, select, textarea, [role=button]";
47
+ function readLimits(options) {
48
+ const viewportWidth = window.innerWidth;
49
+ const viewportHeight = window.innerHeight;
50
+ const maxWidth = Math.min(options.maxWidthPx ?? viewportWidth - 2 * MARGIN_PX, viewportWidth);
51
+ const maxHeight = Math.min(options.maxHeightPx ?? viewportHeight - 2 * MARGIN_PX, viewportHeight);
52
+ return {
53
+ // A viewport smaller than the configured minimum wins: the panel must stay fully visible.
54
+ minWidth: Math.min(options.minWidthPx ?? DEFAULT_MIN_WIDTH_PX, maxWidth),
55
+ minHeight: Math.min(options.minHeightPx ?? DEFAULT_MIN_HEIGHT_PX, maxHeight),
56
+ maxWidth,
57
+ maxHeight,
58
+ viewportWidth,
59
+ viewportHeight,
60
+ };
61
+ }
62
+ // Size first, then position, so a shrunk panel is placed against its final size.
63
+ function fitGeometry(geometry, limits) {
64
+ const width = clamp(geometry.width, limits.minWidth, limits.maxWidth);
65
+ const height = clamp(geometry.height, limits.minHeight, limits.maxHeight);
66
+ return {
67
+ width,
68
+ height,
69
+ x: clamp(geometry.x, 0, Math.max(0, limits.viewportWidth - width)),
70
+ y: clamp(geometry.y, 0, Math.max(0, limits.viewportHeight - height)),
71
+ };
72
+ }
73
+ // The edge being dragged follows the pointer; the opposite edge stays put.
74
+ function resizeAxis(start, delta, movesStartEdge, min, max, viewportSize) {
75
+ if (movesStartEdge) {
76
+ const end = start.position + start.size;
77
+ const size = clamp(start.size - delta, min, Math.min(max, end));
78
+ return { position: end - size, size };
79
+ }
80
+ const size = clamp(start.size + delta, min, Math.min(max, viewportSize - start.position));
81
+ return { position: start.position, size };
82
+ }
83
+ function resizeGeometry(start, edges, delta, limits) {
84
+ let { x, y, width, height } = start;
85
+ for (const edge of edges) {
86
+ if (edge === "left" || edge === "right") {
87
+ ({ position: x, size: width } = resizeAxis({ position: start.x, size: start.width }, delta.dx, edge === "left", limits.minWidth, limits.maxWidth, limits.viewportWidth));
88
+ }
89
+ else {
90
+ ({ position: y, size: height } = resizeAxis({ position: start.y, size: start.height }, delta.dy, edge === "top", limits.minHeight, limits.maxHeight, limits.viewportHeight));
91
+ }
92
+ }
93
+ return { x, y, width, height };
94
+ }
95
+ function isFiniteNumber(value) {
96
+ return typeof value === "number" && Number.isFinite(value);
97
+ }
98
+ function isFloatingPanelGeometry(value) {
99
+ return (typeof value === "object" &&
100
+ value !== null &&
101
+ "x" in value &&
102
+ isFiniteNumber(value.x) &&
103
+ "y" in value &&
104
+ isFiniteNumber(value.y) &&
105
+ "width" in value &&
106
+ isFiniteNumber(value.width) &&
107
+ "height" in value &&
108
+ isFiniteNumber(value.height));
109
+ }
110
+ function readStoredGeometry(storageKey) {
111
+ if (storageKey === undefined)
112
+ return undefined;
113
+ try {
114
+ const raw = window.localStorage.getItem(storageKey);
115
+ if (raw === null)
116
+ return undefined;
117
+ const parsed = JSON.parse(raw);
118
+ return isFloatingPanelGeometry(parsed) ? parsed : undefined;
119
+ }
120
+ catch {
121
+ // Unavailable or corrupt storage just means the default geometry is used.
122
+ return undefined;
123
+ }
124
+ }
125
+ function writeStoredGeometry(storageKey, geometry) {
126
+ if (storageKey === undefined)
127
+ return;
128
+ try {
129
+ window.localStorage.setItem(storageKey, JSON.stringify(geometry));
130
+ }
131
+ catch {
132
+ // Private mode or quota: the position is simply not remembered.
133
+ }
134
+ }
135
+ function initialGeometry(storageKey, defaults, limits) {
136
+ const stored = readStoredGeometry(storageKey);
137
+ if (stored !== undefined)
138
+ return fitGeometry(stored, limits);
139
+ const width = defaults?.width ?? DEFAULT_WIDTH_PX;
140
+ const height = defaults?.height ?? DEFAULT_HEIGHT_PX;
141
+ return fitGeometry({
142
+ width,
143
+ height,
144
+ x: defaults?.x ?? limits.viewportWidth - width - MARGIN_PX,
145
+ y: defaults?.y ?? limits.viewportHeight - height - MARGIN_PX,
146
+ }, limits);
147
+ }
148
+ /** Non-modal panel floating above the page: draggable by its header,
149
+ * resizable from all edges and corners, clamped to the viewport, geometry
150
+ * optionally remembered. On narrow viewports it becomes a full-screen sheet. */
151
+ export function FloatingPanel({ title, headerActions, children, storageKey, defaultGeometry, minWidthPx, minHeightPx, maxWidthPx, maxHeightPx, testId, }) {
152
+ const t = useTranslation();
153
+ const narrow = useIsNarrowViewport();
154
+ const titleId = useId();
155
+ const getLimits = () => readLimits({ minWidthPx, minHeightPx, maxWidthPx, maxHeightPx });
156
+ const getLimitsRef = useRef(getLimits);
157
+ getLimitsRef.current = getLimits;
158
+ const [geometry, setGeometry] = useState(() => initialGeometry(storageKey, defaultGeometry, getLimits()));
159
+ const geometryRef = useRef(geometry);
160
+ geometryRef.current = geometry;
161
+ const applyGeometry = useCallback((next) => {
162
+ geometryRef.current = next;
163
+ setGeometry(next);
164
+ }, []);
165
+ const persistGeometry = () => writeStoredGeometry(storageKey, geometryRef.current);
166
+ useEffect(() => {
167
+ if (narrow)
168
+ return;
169
+ const onWindowResize = () => {
170
+ applyGeometry(fitGeometry(geometryRef.current, getLimitsRef.current()));
171
+ };
172
+ // Resize events fired while narrow were ignored, so re-fit once on the way back to wide.
173
+ onWindowResize();
174
+ window.addEventListener("resize", onWindowResize);
175
+ return () => window.removeEventListener("resize", onWindowResize);
176
+ }, [narrow, applyGeometry]);
177
+ const moveDrag = usePointerDrag({
178
+ onStart: () => geometryRef.current,
179
+ onMove: (start, { dx, dy }) => applyGeometry(fitGeometry({ ...start, x: start.x + dx, y: start.y + dy }, getLimits())),
180
+ onEnd: persistGeometry,
181
+ });
182
+ const onHeaderPointerDown = (event) => {
183
+ // React events from portalled children (menus) bubble through the header although their DOM is elsewhere.
184
+ if (!(event.target instanceof Node) || !event.currentTarget.contains(event.target))
185
+ return;
186
+ if (event.target instanceof Element &&
187
+ event.target.closest(INTERACTIVE_HEADER_TARGET) &&
188
+ !event.target.closest(DRAG_HANDLE_SELECTOR)) {
189
+ return;
190
+ }
191
+ moveDrag.onPointerDown(event);
192
+ };
193
+ const onGripKeyDown = (event) => {
194
+ const delta = arrowKeyDelta(event);
195
+ if (delta === undefined)
196
+ return;
197
+ event.preventDefault();
198
+ const current = geometryRef.current;
199
+ applyGeometry(fitGeometry({ ...current, x: current.x + delta.dx, y: current.y + delta.dy }, getLimits()));
200
+ persistGeometry();
201
+ };
202
+ if (typeof document === "undefined")
203
+ return null;
204
+ if (narrow) {
205
+ return createPortal(_jsxs("div", { role: "dialog", "aria-modal": "false", "aria-labelledby": titleId, "data-testid": testId, className: "fixed inset-0 z-50 flex flex-col bg-background text-foreground", children: [_jsxs("div", { className: "flex shrink-0 items-center gap-2 border-b px-3 py-2", children: [_jsx("div", { id: titleId, className: "min-w-0 flex-1 truncate font-medium", children: title }), headerActions] }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto p-4", children: children })] }), document.body);
206
+ }
207
+ const limits = getLimits();
208
+ const handleContext = {
209
+ geometryRef,
210
+ getLimits,
211
+ applyGeometry,
212
+ persistGeometry,
213
+ };
214
+ return createPortal(_jsxs("div", { role: "dialog", "aria-modal": "false", "aria-labelledby": titleId, "data-testid": testId, className: "fixed z-50 flex flex-col overflow-hidden rounded-lg border bg-background text-foreground shadow-2xl", style: {
215
+ left: geometry.x,
216
+ top: geometry.y,
217
+ width: geometry.width,
218
+ height: geometry.height,
219
+ }, children: [_jsxs("div", { ...moveDrag, onPointerDown: onHeaderPointerDown, className: "flex shrink-0 cursor-grab touch-none select-none items-center gap-2 border-b bg-muted px-3 py-2 active:cursor-grabbing", children: [_jsx("button", { type: "button", "data-drag-handle": "", "aria-label": t("kumiko.widget.floatingPanel.move"), onKeyDown: onGripKeyDown, className: "rounded-xs p-1 text-muted-foreground hover:bg-secondary focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-hidden", children: _jsx(GripVerticalIcon, { className: "size-4" }) }), _jsx("div", { id: titleId, className: "min-w-0 flex-1 truncate font-medium", children: title }), headerActions] }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto p-4", children: children }), EDGES.map((edge) => (_jsx(PanelResizeHandle, { edges: [edge], className: EDGE_HANDLE_CLASS[edge], context: handleContext, separator: { label: t(EDGE_LABEL_KEYS[edge]), geometry, limits } }, edge))), CORNER_HANDLES.map((corner) => (_jsx(PanelResizeHandle, { edges: corner.edges, className: corner.className, context: handleContext }, corner.edges.join("-"))))] }), document.body);
220
+ }
221
+ function PanelResizeHandle({ edges, className, context, separator, }) {
222
+ const drag = usePointerDrag({
223
+ onStart: () => context.geometryRef.current,
224
+ onMove: (start, delta) => context.applyGeometry(resizeGeometry(start, edges, delta, context.getLimits())),
225
+ onEnd: context.persistGeometry,
226
+ });
227
+ if (separator === undefined) {
228
+ return _jsx("div", { "aria-hidden": "true", ...drag, className: className });
229
+ }
230
+ const [edge] = edges;
231
+ const horizontalEdge = edge === "left" || edge === "right";
232
+ const { geometry, limits } = separator;
233
+ const onKeyDown = (event) => {
234
+ const delta = arrowKeyDelta(event);
235
+ if (delta === undefined || (horizontalEdge ? delta.dx === 0 : delta.dy === 0))
236
+ return;
237
+ event.preventDefault();
238
+ context.applyGeometry(resizeGeometry(context.geometryRef.current, edges, delta, context.getLimits()));
239
+ context.persistGeometry();
240
+ };
241
+ return (_jsx("div", { role: "separator", "aria-orientation": horizontalEdge ? "vertical" : "horizontal", "aria-label": separator.label, "aria-valuenow": horizontalEdge ? geometry.width : geometry.height, "aria-valuemin": horizontalEdge ? limits.minWidth : limits.minHeight, "aria-valuemax": horizontalEdge ? limits.maxWidth : limits.maxHeight, tabIndex: 0, ...drag, onKeyDown: onKeyDown, className: className }));
242
+ }