includio-cms 0.25.0 → 0.27.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 (411) hide show
  1. package/API.md +97 -4
  2. package/CHANGELOG.md +118 -0
  3. package/DOCS.md +1 -1
  4. package/README.md +2 -0
  5. package/ROADMAP.md +14 -0
  6. package/dist/admin/auth-client.d.ts +42 -42
  7. package/dist/admin/client/account/lang.d.ts +1 -0
  8. package/dist/admin/client/account/lang.js +4 -2
  9. package/dist/admin/client/account/profile-section.svelte +2 -2
  10. package/dist/admin/client/account/security-section.svelte +27 -4
  11. package/dist/admin/client/account/sessions-section.svelte +1 -1
  12. package/dist/admin/client/admin/admin-after-login-layout-content.svelte +1 -1
  13. package/dist/admin/client/admin/admin-layout.svelte +12 -2
  14. package/dist/admin/client/admin/admin-layout.svelte.d.ts +2 -1
  15. package/dist/admin/client/admin/dashboard-page.svelte +34 -10
  16. package/dist/admin/client/collection/bulk-actions-bar.svelte +86 -44
  17. package/dist/admin/client/collection/bulk-actions-bar.svelte.d.ts +3 -1
  18. package/dist/admin/client/collection/collection-entries.svelte +52 -36
  19. package/dist/admin/client/collection/collection-entries.svelte.d.ts +3 -0
  20. package/dist/admin/client/collection/collection.svelte +28 -14
  21. package/dist/admin/client/collection/collection.svelte.d.ts +3 -0
  22. package/dist/admin/client/collection/data-table.svelte +240 -130
  23. package/dist/admin/client/collection/data-table.svelte.d.ts +9 -0
  24. package/dist/admin/client/collection/date-cell.svelte +4 -4
  25. package/dist/admin/client/collection/row-actions.svelte +2 -1
  26. package/dist/admin/client/collection/sortable-header.svelte +33 -9
  27. package/dist/admin/client/collection/state-display.svelte +102 -0
  28. package/dist/admin/client/collection/state-display.svelte.d.ts +12 -0
  29. package/dist/admin/client/collection/status-badge.svelte +99 -11
  30. package/dist/admin/client/collection/status-badge.svelte.d.ts +15 -1
  31. package/dist/admin/client/collection/table-pagination.svelte +21 -6
  32. package/dist/admin/client/collection/table-toolbar.svelte +105 -80
  33. package/dist/admin/client/collection/table-toolbar.svelte.d.ts +11 -8
  34. package/dist/admin/client/entry/entry-form.svelte +36 -11
  35. package/dist/admin/client/entry/entry-form.svelte.d.ts +1 -0
  36. package/dist/admin/client/entry/entry-header.svelte +22 -15
  37. package/dist/admin/client/entry/entry-header.svelte.d.ts +1 -0
  38. package/dist/admin/client/entry/entry.svelte +269 -165
  39. package/dist/admin/client/entry/header/a11y-header-badge.svelte +47 -0
  40. package/dist/admin/client/entry/header/a11y-header-badge.svelte.d.ts +8 -0
  41. package/dist/admin/client/entry/header/publish-panel.svelte +69 -13
  42. package/dist/admin/client/entry/header/save-indicator.svelte +57 -28
  43. package/dist/admin/client/entry/header/save-indicator.svelte.d.ts +1 -0
  44. package/dist/admin/client/entry/header/status-badge.svelte +60 -15
  45. package/dist/admin/client/entry/header/status-badge.svelte.d.ts +1 -2
  46. package/dist/admin/client/entry/header/version-history-sheet.svelte +1 -1
  47. package/dist/admin/client/entry/hybrid/hybrid-layout.svelte +74 -23
  48. package/dist/admin/client/entry/hybrid/hybrid-preview.svelte +1 -1
  49. package/dist/admin/client/entry/utils.d.ts +14 -0
  50. package/dist/admin/client/entry/utils.js +28 -0
  51. package/dist/admin/client/form/form-submission/form-submission.svelte +2 -2
  52. package/dist/admin/client/form/form-submissions.svelte +143 -194
  53. package/dist/admin/client/form/form-submissions.svelte.d.ts +2 -0
  54. package/dist/admin/client/login/lang.d.ts +3 -0
  55. package/dist/admin/client/login/lang.js +10 -4
  56. package/dist/admin/client/login/login-form.svelte +8 -1
  57. package/dist/admin/client/login/reset-password-page.svelte +24 -3
  58. package/dist/admin/client/login/schema.d.ts +14 -2
  59. package/dist/admin/client/login/schema.js +19 -8
  60. package/dist/admin/client/maintenance/maintenance-page.svelte +16 -17
  61. package/dist/admin/client/media/media-page.svelte +1 -1
  62. package/dist/admin/client/shop/coupon-edit-page.svelte +117 -13
  63. package/dist/admin/client/shop/coupon-form.svelte +282 -138
  64. package/dist/admin/client/shop/coupon-form.svelte.d.ts +1 -9
  65. package/dist/admin/client/shop/coupon-new-page.svelte +40 -10
  66. package/dist/admin/client/shop/coupon-new-page.svelte.d.ts +2 -17
  67. package/dist/admin/client/shop/coupon-schema.d.ts +28 -0
  68. package/dist/admin/client/shop/coupon-schema.js +53 -0
  69. package/dist/admin/client/shop/coupons-list-page.svelte +262 -118
  70. package/dist/admin/client/shop/coupons-list-page.svelte.d.ts +16 -1
  71. package/dist/admin/client/shop/refund-dialog.svelte +37 -1
  72. package/dist/admin/client/shop/refund-dialog.svelte.d.ts +3 -0
  73. package/dist/admin/client/shop/shipping-method-edit-page.svelte +108 -59
  74. package/dist/admin/client/shop/shipping-method-form.svelte +36 -9
  75. package/dist/admin/client/shop/shipping-method-new-page.svelte +44 -13
  76. package/dist/admin/client/shop/shipping-methods-list-page.svelte +101 -59
  77. package/dist/admin/client/shop/shop-order-detail-page.svelte +220 -84
  78. package/dist/admin/client/shop/shop-orders-list-page.svelte +302 -152
  79. package/dist/admin/client/shop/shop-orders-list-page.svelte.d.ts +18 -1
  80. package/dist/admin/client/shop/shop-products-list-page.svelte +355 -118
  81. package/dist/admin/client/shop/shop-products-list-page.svelte.d.ts +19 -1
  82. package/dist/admin/client/users/accept-invite-page.svelte +24 -3
  83. package/dist/admin/client/users/create-user-dialog.svelte +3 -8
  84. package/dist/admin/client/users/lang.d.ts +2 -0
  85. package/dist/admin/client/users/lang.js +4 -0
  86. package/dist/admin/client/users/pending-invitations.svelte +2 -9
  87. package/dist/admin/client/users/user-name-cell.svelte +20 -0
  88. package/dist/admin/client/users/user-name-cell.svelte.d.ts +9 -0
  89. package/dist/admin/client/users/user-role-badge.svelte +16 -0
  90. package/dist/admin/client/users/user-role-badge.svelte.d.ts +7 -0
  91. package/dist/admin/client/users/user-row-actions.svelte +72 -0
  92. package/dist/admin/client/users/user-row-actions.svelte.d.ts +20 -0
  93. package/dist/admin/client/users/user-sessions-sheet.svelte +2 -11
  94. package/dist/admin/client/users/users-page.svelte +283 -497
  95. package/dist/admin/client/users/users-page.svelte.d.ts +12 -1
  96. package/dist/admin/components/dashboard/form-submissions-widget.svelte +59 -74
  97. package/dist/admin/components/dashboard/recent-activity.svelte +17 -5
  98. package/dist/admin/components/dashboard/recent-entries.svelte +19 -7
  99. package/dist/admin/components/dialogs/confirmation-dialog.svelte +105 -0
  100. package/dist/admin/components/dialogs/confirmation-dialog.svelte.d.ts +13 -0
  101. package/dist/admin/components/fields/block-picker-modal.svelte +6 -0
  102. package/dist/admin/components/fields/blocks-field.svelte +46 -1
  103. package/dist/admin/components/fields/boolean-field.svelte +1 -1
  104. package/dist/admin/components/fields/field-renderer.svelte +29 -22
  105. package/dist/admin/components/fields/file-field.svelte +344 -30
  106. package/dist/admin/components/fields/icon-field.svelte +86 -0
  107. package/dist/admin/components/fields/icon-field.svelte.d.ts +8 -0
  108. package/dist/admin/components/fields/icon-picker-dialog.svelte +174 -0
  109. package/dist/admin/components/fields/icon-picker-dialog.svelte.d.ts +11 -0
  110. package/dist/admin/components/fields/media-field.svelte +16 -2
  111. package/dist/admin/components/fields/object-field.svelte +27 -7
  112. package/dist/admin/components/fields/radio-field.svelte +22 -0
  113. package/dist/admin/components/fields/relation-field.svelte +123 -97
  114. package/dist/admin/components/fields/relation-picker-dialog.svelte +2 -2
  115. package/dist/admin/components/fields/seo-field.svelte +60 -30
  116. package/dist/admin/components/fields/shop-field.svelte +219 -24
  117. package/dist/admin/components/fields/simple-array-field.svelte +321 -151
  118. package/dist/admin/components/fields/simple-array-field.svelte.d.ts +3 -0
  119. package/dist/admin/components/fields/slug-field.svelte +146 -21
  120. package/dist/admin/components/fields/text-field-wrapper.svelte +37 -20
  121. package/dist/admin/components/fields/text-field.svelte +7 -2
  122. package/dist/admin/components/fields/url-field-wrapper.svelte +10 -0
  123. package/dist/admin/components/fields/url-field.svelte +36 -23
  124. package/dist/admin/components/forms/form-error-summary.svelte +143 -0
  125. package/dist/admin/components/forms/form-error-summary.svelte.d.ts +27 -0
  126. package/dist/admin/components/layout/app-sidebar.svelte +7 -2
  127. package/dist/admin/components/layout/detail-page-shell.svelte +71 -0
  128. package/dist/admin/components/layout/detail-page-shell.svelte.d.ts +24 -0
  129. package/dist/admin/components/layout/lang.d.ts +5 -0
  130. package/dist/admin/components/layout/lang.js +10 -0
  131. package/dist/admin/components/layout/layout-renderer.svelte +71 -2
  132. package/dist/admin/components/layout/layout-renderer.svelte.d.ts +1 -0
  133. package/dist/admin/components/layout/layout-tabs.svelte +173 -0
  134. package/dist/admin/components/layout/layout-tabs.svelte.d.ts +24 -0
  135. package/dist/admin/components/layout/nav-breadcrumbs.svelte +25 -7
  136. package/dist/admin/components/layout/nav-collections.svelte +23 -36
  137. package/dist/admin/components/layout/nav-forms.svelte +19 -35
  138. package/dist/admin/components/layout/nav-main.svelte +3 -28
  139. package/dist/admin/components/layout/nav-search.svelte +70 -2
  140. package/dist/admin/components/layout/nav-section.svelte +77 -0
  141. package/dist/admin/components/layout/nav-section.svelte.d.ts +22 -0
  142. package/dist/admin/components/layout/nav-shop.svelte +3 -27
  143. package/dist/admin/components/layout/nav-singletons.svelte +16 -28
  144. package/dist/admin/components/layout/page-header.stories.svelte +93 -0
  145. package/dist/admin/components/layout/page-header.stories.svelte.d.ts +27 -0
  146. package/dist/admin/components/layout/page-header.svelte +68 -0
  147. package/dist/admin/components/layout/page-header.svelte.d.ts +17 -0
  148. package/dist/admin/components/layout/site-header.svelte +9 -0
  149. package/dist/admin/components/layout/site-header.svelte.d.ts +2 -17
  150. package/dist/admin/components/media/file/file-name-input.svelte +6 -2
  151. package/dist/admin/components/media/file/file-preview.svelte +130 -17
  152. package/dist/admin/components/media/file-upload.svelte +16 -7
  153. package/dist/admin/components/media/file-upload.svelte.d.ts +1 -0
  154. package/dist/admin/components/media/files-list.svelte +153 -53
  155. package/dist/admin/components/media/files-list.svelte.d.ts +1 -0
  156. package/dist/admin/components/media/media-library.svelte +577 -198
  157. package/dist/admin/components/media/media-library.svelte.d.ts +4 -0
  158. package/dist/admin/components/media/media-selector.svelte +4 -2
  159. package/dist/admin/components/media/media-selector.svelte.d.ts +1 -0
  160. package/dist/admin/components/media/tag-sidebar.svelte +4 -4
  161. package/dist/admin/components/tiptap/FigureNodeView.svelte +10 -0
  162. package/dist/admin/components/tiptap/bubble-menu.svelte +104 -0
  163. package/dist/admin/components/tiptap/bubble-menu.svelte.d.ts +19 -0
  164. package/dist/admin/components/tiptap/content-editor.svelte +28 -24
  165. package/dist/admin/components/tiptap/editor-toolbar.svelte +7 -7
  166. package/dist/admin/components/tiptap/extensions.js +5 -1
  167. package/dist/admin/components/tiptap/image-dialog.svelte +5 -1
  168. package/dist/admin/components/tiptap/link-dialog.svelte +2 -0
  169. package/dist/admin/components/tiptap/tiptap-editor.svelte +18 -20
  170. package/dist/admin/components/tiptap/video-dialog.svelte +1 -1
  171. package/dist/admin/components/variant-form/VariantAttributeRenderer.svelte +109 -0
  172. package/dist/admin/components/variant-form/VariantAttributeRenderer.svelte.d.ts +9 -0
  173. package/dist/admin/helpers/build-icon-set-map.d.ts +8 -0
  174. package/dist/admin/helpers/build-icon-set-map.js +16 -0
  175. package/dist/admin/helpers/index.d.ts +2 -0
  176. package/dist/admin/helpers/index.js +2 -0
  177. package/dist/admin/i18n/errors.d.ts +140 -0
  178. package/dist/admin/i18n/errors.js +151 -0
  179. package/dist/admin/remote/entry.remote.d.ts +59 -4
  180. package/dist/admin/remote/entry.remote.js +239 -62
  181. package/dist/admin/remote/shop.remote.d.ts +87 -48
  182. package/dist/admin/remote/shop.remote.js +70 -8
  183. package/dist/admin/shared/password-generate.d.ts +6 -0
  184. package/dist/admin/shared/password-generate.js +40 -0
  185. package/dist/admin/shared/password-schema.d.ts +6 -0
  186. package/dist/admin/shared/password-schema.js +10 -3
  187. package/dist/admin/state/icon-sets.svelte.d.ts +9 -0
  188. package/dist/admin/state/icon-sets.svelte.js +20 -0
  189. package/dist/admin/styles/admin.css +23 -6
  190. package/dist/admin/styles/tokens.md +244 -0
  191. package/dist/admin/utils/accordionActivation.d.ts +13 -0
  192. package/dist/admin/utils/accordionActivation.js +35 -0
  193. package/dist/admin/utils/entryLabel.d.ts +23 -0
  194. package/dist/admin/utils/entryLabel.js +51 -12
  195. package/dist/admin/utils/field-a11y.d.ts +29 -0
  196. package/dist/admin/utils/field-a11y.js +23 -0
  197. package/dist/admin/utils/fieldPathElement.d.ts +9 -0
  198. package/dist/admin/utils/fieldPathElement.js +18 -0
  199. package/dist/admin/utils/fileDisplay.d.ts +10 -0
  200. package/dist/admin/utils/fileDisplay.js +26 -0
  201. package/dist/admin/utils/flattenFormErrors.d.ts +19 -0
  202. package/dist/admin/utils/flattenFormErrors.js +102 -0
  203. package/dist/admin/utils/formatters.d.ts +12 -0
  204. package/dist/admin/utils/{formatDate.js → formatters.js} +23 -2
  205. package/dist/admin/utils/scrollWithin.d.ts +9 -0
  206. package/dist/admin/utils/scrollWithin.js +32 -0
  207. package/dist/admin/utils/tabActivation.d.ts +12 -0
  208. package/dist/admin/utils/tabActivation.js +24 -0
  209. package/dist/cli/scaffold/admin.js +2 -2
  210. package/dist/cms/runtime/schema.d.ts +1 -0
  211. package/dist/cms/runtime/schema.js +1 -0
  212. package/dist/cms/runtime/types.d.ts +80 -7
  213. package/dist/components/ui/accordion/accordion-content.svelte +17 -3
  214. package/dist/components/ui/accordion/accordion.stories.svelte +21 -1
  215. package/dist/components/ui/alert/alert.stories.svelte +14 -0
  216. package/dist/components/ui/alert-dialog/alert-dialog.stories.svelte +45 -0
  217. package/dist/components/ui/alert-dialog/alert-dialog.stories.svelte.d.ts +27 -0
  218. package/dist/components/ui/avatar/avatar.stories.svelte +27 -0
  219. package/dist/components/ui/badge/badge.stories.svelte +15 -0
  220. package/dist/components/ui/breadcrumb/breadcrumb.stories.svelte +47 -0
  221. package/dist/components/ui/breadcrumb/breadcrumb.svelte +1 -1
  222. package/dist/components/ui/button/button.stories.svelte +53 -6
  223. package/dist/components/ui/button/button.svelte +39 -5
  224. package/dist/components/ui/button/button.svelte.d.ts +4 -0
  225. package/dist/components/ui/button-group/button-group.stories.svelte +44 -0
  226. package/dist/components/ui/button-group/button-group.stories.svelte.d.ts +27 -0
  227. package/dist/components/ui/calendar/calendar.stories.svelte +36 -0
  228. package/dist/components/ui/calendar/calendar.stories.svelte.d.ts +27 -0
  229. package/dist/components/ui/card/card.stories.svelte +7 -0
  230. package/dist/components/ui/carousel/carousel.stories.svelte +43 -0
  231. package/dist/components/ui/carousel/carousel.stories.svelte.d.ts +27 -0
  232. package/dist/components/ui/checkbox/checkbox.stories.svelte +67 -0
  233. package/dist/components/ui/checkbox/checkbox.stories.svelte.d.ts +27 -0
  234. package/dist/components/ui/checkbox/checkbox.svelte +1 -1
  235. package/dist/components/ui/command/command.stories.svelte +18 -0
  236. package/dist/components/ui/data-table/data-table.stories.svelte +61 -0
  237. package/dist/components/ui/data-table/data-table.stories.svelte.d.ts +18 -0
  238. package/dist/components/ui/dialog/dialog-content.svelte +5 -0
  239. package/dist/components/ui/dialog/dialog-content.svelte.d.ts +2 -0
  240. package/dist/components/ui/dialog/dialog.stories.svelte +35 -0
  241. package/dist/components/ui/dropdown-menu/dropdown-menu.stories.svelte +74 -0
  242. package/dist/components/ui/dropdown-menu/dropdown-menu.stories.svelte.d.ts +27 -0
  243. package/dist/components/ui/field/field-context.svelte.d.ts +22 -0
  244. package/dist/components/ui/field/field-context.svelte.js +9 -0
  245. package/dist/components/ui/field/field-control.svelte +18 -0
  246. package/dist/components/ui/field/field-control.svelte.d.ts +8 -0
  247. package/dist/components/ui/field/field-description.svelte +12 -0
  248. package/dist/components/ui/field/field-error.svelte +14 -6
  249. package/dist/components/ui/field/field-label.svelte +10 -0
  250. package/dist/components/ui/field/field.stories.svelte +95 -9
  251. package/dist/components/ui/field/field.svelte +57 -0
  252. package/dist/components/ui/field/field.svelte.d.ts +2 -0
  253. package/dist/components/ui/field/index.d.ts +3 -1
  254. package/dist/components/ui/field/index.js +4 -2
  255. package/dist/components/ui/form/form-field-errors.svelte +1 -1
  256. package/dist/components/ui/form/form.stories.svelte +25 -0
  257. package/dist/components/ui/form/form.stories.svelte.d.ts +26 -0
  258. package/dist/components/ui/input/input.stories.svelte +26 -0
  259. package/dist/components/ui/input-group/input-group-input.svelte.d.ts +1 -1
  260. package/dist/components/ui/input-group/input-group.stories.svelte +43 -0
  261. package/dist/components/ui/input-group/input-group.stories.svelte.d.ts +27 -0
  262. package/dist/components/ui/item/item.stories.svelte +61 -0
  263. package/dist/components/ui/item/item.stories.svelte.d.ts +27 -0
  264. package/dist/components/ui/label/label.stories.svelte +7 -0
  265. package/dist/components/ui/live-region/index.d.ts +1 -0
  266. package/dist/components/ui/live-region/index.js +1 -0
  267. package/dist/components/ui/live-region/live-region-demo.svelte +32 -0
  268. package/dist/components/ui/live-region/live-region-demo.svelte.d.ts +7 -0
  269. package/dist/components/ui/live-region/live-region.stories.svelte +23 -0
  270. package/dist/components/ui/live-region/live-region.stories.svelte.d.ts +26 -0
  271. package/dist/components/ui/live-region/live-region.svelte +12 -0
  272. package/dist/components/ui/live-region/live-region.svelte.d.ts +8 -0
  273. package/dist/components/ui/popover/popover.stories.svelte +34 -0
  274. package/dist/components/ui/radio-group/radio-group.stories.svelte +58 -0
  275. package/dist/components/ui/radio-group/radio-group.stories.svelte.d.ts +27 -0
  276. package/dist/components/ui/resizable/resizable.stories.svelte +56 -0
  277. package/dist/components/ui/resizable/resizable.stories.svelte.d.ts +27 -0
  278. package/dist/components/ui/select/select.stories.svelte +49 -0
  279. package/dist/components/ui/separator/separator.stories.svelte +18 -0
  280. package/dist/components/ui/sheet/sheet.stories.svelte +34 -0
  281. package/dist/components/ui/sidebar/sidebar-input.svelte.d.ts +1 -1
  282. package/dist/components/ui/sidebar/sidebar-menu-button.svelte +1 -0
  283. package/dist/components/ui/sidebar/sidebar-trigger.svelte +1 -1
  284. package/dist/components/ui/sidebar/sidebar.stories.svelte +72 -0
  285. package/dist/components/ui/sidebar/sidebar.stories.svelte.d.ts +27 -0
  286. package/dist/components/ui/skeleton/skeleton.stories.svelte +39 -0
  287. package/dist/components/ui/skeleton/skeleton.stories.svelte.d.ts +27 -0
  288. package/dist/components/ui/skeleton/skeleton.svelte +6 -0
  289. package/dist/components/ui/sonner/index.d.ts +1 -1
  290. package/dist/components/ui/sonner/index.js +1 -1
  291. package/dist/components/ui/sonner/sonner.stories.svelte +7 -0
  292. package/dist/components/ui/sonner/sonner.svelte +17 -1
  293. package/dist/components/ui/sonner/sonner.svelte.d.ts +6 -0
  294. package/dist/components/ui/spinner/spinner.stories.svelte +30 -0
  295. package/dist/components/ui/spinner/spinner.stories.svelte.d.ts +27 -0
  296. package/dist/components/ui/switch/switch.stories.svelte +56 -0
  297. package/dist/components/ui/switch/switch.stories.svelte.d.ts +27 -0
  298. package/dist/components/ui/table/table-cell.svelte +1 -1
  299. package/dist/components/ui/table/table-head.svelte +1 -1
  300. package/dist/components/ui/table/table.stories.svelte +68 -0
  301. package/dist/components/ui/table/table.stories.svelte.d.ts +27 -0
  302. package/dist/components/ui/table/table.svelte +1 -1
  303. package/dist/components/ui/tabs/tabs.stories.svelte +48 -0
  304. package/dist/components/ui/tabs/tabs.stories.svelte.d.ts +27 -0
  305. package/dist/components/ui/textarea/textarea.stories.svelte +21 -0
  306. package/dist/components/ui/toggle/toggle.stories.svelte +23 -0
  307. package/dist/components/ui/toggle-group/toggle-group.stories.svelte +43 -0
  308. package/dist/components/ui/tooltip/tooltip.stories.svelte +46 -6
  309. package/dist/core/cms.d.ts +11 -2
  310. package/dist/core/cms.js +29 -0
  311. package/dist/core/fields/fieldSchemaToTs.d.ts +7 -0
  312. package/dist/core/fields/fieldSchemaToTs.js +241 -90
  313. package/dist/core/fields/layoutUtils.d.ts +4 -1
  314. package/dist/core/fields/layoutUtils.js +41 -4
  315. package/dist/core/fields/resolveSeo.d.ts +70 -0
  316. package/dist/core/fields/resolveSeo.js +88 -0
  317. package/dist/core/fields/seoFieldDescriptor.d.ts +43 -0
  318. package/dist/core/fields/seoFieldDescriptor.js +74 -0
  319. package/dist/core/fields/slugPath.d.ts +13 -0
  320. package/dist/core/fields/slugPath.js +32 -0
  321. package/dist/core/fields/urlUtils.d.ts +8 -0
  322. package/dist/core/fields/urlUtils.js +27 -0
  323. package/dist/core/index.d.ts +1 -0
  324. package/dist/core/index.js +1 -0
  325. package/dist/core/server/entries/operations/create.js +13 -0
  326. package/dist/core/server/entries/operations/get.d.ts +7 -0
  327. package/dist/core/server/entries/operations/get.js +10 -6
  328. package/dist/core/server/entries/operations/slugUniqueness.d.ts +37 -0
  329. package/dist/core/server/entries/operations/slugUniqueness.js +116 -0
  330. package/dist/core/server/entries/operations/update.d.ts +6 -1
  331. package/dist/core/server/entries/operations/update.js +24 -1
  332. package/dist/core/server/fields/slugResolver.d.ts +3 -13
  333. package/dist/core/server/fields/slugResolver.js +8 -37
  334. package/dist/core/server/generator/fields.d.ts +2 -0
  335. package/dist/core/server/generator/fields.js +44 -18
  336. package/dist/core/server/generator/formFields.js +2 -1
  337. package/dist/core/server/generator/generator.js +6 -5
  338. package/dist/core/server/generator/utils.d.ts +1 -0
  339. package/dist/core/server/generator/utils.js +4 -0
  340. package/dist/db-postgres/schema/shop/order.d.ts +37 -1
  341. package/dist/db-postgres/schema/shop/order.js +3 -1
  342. package/dist/db-postgres/schema/shop/payment.d.ts +20 -0
  343. package/dist/db-postgres/schema/shop/payment.js +4 -1
  344. package/dist/db-postgres/schema/shop/product.d.ts +20 -0
  345. package/dist/db-postgres/schema/shop/product.js +3 -1
  346. package/dist/db-postgres/schema/shop/productVariant.d.ts +12 -2
  347. package/dist/db-postgres/schema/shop/productVariant.js +22 -0
  348. package/dist/shop/cart/types.d.ts +1 -0
  349. package/dist/shop/client/index.d.ts +54 -0
  350. package/dist/shop/client/index.js +5 -1
  351. package/dist/shop/expiry.d.ts +35 -0
  352. package/dist/shop/expiry.js +68 -0
  353. package/dist/shop/http/balance-handler.d.ts +20 -0
  354. package/dist/shop/http/balance-handler.js +91 -0
  355. package/dist/shop/http/cart-handler.js +19 -0
  356. package/dist/shop/http/checkout-handler.js +19 -1
  357. package/dist/shop/http/index.d.ts +2 -0
  358. package/dist/shop/http/index.js +2 -0
  359. package/dist/shop/http/upcoming-handler.d.ts +16 -0
  360. package/dist/shop/http/upcoming-handler.js +65 -0
  361. package/dist/shop/http/webhook-handler.js +46 -9
  362. package/dist/shop/index.d.ts +4 -1
  363. package/dist/shop/index.js +7 -1
  364. package/dist/shop/server/balance-payment.d.ts +40 -0
  365. package/dist/shop/server/balance-payment.js +140 -0
  366. package/dist/shop/server/cart-hydrate.js +2 -0
  367. package/dist/shop/server/init.d.ts +14 -0
  368. package/dist/shop/server/init.js +35 -0
  369. package/dist/shop/server/orders.d.ts +35 -0
  370. package/dist/shop/server/orders.js +155 -2
  371. package/dist/shop/server/payment-policy.d.ts +35 -0
  372. package/dist/shop/server/payment-policy.js +55 -0
  373. package/dist/shop/server/payments.d.ts +29 -0
  374. package/dist/shop/server/payments.js +64 -0
  375. package/dist/shop/server/populate.d.ts +1 -1
  376. package/dist/shop/server/refund.d.ts +17 -12
  377. package/dist/shop/server/refund.js +96 -13
  378. package/dist/shop/server/shop-data.d.ts +6 -1
  379. package/dist/shop/server/shop-data.js +44 -7
  380. package/dist/shop/template.d.ts +13 -0
  381. package/dist/shop/template.js +98 -0
  382. package/dist/shop/types.d.ts +142 -1
  383. package/dist/shop/variant-attributes.d.ts +28 -0
  384. package/dist/shop/variant-attributes.js +69 -0
  385. package/dist/sveltekit/server/handle.js +17 -0
  386. package/dist/sveltekit/server/index.d.ts +1 -0
  387. package/dist/sveltekit/server/index.js +2 -0
  388. package/dist/types/cms.d.ts +4 -3
  389. package/dist/types/cms.schema.d.ts +1 -1
  390. package/dist/types/cms.schema.js +13 -2
  391. package/dist/types/fields.d.ts +56 -2
  392. package/dist/types/index.d.ts +2 -2
  393. package/dist/types/index.js +1 -1
  394. package/dist/types/layout.d.ts +35 -2
  395. package/dist/types/plugins.d.ts +40 -0
  396. package/dist/types/plugins.js +4 -1
  397. package/dist/updates/0.26.0/index.d.ts +2 -0
  398. package/dist/updates/0.26.0/index.js +51 -0
  399. package/dist/updates/0.26.1/index.d.ts +2 -0
  400. package/dist/updates/0.26.1/index.js +19 -0
  401. package/dist/updates/0.27.0/index.d.ts +2 -0
  402. package/dist/updates/0.27.0/index.js +50 -0
  403. package/dist/updates/index.js +7 -1
  404. package/package.json +29 -7
  405. package/dist/admin/client/collection/empty-state.svelte +0 -28
  406. package/dist/admin/client/collection/empty-state.svelte.d.ts +0 -9
  407. package/dist/admin/client/form/submission-status-badge.svelte +0 -41
  408. package/dist/admin/client/form/submission-status-badge.svelte.d.ts +0 -7
  409. package/dist/admin/components/media/file-preview.svelte +0 -51
  410. package/dist/admin/components/media/file-preview.svelte.d.ts +0 -6
  411. package/dist/admin/utils/formatDate.d.ts +0 -5
