@coffer-org/web-ui 4.0.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 (387) hide show
  1. package/README.md +87 -0
  2. package/dist/Markdown.d.ts +4 -0
  3. package/dist/Markdown.js +13 -0
  4. package/dist/MarkdownEditor.d.ts +6 -0
  5. package/dist/MarkdownEditor.js +83 -0
  6. package/dist/actions/ActionButton.d.ts +7 -0
  7. package/dist/actions/ActionButton.js +54 -0
  8. package/dist/actions/PopupBody.d.ts +4 -0
  9. package/dist/actions/PopupBody.js +8 -0
  10. package/dist/actions/TemplateHost.d.ts +4 -0
  11. package/dist/actions/TemplateHost.js +36 -0
  12. package/dist/actions/registry.d.ts +20 -0
  13. package/dist/actions/registry.js +9 -0
  14. package/dist/components/ExtendReasonInfo.d.ts +6 -0
  15. package/dist/components/ExtendReasonInfo.js +12 -0
  16. package/dist/components/FavoriteButton.d.ts +8 -0
  17. package/dist/components/FavoriteButton.js +7 -0
  18. package/dist/components/Icon.d.ts +6 -0
  19. package/dist/components/Icon.js +12 -0
  20. package/dist/components/PageBody.d.ts +4 -0
  21. package/dist/components/PageBody.js +4 -0
  22. package/dist/components/RecordInline.d.ts +8 -0
  23. package/dist/components/RecordInline.js +29 -0
  24. package/dist/components/SaveStatus.d.ts +1 -0
  25. package/dist/components/SaveStatus.js +11 -0
  26. package/dist/components/Toolbar.d.ts +5 -0
  27. package/dist/components/Toolbar.js +4 -0
  28. package/dist/components/icons/color.d.ts +2 -0
  29. package/dist/components/icons/color.js +10 -0
  30. package/dist/components/icons/flag.d.ts +2 -0
  31. package/dist/components/icons/flag.js +6 -0
  32. package/dist/components/icons/lucide.d.ts +2 -0
  33. package/dist/components/icons/lucide.js +111 -0
  34. package/dist/components/icons/registry.d.ts +4 -0
  35. package/dist/components/icons/registry.js +22 -0
  36. package/dist/components/icons/simple.d.ts +2 -0
  37. package/dist/components/icons/simple.js +15 -0
  38. package/dist/components/icons/tabler.d.ts +2 -0
  39. package/dist/components/icons/tabler.js +11 -0
  40. package/dist/components/ui/FieldLabel.d.ts +9 -0
  41. package/dist/components/ui/FieldLabel.js +4 -0
  42. package/dist/components/ui/badge.d.ts +9 -0
  43. package/dist/components/ui/badge.js +19 -0
  44. package/dist/components/ui/button.d.ts +11 -0
  45. package/dist/components/ui/button.js +33 -0
  46. package/dist/components/ui/calendar.d.ts +8 -0
  47. package/dist/components/ui/calendar.js +72 -0
  48. package/dist/components/ui/card.d.ts +8 -0
  49. package/dist/components/ui/card.js +16 -0
  50. package/dist/components/ui/checkbox.d.ts +4 -0
  51. package/dist/components/ui/checkbox.js +8 -0
  52. package/dist/components/ui/chip-cls.d.ts +1 -0
  53. package/dist/components/ui/chip-cls.js +1 -0
  54. package/dist/components/ui/combobox.d.ts +24 -0
  55. package/dist/components/ui/combobox.js +58 -0
  56. package/dist/components/ui/confirm.d.ts +10 -0
  57. package/dist/components/ui/confirm.js +37 -0
  58. package/dist/components/ui/date-picker.d.ts +5 -0
  59. package/dist/components/ui/date-picker.js +16 -0
  60. package/dist/components/ui/dialog.d.ts +16 -0
  61. package/dist/components/ui/dialog.js +27 -0
  62. package/dist/components/ui/dropdown-menu.d.ts +15 -0
  63. package/dist/components/ui/dropdown-menu.js +18 -0
  64. package/dist/components/ui/input-group.d.ts +16 -0
  65. package/dist/components/ui/input-group.js +56 -0
  66. package/dist/components/ui/input.d.ts +3 -0
  67. package/dist/components/ui/input.js +6 -0
  68. package/dist/components/ui/label.d.ts +4 -0
  69. package/dist/components/ui/label.js +7 -0
  70. package/dist/components/ui/mask-input.d.ts +61 -0
  71. package/dist/components/ui/mask-input.js +1052 -0
  72. package/dist/components/ui/month-year-picker.d.ts +10 -0
  73. package/dist/components/ui/month-year-picker.js +46 -0
  74. package/dist/components/ui/option-icon.d.ts +15 -0
  75. package/dist/components/ui/option-icon.js +19 -0
  76. package/dist/components/ui/phone-input.d.ts +13 -0
  77. package/dist/components/ui/phone-input.js +53 -0
  78. package/dist/components/ui/popover.d.ts +11 -0
  79. package/dist/components/ui/popover.js +28 -0
  80. package/dist/components/ui/rating.d.ts +44 -0
  81. package/dist/components/ui/rating.js +501 -0
  82. package/dist/components/ui/scroll-area.d.ts +5 -0
  83. package/dist/components/ui/scroll-area.js +11 -0
  84. package/dist/components/ui/select-combobox.d.ts +25 -0
  85. package/dist/components/ui/select-combobox.js +36 -0
  86. package/dist/components/ui/select.d.ts +13 -0
  87. package/dist/components/ui/select.js +24 -0
  88. package/dist/components/ui/separator.d.ts +4 -0
  89. package/dist/components/ui/separator.js +7 -0
  90. package/dist/components/ui/switch.d.ts +4 -0
  91. package/dist/components/ui/switch.js +7 -0
  92. package/dist/components/ui/textarea.d.ts +3 -0
  93. package/dist/components/ui/textarea.js +6 -0
  94. package/dist/components/ui/time-picker.d.ts +84 -0
  95. package/dist/components/ui/time-picker.js +1258 -0
  96. package/dist/components/visually-hidden-input.d.ts +10 -0
  97. package/dist/components/visually-hidden-input.js +96 -0
  98. package/dist/hooks/use-as-ref.d.ts +3 -0
  99. package/dist/hooks/use-as-ref.js +10 -0
  100. package/dist/hooks/use-isomorphic-layout-effect.d.ts +3 -0
  101. package/dist/hooks/use-isomorphic-layout-effect.js +3 -0
  102. package/dist/hooks/use-lazy-ref.d.ts +3 -0
  103. package/dist/hooks/use-lazy-ref.js +9 -0
  104. package/dist/hooks/use-media-query.d.ts +3 -0
  105. package/dist/hooks/use-media-query.js +26 -0
  106. package/dist/host-services.d.ts +47 -0
  107. package/dist/host-services.js +35 -0
  108. package/dist/index.d.ts +70 -0
  109. package/dist/index.js +69 -0
  110. package/dist/layout.d.ts +29 -0
  111. package/dist/layout.js +323 -0
  112. package/dist/lib/cat-color.d.ts +1 -0
  113. package/dist/lib/cat-color.js +7 -0
  114. package/dist/lib/compose-refs.d.ts +5 -0
  115. package/dist/lib/compose-refs.js +38 -0
  116. package/dist/lib/date-format.d.ts +9 -0
  117. package/dist/lib/date-format.js +31 -0
  118. package/dist/lib/format.d.ts +14 -0
  119. package/dist/lib/format.js +34 -0
  120. package/dist/lib/json.d.ts +1 -0
  121. package/dist/lib/json.js +12 -0
  122. package/dist/lib/multi-value.d.ts +3 -0
  123. package/dist/lib/multi-value.js +22 -0
  124. package/dist/lib/record-option.d.ts +7 -0
  125. package/dist/lib/record-option.js +52 -0
  126. package/dist/lib/use-record-labels.d.ts +14 -0
  127. package/dist/lib/use-record-labels.js +130 -0
  128. package/dist/lib/utils.d.ts +2 -0
  129. package/dist/lib/utils.js +5 -0
  130. package/dist/list-view.d.ts +26 -0
  131. package/dist/list-view.js +11 -0
  132. package/dist/markdown/blocks.d.ts +10 -0
  133. package/dist/markdown/blocks.js +57 -0
  134. package/dist/markdown/extensions.d.ts +6 -0
  135. package/dist/markdown/extensions.js +25 -0
  136. package/dist/markdown/slash.d.ts +23 -0
  137. package/dist/markdown/slash.js +27 -0
  138. package/dist/markdown/toolbar.d.ts +13 -0
  139. package/dist/markdown/toolbar.js +119 -0
  140. package/dist/plugin-ui.d.ts +43 -0
  141. package/dist/plugin-ui.js +37 -0
  142. package/dist/registry.d.ts +26 -0
  143. package/dist/registry.js +169 -0
  144. package/dist/render/blocks/accordion.d.ts +1 -0
  145. package/dist/render/blocks/accordion.js +15 -0
  146. package/dist/render/blocks/aside.d.ts +1 -0
  147. package/dist/render/blocks/aside.js +14 -0
  148. package/dist/render/blocks/balance.d.ts +1 -0
  149. package/dist/render/blocks/balance.js +14 -0
  150. package/dist/render/blocks/bento.d.ts +1 -0
  151. package/dist/render/blocks/bento.js +24 -0
  152. package/dist/render/blocks/block-shell.d.ts +15 -0
  153. package/dist/render/blocks/block-shell.js +20 -0
  154. package/dist/render/blocks/calendar.d.ts +1 -0
  155. package/dist/render/blocks/calendar.js +142 -0
  156. package/dist/render/blocks/callout.d.ts +1 -0
  157. package/dist/render/blocks/callout.js +21 -0
  158. package/dist/render/blocks/chart.d.ts +1 -0
  159. package/dist/render/blocks/chart.js +46 -0
  160. package/dist/render/blocks/compare.d.ts +1 -0
  161. package/dist/render/blocks/compare.js +16 -0
  162. package/dist/render/blocks/countdown.d.ts +1 -0
  163. package/dist/render/blocks/countdown.js +36 -0
  164. package/dist/render/blocks/deck.d.ts +1 -0
  165. package/dist/render/blocks/deck.js +29 -0
  166. package/dist/render/blocks/epigraph.d.ts +1 -0
  167. package/dist/render/blocks/epigraph.js +13 -0
  168. package/dist/render/blocks/facets.d.ts +1 -0
  169. package/dist/render/blocks/facets.js +23 -0
  170. package/dist/render/blocks/figure.d.ts +1 -0
  171. package/dist/render/blocks/figure.js +13 -0
  172. package/dist/render/blocks/grid.d.ts +1 -0
  173. package/dist/render/blocks/grid.js +21 -0
  174. package/dist/render/blocks/heatmap.d.ts +1 -0
  175. package/dist/render/blocks/heatmap.js +135 -0
  176. package/dist/render/blocks/idcard.d.ts +1 -0
  177. package/dist/render/blocks/idcard.js +30 -0
  178. package/dist/render/blocks/identity.d.ts +1 -0
  179. package/dist/render/blocks/identity.js +46 -0
  180. package/dist/render/blocks/index.d.ts +40 -0
  181. package/dist/render/blocks/index.js +40 -0
  182. package/dist/render/blocks/journal.d.ts +1 -0
  183. package/dist/render/blocks/journal.js +50 -0
  184. package/dist/render/blocks/ledger.d.ts +1 -0
  185. package/dist/render/blocks/ledger.js +23 -0
  186. package/dist/render/blocks/manifest.d.ts +1 -0
  187. package/dist/render/blocks/manifest.js +50 -0
  188. package/dist/render/blocks/masthead.d.ts +1 -0
  189. package/dist/render/blocks/masthead.js +15 -0
  190. package/dist/render/blocks/meter.d.ts +1 -0
  191. package/dist/render/blocks/meter.js +32 -0
  192. package/dist/render/blocks/nutrition.d.ts +1 -0
  193. package/dist/render/blocks/nutrition.js +27 -0
  194. package/dist/render/blocks/people.d.ts +1 -0
  195. package/dist/render/blocks/people.js +35 -0
  196. package/dist/render/blocks/plaque.d.ts +1 -0
  197. package/dist/render/blocks/plaque.js +15 -0
  198. package/dist/render/blocks/properties.d.ts +1 -0
  199. package/dist/render/blocks/properties.js +22 -0
  200. package/dist/render/blocks/prose.d.ts +1 -0
  201. package/dist/render/blocks/prose.js +17 -0
  202. package/dist/render/blocks/receipt.d.ts +1 -0
  203. package/dist/render/blocks/receipt.js +23 -0
  204. package/dist/render/blocks/registry.d.ts +16 -0
  205. package/dist/render/blocks/registry.js +7 -0
  206. package/dist/render/blocks/relation-source.d.ts +2 -0
  207. package/dist/render/blocks/relation-source.js +6 -0
  208. package/dist/render/blocks/route.d.ts +1 -0
  209. package/dist/render/blocks/route.js +23 -0
  210. package/dist/render/blocks/score.d.ts +1 -0
  211. package/dist/render/blocks/score.js +15 -0
  212. package/dist/render/blocks/series.d.ts +23 -0
  213. package/dist/render/blocks/series.js +57 -0
  214. package/dist/render/blocks/specimen.d.ts +1 -0
  215. package/dist/render/blocks/specimen.js +19 -0
  216. package/dist/render/blocks/split.d.ts +1 -0
  217. package/dist/render/blocks/split.js +15 -0
  218. package/dist/render/blocks/spread.d.ts +1 -0
  219. package/dist/render/blocks/spread.js +15 -0
  220. package/dist/render/blocks/stack.d.ts +1 -0
  221. package/dist/render/blocks/stack.js +15 -0
  222. package/dist/render/blocks/stats.d.ts +1 -0
  223. package/dist/render/blocks/stats.js +23 -0
  224. package/dist/render/blocks/status.d.ts +1 -0
  225. package/dist/render/blocks/status.js +18 -0
  226. package/dist/render/blocks/table.d.ts +11 -0
  227. package/dist/render/blocks/table.js +136 -0
  228. package/dist/render/blocks/tabs.d.ts +1 -0
  229. package/dist/render/blocks/tabs.js +17 -0
  230. package/dist/render/blocks/terminal.d.ts +1 -0
  231. package/dist/render/blocks/terminal.js +18 -0
  232. package/dist/render/blocks/timeline.d.ts +1 -0
  233. package/dist/render/blocks/timeline.js +31 -0
  234. package/dist/render/box.d.ts +5 -0
  235. package/dist/render/box.js +23 -0
  236. package/dist/render/chrome.d.ts +64 -0
  237. package/dist/render/chrome.js +92 -0
  238. package/dist/render/core/amount.d.ts +1 -0
  239. package/dist/render/core/amount.js +19 -0
  240. package/dist/render/core/attachment.d.ts +29 -0
  241. package/dist/render/core/attachment.js +102 -0
  242. package/dist/render/core/choice.d.ts +1 -0
  243. package/dist/render/core/choice.js +40 -0
  244. package/dist/render/core/composite.d.ts +1 -0
  245. package/dist/render/core/composite.js +132 -0
  246. package/dist/render/core/datetime.d.ts +2 -0
  247. package/dist/render/core/datetime.js +215 -0
  248. package/dist/render/core/dimensions.d.ts +1 -0
  249. package/dist/render/core/dimensions.js +44 -0
  250. package/dist/render/core/embed.d.ts +11 -0
  251. package/dist/render/core/embed.js +99 -0
  252. package/dist/render/core/file.d.ts +12 -0
  253. package/dist/render/core/file.js +238 -0
  254. package/dist/render/core/illustrated.d.ts +39 -0
  255. package/dist/render/core/illustrated.js +113 -0
  256. package/dist/render/core/index.d.ts +23 -0
  257. package/dist/render/core/index.js +23 -0
  258. package/dist/render/core/layout-elements.d.ts +1 -0
  259. package/dist/render/core/layout-elements.js +78 -0
  260. package/dist/render/core/localpath.d.ts +6 -0
  261. package/dist/render/core/localpath.js +73 -0
  262. package/dist/render/core/money.d.ts +1 -0
  263. package/dist/render/core/money.js +39 -0
  264. package/dist/render/core/number.d.ts +17 -0
  265. package/dist/render/core/number.js +104 -0
  266. package/dist/render/core/period.d.ts +1 -0
  267. package/dist/render/core/period.js +60 -0
  268. package/dist/render/core/range.d.ts +1 -0
  269. package/dist/render/core/range.js +55 -0
  270. package/dist/render/core/reminder.d.ts +2 -0
  271. package/dist/render/core/reminder.js +56 -0
  272. package/dist/render/core/source.d.ts +3 -0
  273. package/dist/render/core/source.js +65 -0
  274. package/dist/render/core/stepper.d.ts +1 -0
  275. package/dist/render/core/stepper.js +81 -0
  276. package/dist/render/core/storage-group.d.ts +1 -0
  277. package/dist/render/core/storage-group.js +30 -0
  278. package/dist/render/core/text.d.ts +15 -0
  279. package/dist/render/core/text.js +370 -0
  280. package/dist/render/core/tristate.d.ts +1 -0
  281. package/dist/render/core/tristate.js +42 -0
  282. package/dist/render/core/weight.d.ts +4 -0
  283. package/dist/render/core/weight.js +38 -0
  284. package/dist/render/dispatch.d.ts +12 -0
  285. package/dist/render/dispatch.js +53 -0
  286. package/dist/render/display-override.d.ts +25 -0
  287. package/dist/render/display-override.js +23 -0
  288. package/dist/render/editor-mode.d.ts +4 -0
  289. package/dist/render/editor-mode.js +11 -0
  290. package/dist/render/field-address.d.ts +14 -0
  291. package/dist/render/field-address.js +10 -0
  292. package/dist/render/field-errors.d.ts +8 -0
  293. package/dist/render/field-errors.js +25 -0
  294. package/dist/render/field-frame.d.ts +20 -0
  295. package/dist/render/field-frame.js +78 -0
  296. package/dist/render/field-scope.d.ts +8 -0
  297. package/dist/render/field-scope.js +25 -0
  298. package/dist/render/field-width.d.ts +7 -0
  299. package/dist/render/field-width.js +12 -0
  300. package/dist/render/form-types.d.ts +12 -0
  301. package/dist/render/form-types.js +1 -0
  302. package/dist/render/hybrid-scope.d.ts +5 -0
  303. package/dist/render/hybrid-scope.js +9 -0
  304. package/dist/render/image.d.ts +64 -0
  305. package/dist/render/image.js +322 -0
  306. package/dist/render/live-kind.d.ts +7 -0
  307. package/dist/render/live-kind.js +104 -0
  308. package/dist/render/markdown-view.d.ts +5 -0
  309. package/dist/render/markdown-view.js +17 -0
  310. package/dist/render/multi.d.ts +10 -0
  311. package/dist/render/multi.js +26 -0
  312. package/dist/render/multiple-chips.d.ts +24 -0
  313. package/dist/render/multiple-chips.js +18 -0
  314. package/dist/render/option-list.d.ts +15 -0
  315. package/dist/render/option-list.js +47 -0
  316. package/dist/render/option-value.d.ts +20 -0
  317. package/dist/render/option-value.js +41 -0
  318. package/dist/render/picker-scrim.d.ts +7 -0
  319. package/dist/render/picker-scrim.js +32 -0
  320. package/dist/render/picker-surface.d.ts +5 -0
  321. package/dist/render/picker-surface.js +9 -0
  322. package/dist/render/popover-field.d.ts +16 -0
  323. package/dist/render/popover-field.js +153 -0
  324. package/dist/render/record-save-ctx.d.ts +11 -0
  325. package/dist/render/record-save-ctx.js +41 -0
  326. package/dist/render/shared.d.ts +92 -0
  327. package/dist/render/shared.js +182 -0
  328. package/dist/render/slot.d.ts +45 -0
  329. package/dist/render/slot.js +194 -0
  330. package/dist/render/sort-rows.d.ts +3 -0
  331. package/dist/render/sort-rows.js +38 -0
  332. package/dist/render/use-collection-rows.d.ts +10 -0
  333. package/dist/render/use-collection-rows.js +11 -0
  334. package/dist/render/use-field-edit.d.ts +28 -0
  335. package/dist/render/use-field-edit.js +50 -0
  336. package/dist/render/use-file-upload.d.ts +10 -0
  337. package/dist/render/use-file-upload.js +35 -0
  338. package/dist/render/use-form-bridge.d.ts +21 -0
  339. package/dist/render/use-form-bridge.js +53 -0
  340. package/dist/render/use-hybrid.d.ts +2 -0
  341. package/dist/render/use-hybrid.js +5 -0
  342. package/dist/render/widgets/attachment-group.d.ts +1 -0
  343. package/dist/render/widgets/attachment-group.js +35 -0
  344. package/dist/render/widgets/checklist-content.d.ts +15 -0
  345. package/dist/render/widgets/checklist-content.js +12 -0
  346. package/dist/render/widgets/checklist-group.d.ts +11 -0
  347. package/dist/render/widgets/checklist-group.js +47 -0
  348. package/dist/render/widgets/checklist-row.d.ts +14 -0
  349. package/dist/render/widgets/checklist-row.js +14 -0
  350. package/dist/render/widgets/display.d.ts +33 -0
  351. package/dist/render/widgets/display.js +97 -0
  352. package/dist/render/widgets/edit.d.ts +63 -0
  353. package/dist/render/widgets/edit.js +283 -0
  354. package/dist/render/widgets/fixed-collection.d.ts +11 -0
  355. package/dist/render/widgets/fixed-collection.js +34 -0
  356. package/dist/render/widgets/flow-row.d.ts +14 -0
  357. package/dist/render/widgets/flow-row.js +27 -0
  358. package/dist/render/widgets/group-edit.d.ts +9 -0
  359. package/dist/render/widgets/group-edit.js +23 -0
  360. package/dist/render/widgets/group-field.d.ts +9 -0
  361. package/dist/render/widgets/group-field.js +42 -0
  362. package/dist/render/widgets/group-widgets.d.ts +2 -0
  363. package/dist/render/widgets/group-widgets.js +41 -0
  364. package/dist/render/widgets/group.d.ts +26 -0
  365. package/dist/render/widgets/group.js +139 -0
  366. package/dist/render/widgets/illustrated-group.d.ts +1 -0
  367. package/dist/render/widgets/illustrated-group.js +41 -0
  368. package/dist/render/widgets/table-group.d.ts +11 -0
  369. package/dist/render/widgets/table-group.js +58 -0
  370. package/dist/render/widgets/two-cell-group.d.ts +21 -0
  371. package/dist/render/widgets/two-cell-group.js +42 -0
  372. package/dist/render/widgets/url-widget.d.ts +7 -0
  373. package/dist/render/widgets/url-widget.js +15 -0
  374. package/dist/render/widgets/weekday-grid.d.ts +13 -0
  375. package/dist/render/widgets/weekday-grid.js +47 -0
  376. package/dist/render/write.d.ts +5 -0
  377. package/dist/render/write.js +14 -0
  378. package/dist/schema-registry.d.ts +6 -0
  379. package/dist/schema-registry.js +25 -0
  380. package/dist/site-links.d.ts +13 -0
  381. package/dist/site-links.js +18 -0
  382. package/dist/strip-markdown.d.ts +1 -0
  383. package/dist/strip-markdown.js +11 -0
  384. package/dist/theme.css +189 -0
  385. package/dist/types.d.ts +84 -0
  386. package/dist/types.js +1 -0
  387. package/package.json +85 -0