@@ -1,18 +1,110 @@
1
1
  <script lang="ts">
2
2
  import * as Dialog from '../../../components/ui/dialog/index.js';
3
3
  import { onMount } from 'svelte';
4
- import FilePreview from '../media/file-preview.svelte';
5
- import { buttonVariants } from '../../../components/ui/button/button.svelte';
6
- import MediaLibrary from '../media/media-library.svelte';
4
+ import Button from '../../../components/ui/button/button.svelte';
7
5
  import type { FileField } from '../../../types/fields.js';
8
- import z from 'zod';
6
+ import type { InterfaceLanguage } from '../../../types/languages.js';
7
+ import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
8
+ import Plus from '@tabler/icons-svelte/icons/plus';
9
+ import X from '@tabler/icons-svelte/icons/x';
10
+ import ChevronLeft from '@tabler/icons-svelte/icons/chevron-left';
11
+ import ChevronRight from '@tabler/icons-svelte/icons/chevron-right';
12
+ import AlertTriangle from '@tabler/icons-svelte/icons/alert-triangle';
13
+ import FileMiniature from '../media/file/file-miniature.svelte';
14
+ import { getRemotes } from '../../../sveltekit/index.js';
15
+ import MediaSelector from '../media/media-selector.svelte';
16
+ import type { MediaFile } from '../../../types/media.js';
17
+ import { formatFileSize, getFileExtension } from '../../utils/fileDisplay.js';
18
+
19
+ const lang: Record<
20
+ InterfaceLanguage,
21
+ {
22
+ selectFile: string;
23
+ addFile: string;
24
+ remove: string;
25
+ change: string;
26
+ of: string;
27
+ missingFile: string;
28
+ removeAria: string;
29
+ }
30
+ > = {
31
+ pl: {
32
+ selectFile: 'Wybierz plik',
33
+ addFile: 'Dodaj plik',
34
+ remove: 'Usuń',
35
+ change: 'Zmień',
36
+ of: 'z',
37
+ missingFile: 'Brakujący plik',
38
+ removeAria: 'Usuń plik'
39
+ },
40
+ en: {
41
+ selectFile: 'Select file',
42
+ addFile: 'Add file',
43
+ remove: 'Remove',
44
+ change: 'Change',
45
+ of: 'of',
46
+ missingFile: 'Missing file',
47
+ removeAria: 'Remove file'
48
+ }
49
+ };
50
+
51
+ const interfaceLanguage = useInterfaceLanguage();
52
+ const remotes = getRemotes();
53
+
54
+ let currentIndex = $state(0);
55
+ let dialogOpen = $state(false);
56
+
57
+ function openPicker() {
58
+ dialogOpen = true;
59
+ }
9
60
 
10
61
  type Props = {
11
62
  field: FileField;
12
63
  value: string | string[] | undefined;
13
64
  };
14
65
 
15
- let { field, value = $bindable(), ...props }: Props = $props();
66
+ let { field, value = $bindable() }: Props = $props();
67
+
68
+ // Single-file state: fetch MediaFile via a plain fetch inside an $effect instead of
69
+ // `$derived(remotes.getFileById(value))`. Reason: wrapping the remote query in a
70
+ // $derived couples its lifecycle to the derivation's refcounting — rapid toggling
71
+ // of value ('' → id) around dialog unmount triggered "query instance is no longer
72
+ // active" errors. Wzorzec z media-field.
73
+ let singleFile = $state<MediaFile | null>(null);
74
+ let singleFileReady = $state(false);
75
+ $effect(() => {
76
+ const v = value;
77
+ if (typeof v !== 'string' || !v) {
78
+ singleFile = null;
79
+ singleFileReady = true;
80
+ return;
81
+ }
82
+ singleFileReady = false;
83
+ let cancelled = false;
84
+ (async () => {
85
+ try {
86
+ const data = await remotes.getFileById(v);
87
+ if (!cancelled) {
88
+ singleFile = data as MediaFile;
89
+ singleFileReady = true;
90
+ }
91
+ } catch {
92
+ if (!cancelled) {
93
+ singleFile = null;
94
+ singleFileReady = true;
95
+ }
96
+ }
97
+ })();
98
+ return () => {
99
+ cancelled = true;
100
+ };
101
+ });
102
+
103
+ const mFilesQuery = $derived(
104
+ Array.isArray(value) && value.length > 0
105
+ ? remotes.getMediaFiles({ data: { ids: value as string[] } })
106
+ : null
107
+ );
16
108
 