@@ -0,0 +1,215 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Input } from "../../components/ui/input.js";
3
+ import { registerRenderer } from "../../registry.js";
4
+ import { MultiTagInput, MultipleChips, Dash, orDash, useTranslation } from "../shared.js";
5
+ import { Icon } from "../../components/Icon.js";
6
+ import { parseJSON } from "../../lib/json.js";
7
+ import { DatePicker } from "../../components/ui/date-picker.js";
8
+ import { YearPicker, MonthPicker, YearGridPanel, MonthGridPanel } from "../../components/ui/month-year-picker.js";
9
+ import { TimePicker, TimePickerInputGroup, TimePickerInput, TimePickerSeparator, TimePickerTrigger, TimePickerContent, TimePickerHour, TimePickerMinute, TimePickerSecond, TimePickerClear, } from "../../components/ui/time-picker.js";
10
+ import { isoToDisplay, isoDateTimeToDisplay, splitDateTime, joinDateTime, isoToDate, dateToIso, } from "../../lib/date-format.js";
11
+ import { Calendar } from "../../components/ui/calendar.js";
12
+ import { Button } from "../../components/ui/button.js";
13
+ import { OptionList } from "../option-list.js";
14
+ import { FieldFrame } from "../field-frame.js";
15
+ function gran(hints, def) {
16
+ return hints['granularity'] ?? def;
17
+ }
18
+ function dtLocal(v, granularity = 'minute') {
19
+ const s = String(v ?? '');
20
+ if (granularity === 'second')
21
+ return s.length > 19 ? s.slice(0, 19) : s;
22
+ if (granularity === 'hour')
23
+ return s.length > 13 ? s.slice(0, 13) : s;
24
+ return s.length > 16 ? s.slice(0, 16) : s;
25
+ }
26
+ function dateChipContent(icon, format) {
27
+ return function DateChip(item) {
28
+ return (_jsxs(_Fragment, { children: [_jsx(Icon, { name: icon, size: 13, className: "shrink-0 text-muted" }), _jsx("span", { children: format ? format(String(item)) : String(item) })] }));
29
+ };
30
+ }
31
+ function DateChips({ value, format, icon }) {
32
+ const items = parseJSON(value, []);
33
+ return _jsx(MultipleChips, { items: items, content: dateChipContent(icon, format) });
34
+ }
35
+ function DateValue({ value }) {
36
+ return _jsx(_Fragment, { children: dateChipContent('lucide:calendar')(value) });
37
+ }
38
+ function DateTimeValue({ value }) {
39
+ return _jsx(_Fragment, { children: dateChipContent('lucide:calendar-clock', (s) => s.replace('T', ' '))(value) });
40
+ }
41
+ function DateDisplay({ field, value }) {
42
+ if (field.hints['multiple'] === true)
43
+ return _jsx(DateChips, { value: value, icon: "lucide:calendar" });
44
+ return orDash(value, (v) => {
45
+ const g = gran(field.hints, 'day');
46
+ return _jsx("span", { children: g === 'month' || g === 'year' ? String(v) : isoToDisplay(String(v)) || String(v) });
47
+ });
48
+ }
49
+ function DateInput({ name, field, value, onChange }) {
50
+ const g = gran(field.hints, 'day');
51
+ const inputType = g === 'month' ? 'month' : g === 'year' ? 'number' : 'date';
52
+ if (field.hints['multiple'] === true) {
53
+ return (_jsx(MultiTagInput, { value: value, onChange: onChange, display: dateChipContent('lucide:calendar'), renderInput: (v, set, add) => (_jsx(Input, { type: inputType, value: v, onChange: (e) => set(e.target.value), className: "flex-1", onKeyDown: (e) => {
54
+ if (e.key === 'Enter') {
55
+ e.preventDefault();
56
+ add();
57
+ }
58
+ } })) }));
59
+ }
60
+ const single = typeof value === 'string' ? value : String(value ?? '');
61
+ if (g === 'year')
62
+ return _jsx(YearPicker, { value: single, onChange: onChange });
63
+ if (g === 'month')
64
+ return _jsx(MonthPicker, { value: single, onChange: onChange });
65
+ return _jsx(DatePicker, { value: single, onChange: onChange });
66
+ }
67
+ function DatePage(p) {
68
+ return (_jsx(FieldFrame, { field: p.field, value: p.value, label: p.label, required: p.required, error: p.error, mode: p.mode, geom: "inline", width: "w-40", bare: p.bare, view: (v) => _jsx(DateDisplay, { field: p.field, value: v }), edit: (value, onChange) => (_jsx(DateInput, { name: `field-${p.field.label}`, field: p.field, value: value, onChange: onChange })) }));
69
+ }
70
+ function dateKeys(value, field) {
71
+ if (value == null || value === '')
72
+ return [];
73
+ const g = gran(field.hints, 'day');
74
+ const s = String(value);
75
+ const label = g === 'year' || g === 'month' ? s : isoToDisplay(s) || s;
76
+ return [{ value: s, rawValue: value, label }];
77
+ }
78
+ export function DatePickerBody({ field, draft, save }) {
79
+ const g = gran(field.hints, 'day');
80
+ const single = typeof draft === 'string' ? draft : String(draft ?? '');
81
+ if (g === 'year')
82
+ return _jsx(YearGridPanel, { value: single, onChange: (v) => save(v) });
83
+ if (g === 'month')
84
+ return _jsx(MonthGridPanel, { value: single, onChange: (v) => save(v) });
85
+ const date = isoToDate(single);
86
+ return (_jsx(Calendar, { mode: "single", selected: date, captionLayout: "dropdown", defaultMonth: date, className: "w-full", onSelect: (d) => save(d ? dateToIso(d) : '') }));
87
+ }
88
+ registerRenderer({
89
+ kinds: ['date'],
90
+ Page: DatePage,
91
+ Value: DateValue,
92
+ Picker: DatePickerBody,
93
+ keys: dateKeys,
94
+ });
95
+ const TIME_VALIDATE = {
96
+ hour: /^([01]\d|2[0-3])$/,
97
+ minute: /^([01]\d|2[0-3]):[0-5]\d$/,
98
+ second: /^([01]\d|2[0-3]):[0-5]\d:[0-5]\d$/,
99
+ };
100
+ function TimeDisplay({ field, value }) {
101
+ if (field.hints['multiple'] !== true && (value == null || value === ''))
102
+ return _jsx(Dash, {});
103
+ return field.hints['multiple'] === true ? (_jsx(DateChips, { value: value, icon: "lucide:clock" })) : (_jsx("span", { className: "font-mono text-sm", children: String(value) }));
104
+ }
105
+ function TimeInput({ name, field, value, onChange }) {
106
+ const g = gran(field.hints, 'second');
107
+ const step = g === 'second' ? 1 : g === 'minute' ? 60 : 3600;
108
+ if (field.hints['multiple'] === true) {
109
+ return (_jsx(MultiTagInput, { value: value, onChange: onChange, validate: (v) => TIME_VALIDATE[g]?.test(v) ?? false, display: dateChipContent('lucide:clock'), renderInput: (v, set, add) => (_jsx(Input, { type: "time", step: step, value: v, onChange: (e) => set(e.target.value), className: "w-36", onKeyDown: (e) => {
110
+ if (e.key === 'Enter') {
111
+ e.preventDefault();
112
+ add();
113
+ }
114
+ } })) }));
115
+ }
116
+ const single = typeof value === 'string' ? value : String(value ?? '');
117
+ if (g === 'hour') {
118
+ return (_jsx(Input, { id: name, type: "time", step: step, value: single, onChange: (e) => onChange(e.target.value), className: `appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none` }));
119
+ }
120
+ const withSeconds = g === 'second';
121
+ return (_jsxs(TimePicker, { value: single, onValueChange: onChange, showSeconds: withSeconds, children: [_jsxs(TimePickerInputGroup, { id: name, children: [_jsx(TimePickerInput, { segment: "hour" }), _jsx(TimePickerSeparator, {}), _jsx(TimePickerInput, { segment: "minute" }), withSeconds && (_jsxs(_Fragment, { children: [_jsx(TimePickerSeparator, {}), _jsx(TimePickerInput, { segment: "second" })] })), _jsx(TimePickerTrigger, {})] }), _jsxs(TimePickerContent, { children: [_jsx(TimePickerHour, {}), _jsx(TimePickerMinute, {}), withSeconds && _jsx(TimePickerSecond, {}), _jsx(TimePickerClear, {})] })] }));
122
+ }
123
+ function TimePage(p) {
124
+ return (_jsx(FieldFrame, { field: p.field, value: p.value, label: p.label, required: p.required, error: p.error, mode: p.mode, geom: "inline", width: "w-32", bare: p.bare, view: (v) => _jsx(TimeDisplay, { field: p.field, value: v }), edit: (value, onChange) => (_jsx(TimeInput, { name: `field-${p.field.label}`, field: p.field, value: value, onChange: onChange })) }));
125
+ }
126
+ function pad2(n) {
127
+ return String(n).padStart(2, '0');
128
+ }
129
+ const HOURS = Array.from({ length: 24 }, (_, i) => ({ value: pad2(i), title: pad2(i) }));
130
+ const MINUTES = Array.from({ length: 60 }, (_, i) => ({ value: pad2(i), title: pad2(i) }));
131
+ function ClockColumns({ value, onChange, granularity, }) {
132
+ const [h = '', m = '', sec = ''] = value.split(':');
133
+ const withMinutes = granularity !== 'hour';
134
+ const withSeconds = granularity === 'second';
135
+ const build = (nh, nm, ns) => {
136
+ if (!withMinutes)
137
+ return nh;
138
+ const base = `${nh || '00'}:${nm || '00'}`;
139
+ return withSeconds ? `${base}:${ns || '00'}` : base;
140
+ };
141
+ const column = (opts, cur, pick) => (_jsx("div", { className: "min-w-0 flex-1", children: _jsx(OptionList, { options: opts, value: cur, multiple: false, literal: true, search: false, onPick: (v) => pick(String(v)) }) }));
142
+ return (_jsxs("div", { className: "flex min-w-0 gap-1", children: [column(HOURS, h, (v) => onChange(build(v, m, sec))), withMinutes && column(MINUTES, m, (v) => onChange(build(h, v, sec))), withSeconds && column(MINUTES, sec, (v) => onChange(build(h, m, v)))] }));
143
+ }
144
+ function ClockActions({ save, remove, removeLabel }) {
145
+ const { t } = useTranslation();
146
+ return (_jsxs("div", { className: "mt-2 flex items-center justify-end gap-2 border-t border-border pt-2", children: [remove && (_jsx(Button, { type: "button", variant: "destructiveOutline", size: "sm", className: "mr-auto", onClick: remove, children: removeLabel ?? t('actions.delete') })), _jsx(Button, { type: "button", size: "sm", onClick: () => save(), children: t('actions.save') })] }));
147
+ }
148
+ function TimePickerBody({ field, draft, setDraft, save, remove, removeLabel }) {
149
+ const g = gran(field.hints, 'second');
150
+ const single = typeof draft === 'string' ? draft : String(draft ?? '');
151
+ return (_jsxs("div", { className: "flex min-w-0 flex-col", children: [_jsx(ClockColumns, { value: single, onChange: setDraft, granularity: g }), _jsx(ClockActions, { save: save, remove: remove, removeLabel: removeLabel })] }));
152
+ }
153
+ function TimeValue({ value }) {
154
+ return _jsx("span", { className: "font-mono text-sm", children: String(value ?? '') });
155
+ }
156
+ registerRenderer({
157
+ kinds: ['time'],
158
+ Page: TimePage,
159
+ Value: TimeValue,
160
+ Picker: TimePickerBody,
161
+ });
162
+ const DT_VALIDATE = {
163
+ hour: /^\d{4}-\d{2}-\d{2}T([01]\d|2[0-3])$/,
164
+ minute: /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/,
165
+ second: /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/,
166
+ };
167
+ function DateTimeDisplay({ field, value }) {
168
+ if (field.hints['multiple'] !== true && (value == null || value === ''))
169
+ return _jsx(Dash, {});
170
+ const g = gran(field.hints, 'minute');
171
+ return field.hints['multiple'] === true ? (_jsx(DateChips, { value: value, icon: "lucide:calendar-clock", format: (s) => dtLocal(s, g).replace('T', ' ') })) : (_jsx("span", { children: isoDateTimeToDisplay(dtLocal(value, g)) }));
172
+ }
173
+ function DateTimeInput({ name, field, value, onChange }) {
174
+ const g = gran(field.hints, 'minute');
175
+ const step = g === 'second' ? 1 : g === 'hour' ? 3600 : 60;
176
+ if (field.hints['multiple'] === true) {
177
+ return (_jsx(MultiTagInput, { value: value, onChange: onChange, validate: (v) => DT_VALIDATE[g]?.test(v) ?? false, display: dateChipContent('lucide:calendar-clock', (s) => dtLocal(s, g).replace('T', ' ')), renderInput: (v, set, add) => (_jsx(Input, { type: "datetime-local", step: step, value: v, onChange: (e) => set(e.target.value), className: "w-52", onKeyDown: (e) => {
178
+ if (e.key === 'Enter') {
179
+ e.preventDefault();
180
+ add();
181
+ }
182
+ } })) }));
183
+ }
184
+ const { date, time } = splitDateTime(dtLocal(value, g));
185
+ return (_jsxs("div", { className: "flex gap-2", children: [_jsx(DatePicker, { value: date, onChange: (d) => onChange(joinDateTime(d, time || '00:00')) }), _jsx(Input, { id: name, type: "time", step: step, value: time, onChange: (e) => onChange(joinDateTime(date, e.target.value)), className: `w-32 appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none` })] }));
186
+ }
187
+ function DateTimePage(p) {
188
+ return (_jsx(FieldFrame, { field: p.field, value: p.value, label: p.label, required: p.required, error: p.error, mode: p.mode, geom: "inline", width: "w-56", bare: p.bare, view: (v) => _jsx(DateTimeDisplay, { field: p.field, value: v }), edit: (value, onChange) => (_jsx(DateTimeInput, { name: `field-${p.field.label}`, field: p.field, value: value, onChange: onChange })) }));
189
+ }
190
+ function DateTimeInline({ value, field }) {
191
+ const g = gran(field.hints, 'minute');
192
+ return _jsx("span", { className: "truncate", children: isoDateTimeToDisplay(dtLocal(value, g)) });
193
+ }
194
+ function dateTimeKeys(value, field) {
195
+ if (value == null || value === '')
196
+ return [];
197
+ const g = gran(field.hints, 'minute');
198
+ const s = dtLocal(value, g);
199
+ return [{ value: s, rawValue: value, label: isoDateTimeToDisplay(s) }];
200
+ }
201
+ function DateTimePickerBody({ field, draft, setDraft, save, remove, removeLabel }) {
202
+ const g = gran(field.hints, 'minute');
203
+ const { date, time } = splitDateTime(dtLocal(draft, g));
204
+ const day = isoToDate(date);
205
+ return (_jsxs("div", { className: "flex min-w-0 flex-col gap-2", children: [_jsxs("div", { className: "flex min-w-0 flex-wrap gap-2", children: [_jsx(Calendar, { mode: "single", selected: day, captionLayout: "dropdown", defaultMonth: day, onSelect: (d) => setDraft(joinDateTime(d ? dateToIso(d) : '', time || '00:00')) }), _jsx("div", { className: "min-w-0 flex-1", children: _jsx(ClockColumns, { value: time, onChange: (v) => setDraft(joinDateTime(date, v)), granularity: g }) })] }), _jsx(ClockActions, { save: save, remove: remove, removeLabel: removeLabel })] }));
206
+ }
207
+ registerRenderer({
208
+ kinds: ['datetime'],
209
+ pickerSize: 'large',
210
+ Page: DateTimePage,
211
+ Picker: DateTimePickerBody,
212
+ Inline: DateTimeInline,
213
+ Value: DateTimeValue,
214
+ keys: dateTimeKeys,
215
+ });
@@ -0,0 +1 @@
1
+ export declare function fmtDimensions(v: unknown): string;
@@ -0,0 +1,44 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Input } from "../../components/ui/input.js";
3
+ import { registerRenderer } from "../../registry.js";
4
+ import { EDIT_INLINE_PAD } from "../chrome.js";
5
+ import { FieldFrame } from "../field-frame.js";
6
+ import { Dash } from "../shared.js";
7
+ function parse(v) {
8
+ if (v && typeof v === 'object')
9
+ return v;
10
+ try {
11
+ const o = JSON.parse(String(v ?? ''));
12
+ return o && typeof o === 'object' ? o : {};
13
+ }
14
+ catch {
15
+ return {};
16
+ }
17
+ }
18
+ export function fmtDimensions(v) {
19
+ const d = parse(v);
20
+ if (d.w == null || d.h == null || d.d == null)
21
+ return '';
22
+ const cm = (mm) => Number((mm / 10).toFixed(1));
23
+ return `${cm(d.w)}×${cm(d.h)}×${cm(d.d)} см`;
24
+ }
25
+ function DimInput({ value, onChange }) {
26
+ const d = parse(value);
27
+ const set = (k, raw) => {
28
+ const next = { ...d, [k]: raw === '' ? undefined : Math.round(Number(raw)) };
29
+ onChange(next.w == null && next.h == null && next.d == null ? '' : next);
30
+ };
31
+ const cell = (k, ph) => (_jsx(Input, { type: "number", min: 0, className: "w-20 min-w-0", placeholder: ph, value: d[k] ?? '', onChange: (e) => set(k, e.target.value) }));
32
+ return (_jsxs("div", { className: `flex items-center gap-1 min-w-0 ${EDIT_INLINE_PAD}`, children: [cell('w', 'Ш'), _jsx("span", { className: "text-muted", children: "\u00D7" }), cell('h', 'В'), _jsx("span", { className: "text-muted", children: "\u00D7" }), cell('d', 'Г'), _jsx("span", { className: "ml-1 text-xs text-muted", children: "\u043C\u043C" })] }));
33
+ }
34
+ function DimView({ value }) {
35
+ const s = fmtDimensions(value);
36
+ return s ? _jsx("span", { className: "tabular-nums", children: s }) : _jsx(Dash, {});
37
+ }
38
+ function DimPage(p) {
39
+ return (_jsx(FieldFrame, { field: p.field, value: p.value, label: p.label, required: p.required, error: p.error, mode: p.mode, geom: "inline", width: "w-fit", bare: p.bare, view: (v) => _jsx(DimView, { value: v }), edit: (value, onChange) => _jsx(DimInput, { value: value, onChange: onChange }) }));
40
+ }
41
+ function DimInline({ value }) {
42
+ return _jsx(DimView, { value: value });
43
+ }
44
+ registerRenderer({ kinds: ['dimensions'], Page: DimPage, Inline: DimInline });
@@ -0,0 +1,11 @@
1
+ import { type EmbedRec } from '../../host-services.ts';
2
+ import type { FieldInlineProps } from '../../types.ts';
3
+ export declare function parseEmbeds(value: unknown): EmbedRec[];
4
+ export declare function getHostname(url: string): string;
5
+ export declare function safeHref(url: string): string | undefined;
6
+ export declare function EmbedGallery({ value, onChange, multiple, }: {
7
+ value: unknown;
8
+ onChange?: (v: EmbedRec[] | '') => void;
9
+ multiple?: boolean;
10
+ }): import("react").JSX.Element;
11
+ export declare function EmbedInline(p: FieldInlineProps): import("react").JSX.Element | null;
@@ -0,0 +1,99 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { Play, Link2, Trash2, ExternalLink } from 'lucide-react';
4
+ import { Button } from "../../components/ui/button.js";
5
+ import { Input } from "../../components/ui/input.js";
6
+ import { Dialog, DialogContent, DialogTitle } from "../../components/ui/dialog.js";
7
+ import { resolveEmbed } from "../../host-services.js";
8
+ import { registerRenderer } from "../../registry.js";
9
+ import { useTranslation } from "../shared.js";
10
+ import { FieldFrame } from "../field-frame.js";
11
+ export function parseEmbeds(value) {
12
+ const isRec = (x) => !!x && typeof x === 'object' && typeof x.url === 'string';
13
+ if (Array.isArray(value))
14
+ return value.filter(isRec);
15
+ if (isRec(value))
16
+ return [value];
17
+ try {
18
+ const v = JSON.parse(String(value ?? ''));
19
+ if (Array.isArray(v))
20
+ return v.filter(isRec);
21
+ if (isRec(v))
22
+ return [v];
23
+ return [];
24
+ }
25
+ catch {
26
+ return [];
27
+ }
28
+ }
29
+ const playable = (r) => (r.type === 'video' || r.type === 'rich') && !!r.html;
30
+ export function getHostname(url) {
31
+ try {
32
+ return new URL(url).hostname;
33
+ }
34
+ catch {
35
+ return url;
36
+ }
37
+ }
38
+ export function safeHref(url) {
39
+ try {
40
+ const parsed = new URL(url);
41
+ if (parsed.protocol === 'http:' || parsed.protocol === 'https:') {
42
+ return url;
43
+ }
44
+ }
45
+ catch {
46
+ }
47
+ return undefined;
48
+ }
49
+ function EmbedCard({ rec, onOpen, onRemove }) {
50
+ return (_jsxs("div", { className: "group relative @container", children: [_jsxs("button", { type: "button", onClick: onOpen, className: "flex w-full items-center gap-2 overflow-hidden rounded-lg border border-border text-left @[20rem]:flex-col @[20rem]:items-stretch @[20rem]:gap-0", children: [_jsxs("div", { className: "relative aspect-video w-20 shrink-0 bg-muted/40 @[20rem]:w-full", children: [rec.thumbnail ? (_jsx("img", { src: rec.thumbnail, alt: "", className: "h-full w-full object-cover" })) : (_jsx(Link2, { className: "absolute inset-0 m-auto text-muted", size: 20 })), playable(rec) && (_jsx("span", { className: "absolute inset-0 m-auto flex h-8 w-8 items-center justify-center rounded-full bg-black/60 text-white", children: _jsx(Play, { size: 16 }) }))] }), _jsxs("div", { className: "min-w-0 flex-1 px-2 py-2", children: [_jsx("div", { className: "truncate text-sm font-medium", children: rec.title ?? rec.url }), rec.provider && _jsx("div", { className: "truncate text-xs text-muted", children: rec.provider })] })] }), onRemove && (_jsx(Button, { type: "button", variant: "ghost", size: "iconSm", className: "absolute right-1 top-1 text-muted opacity-0 hover:text-danger group-hover:opacity-100", onClick: onRemove, children: _jsx(Trash2, { size: 13 }) }))] }));
51
+ }
52
+ function EmbedViewCard({ rec }) {
53
+ const title = rec.title || getHostname(rec.url);
54
+ const href = safeHref(rec.url);
55
+ const contentClass = 'group flex items-center gap-2 overflow-hidden rounded-lg border border-border bg-muted/5 px-3 py-2 transition-colors hover:bg-muted/15';
56
+ const content = (_jsxs(_Fragment, { children: [_jsx("div", { className: "shrink-0 text-muted group-hover:text-text", children: playable(rec) ? _jsx(Play, { size: 16 }) : _jsx(Link2, { size: 16 }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "truncate text-sm font-medium", children: title }), _jsx("div", { className: "truncate text-xs text-muted", children: rec.url })] })] }));
57
+ return href ? (_jsx("a", { href: href, target: "_blank", rel: "noopener noreferrer", className: contentClass, children: content })) : (_jsx("div", { className: contentClass, children: content }));
58
+ }
59
+ function EmbedDialog({ rec, onClose }) {
60
+ const { t } = useTranslation();
61
+ const href = safeHref(rec.url);
62
+ return (_jsx(Dialog, { open: true, onOpenChange: (o) => !o && onClose(), children: _jsxs(DialogContent, { className: "max-w-2xl", children: [_jsx(DialogTitle, { className: "truncate", children: rec.title ?? rec.url }), playable(rec) ? (_jsx("iframe", { title: rec.title ?? 'embed', srcDoc: rec.html, sandbox: "allow-scripts allow-presentation", allowFullScreen: true, className: "aspect-video w-full rounded-md border-0" })) : (_jsxs("div", { className: "flex flex-col gap-3", children: [rec.thumbnail && _jsx("img", { src: rec.thumbnail, alt: "", className: "w-full rounded-md" }), rec.description && _jsx("p", { className: "text-sm text-muted", children: rec.description })] })), href && (_jsx("a", { href: href, target: "_blank", rel: "noopener noreferrer", children: _jsxs(Button, { type: "button", variant: "outline", className: "w-full", children: [_jsx(ExternalLink, { size: 15 }), " ", t('embed.open')] }) }))] }) }));
63
+ }
64
+ export function EmbedGallery({ value, onChange, multiple, }) {
65
+ const { t } = useTranslation();
66
+ const list = parseEmbeds(value);
67
+ const [open, setOpen] = useState(null);
68
+ const [url, setUrl] = useState('');
69
+ const [busy, setBusy] = useState(false);
70
+ const [err, setErr] = useState(false);
71
+ const save = (next) => onChange?.(next.length ? next : '');
72
+ async function add() {
73
+ if (busy)
74
+ return;
75
+ if (!url.trim())
76
+ return;
77
+ setBusy(true);
78
+ setErr(false);
79
+ const rec = await resolveEmbed(url.trim());
80
+ setBusy(false);
81
+ if (!rec)
82
+ return setErr(true);
83
+ save([...list, rec]);
84
+ setUrl('');
85
+ }
86
+ return (_jsxs("div", { className: "flex flex-col gap-2", children: [onChange ? (_jsx("div", { className: "grid grid-cols-1 gap-2 @md:grid-cols-2 @2xl:grid-cols-3", children: list.map((rec, i) => (_jsx(EmbedCard, { rec: rec, onOpen: () => setOpen(rec), onRemove: () => save(list.filter((_, j) => j !== i)) }, `${rec.url}-${i}`))) })) : (list.length > 0 && (_jsx("div", { className: "flex flex-col gap-2", children: list.map((rec, i) => (_jsx(EmbedViewCard, { rec: rec }, `${rec.url}-${i}`))) }))), onChange && (multiple || list.length === 0) && (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsxs("div", { className: "flex gap-2", children: [_jsx(Input, { value: url, placeholder: t('embed.pastePlaceholder'), onChange: (e) => setUrl(e.target.value), onKeyDown: (e) => e.key === 'Enter' && (e.preventDefault(), add()) }), _jsx(Button, { type: "button", variant: "outline", disabled: busy || !url.trim(), onClick: add, children: busy ? '…' : '+' })] }), err && _jsx("span", { className: "text-xs text-danger", children: t('embed.resolveFailed') })] })), open && _jsx(EmbedDialog, { rec: open, onClose: () => setOpen(null) })] }));
87
+ }
88
+ function EmbedPage(p) {
89
+ const multiple = p.field.hints['multiple'] === true;
90
+ return (_jsx(FieldFrame, { field: p.field, value: p.value, label: p.label, required: p.required, error: p.error, mode: p.mode, geom: "none", width: "w-full", bare: p.bare, view: (v) => _jsx(EmbedGallery, { value: v, multiple: multiple }), edit: (v, onChange) => (_jsx(EmbedGallery, { value: v, onChange: onChange, multiple: multiple })) }));
91
+ }
92
+ export function EmbedInline(p) {
93
+ const list = parseEmbeds(p.value);
94
+ if (list.length === 0)
95
+ return null;
96
+ const first = list[0];
97
+ return (_jsxs("span", { className: "inline-flex min-w-0 items-center gap-1", children: [playable(first) ? _jsx(Play, { size: 12 }) : _jsx(Link2, { size: 12 }), _jsx("span", { className: "truncate", children: first.title ?? getHostname(first.url) }), list.length > 1 && _jsxs("span", { className: "text-muted", children: ["+", list.length - 1] })] }));
98
+ }
99
+ registerRenderer({ kinds: ['embed'], layout: 'block', Page: EmbedPage, Inline: EmbedInline, pickerSize: 'large' });
@@ -0,0 +1,12 @@
1
+ import { type FileRec } from '../image.tsx';
2
+ export type FilePreviewKind = 'pdf' | 'text';
3
+ export declare function filePreviewKind(rec: FileRec): FilePreviewKind | null;
4
+ export declare function SingleFileWidget({ kind, value, onChange, }: {
5
+ kind: string;
6
+ value: unknown;
7
+ onChange: (v: FileRec | '') => void;
8
+ }): import("react").JSX.Element;
9
+ export declare function ReadOnlyFiles({ kind, value }: {
10
+ kind: string;
11
+ value: unknown;
12
+ }): import("react").JSX.Element;
@@ -0,0 +1,238 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useState } from 'react';
3
+ import { FileText, Download, Trash2, Music, Film, Eye } from 'lucide-react';
4
+ import { mediaUrl } from "../../host-services.js";
5
+ import { Button } from "../../components/ui/button.js";
6
+ import { confirmDialog } from "../../components/ui/confirm.js";
7
+ import { Dialog, DialogBody, DialogContent, DialogHeader, DialogTitle } from "../../components/ui/dialog.js";
8
+ import { registerRenderer, multiBlock } from "../../registry.js";
9
+ import { useTranslation, UploadButton, Dash } from "../shared.js";
10
+ import { useFileUpload } from "../use-file-upload.js";
11
+ import { FieldFrame } from "../field-frame.js";
12
+ import { useHybridScope } from "../hybrid-scope.js";
13
+ import { ImageWidget, ImagesWidget, ImageInline, GalleryView, AvatarView, CoverView, AvatarWidget, CoverWidget, AvatarInline, parseFileRecs, } from "../image.js";
14
+ function confirmDeleteFile(t) {
15
+ return confirmDialog({
16
+ title: t('actions.confirmDelete'),
17
+ confirmLabel: t('actions.delete'),
18
+ variant: 'destructive',
19
+ });
20
+ }
21
+ function fileSizeLabel(size) {
22
+ if (size == null)
23
+ return null;
24
+ return size >= 1024 * 1024 ? `${(size / 1024 / 1024).toFixed(1)} MB` : `${Math.round(size / 1024)} KB`;
25
+ }
26
+ const TEXT_PREVIEW_EXT = /\.(txt|md|markdown|csv|json|xml|log|yaml|yml|ini|toml|js|jsx|ts|tsx|css|html)$/i;
27
+ export function filePreviewKind(rec) {
28
+ const mime = rec.mime?.toLowerCase().split(';', 1)[0];
29
+ if (mime === 'application/pdf' || /\.pdf$/i.test(rec.name))
30
+ return 'pdf';
31
+ if (mime?.startsWith('text/') ||
32
+ mime === 'application/json' ||
33
+ mime === 'application/xml' ||
34
+ TEXT_PREVIEW_EXT.test(rec.name))
35
+ return 'text';
36
+ return null;
37
+ }
38
+ const MAX_TEXT_PREVIEW = 1_000_000;
39
+ function FilePreviewDialog({ rec, kind, onClose }) {
40
+ const { t } = useTranslation();
41
+ const [text, setText] = useState(null);
42
+ const [failed, setFailed] = useState(false);
43
+ useEffect(() => {
44
+ if (kind !== 'text')
45
+ return;
46
+ const controller = new AbortController();
47
+ setText(null);
48
+ setFailed(false);
49
+ fetch(mediaUrl(rec.name), { signal: controller.signal })
50
+ .then((response) => {
51
+ if (!response.ok)
52
+ throw new Error(`preview_${response.status}`);
53
+ return response.text();
54
+ })
55
+ .then((body) => setText(body.length > MAX_TEXT_PREVIEW
56
+ ? `${body.slice(0, MAX_TEXT_PREVIEW)}\n\n${t('files.previewTruncated')}`
57
+ : body))
58
+ .catch((error) => {
59
+ if (error?.name !== 'AbortError')
60
+ setFailed(true);
61
+ });
62
+ return () => controller.abort();
63
+ }, [kind, rec.name, t]);
64
+ return (_jsx(Dialog, { open: true, onOpenChange: (open) => !open && onClose(), children: _jsxs(DialogContent, { className: "h-[88dvh] max-w-5xl overflow-hidden md:h-[85dvh]", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { className: "truncate pr-8", children: rec.name }) }), _jsx(DialogBody, { children: kind === 'pdf' ? (_jsx("iframe", { title: rec.name, src: mediaUrl(rec.name), className: "h-full w-full rounded-lg border border-border bg-white" })) : failed ? (_jsx("div", { className: "flex h-full items-center justify-center text-sm text-danger", children: t('files.previewFailed') })) : text === null ? (_jsx("div", { className: "flex h-full items-center justify-center text-sm text-muted", children: t('files.loadingPreview') })) : (_jsx("pre", { className: "h-full overflow-auto whitespace-pre-wrap break-words rounded-lg border border-border bg-bg p-4 font-mono text-xs leading-relaxed", children: text })) })] }) }));
65
+ }
66
+ function FilePreviewButton({ rec }) {
67
+ const { t } = useTranslation();
68
+ const kind = filePreviewKind(rec);
69
+ const [open, setOpen] = useState(false);
70
+ if (!kind)
71
+ return null;
72
+ return (_jsxs(_Fragment, { children: [_jsx(Button, { type: "button", variant: "ghost", size: "iconSm", className: "text-muted hover:text-accent", title: t('files.preview'), "aria-label": t('files.preview'), onClick: () => setOpen(true), children: _jsx(Eye, { size: 15 }) }), open && _jsx(FilePreviewDialog, { rec: rec, kind: kind, onClose: () => setOpen(false) })] }));
73
+ }
74
+ function FileRowContent({ rec }) {
75
+ return (_jsxs(_Fragment, { children: [_jsx(FileText, { size: 16, className: "text-muted shrink-0" }), _jsx("span", { className: "min-w-0 flex-1 truncate text-sm", children: rec.name }), fileSizeLabel(rec.size) && _jsx("span", { className: "text-xs text-muted", children: fileSizeLabel(rec.size) })] }));
76
+ }
77
+ function MediaPlayer({ kind, rec, className }) {
78
+ return kind === 'audio' ? (_jsx("audio", { controls: true, src: mediaUrl(rec.name), className: className })) : (_jsx("video", { controls: true, src: mediaUrl(rec.name), className: className }));
79
+ }
80
+ const ACCEPT = {
81
+ file: '*',
82
+ document: '.pdf,.doc,.docx,.xls,.xlsx,.txt,.csv',
83
+ audio: 'audio/*',
84
+ video: 'video/*',
85
+ image: 'image/*',
86
+ media: 'image/*,video/*',
87
+ avatar: 'image/*',
88
+ cover: 'image/*',
89
+ poster: 'image/*',
90
+ };
91
+ function FileListWidget({ kind, value, onChange, }) {
92
+ const { t } = useTranslation();
93
+ const { busy, error, progress, upload, retry, canRetry } = useFileUpload();
94
+ const list = parseFileRecs(value);
95
+ const save = (next) => onChange(next.length ? next : '');
96
+ async function add(f) {
97
+ if (!f)
98
+ return;
99
+ const rec = await upload(f);
100
+ if (rec)
101
+ save([...list, rec]);
102
+ }
103
+ async function retryAdd() {
104
+ const rec = await retry();
105
+ if (rec)
106
+ save([...list, rec]);
107
+ }
108
+ return (_jsxs("div", { className: "flex flex-col gap-2", children: [list.map((rec, i) => (_jsxs("div", { className: "flex min-h-9 items-center gap-2 rounded-lg border border-border px-2", children: [_jsx(FileRowContent, { rec: rec }), _jsx(FilePreviewButton, { rec: rec }), _jsx("a", { href: mediaUrl(rec.name), download: true, className: "text-muted hover:text-accent", title: t('files.download'), "aria-label": t('files.download'), children: _jsx(Download, { size: 15 }) }), _jsx(Button, { type: "button", variant: "ghost", size: "iconSm", disabled: busy, className: "text-muted hover:text-danger", onClick: async () => {
109
+ if (await confirmDeleteFile(t))
110
+ save(list.filter((_, j) => j !== i));
111
+ }, children: _jsx(Trash2, { size: 13 }) })] }, `${rec.name}-${i}`))), _jsx(UploadButton, { accept: ACCEPT[kind] ?? '*', busy: busy, error: error, progress: progress, onPick: add, onRetry: () => void retryAdd(), canRetry: canRetry })] }));
112
+ }
113
+ export function SingleFileWidget({ kind, value, onChange, }) {
114
+ const { t } = useTranslation();
115
+ const { busy, error, progress, upload, retry, canRetry } = useFileUpload();
116
+ const rec = parseFileRecs(value)[0];
117
+ async function add(f) {
118
+ if (!f)
119
+ return;
120
+ const next = await upload(f);
121
+ if (next)
122
+ onChange(next);
123
+ }
124
+ async function retryAdd() {
125
+ const next = await retry();
126
+ if (next)
127
+ onChange(next);
128
+ }
129
+ if (!rec) {
130
+ return (_jsx(UploadButton, { accept: ACCEPT[kind] ?? '*', busy: busy, error: error, progress: progress, onPick: (f) => void add(f), onRetry: () => void retryAdd(), canRetry: canRetry, compact: true }));
131
+ }
132
+ return (_jsxs("div", { "data-testid": "single-file", className: "flex min-h-9 items-center gap-2 rounded-lg border border-border px-2 hover:border-accent", children: [_jsx(FileRowContent, { rec: rec }), _jsx(FilePreviewButton, { rec: rec }), _jsx("a", { href: mediaUrl(rec.name), download: true, className: "text-muted hover:text-accent", title: t('files.download'), "aria-label": t('files.download'), children: _jsx(Download, { size: 15 }) }), _jsx(Button, { type: "button", variant: "ghost", size: "iconSm", disabled: busy, className: "text-muted hover:text-danger", onClick: async () => {
133
+ if (await confirmDeleteFile(t))
134
+ onChange('');
135
+ }, children: _jsx(Trash2, { size: 13 }) })] }));
136
+ }
137
+ function MediaListWidget({ kind, value, onChange, }) {
138
+ const { t } = useTranslation();
139
+ const { busy, error, progress, upload, retry, canRetry } = useFileUpload();
140
+ const list = parseFileRecs(value);
141
+ const save = (next) => onChange(next.length ? next : '');
142
+ async function add(f) {
143
+ if (!f)
144
+ return;
145
+ const rec = await upload(f);
146
+ if (rec)
147
+ save([...list, rec]);
148
+ }
149
+ async function retryAdd() {
150
+ const rec = await retry();
151
+ if (rec)
152
+ save([...list, rec]);
153
+ }
154
+ return (_jsxs("div", { className: "flex flex-col gap-2", children: [list.map((rec, i) => (_jsxs("div", { className: "flex items-start gap-2", children: [_jsx(MediaPlayer, { kind: kind, rec: rec, className: kind === 'audio' ? 'min-w-0 flex-1' : 'min-w-0 flex-1 max-h-72 rounded-lg' }), _jsx(Button, { type: "button", variant: "ghost", size: "iconSm", disabled: busy, className: "shrink-0 text-muted hover:text-danger", onClick: async () => {
155
+ if (await confirmDeleteFile(t))
156
+ save(list.filter((_, j) => j !== i));
157
+ }, children: _jsx(Trash2, { size: 13 }) })] }, `${rec.name}-${i}`))), _jsx(UploadButton, { accept: ACCEPT[kind] ?? '*', busy: busy, error: error, progress: progress, onPick: add, onRetry: () => void retryAdd(), canRetry: canRetry })] }));
158
+ }
159
+ function widgetFor(kind, value, onChange, multiple) {
160
+ if (kind === 'avatar') {
161
+ return _jsx(AvatarWidget, { value: value, onChange: onChange, accept: ACCEPT[kind], multiple: multiple });
162
+ }
163
+ if (kind === 'cover') {
164
+ return _jsx(CoverWidget, { value: value, onChange: onChange, accept: ACCEPT[kind], multiple: multiple });
165
+ }
166
+ if (kind === 'image' || kind === 'media' || kind === 'poster') {
167
+ const poster = kind === 'poster';
168
+ return multiple ? (_jsx(ImagesWidget, { value: value, onChange: onChange, accept: ACCEPT[kind], poster: poster })) : (_jsx(ImageWidget, { value: value, onChange: onChange, accept: ACCEPT[kind], poster: poster }));
169
+ }
170
+ if (kind === 'audio' || kind === 'video') {
171
+ return _jsx(MediaListWidget, { kind: kind, value: value, onChange: onChange });
172
+ }
173
+ return _jsx(FileListWidget, { kind: kind, value: value, onChange: onChange });
174
+ }
175
+ export function ReadOnlyFiles({ kind, value }) {
176
+ const { t } = useTranslation();
177
+ const list = parseFileRecs(value);
178
+ if (!list.length)
179
+ return _jsx(Dash, {});
180
+ if (kind === 'avatar')
181
+ return _jsx(AvatarView, { value: value });
182
+ if (kind === 'cover')
183
+ return _jsx(CoverView, { value: value });
184
+ if (kind === 'image' || kind === 'media')
185
+ return _jsx(GalleryView, { value: value });
186
+ if (kind === 'poster')
187
+ return _jsx(GalleryView, { value: value, poster: true });
188
+ if (kind === 'audio' || kind === 'video') {
189
+ return (_jsx("div", { className: "flex flex-col gap-2", children: list.map((rec, i) => (_jsx(MediaPlayer, { kind: kind, rec: rec, className: kind === 'audio' ? 'w-full' : 'max-h-72 rounded-lg' }, `${rec.name}-${i}`))) }));
190
+ }
191
+ return (_jsx("div", { className: "flex flex-col gap-2", children: list.map((rec, i) => (_jsxs("div", { className: "flex min-h-9 items-center gap-2 rounded-lg border border-border px-2 hover:border-accent", children: [_jsx(FileRowContent, { rec: rec }), _jsx(FilePreviewButton, { rec: rec }), _jsx("a", { href: mediaUrl(rec.name), download: true, className: "text-muted hover:text-accent", title: t('files.download'), "aria-label": t('files.download'), children: _jsx(Download, { size: 15 }) })] }, `${rec.name}-${i}`))) }));
192
+ }
193
+ function FilePage(p) {
194
+ const kind = p.field.kind;
195
+ const multiple = p.field.hints['multiple'] === true;
196
+ const live = useHybridScope();
197
+ return (_jsx(FieldFrame, { field: p.field, value: p.value, label: p.label, required: p.required, error: p.error, mode: p.mode, geom: "none", width: "w-full", bare: p.bare, view: (v, commit) => commit && live ? (widgetFor(kind, v ?? '', (nv) => void commit(nv), multiple)) : (_jsx(ReadOnlyFiles, { kind: kind, value: v ?? '' })), edit: (v, onChange) => widgetFor(kind, v ?? '', onChange, multiple) }));
198
+ }
199
+ function FileInline(p) {
200
+ const rec = parseFileRecs(p.value)[0];
201
+ if (!rec)
202
+ return _jsx("span", { className: "text-muted", children: "\u2014" });
203
+ const kind = p.field?.kind;
204
+ const Ico = kind === 'audio' ? Music : kind === 'video' ? Film : FileText;
205
+ const sz = fileSizeLabel(rec.size);
206
+ return (_jsxs("span", { className: "inline-flex min-w-0 items-center gap-1.5 text-sm", children: [_jsx(Ico, { size: 14, className: "text-muted shrink-0" }), _jsx("span", { className: "truncate", children: rec.name }), sz && _jsx("span", { className: "shrink-0 text-xs text-muted", children: sz })] }));
207
+ }
208
+ registerRenderer({
209
+ kinds: ['image', 'media', 'poster'],
210
+ layout: multiBlock,
211
+ box: 'none',
212
+ Page: FilePage,
213
+ Inline: ImageInline,
214
+ editor: 'inplace',
215
+ });
216
+ registerRenderer({
217
+ kinds: ['file', 'document', 'audio', 'video'],
218
+ layout: multiBlock,
219
+ box: 'none',
220
+ Page: FilePage,
221
+ Inline: FileInline,
222
+ editor: 'inplace',
223
+ });
224
+ registerRenderer({
225
+ kinds: ['avatar'],
226
+ layout: multiBlock,
227
+ box: 'none',
228
+ Page: FilePage,
229
+ Inline: AvatarInline,
230
+ editor: 'inplace',
231
+ });
232
+ registerRenderer({
233
+ kinds: ['cover'],
234
+ layout: 'block',
235
+ Page: FilePage,
236
+ Inline: ImageInline,
237
+ editor: 'inplace',
238
+ });