17
109
  onMount(() => {
18
110
  if (value === undefined) {
@@ -21,33 +113,255 @@
21
113
  });
22
114
  </script>
23
115
 
24
- {#if value !== undefined}
25
- <Dialog.Root>
26
- <Dialog.Trigger type="button" class={buttonVariants({ variant: 'outline' })}
27
- >Select File</Dialog.Trigger
28
- >
29
- <Dialog.Content class="max-h-[80vh] w-[80vw] !max-w-none overflow-y-scroll">
30
- <MediaLibrary bind:selected={value} multiple={field.multiple} accept={field.accept} />
31
- </Dialog.Content>
32
- </Dialog.Root>
116
+ {#snippet filePreview(file: MediaFile)}
117
+ <div class="aspect-square w-full overflow-hidden rounded-2xl border">
118
+ <FileMiniature {file} />
119
+ </div>
120
+ {/snippet}
33
121
 
34
- <div class="flex flex-wrap gap-2">
35
- {#if field.multiple && Array.isArray(value)}
36
- {#each value as item}
37
- {#if z.string().uuid().safeParse(item).success}
38
- <FilePreview
39
- id={item}
40
- class="aspect-square w-full max-w-[100px] object-cover object-center"
41
- />
122
+ {#snippet missingPlaceholder()}
123
+ <div
124
+ class="flex aspect-square w-full flex-col items-center justify-center gap-2 rounded-2xl border-2 border-dashed border-warning/50 bg-warning/5 p-6"
125
+ >
126
+ <div class="rounded-full bg-warning/10 p-3">
127
+ <AlertTriangle class="h-6 w-6 text-warning" />
128
+ </div>
129
+ <span class="text-sm text-warning">{lang[interfaceLanguage.current].missingFile}</span>
130
+ </div>
131
+ {/snippet}
132
+
133
+ {#snippet listRow(file: MediaFile, onRemove: () => void)}
134
+ {@const ext = getFileExtension(file.url)}
135
+ <div class="flex items-center gap-3 rounded-lg border bg-card p-2 shadow-sm">
136
+ <div class="relative aspect-square w-10 shrink-0 overflow-hidden rounded">
137
+ <FileMiniature {file} mode="thumb" />
138
+ </div>
139
+ <div class="min-w-0 flex-1">
140
+ <div class="flex items-center gap-1.5 min-w-0">
141
+ <p class="truncate text-sm font-medium">{file.name}</p>
142
+ {#if ext}
143
+ <span class="shrink-0 rounded bg-muted px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground">{ext}</span>
42
144
  {/if}
43
- {/each}
44
- {:else if typeof value === 'string'}
45
- {#if z.string().uuid().safeParse(value).success}
46
- <FilePreview
47
- id={value}
48
- class="aspect-square w-full max-w-[100px] object-cover object-center"
49
- />
50
- {/if}
145
+ </div>
146
+ <p class="text-xs text-muted-foreground">{formatFileSize(file.size)}</p>
147
+ </div>
148
+ {#if !field.required || (Array.isArray(value) && value.length > 1)}
149
+ <Button
150
+ variant="ghost"
151
+ size="icon"
152
+ class="h-7 w-7 shrink-0 text-muted-foreground hover:text-destructive"
153
+ aria-label={lang[interfaceLanguage.current].removeAria}
154
+ onclick={onRemove}
155
+ >
156
+ <X class="h-4 w-4" />
157
+ </Button>
51
158
  {/if}
52
159
  </div>
160
+ {/snippet}
161
+
162
+ {#snippet listMissingRow(onRemove: () => void)}
163
+ <div class="flex items-center gap-3 rounded-lg border border-warning/40 bg-warning/5 p-2">
164
+ <div class="flex aspect-square w-10 shrink-0 items-center justify-center rounded bg-warning/10">
165
+ <AlertTriangle class="h-4 w-4 text-warning" />
166
+ </div>
167
+ <p class="min-w-0 flex-1 truncate text-sm text-warning">{lang[interfaceLanguage.current].missingFile}</p>
168
+ {#if !field.required || (Array.isArray(value) && value.length > 1)}
169
+ <Button
170
+ variant="ghost"
171
+ size="icon"
172
+ class="h-7 w-7 shrink-0 text-muted-foreground hover:text-destructive"
173
+ aria-label={lang[interfaceLanguage.current].removeAria}
174
+ onclick={onRemove}
175
+ >
176
+ <X class="h-4 w-4" />
177
+ </Button>
178
+ {/if}
179
+ </div>
180
+ {/snippet}
181
+
182
+ {#snippet mediaActions(onRemove: () => void)}
183
+ <div class="flex items-center justify-between gap-2 mt-1.5">
184
+ <Button size="sm" variant="secondary" class="h-8" onclick={openPicker}>
185
+ <Plus class="h-4 w-4" />
186
+ {lang[interfaceLanguage.current].change}
187
+ </Button>
188
+ {#if !field.required}
189
+ <Button variant="destructive" size="sm" class="h-8" onclick={onRemove}>
190
+ <X class="h-4 w-4" />
191
+ {lang[interfaceLanguage.current].remove}
192
+ </Button>
193
+ {/if}
194
+ </div>
195
+ {/snippet}
196
+
197
+ {#if value !== undefined}
198
+ {#if field.display === 'list'}
199
+ <!-- Compact list variant (non-visual files: PDF, audio, raw) -->
200
+ <div class="space-y-2 max-w-md">
201
+ {#if typeof value === 'string' && value !== ''}
202
+ {#if !singleFileReady}
203
+ <div class="animate-pulse bg-muted h-14 rounded-lg"></div>
204
+ {:else if singleFile}
205
+ {@render listRow(singleFile, () => { value = ''; })}
206
+ {:else}
207
+ {@render listMissingRow(() => { value = ''; })}
208
+ {/if}
209
+ {:else if Array.isArray(value) && value.length > 0}
210
+ {#if !mFilesQuery?.ready}
211
+ <div class="animate-pulse bg-muted h-14 rounded-lg"></div>
212
+ {:else if mFilesQuery?.current}
213
+ {@const files = mFilesQuery.current}
214
+ {#each value as id, i (id + '__' + i)}
215
+ {@const file = files.find((f) => f.id === id)}
216
+ {#if file}
217
+ {@render listRow(file, () => {
218
+ if (Array.isArray(value)) {
219
+ value = value.filter((_, idx) => idx !== i);
220
+ }
221
+ })}
222
+ {:else}
223
+ {@render listMissingRow(() => {
224
+ if (Array.isArray(value)) {
225
+ value = value.filter((_, idx) => idx !== i);
226
+ }
227
+ })}
228
+ {/if}
229
+ {/each}
230
+ {/if}
231
+ {/if}
232
+
233
+ {#if field.multiple || !value || (typeof value === 'string' && value === '') || (Array.isArray(value) && value.length === 0)}
234
+ <Button variant="outline" size="sm" onclick={openPicker}>
235
+ <Plus class="h-4 w-4" />
236
+ {field.multiple && Array.isArray(value) && value.length > 0
237
+ ? lang[interfaceLanguage.current].addFile
238
+ : lang[interfaceLanguage.current].selectFile}
239
+ </Button>
240
+ {:else}
241
+ <Button variant="outline" size="sm" onclick={openPicker}>
242
+ <Plus class="h-4 w-4" />
243
+ {lang[interfaceLanguage.current].change}
244
+ </Button>
245
+ {/if}
246
+ </div>
247
+ {:else if value && ((typeof value === 'string' && value !== '') || (Array.isArray(value) && value.length > 0))}
248
+ <div class="max-w-64">
249
+ {#if typeof value === 'string'}
250
+ {#if !singleFileReady}
251
+ <div class="animate-pulse bg-muted w-full aspect-square rounded-2xl"></div>
252
+ {:else if singleFile}
253
+ {@render filePreview(singleFile)}
254
+ {@render mediaActions(() => { value = ''; })}
255
+ {:else}
256
+ {@render missingPlaceholder()}
257
+ {@render mediaActions(() => { value = ''; })}
258
+ {/if}
259
+ {:else if Array.isArray(value) && value.length > 0}
260
+ {@const valueArr = value}
261
+ {#if !mFilesQuery?.ready}
262
+ <div class="animate-pulse bg-muted w-full aspect-square rounded-2xl"></div>
263
+ {:else if mFilesQuery?.current}
264
+ {@const file = mFilesQuery.current[currentIndex]}
265
+ {#if file}
266
+ <div class="relative">
267
+ {@render filePreview(file)}
268
+
269
+ {#if valueArr.length > 1}
270
+ <div class="absolute inset-x-0 top-1/2 flex -translate-y-1/2 justify-between px-1 pointer-events-none">
271
+ <button
272
+ type="button"
273
+ class="pointer-events-auto flex h-8 w-8 items-center justify-center rounded-full bg-white/80 backdrop-blur shadow-md transition hover:bg-white hover:scale-105 dark:bg-background/80 dark:hover:bg-background"
274
+ onclick={() => { currentIndex = currentIndex > 0 ? currentIndex - 1 : valueArr.length - 1; }}
275
+ >
276
+ <ChevronLeft class="h-5 w-5" />
277
+ </button>
278
+ <button
279
+ type="button"
280
+ class="pointer-events-auto flex h-8 w-8 items-center justify-center rounded-full bg-white/80 backdrop-blur shadow-md transition hover:bg-white hover:scale-105 dark:bg-background/80 dark:hover:bg-background"
281
+ onclick={() => { currentIndex = currentIndex < valueArr.length - 1 ? currentIndex + 1 : 0; }}
282
+ >
283
+ <ChevronRight class="h-5 w-5" />
284
+ </button>
285
+ </div>
286
+ <div class="absolute top-2 right-2 rounded-full bg-plum-darker/60 px-2 py-0.5 text-xs font-medium text-white backdrop-blur">
287
+ {currentIndex + 1} / {valueArr.length}
288
+ </div>
289
+ {/if}
290
+ </div>
291
+ {@render mediaActions(() => {
292
+ if (Array.isArray(value)) {
293
+ const next = value.filter((_, i) => i !== currentIndex);
294
+ value = field.multiple ? next : next[0] ?? '';
295
+ if (currentIndex >= next.length) currentIndex = Math.max(0, next.length - 1);
296
+ }
297
+ })}
298
+ {:else}
299
+ <div class="relative">
300
+ {@render missingPlaceholder()}
301
+ {#if valueArr.length > 1}
302
+ <div class="absolute inset-x-0 top-1/2 flex -translate-y-1/2 justify-between px-1 pointer-events-none">
303
+ <button
304
+ type="button"
305
+ class="pointer-events-auto flex h-8 w-8 items-center justify-center rounded-full bg-white/80 backdrop-blur shadow-md transition hover:bg-white hover:scale-105 dark:bg-background/80 dark:hover:bg-background"
306
+ onclick={() => { currentIndex = currentIndex > 0 ? currentIndex - 1 : valueArr.length - 1; }}
307
+ >
308
+ <ChevronLeft class="h-5 w-5" />
309
+ </button>
310
+ <button
311
+ type="button"
312
+ class="pointer-events-auto flex h-8 w-8 items-center justify-center rounded-full bg-white/80 backdrop-blur shadow-md transition hover:bg-white hover:scale-105 dark:bg-background/80 dark:hover:bg-background"
313
+ onclick={() => { currentIndex = currentIndex < valueArr.length - 1 ? currentIndex + 1 : 0; }}
314
+ >
315
+ <ChevronRight class="h-5 w-5" />
316
+ </button>
317
+ </div>
318
+ <div class="absolute top-2 right-2 rounded-full bg-plum-darker/60 px-2 py-0.5 text-xs font-medium text-white backdrop-blur">
319
+ {currentIndex + 1} / {valueArr.length}
320
+ </div>
321
+ {/if}
322
+ </div>
323
+ {@render mediaActions(() => {
324
+ if (Array.isArray(value)) {
325
+ const next = value.filter((_, i) => i !== currentIndex);
326
+ value = field.multiple ? next : next[0] ?? '';
327
+ if (currentIndex >= next.length) currentIndex = Math.max(0, next.length - 1);
328
+ }
329
+ })}
330
+ {/if}
331
+ {:else}
332
+ {@render missingPlaceholder()}
333
+ {@render mediaActions(() => { value = field.multiple ? [] : ''; })}
334
+ {/if}
335
+ {/if}
336
+ </div>
337
+ {:else}
338
+ <button
339
+ type="button"
340
+ class="flex aspect-square max-w-64 w-full flex-col items-center justify-center gap-2 rounded-2xl border-2 border-dashed border-border bg-card/80 p-6 transition-colors hover:border-primary/50 hover:bg-card"
341
+ onclick={openPicker}
342
+ >
343
+ <div class="rounded-full bg-muted p-3">
344
+ <Plus class="h-6 w-6 text-muted-foreground" />
345
+ </div>
346
+ <span class="text-sm text-muted-foreground">{lang[interfaceLanguage.current].selectFile}</span>
347
+ </button>
348
+ {/if}
349
+
350
+ <Dialog.Root bind:open={dialogOpen}>
351
+ <Dialog.Content
352
+ fullscreenMobile
353
+ class="flex h-[85vh] w-full max-w-6xl! flex-col overflow-hidden p-0 sm:max-w-6xl!"
354
+ >
355
+ <Dialog.Title class="sr-only">{lang[interfaceLanguage.current].selectFile}</Dialog.Title>
356
+ <Dialog.Description class="sr-only">{lang[interfaceLanguage.current].selectFile}</Dialog.Description>
357
+ <MediaSelector
358
+ bind:selected={value}
359
+ multiple={field.multiple}
360
+ accept={field.accept}
361
+ maxSizeMB={field.maxSizeMB}
362
+ onConfirm={() => (dialogOpen = false)}
363
+ onCancel={() => (dialogOpen = false)}
364
+ />
365
+ </Dialog.Content>
366
+ </Dialog.Root>
53
367
  {/if}
@@ -0,0 +1,86 @@
1
+ <script lang="ts">
2
+ import type { IconField } from '../../../types/fields.js';
3
+ import { resolveIconSet } from '../../state/icon-sets.svelte.js';
4
+ import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
5
+ import { getLocalizedLabel } from '../../utils/collectionLabel.js';
6
+ import IconPickerDialog from './icon-picker-dialog.svelte';
7
+ import Plus from '@lucide/svelte/icons/plus';
8
+ import X from '@lucide/svelte/icons/x';
9
+ import HelpCircle from '@lucide/svelte/icons/help-circle';
10
+
11
+ type Props = {
12
+ field: IconField;
13
+ value: string | undefined;
14
+ };
15
+
16
+ let { field, value = $bindable(), ...props }: Props = $props();
17
+
18
+ const interfaceLanguage = useInterfaceLanguage();
19
+ const iconSet = $derived(resolveIconSet(field.set));
20
+ const iconDef = $derived(value && iconSet ? iconSet.icons[value] : undefined);
21
+ const isMissing = $derived(!!value && !!iconSet && !iconDef);
22
+ const label = $derived.by(() =>
23
+ iconDef ? getLocalizedLabel(iconDef.label, interfaceLanguage.current) : (value ?? '')
24
+ );
25
+
26
+ let dialogOpen = $state(false);
27
+
28
+ function clear() {
29
+ value = '';
30
+ }
31
+ function openDialog() {
32
+ dialogOpen = true;
33
+ }
34
+ function onConfirm(newValue: string) {
35
+ value = newValue;
36
+ dialogOpen = false;
37
+ }
38
+ </script>
39
+
40
+ {#if !iconSet}
41
+ <p class="text-sm text-destructive">
42
+ Brak zarejestrowanego zestawu ikon. Dodaj <code>IconSetPlugin</code> do
43
+ <code>plugins:</code> w cms.config i przekaż przez <code>buildIconSetMap(...)</code> do AdminLayout.
44
+ </p>
45
+ {:else}
46
+ <div class="relative inline-block">
47
+ <button
48
+ type="button"
49
+ onclick={openDialog}
50
+ class="flex h-24 w-24 flex-col items-center justify-center gap-1.5 rounded-lg border bg-muted/30 transition-colors hover:border-primary hover:bg-muted/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
51
+ aria-label={value ? `Zmień ikonę: ${label}` : 'Wybierz ikonę'}
52
+ {...props}
53
+ >
54
+ {#if !value}
55
+ <Plus class="h-7 w-7 text-muted-foreground" />
56
+ <span class="text-xs text-muted-foreground">Wybierz ikonę</span>
57
+ {:else if isMissing}
58
+ <HelpCircle class="h-7 w-7 text-warning" />
59
+ <span class="line-clamp-1 px-1 text-xs text-muted-foreground">{value}</span>
60
+ {:else if iconDef}
61
+ {@const Comp = iconDef.component}
62
+ <Comp size={32} />
63
+ <span class="line-clamp-1 px-1 text-xs">{label}</span>
64
+ {/if}
65
+ </button>
66
+
67
+ {#if value}
68
+ <button
69
+ type="button"
70
+ onclick={clear}
71
+ class="absolute -right-2 -top-2 rounded-full border bg-background p-1 text-muted-foreground shadow-sm transition-colors hover:text-destructive"
72
+ aria-label="Wyczyść ikonę"
73
+ >
74
+ <X class="h-3 w-3" />
75
+ </button>
76
+ {/if}
77
+ </div>
78
+
79
+ {#if isMissing}
80
+ <p class="mt-2 text-sm text-warning">
81
+ Ikona <code>{value}</code> nie jest już dostępna w bibliotece. Wybierz inną lub usuń.
82
+ </p>
83
+ {/if}
84
+
85
+ <IconPickerDialog bind:open={dialogOpen} {iconSet} initialValue={value} {onConfirm} />
86
+ {/if}
@@ -0,0 +1,8 @@
1
+ import type { IconField } from '../../../types/fields.js';
2
+ type Props = {
3
+ field: IconField;
4
+ value: string | undefined;
5
+ };
6
+ declare const IconField: import("svelte").Component<Props, {}, "value">;
7
+ type IconField = ReturnType<typeof IconField>;
8
+ export default IconField;
@@ -0,0 +1,174 @@
1
+ <script lang="ts">
2
+ import * as Dialog from '../../../components/ui/dialog/index.js';
3
+ import { Input } from '../../../components/ui/input/index.js';
4
+ import { Button } from '../../../components/ui/button/index.js';
5
+ import { LiveRegion } from '../../../components/ui/live-region/index.js';
6
+ import type { IconSetPlugin } from '../../../types/plugins.js';
7
+ import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
8
+ import { getLocalizedLabel } from '../../utils/collectionLabel.js';
9
+ import type { InterfaceLanguage } from '../../../types/languages.js';
10
+ import type { Component } from 'svelte';
11
+
12
+ type Props = {
13
+ open: boolean;
14
+ iconSet: IconSetPlugin;
15
+ initialValue: string | undefined;
16
+ onConfirm: (value: string) => void;
17
+ };
18
+
19
+ let { open = $bindable(), iconSet, initialValue, onConfirm }: Props = $props();
20
+
21
+ const lang: Record<
22
+ InterfaceLanguage,
23
+ {
24
+ title: string;
25
+ search: string;
26
+ cancel: string;
27
+ confirm: string;
28
+ noResults: string;
29
+ resultsCount: (n: number) => string;
30
+ close: string;
31
+ }
32
+ > = {
33
+ en: {
34
+ title: 'Select icon',
35
+ search: 'Search icons...',
36
+ cancel: 'Cancel',
37
+ confirm: 'OK',
38
+ noResults: 'No icons match',
39
+ resultsCount: (n) => `${n} icons`,
40
+ close: 'Close'
41
+ },
42
+ pl: {
43
+ title: 'Wybierz ikonę',
44
+ search: 'Szukaj ikon...',
45
+ cancel: 'Anuluj',
46
+ confirm: 'OK',
47
+ noResults: 'Brak pasujących ikon',
48
+ resultsCount: (n) => `Wyniki: ${n}`,
49
+ close: 'Zamknij'
50
+ }
51
+ };
52
+
53
+ const interfaceLanguage = useInterfaceLanguage();
54
+ const t = $derived(lang[interfaceLanguage.current]);
55
+
56
+ let searchValue = $state('');
57
+ let selected = $state<string>('');
58
+ let wasOpen = false;
59
+
60
+ // Re-sync gdy dialog się otwiera ze świeżą wartością początkową.
61
+ // Wzorzec "edge-trigger": resetujemy TYLKO na przejściu closed→open,
62
+ // żeby kliknięcia użytkownika w grid nie były nadpisywane przez re-run efektu.
63
+ $effect(() => {
64
+ if (open && !wasOpen) {
65
+ selected = initialValue ?? '';
66
+ searchValue = '';
67
+ }
68
+ wasOpen = open;
69
+ });
70
+
71
+ type IconEntry = {
72
+ key: string;
73
+ label: string;
74
+ keywords: string[];
75
+ component: Component;
76
+ };
77
+
78
+ const allEntries = $derived<IconEntry[]>(
79
+ Object.entries(iconSet.icons).map(([key, def]) => ({
80
+ key,
81
+ label: getLocalizedLabel(def.label, interfaceLanguage.current),
82
+ keywords: def.keywords ?? [],
83
+ component: def.component
84
+ }))
85
+ );
86
+
87
+ const filtered = $derived.by(() => {
88
+ const q = searchValue.trim().toLowerCase();
89
+ if (!q) return allEntries;
90
+ return allEntries.filter(
91
+ (e) =>
92
+ e.key.toLowerCase().includes(q) ||
93
+ e.label.toLowerCase().includes(q) ||
94
+ e.keywords.some((k) => k.toLowerCase().includes(q))
95
+ );
96
+ });
97
+
98
+ function confirm() {
99
+ if (selected) onConfirm(selected);
100
+ }
101
+ function cancel() {
102
+ open = false;
103
+ }
104
+ function select(key: string) {
105
+ selected = key;
106
+ }
107
+ function handleOpenChange(next: boolean) {
108
+ open = next;
109
+ }
110
+ </script>
111
+
112
+ <Dialog.Root bind:open onOpenChange={handleOpenChange}>
113
+ <Dialog.Content class="flex max-h-[85vh] flex-col gap-0 p-0 sm:max-w-2xl">
114
+ <Dialog.Header class="border-b px-4 py-3">
115
+ <Dialog.Title class="text-base font-semibold">{t.title}</Dialog.Title>
116
+ <Dialog.Description class="sr-only">{t.title}</Dialog.Description>
117
+ </Dialog.Header>
118
+
119
+ <div class="border-b px-4 py-2">
120
+ <Input
121
+ type="search"
122
+ placeholder={t.search}
123
+ bind:value={searchValue}
124
+ autofocus
125
+ class="h-9"
126
+ aria-label={t.search}
127
+ />
128
+ </div>
129
+
130
+ <div class="flex-1 overflow-y-auto p-4">
131
+ {#if filtered.length === 0}
132
+ <p class="text-center text-sm text-muted-foreground py-8">{t.noResults}</p>
133
+ {:else}
134
+ <div
135
+ role="listbox"
136
+ aria-label={t.title}
137
+ class="grid grid-cols-3 gap-2 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6"
138
+ >
139
+ {#each filtered as entry (entry.key)}
140
+ {@const isSelected = selected === entry.key}
141
+ {@const Comp = entry.component}
142
+ <button
143
+ type="button"
144
+ role="option"
145
+ aria-selected={isSelected}
146
+ onclick={() => select(entry.key)}
147
+ ondblclick={() => {
148
+ select(entry.key);
149
+ confirm();
150
+ }}
151
+ class={[
152
+ 'flex h-20 flex-col items-center justify-center gap-1 rounded-md border p-1.5 transition-colors',
153
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
154
+ isSelected
155
+ ? 'border-primary bg-primary/10'
156
+ : 'border-muted bg-background hover:bg-muted/50'
157
+ ].join(' ')}
158
+ >
159
+ <Comp size={24} />
160
+ <span class="line-clamp-1 w-full text-center text-[10px]">{entry.label}</span>
161
+ </button>
162
+ {/each}
163
+ </div>
164
+ {/if}
165
+ </div>
166
+
167
+ <Dialog.Footer class="border-t px-4 py-3 sm:justify-end">
168
+ <Button type="button" variant="outline" onclick={cancel}>{t.cancel}</Button>
169
+ <Button type="button" onclick={confirm} disabled={!selected}>{t.confirm}</Button>
170
+ </Dialog.Footer>
171
+
172
+ <LiveRegion message={t.resultsCount(filtered.length)} />
173
+ </Dialog.Content>
174
+ </Dialog.Root>
@@ -0,0 +1,11 @@
1
+ import type { IconSetPlugin } from '../../../types/plugins.js';
2
+ import type { Component } from 'svelte';
3
+ type Props = {
4
+ open: boolean;
5
+ iconSet: IconSetPlugin;
6
+ initialValue: string | undefined;
7
+ onConfirm: (value: string) => void;
8
+ };
9
+ declare const IconPickerDialog: Component<Props, {}, "open">;
10
+ type IconPickerDialog = ReturnType<typeof IconPickerDialog>;
11
+ export default IconPickerDialog;