@modern-admin/web 0.3.5 → 0.4.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 (171) hide show
  1. package/README.md +42 -0
  2. package/dist/config/runtime-config.d.ts +84 -0
  3. package/dist/config/runtime-config.js +19 -0
  4. package/dist/config/vite.d.ts +84 -0
  5. package/dist/config/vite.js +166 -0
  6. package/dist/lib/index.js +0 -1
  7. package/dist/lib/index.js.map +1 -1
  8. package/dist/lib/mount.d.ts.map +1 -1
  9. package/dist/standalone/assets/{action-component-host-CXIbN-8V.js → action-component-host-CuzX-99S.js} +2 -2
  10. package/dist/standalone/assets/action-component-host-CuzX-99S.js.br +0 -0
  11. package/dist/standalone/assets/action-component-host-CuzX-99S.js.gz +0 -0
  12. package/dist/standalone/assets/{action-component-host-CXIbN-8V.js.map → action-component-host-CuzX-99S.js.map} +1 -1
  13. package/dist/standalone/assets/{action-page-OsWfKQ6C.js → action-page-BUTCUZ7e.js} +2 -2
  14. package/dist/standalone/assets/action-page-BUTCUZ7e.js.br +0 -0
  15. package/dist/standalone/assets/action-page-BUTCUZ7e.js.gz +0 -0
  16. package/dist/standalone/assets/{action-page-OsWfKQ6C.js.map → action-page-BUTCUZ7e.js.map} +1 -1
  17. package/dist/standalone/assets/{ai-assistant-widget-Bt4Z7Fmr.js → ai-assistant-widget-B3Xy7bCi.js} +2 -2
  18. package/dist/standalone/assets/ai-assistant-widget-B3Xy7bCi.js.br +0 -0
  19. package/dist/standalone/assets/ai-assistant-widget-B3Xy7bCi.js.gz +0 -0
  20. package/dist/standalone/assets/{ai-assistant-widget-Bt4Z7Fmr.js.map → ai-assistant-widget-B3Xy7bCi.js.map} +1 -1
  21. package/dist/standalone/assets/{audit-log-page-2wuGiWg-.js → audit-log-page-GHzf7mLS.js} +2 -2
  22. package/dist/standalone/assets/audit-log-page-GHzf7mLS.js.br +0 -0
  23. package/dist/standalone/assets/audit-log-page-GHzf7mLS.js.gz +0 -0
  24. package/dist/standalone/assets/{audit-log-page-2wuGiWg-.js.map → audit-log-page-GHzf7mLS.js.map} +1 -1
  25. package/dist/standalone/assets/{calendar-BZVUWakC.js → calendar-DxUwrP2r.js} +2 -2
  26. package/dist/standalone/assets/calendar-DxUwrP2r.js.br +0 -0
  27. package/dist/standalone/assets/calendar-DxUwrP2r.js.gz +0 -0
  28. package/dist/standalone/assets/{calendar-BZVUWakC.js.map → calendar-DxUwrP2r.js.map} +1 -1
  29. package/dist/standalone/assets/{chart-widget-BtJb6YXV.js → chart-widget-DnK78Joj.js} +2 -2
  30. package/dist/standalone/assets/chart-widget-DnK78Joj.js.br +0 -0
  31. package/dist/standalone/assets/chart-widget-DnK78Joj.js.gz +0 -0
  32. package/dist/standalone/assets/{chart-widget-BtJb6YXV.js.map → chart-widget-DnK78Joj.js.map} +1 -1
  33. package/dist/standalone/assets/{command-BNF_evMy.js → command-Df4Effbo.js} +2 -2
  34. package/dist/standalone/assets/command-Df4Effbo.js.br +0 -0
  35. package/dist/standalone/assets/command-Df4Effbo.js.gz +0 -0
  36. package/dist/standalone/assets/{command-BNF_evMy.js.map → command-Df4Effbo.js.map} +1 -1
  37. package/dist/standalone/assets/{date-picker-LfbUehd1.js → date-picker-BM6tYOT1.js} +2 -2
  38. package/dist/standalone/assets/date-picker-BM6tYOT1.js.br +0 -0
  39. package/dist/standalone/assets/date-picker-BM6tYOT1.js.gz +0 -0
  40. package/dist/standalone/assets/{date-picker-LfbUehd1.js.map → date-picker-BM6tYOT1.js.map} +1 -1
  41. package/dist/standalone/assets/{edit-page-DCRB9Y_q.js → edit-page-C_QYbzrq.js} +2 -2
  42. package/dist/standalone/assets/edit-page-C_QYbzrq.js.br +0 -0
  43. package/dist/standalone/assets/edit-page-C_QYbzrq.js.gz +0 -0
  44. package/dist/standalone/assets/{edit-page-DCRB9Y_q.js.map → edit-page-C_QYbzrq.js.map} +1 -1
  45. package/dist/standalone/assets/{empty-DAEAjFd3.js → empty-BQFNUnfh.js} +2 -2
  46. package/dist/standalone/assets/empty-BQFNUnfh.js.br +0 -0
  47. package/dist/standalone/assets/empty-BQFNUnfh.js.gz +0 -0
  48. package/dist/standalone/assets/{empty-DAEAjFd3.js.map → empty-BQFNUnfh.js.map} +1 -1
  49. package/dist/standalone/assets/{global-search-dialog-HMLztOho.js → global-search-dialog-DMgCfJpF.js} +2 -2
  50. package/dist/standalone/assets/global-search-dialog-DMgCfJpF.js.br +0 -0
  51. package/dist/standalone/assets/global-search-dialog-DMgCfJpF.js.gz +0 -0
  52. package/dist/standalone/assets/{global-search-dialog-HMLztOho.js.map → global-search-dialog-DMgCfJpF.js.map} +1 -1
  53. package/dist/standalone/assets/{heavy-fields-ZuqfOFMv.js → heavy-fields-Do2qd-Wi.js} +2 -2
  54. package/dist/standalone/assets/heavy-fields-Do2qd-Wi.js.br +0 -0
  55. package/dist/standalone/assets/{heavy-fields-ZuqfOFMv.js.gz → heavy-fields-Do2qd-Wi.js.gz} +0 -0
  56. package/dist/standalone/assets/{heavy-fields-ZuqfOFMv.js.map → heavy-fields-Do2qd-Wi.js.map} +1 -1
  57. package/dist/standalone/assets/{home-page-HmheewiP.js → home-page-eGVeZnKR.js} +3 -3
  58. package/dist/standalone/assets/home-page-eGVeZnKR.js.br +0 -0
  59. package/dist/standalone/assets/home-page-eGVeZnKR.js.gz +0 -0
  60. package/dist/standalone/assets/{home-page-HmheewiP.js.map → home-page-eGVeZnKR.js.map} +1 -1
  61. package/dist/standalone/assets/{icon-registry-BV44--RL.js → icon-registry-BroqBCns.js} +2 -2
  62. package/dist/standalone/assets/icon-registry-BroqBCns.js.br +0 -0
  63. package/dist/standalone/assets/{icon-registry-BV44--RL.js.gz → icon-registry-BroqBCns.js.gz} +0 -0
  64. package/dist/standalone/assets/{icon-registry-BV44--RL.js.map → icon-registry-BroqBCns.js.map} +1 -1
  65. package/dist/standalone/assets/index-C3MuojaM.js +15 -0
  66. package/dist/standalone/assets/index-C3MuojaM.js.br +0 -0
  67. package/dist/standalone/assets/index-C3MuojaM.js.gz +0 -0
  68. package/dist/standalone/assets/{index-t4woXGa-.js.map → index-C3MuojaM.js.map} +1 -1
  69. package/dist/standalone/assets/{json-editor-sGw4fDoj.js → json-editor-BQLKoybQ.js} +2 -2
  70. package/dist/standalone/assets/json-editor-BQLKoybQ.js.br +0 -0
  71. package/dist/standalone/assets/json-editor-BQLKoybQ.js.gz +0 -0
  72. package/dist/standalone/assets/{json-editor-sGw4fDoj.js.map → json-editor-BQLKoybQ.js.map} +1 -1
  73. package/dist/standalone/assets/{property-renderer-DyCwdVkh.js → property-renderer--LvNtRJE.js} +4 -4
  74. package/dist/standalone/assets/property-renderer--LvNtRJE.js.br +0 -0
  75. package/dist/standalone/assets/property-renderer--LvNtRJE.js.gz +0 -0
  76. package/dist/standalone/assets/{property-renderer-DyCwdVkh.js.map → property-renderer--LvNtRJE.js.map} +1 -1
  77. package/dist/standalone/assets/{reference-Dfd804y3.js → reference-UuQ6Jts1.js} +2 -2
  78. package/dist/standalone/assets/reference-UuQ6Jts1.js.br +0 -0
  79. package/dist/standalone/assets/reference-UuQ6Jts1.js.gz +0 -0
  80. package/dist/standalone/assets/{reference-Dfd804y3.js.map → reference-UuQ6Jts1.js.map} +1 -1
  81. package/dist/standalone/assets/{revisions-button-DXZAh2Lu.js → revisions-button-DHcQjj4r.js} +2 -2
  82. package/dist/standalone/assets/revisions-button-DHcQjj4r.js.br +0 -0
  83. package/dist/standalone/assets/revisions-button-DHcQjj4r.js.gz +0 -0
  84. package/dist/standalone/assets/{revisions-button-DXZAh2Lu.js.map → revisions-button-DHcQjj4r.js.map} +1 -1
  85. package/dist/standalone/assets/{select-BWf6walM.js → select-B30EsRTA.js} +2 -2
  86. package/dist/standalone/assets/select-B30EsRTA.js.br +0 -0
  87. package/dist/standalone/assets/select-B30EsRTA.js.gz +0 -0
  88. package/dist/standalone/assets/{select-BWf6walM.js.map → select-B30EsRTA.js.map} +1 -1
  89. package/dist/standalone/assets/{settings-page-Co7sWbbL.js → settings-page-3r5atVKG.js} +2 -2
  90. package/dist/standalone/assets/settings-page-3r5atVKG.js.br +0 -0
  91. package/dist/standalone/assets/settings-page-3r5atVKG.js.gz +0 -0
  92. package/dist/standalone/assets/{settings-page-Co7sWbbL.js.map → settings-page-3r5atVKG.js.map} +1 -1
  93. package/dist/standalone/assets/{show-page-D-_vldWV.js → show-page-CmRDBZRg.js} +2 -2
  94. package/dist/standalone/assets/show-page-CmRDBZRg.js.br +0 -0
  95. package/dist/standalone/assets/show-page-CmRDBZRg.js.gz +0 -0
  96. package/dist/standalone/assets/{show-page-D-_vldWV.js.map → show-page-CmRDBZRg.js.map} +1 -1
  97. package/dist/standalone/assets/{tabs-khcHka8W.js → tabs-BXeUKAN4.js} +2 -2
  98. package/dist/standalone/assets/tabs-BXeUKAN4.js.br +0 -0
  99. package/dist/standalone/assets/tabs-BXeUKAN4.js.gz +0 -0
  100. package/dist/standalone/assets/{tabs-khcHka8W.js.map → tabs-BXeUKAN4.js.map} +1 -1
  101. package/dist/standalone/assets/{value-format-CPnbnlE6.js → value-format-BikdS7In.js} +2 -2
  102. package/dist/standalone/assets/value-format-BikdS7In.js.br +0 -0
  103. package/dist/standalone/assets/value-format-BikdS7In.js.gz +0 -0
  104. package/dist/standalone/assets/{value-format-CPnbnlE6.js.map → value-format-BikdS7In.js.map} +1 -1
  105. package/dist/standalone/assets/{wizard-create-page-CFwMs8E3.js → wizard-create-page-ChrGqxlO.js} +2 -2
  106. package/dist/standalone/assets/wizard-create-page-ChrGqxlO.js.br +0 -0
  107. package/dist/standalone/assets/wizard-create-page-ChrGqxlO.js.gz +0 -0
  108. package/dist/standalone/assets/{wizard-create-page-CFwMs8E3.js.map → wizard-create-page-ChrGqxlO.js.map} +1 -1
  109. package/dist/standalone/assets/{zod-BbfEBA4A.js → zod-CF8JsamI.js} +2 -2
  110. package/dist/standalone/assets/zod-CF8JsamI.js.br +0 -0
  111. package/dist/standalone/assets/zod-CF8JsamI.js.gz +0 -0
  112. package/dist/standalone/assets/{zod-BbfEBA4A.js.map → zod-CF8JsamI.js.map} +1 -1
  113. package/dist/standalone/index.html +4 -4
  114. package/dist/standalone/index.html.br +0 -0
  115. package/dist/standalone/index.html.gz +0 -0
  116. package/package.json +27 -5
  117. package/src/mount.tsx +8 -2
  118. package/src/standalone.tsx +1 -0
  119. package/src/vite.ts +219 -0
  120. package/vite.config.ts +13 -104
  121. package/dist/standalone/assets/action-component-host-CXIbN-8V.js.br +0 -0
  122. package/dist/standalone/assets/action-component-host-CXIbN-8V.js.gz +0 -0
  123. package/dist/standalone/assets/action-page-OsWfKQ6C.js.br +0 -0
  124. package/dist/standalone/assets/action-page-OsWfKQ6C.js.gz +0 -0
  125. package/dist/standalone/assets/ai-assistant-widget-Bt4Z7Fmr.js.br +0 -0
  126. package/dist/standalone/assets/ai-assistant-widget-Bt4Z7Fmr.js.gz +0 -0
  127. package/dist/standalone/assets/audit-log-page-2wuGiWg-.js.br +0 -0
  128. package/dist/standalone/assets/audit-log-page-2wuGiWg-.js.gz +0 -0
  129. package/dist/standalone/assets/calendar-BZVUWakC.js.br +0 -0
  130. package/dist/standalone/assets/calendar-BZVUWakC.js.gz +0 -0
  131. package/dist/standalone/assets/chart-widget-BtJb6YXV.js.br +0 -0
  132. package/dist/standalone/assets/chart-widget-BtJb6YXV.js.gz +0 -0
  133. package/dist/standalone/assets/command-BNF_evMy.js.br +0 -0
  134. package/dist/standalone/assets/command-BNF_evMy.js.gz +0 -0
  135. package/dist/standalone/assets/date-picker-LfbUehd1.js.br +0 -0
  136. package/dist/standalone/assets/date-picker-LfbUehd1.js.gz +0 -0
  137. package/dist/standalone/assets/edit-page-DCRB9Y_q.js.br +0 -0
  138. package/dist/standalone/assets/edit-page-DCRB9Y_q.js.gz +0 -0
  139. package/dist/standalone/assets/empty-DAEAjFd3.js.br +0 -0
  140. package/dist/standalone/assets/empty-DAEAjFd3.js.gz +0 -0
  141. package/dist/standalone/assets/global-search-dialog-HMLztOho.js.br +0 -0
  142. package/dist/standalone/assets/global-search-dialog-HMLztOho.js.gz +0 -0
  143. package/dist/standalone/assets/heavy-fields-ZuqfOFMv.js.br +0 -0
  144. package/dist/standalone/assets/home-page-HmheewiP.js.br +0 -0
  145. package/dist/standalone/assets/home-page-HmheewiP.js.gz +0 -0
  146. package/dist/standalone/assets/icon-registry-BV44--RL.js.br +0 -0
  147. package/dist/standalone/assets/index-t4woXGa-.js +0 -15
  148. package/dist/standalone/assets/index-t4woXGa-.js.br +0 -0
  149. package/dist/standalone/assets/index-t4woXGa-.js.gz +0 -0
  150. package/dist/standalone/assets/json-editor-sGw4fDoj.js.br +0 -0
  151. package/dist/standalone/assets/json-editor-sGw4fDoj.js.gz +0 -0
  152. package/dist/standalone/assets/property-renderer-DyCwdVkh.js.br +0 -0
  153. package/dist/standalone/assets/property-renderer-DyCwdVkh.js.gz +0 -0
  154. package/dist/standalone/assets/reference-Dfd804y3.js.br +0 -0
  155. package/dist/standalone/assets/reference-Dfd804y3.js.gz +0 -0
  156. package/dist/standalone/assets/revisions-button-DXZAh2Lu.js.br +0 -0
  157. package/dist/standalone/assets/revisions-button-DXZAh2Lu.js.gz +0 -0
  158. package/dist/standalone/assets/select-BWf6walM.js.br +0 -0
  159. package/dist/standalone/assets/select-BWf6walM.js.gz +0 -0
  160. package/dist/standalone/assets/settings-page-Co7sWbbL.js.br +0 -0
  161. package/dist/standalone/assets/settings-page-Co7sWbbL.js.gz +0 -0
  162. package/dist/standalone/assets/show-page-D-_vldWV.js.br +0 -0
  163. package/dist/standalone/assets/show-page-D-_vldWV.js.gz +0 -0
  164. package/dist/standalone/assets/tabs-khcHka8W.js.br +0 -0
  165. package/dist/standalone/assets/tabs-khcHka8W.js.gz +0 -0
  166. package/dist/standalone/assets/value-format-CPnbnlE6.js.br +0 -0
  167. package/dist/standalone/assets/value-format-CPnbnlE6.js.gz +0 -0
  168. package/dist/standalone/assets/wizard-create-page-CFwMs8E3.js.br +0 -0
  169. package/dist/standalone/assets/wizard-create-page-CFwMs8E3.js.gz +0 -0
  170. package/dist/standalone/assets/zod-BbfEBA4A.js.br +0 -0
  171. package/dist/standalone/assets/zod-BbfEBA4A.js.gz +0 -0
package/README.md CHANGED
@@ -15,6 +15,48 @@ end-to-end Zod validation.
15
15
  bun add @modern-admin/web
16
16
  ```
17
17
 
18
+ ## Building your own bundle (custom components)
19
+
20
+ The prebuilt standalone bundle registers no custom components, so as soon as a
21
+ resource uses `component: 'MyThing'` on an action or `components: { edit: … }`
22
+ on a property you need your own build. Three files, then point
23
+ `ModernAdminStaticUiModule`'s `webPackage` at your package:
24
+
25
+ ```ts
26
+ // vite.config.ts
27
+ import { defineAdminAppConfig } from '@modern-admin/web/vite'
28
+
29
+ // Dev server, the `dist/standalone/` layout the Nest middleware expects,
30
+ // precompressed assets and prefetch hints — all preconfigured.
31
+ export default defineAdminAppConfig({ apiProxy: 'http://localhost:3011' })
32
+ ```
33
+
34
+ ```css
35
+ /* src/styles.css — you own the Tailwind root so your classes get scanned */
36
+ @import "@modern-admin/react/styles.css";
37
+ @source "./**/*.{ts,tsx}";
38
+ ```
39
+
40
+ ```tsx
41
+ // src/main.tsx
42
+ import { mount, readWindowConfig } from '@modern-admin/web'
43
+ import { ComponentLoader } from '@modern-admin/react'
44
+ import { SendPushForm } from './components/send-push-form.js'
45
+ import './styles.css'
46
+
47
+ mount(document.getElementById('root')!, {
48
+ config: readWindowConfig(),
49
+ components: new ComponentLoader().add('SendPushForm', SendPushForm),
50
+ })
51
+ ```
52
+
53
+ `vite dev` gives you React Fast Refresh on those components. Editing the module
54
+ that builds the `ComponentLoader` itself falls back to a full reload — HMR can
55
+ only patch modules whose exports are all components.
56
+
57
+ `mount()` imports no CSS of its own: a second Tailwind root would compile the
58
+ framework stylesheet twice, without your `@source`. Import it as shown above.
59
+
18
60
  ## Documentation
19
61
 
20
62
  Setup guides, architecture, and usage examples live in the
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Runtime configuration for the prebuilt admin SPA.
3
+ *
4
+ * The standalone bundle reads this from `window.__MODERN_ADMIN__`; the
5
+ * library `mount()` API takes it as an argument. Either way it is the
6
+ * single source of truth for "where is the API", "which translations to
7
+ * apply", and how the admin client should behave at runtime.
8
+ *
9
+ * No build-time env vars: one bundle serves any deployment.
10
+ */
11
+ import type { MetadataTranslations } from '@modern-admin/react';
12
+ export interface ModernAdminBrand {
13
+ /** Shown in the sidebar header and login screen. */
14
+ title?: string;
15
+ /** Optional logo image URL. */
16
+ logoUrl?: string;
17
+ }
18
+ export interface ModernAdminRuntimeConfig {
19
+ /**
20
+ * Absolute base URL of the admin API (the @modern-admin/nest backend).
21
+ * Leave undefined to use same-origin — recommended when the SPA is served
22
+ * by the same NestJS process that exposes the admin API.
23
+ */
24
+ apiUrl?: string;
25
+ /** RequestCredentials forwarded to every fetch. Defaults to 'include'. */
26
+ credentials?: RequestCredentials;
27
+ /** Extra headers attached to every request (e.g. CSRF, custom auth). */
28
+ headers?: Record<string, string>;
29
+ /** Helper line shown on the login screen — e.g. demo credentials. */
30
+ loginHint?: string;
31
+ /** Initial UI locale code. Falls back to the persisted choice / 'en'. */
32
+ defaultLocale?: string;
33
+ /** Locale used when a translation is missing. Defaults to 'en'. */
34
+ fallbackLocale?: string;
35
+ /**
36
+ * Whitelist of locale codes to expose in the header switcher (filters
37
+ * the built-in 9-locale bundle: `en`, `ru`, `de`, `es`, `fr`, `it`, `ja`,
38
+ * `pl`, `pt-BR`). Behaviour:
39
+ * - omitted / empty array → all built-in locales available
40
+ * - single code → switcher hides, that locale is forced
41
+ * - multiple codes → switcher lists only those locales
42
+ */
43
+ locales?: string[];
44
+ /** Optional per-resource / per-property metadata translations. */
45
+ metadataTranslations?: MetadataTranslations;
46
+ /** Branding overrides (title, logo). */
47
+ brand?: ModernAdminBrand;
48
+ /** Persist the demo session credentials in localStorage. */
49
+ persistDemoSession?: boolean;
50
+ /**
51
+ * Path under which the host mounts Better Auth's Node handler. Drives
52
+ * the sign-in / sign-out endpoints — defaults to `/admin/api/auth`,
53
+ * matching the canonical CLI scaffold (and `ModernAdminStaticUiModule`
54
+ * mounted at `/admin`). Override only when the host mounts Better Auth
55
+ * elsewhere; pass *without* a trailing slash.
56
+ */
57
+ authBasePath?: string;
58
+ /**
59
+ * URL prefix where the SPA is mounted (e.g. `/admin`). Injected
60
+ * automatically by `ModernAdminStaticUiMiddleware` from its `path` option
61
+ * so you normally do not need to set this. The router uses it as its
62
+ * basepath so all internal navigation and deep-link refreshes stay under
63
+ * the correct prefix. Pass without a trailing slash.
64
+ */
65
+ basePath?: string;
66
+ /**
67
+ * When true, the sidebar resource list shows the raw resource id next
68
+ * to the localized label (e.g. "Posts (posts)") whenever the label
69
+ * differs from the id. Defaults to `false` — keeping the sidebar tidy.
70
+ * The home page tile and selector dropdowns (chart builder, etc.)
71
+ * always show both regardless of this flag.
72
+ */
73
+ showSidebarResourceIds?: boolean;
74
+ }
75
+ declare global {
76
+ interface Window {
77
+ __MODERN_ADMIN__?: ModernAdminRuntimeConfig;
78
+ }
79
+ }
80
+ /**
81
+ * Reads runtime config from `window.__MODERN_ADMIN__`. Returns an empty
82
+ * object (same-origin defaults) when nothing is injected.
83
+ */
84
+ export declare function readWindowConfig(): ModernAdminRuntimeConfig;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Runtime configuration for the prebuilt admin SPA.
3
+ *
4
+ * The standalone bundle reads this from `window.__MODERN_ADMIN__`; the
5
+ * library `mount()` API takes it as an argument. Either way it is the
6
+ * single source of truth for "where is the API", "which translations to
7
+ * apply", and how the admin client should behave at runtime.
8
+ *
9
+ * No build-time env vars: one bundle serves any deployment.
10
+ */
11
+ /**
12
+ * Reads runtime config from `window.__MODERN_ADMIN__`. Returns an empty
13
+ * object (same-origin defaults) when nothing is injected.
14
+ */
15
+ export function readWindowConfig() {
16
+ if (typeof window === 'undefined')
17
+ return {};
18
+ return window.__MODERN_ADMIN__ ?? {};
19
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Vite config kit for apps that build their own copy of the admin SPA.
3
+ *
4
+ * The prebuilt `@modern-admin/web` standalone bundle registers no custom
5
+ * components, so any app that uses `component: '…'` on an action or
6
+ * `components: { edit: '…' }` on a property has to build its own bundle. That
7
+ * bundle has to match what `@modern-admin/nest`'s StaticUiMiddleware expects
8
+ * — `dist/standalone/{index.html,assets/**}` with relative `./assets/` URLs —
9
+ * which used to mean copying this package's `vite.config.ts` wholesale.
10
+ *
11
+ * // vite.config.ts
12
+ * import { defineAdminAppConfig } from '@modern-admin/web/vite'
13
+ * export default defineAdminAppConfig({ apiProxy: 'http://localhost:3011' })
14
+ *
15
+ * // src/main.tsx
16
+ * import { mount, readWindowConfig } from '@modern-admin/web'
17
+ * import './styles.css'
18
+ * mount(document.getElementById('root')!, {
19
+ * config: readWindowConfig(),
20
+ * components: new ComponentLoader().add('SendPushForm', SendPushForm),
21
+ * })
22
+ *
23
+ * `packages/web/vite.config.ts` consumes this same factory, so the kit can't
24
+ * drift from the bundle we ship.
25
+ */
26
+ import type { ConfigEnv, Plugin, PluginOption, UserConfig } from 'vite';
27
+ import type { ModernAdminRuntimeConfig } from './runtime-config.js';
28
+ export interface AdminAppConfigOptions {
29
+ /** Dev server port. Defaults to `WEB_PORT`, then 3000. */
30
+ port?: number;
31
+ /**
32
+ * Origin of the NestJS admin API, proxied during `vite dev` so the SPA and
33
+ * the API stay same-origin and session cookies work (e.g.
34
+ * `http://localhost:3011`). Omit if you serve both yourself.
35
+ */
36
+ apiProxy?: string;
37
+ /**
38
+ * Path prefixes forwarded to `apiProxy`. Defaults to the REST/auth prefix
39
+ * every `@modern-admin/nest` controller lives under plus the socket.io
40
+ * endpoint used by `@modern-admin/realtime`.
41
+ */
42
+ apiProxyPath?: string | string[];
43
+ /**
44
+ * Runtime config for `vite dev` only. In production the host server
45
+ * injects `window.__MODERN_ADMIN__` into the shell, so there is nothing to
46
+ * configure at build time — this fills the same slot while developing.
47
+ */
48
+ devConfig?: ModernAdminRuntimeConfig;
49
+ /** Directory the standalone build writes to. Rarely worth changing. */
50
+ outDir?: string;
51
+ /** Extra plugins, appended after the built-in ones. */
52
+ plugins?: PluginOption[];
53
+ }
54
+ /**
55
+ * Emits `.br` and `.gz` siblings next to every compressible standalone
56
+ * asset. StaticUiMiddleware serves whichever variant the browser's
57
+ * `Accept-Encoding` allows and falls back to the plain file, so this is an
58
+ * optimisation rather than a correctness requirement.
59
+ */
60
+ export declare function precompressPlugin(outDir?: string): Plugin;
61
+ /**
62
+ * Injects low-priority `<link rel="prefetch">` hints for the lazy chunks a
63
+ * fresh session is almost certain to need right after the shell renders:
64
+ * the dashboard landing page, the full icon registry (sidebar), and the
65
+ * resource list page + property renderer (first sidebar click). The browser
66
+ * fetches them during idle time instead of on first navigation, without
67
+ * competing with the critical-path entry chunk. Relative `./assets/` hrefs
68
+ * are rewritten to the mount path by StaticUiMiddleware like every other
69
+ * asset reference.
70
+ */
71
+ export declare function prefetchHintsPlugin(): Plugin;
72
+ /**
73
+ * Injects `window.__MODERN_ADMIN__` while developing. In production the same
74
+ * slot is filled per-request by the host server (StaticUiMiddleware replaces
75
+ * the `<!--MODERN_ADMIN_CONFIG-->` marker, or falls back to `</head>`), so
76
+ * this plugin is dev-only and never affects the built bundle.
77
+ */
78
+ export declare function devRuntimeConfigPlugin(config: ModernAdminRuntimeConfig): Plugin;
79
+ /**
80
+ * Builds the Vite config for a custom admin SPA. Pass the result straight to
81
+ * `export default` — it resolves `vite dev` and `vite build` itself, so no
82
+ * `--mode` flag is required.
83
+ */
84
+ export declare function defineAdminAppConfig(options?: AdminAppConfigOptions): (env: ConfigEnv) => UserConfig;
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Vite config kit for apps that build their own copy of the admin SPA.
3
+ *
4
+ * The prebuilt `@modern-admin/web` standalone bundle registers no custom
5
+ * components, so any app that uses `component: '…'` on an action or
6
+ * `components: { edit: '…' }` on a property has to build its own bundle. That
7
+ * bundle has to match what `@modern-admin/nest`'s StaticUiMiddleware expects
8
+ * — `dist/standalone/{index.html,assets/**}` with relative `./assets/` URLs —
9
+ * which used to mean copying this package's `vite.config.ts` wholesale.
10
+ *
11
+ * // vite.config.ts
12
+ * import { defineAdminAppConfig } from '@modern-admin/web/vite'
13
+ * export default defineAdminAppConfig({ apiProxy: 'http://localhost:3011' })
14
+ *
15
+ * // src/main.tsx
16
+ * import { mount, readWindowConfig } from '@modern-admin/web'
17
+ * import './styles.css'
18
+ * mount(document.getElementById('root')!, {
19
+ * config: readWindowConfig(),
20
+ * components: new ComponentLoader().add('SendPushForm', SendPushForm),
21
+ * })
22
+ *
23
+ * `packages/web/vite.config.ts` consumes this same factory, so the kit can't
24
+ * drift from the bundle we ship.
25
+ */
26
+ import path from 'node:path';
27
+ import { promises as fs } from 'node:fs';
28
+ import { promisify } from 'node:util';
29
+ import zlib from 'node:zlib';
30
+ import react from '@vitejs/plugin-react';
31
+ import tailwindcss from '@tailwindcss/vite';
32
+ const brotli = promisify(zlib.brotliCompress);
33
+ const gzip = promisify(zlib.gzip);
34
+ /** Layout StaticUiMiddleware resolves as `<webPackage>/dist/standalone`. */
35
+ const STANDALONE_OUT_DIR = 'dist/standalone';
36
+ /**
37
+ * Emits `.br` and `.gz` siblings next to every compressible standalone
38
+ * asset. StaticUiMiddleware serves whichever variant the browser's
39
+ * `Accept-Encoding` allows and falls back to the plain file, so this is an
40
+ * optimisation rather than a correctness requirement.
41
+ */
42
+ export function precompressPlugin(outDir = STANDALONE_OUT_DIR) {
43
+ const COMPRESSIBLE = new Set(['.js', '.mjs', '.css', '.html', '.svg', '.json']);
44
+ const MIN_SIZE = 1024;
45
+ return {
46
+ name: 'modern-admin:precompress',
47
+ apply: 'build',
48
+ async writeBundle(options) {
49
+ const dir = options.dir ?? path.resolve(process.cwd(), outDir);
50
+ const walk = async (d) => {
51
+ const entries = await fs.readdir(d, { withFileTypes: true });
52
+ const nested = await Promise.all(entries.map((e) => (e.isDirectory() ? walk(path.join(d, e.name)) : [path.join(d, e.name)])));
53
+ return nested.flat();
54
+ };
55
+ const files = (await walk(dir)).filter((f) => COMPRESSIBLE.has(path.extname(f)));
56
+ await Promise.all(files.map(async (file) => {
57
+ const source = await fs.readFile(file);
58
+ if (source.length < MIN_SIZE)
59
+ return;
60
+ const [br, gz] = await Promise.all([
61
+ brotli(source, { params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 11 } }),
62
+ gzip(source, { level: zlib.constants.Z_BEST_COMPRESSION }),
63
+ ]);
64
+ await Promise.all([fs.writeFile(`${file}.br`, br), fs.writeFile(`${file}.gz`, gz)]);
65
+ }));
66
+ },
67
+ };
68
+ }
69
+ /**
70
+ * Injects low-priority `<link rel="prefetch">` hints for the lazy chunks a
71
+ * fresh session is almost certain to need right after the shell renders:
72
+ * the dashboard landing page, the full icon registry (sidebar), and the
73
+ * resource list page + property renderer (first sidebar click). The browser
74
+ * fetches them during idle time instead of on first navigation, without
75
+ * competing with the critical-path entry chunk. Relative `./assets/` hrefs
76
+ * are rewritten to the mount path by StaticUiMiddleware like every other
77
+ * asset reference.
78
+ */
79
+ export function prefetchHintsPlugin() {
80
+ const PREFETCH_CHUNKS = new Set(['home-page', 'icon-registry', 'list-page', 'property-renderer']);
81
+ return {
82
+ name: 'modern-admin:prefetch-hints',
83
+ apply: 'build',
84
+ transformIndexHtml: {
85
+ order: 'post',
86
+ handler(_html, ctx) {
87
+ const chunks = Object.values(ctx.bundle ?? {}).filter((out) => out.type === 'chunk' && out.name && PREFETCH_CHUNKS.has(out.name));
88
+ return chunks.map((chunk) => ({
89
+ tag: 'link',
90
+ attrs: { rel: 'prefetch', as: 'script', href: `./${chunk.fileName}` },
91
+ injectTo: 'head',
92
+ }));
93
+ },
94
+ },
95
+ };
96
+ }
97
+ /**
98
+ * Injects `window.__MODERN_ADMIN__` while developing. In production the same
99
+ * slot is filled per-request by the host server (StaticUiMiddleware replaces
100
+ * the `<!--MODERN_ADMIN_CONFIG-->` marker, or falls back to `</head>`), so
101
+ * this plugin is dev-only and never affects the built bundle.
102
+ */
103
+ export function devRuntimeConfigPlugin(config) {
104
+ return {
105
+ name: 'modern-admin:dev-runtime-config',
106
+ apply: 'serve',
107
+ transformIndexHtml: {
108
+ order: 'pre',
109
+ handler() {
110
+ // `</script>` inside the JSON would close the tag early.
111
+ const json = JSON.stringify(config).replace(/</g, '\\u003c');
112
+ return [
113
+ {
114
+ tag: 'script',
115
+ children: `window.__MODERN_ADMIN__ = ${json};`,
116
+ injectTo: 'head-prepend',
117
+ },
118
+ ];
119
+ },
120
+ },
121
+ };
122
+ }
123
+ /**
124
+ * `ws: true` so the realtime gateway's socket.io upgrade survives the proxy;
125
+ * `changeOrigin` keeps the session cookie's Origin check happy.
126
+ */
127
+ function buildProxy(target, paths) {
128
+ const list = Array.isArray(paths) ? paths : [paths];
129
+ return Object.fromEntries(list.map((p) => [p, { target, changeOrigin: true, ws: true }]));
130
+ }
131
+ /**
132
+ * Builds the Vite config for a custom admin SPA. Pass the result straight to
133
+ * `export default` — it resolves `vite dev` and `vite build` itself, so no
134
+ * `--mode` flag is required.
135
+ */
136
+ export function defineAdminAppConfig(options = {}) {
137
+ const { apiProxy, apiProxyPath = ['/admin/api', '/socket.io'], devConfig, outDir = STANDALONE_OUT_DIR, plugins: extraPlugins = [], } = options;
138
+ return ({ command }) => {
139
+ const shared = [react(), tailwindcss()];
140
+ if (command === 'build') {
141
+ return {
142
+ plugins: [...shared, precompressPlugin(outDir), prefetchHintsPlugin(), ...extraPlugins],
143
+ // Relative base so one bundle can be mounted under any path — the
144
+ // host server rewrites `./assets/` to its own mount prefix.
145
+ base: './',
146
+ build: {
147
+ outDir,
148
+ emptyOutDir: true,
149
+ sourcemap: true,
150
+ },
151
+ };
152
+ }
153
+ return {
154
+ plugins: [
155
+ ...shared,
156
+ ...(devConfig ? [devRuntimeConfigPlugin(devConfig)] : []),
157
+ ...extraPlugins,
158
+ ],
159
+ server: {
160
+ port: options.port ?? Number(process.env.WEB_PORT ?? 3000),
161
+ host: true,
162
+ ...(apiProxy ? { proxy: buildProxy(apiProxy, apiProxyPath) } : {}),
163
+ },
164
+ };
165
+ };
166
+ }
package/dist/lib/index.js CHANGED
@@ -4,7 +4,6 @@ import { builtinLocales as a } from "@modern-admin/i18n";
4
4
  import { jsx as o } from "react/jsx-runtime";
5
5
  import { createRoot as s } from "react-dom/client";
6
6
  import { initTheme as c } from "@modern-admin/ui";
7
- import "@modern-admin/ui/styles.css";
8
7
  //#region src/app.tsx
9
8
  function l({ config: e, components: s }) {
10
9
  return /* @__PURE__ */ o(r, {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/app.tsx","../../src/mount.tsx","../../src/runtime-config.ts"],"sourcesContent":["/**\n * Root <App> for the admin SPA. Wraps the AdminApp shell with i18n and the\n * ModernAdmin provider, wiring everything from a single runtime config.\n *\n * Used by both the standalone bundle (window.__MODERN_ADMIN__) and the\n * library `mount()` entry point — hosts compose it the same way.\n */\n\nimport { type ReactElement, useMemo } from 'react'\nimport {\n AdminApp,\n type ComponentLoader,\n I18nProvider,\n ModernAdminProvider,\n} from '@modern-admin/react'\nimport { builtinLocales } from '@modern-admin/i18n'\nimport type { ModernAdminRuntimeConfig } from './runtime-config.js'\n\nexport interface AppProps {\n config: ModernAdminRuntimeConfig\n /**\n * Optional custom property-type components. The standalone bundle leaves\n * this unset; the library `mount()` lets callers pass demo / project-\n * specific components.\n */\n components?: ComponentLoader\n}\n\nexport function App({ config, components }: AppProps): ReactElement {\n // Filter the built-in 9-locale bundle by the host's whitelist. Empty /\n // omitted → expose everything; the language switcher in the header\n // collapses itself when only one locale survives the filter.\n const enabledLocales = useMemo(() => {\n if (!config.locales || config.locales.length === 0) return builtinLocales\n const codes = new Set(config.locales)\n const filtered = builtinLocales.filter((l) => codes.has(l.code))\n return filtered.length > 0 ? filtered : builtinLocales\n }, [config.locales])\n return (\n <I18nProvider\n locales={enabledLocales}\n defaultLocale={config.defaultLocale}\n fallbackLocale={config.fallbackLocale}\n metadataTranslations={config.metadataTranslations}\n >\n <ModernAdminProvider\n components={components}\n clientOptions={{\n baseUrl: config.apiUrl,\n credentials: config.credentials ?? 'include',\n headers: config.headers,\n persistDemoSession: config.persistDemoSession,\n authBasePath: config.authBasePath,\n }}\n >\n <AdminApp\n loginHint={config.loginHint}\n basePath={config.basePath}\n showSidebarResourceIds={config.showSidebarResourceIds}\n />\n </ModernAdminProvider>\n </I18nProvider>\n )\n}\n","/**\n * Imperative mounting API for embedding the admin SPA inside another React\n * tree or vanilla HTML page. Consumers call once at startup:\n *\n * import { mount } from '@modern-admin/web'\n * mount(document.getElementById('root')!, { apiUrl: 'https://api.example.com' })\n *\n * For the prebuilt standalone bundle, `src/standalone.tsx` calls this for\n * you using `window.__MODERN_ADMIN__`.\n */\n\nimport { StrictMode } from 'react'\nimport { createRoot, type Root } from 'react-dom/client'\nimport { type ComponentLoader } from '@modern-admin/react'\nimport { initTheme } from '@modern-admin/ui'\nimport '@modern-admin/ui/styles.css'\nimport { App } from './app.js'\nimport type { ModernAdminRuntimeConfig } from './runtime-config.js'\n\nexport interface MountOptions {\n config: ModernAdminRuntimeConfig\n /** Optional ComponentLoader with custom property-type components. */\n components?: ComponentLoader\n}\n\nexport interface MountedAdmin {\n /** Unmounts the admin SPA from its container. */\n unmount(): void\n}\n\n/**\n * Renders the admin SPA into `container`. Returns a handle with `unmount()`\n * for hosts that need to tear it down dynamically (rare — most users mount\n * once and never unmount).\n */\nexport function mount(container: Element, options: MountOptions): MountedAdmin {\n initTheme()\n const root: Root = createRoot(container)\n root.render(\n <StrictMode>\n <App config={options.config} components={options.components} />\n </StrictMode>,\n )\n return {\n unmount: () => root.unmount(),\n }\n}\n","/**\n * Runtime configuration for the prebuilt admin SPA.\n *\n * The standalone bundle reads this from `window.__MODERN_ADMIN__`; the\n * library `mount()` API takes it as an argument. Either way it is the\n * single source of truth for \"where is the API\", \"which translations to\n * apply\", and how the admin client should behave at runtime.\n *\n * No build-time env vars: one bundle serves any deployment.\n */\n\nimport type { MetadataTranslations } from '@modern-admin/react'\n\nexport interface ModernAdminBrand {\n /** Shown in the sidebar header and login screen. */\n title?: string\n /** Optional logo image URL. */\n logoUrl?: string\n}\n\nexport interface ModernAdminRuntimeConfig {\n /**\n * Absolute base URL of the admin API (the @modern-admin/nest backend).\n * Leave undefined to use same-origin — recommended when the SPA is served\n * by the same NestJS process that exposes the admin API.\n */\n apiUrl?: string\n /** RequestCredentials forwarded to every fetch. Defaults to 'include'. */\n credentials?: RequestCredentials\n /** Extra headers attached to every request (e.g. CSRF, custom auth). */\n headers?: Record<string, string>\n /** Helper line shown on the login screen — e.g. demo credentials. */\n loginHint?: string\n /** Initial UI locale code. Falls back to the persisted choice / 'en'. */\n defaultLocale?: string\n /** Locale used when a translation is missing. Defaults to 'en'. */\n fallbackLocale?: string\n /**\n * Whitelist of locale codes to expose in the header switcher (filters\n * the built-in 9-locale bundle: `en`, `ru`, `de`, `es`, `fr`, `it`, `ja`,\n * `pl`, `pt-BR`). Behaviour:\n * - omitted / empty array → all built-in locales available\n * - single code → switcher hides, that locale is forced\n * - multiple codes → switcher lists only those locales\n */\n locales?: string[]\n /** Optional per-resource / per-property metadata translations. */\n metadataTranslations?: MetadataTranslations\n /** Branding overrides (title, logo). */\n brand?: ModernAdminBrand\n /** Persist the demo session credentials in localStorage. */\n persistDemoSession?: boolean\n /**\n * Path under which the host mounts Better Auth's Node handler. Drives\n * the sign-in / sign-out endpoints — defaults to `/admin/api/auth`,\n * matching the canonical CLI scaffold (and `ModernAdminStaticUiModule`\n * mounted at `/admin`). Override only when the host mounts Better Auth\n * elsewhere; pass *without* a trailing slash.\n */\n authBasePath?: string\n /**\n * URL prefix where the SPA is mounted (e.g. `/admin`). Injected\n * automatically by `ModernAdminStaticUiMiddleware` from its `path` option\n * so you normally do not need to set this. The router uses it as its\n * basepath so all internal navigation and deep-link refreshes stay under\n * the correct prefix. Pass without a trailing slash.\n */\n basePath?: string\n /**\n * When true, the sidebar resource list shows the raw resource id next\n * to the localized label (e.g. \"Posts (posts)\") whenever the label\n * differs from the id. Defaults to `false` — keeping the sidebar tidy.\n * The home page tile and selector dropdowns (chart builder, etc.)\n * always show both regardless of this flag.\n */\n showSidebarResourceIds?: boolean\n}\n\ndeclare global {\n interface Window {\n __MODERN_ADMIN__?: ModernAdminRuntimeConfig\n }\n}\n\n/**\n * Reads runtime config from `window.__MODERN_ADMIN__`. Returns an empty\n * object (same-origin defaults) when nothing is injected.\n */\nexport function readWindowConfig(): ModernAdminRuntimeConfig {\n if (typeof window === 'undefined') return {}\n return window.__MODERN_ADMIN__ ?? {}\n}\n"],"mappings":";;;;;;;;AA4BA,SAAgB,EAAI,EAAE,WAAQ,iBAAsC;CAUlE,OACE,kBAAC,GAAD;EACE,SARmB,QAAc;GACnC,IAAI,CAAC,EAAO,WAAW,EAAO,QAAQ,WAAW,GAAG,OAAO;GAC3D,IAAM,IAAQ,IAAI,IAAI,EAAO,OAAO,GAC9B,IAAW,EAAe,QAAQ,MAAM,EAAM,IAAI,EAAE,IAAI,CAAC;GAC/D,OAAO,EAAS,SAAS,IAAI,IAAW;EAC1C,GAAG,CAAC,EAAO,OAAO,CAGL;EACT,eAAe,EAAO;EACtB,gBAAgB,EAAO;EACvB,sBAAsB,EAAO;YAE7B,kBAAC,GAAD;GACc;GACZ,eAAe;IACb,SAAS,EAAO;IAChB,aAAa,EAAO,eAAe;IACnC,SAAS,EAAO;IAChB,oBAAoB,EAAO;IAC3B,cAAc,EAAO;GACvB;aAEA,kBAAC,GAAD;IACE,WAAW,EAAO;IAClB,UAAU,EAAO;IACjB,wBAAwB,EAAO;GAChC,CAAA;EACkB,CAAA;CACT,CAAA;AAElB;;;AC5BA,SAAgB,EAAM,GAAoB,GAAqC;CAC7E,EAAU;CACV,IAAM,IAAa,EAAW,CAAS;CAMvC,OALA,EAAK,OACH,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;EAAK,QAAQ,EAAQ;EAAQ,YAAY,EAAQ;CAAa,CAAA,EACpD,CAAA,CACd,GACO,EACL,eAAe,EAAK,QAAQ,EAC9B;AACF;;;AC0CA,SAAgB,IAA6C;CAE3D,OADI,OAAO,SAAW,MAAoB,CAAC,IACpC,OAAO,oBAAoB,CAAC;AACrC"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/app.tsx","../../src/mount.tsx","../../src/runtime-config.ts"],"sourcesContent":["/**\n * Root <App> for the admin SPA. Wraps the AdminApp shell with i18n and the\n * ModernAdmin provider, wiring everything from a single runtime config.\n *\n * Used by both the standalone bundle (window.__MODERN_ADMIN__) and the\n * library `mount()` entry point — hosts compose it the same way.\n */\n\nimport { type ReactElement, useMemo } from 'react'\nimport {\n AdminApp,\n type ComponentLoader,\n I18nProvider,\n ModernAdminProvider,\n} from '@modern-admin/react'\nimport { builtinLocales } from '@modern-admin/i18n'\nimport type { ModernAdminRuntimeConfig } from './runtime-config.js'\n\nexport interface AppProps {\n config: ModernAdminRuntimeConfig\n /**\n * Optional custom property-type components. The standalone bundle leaves\n * this unset; the library `mount()` lets callers pass demo / project-\n * specific components.\n */\n components?: ComponentLoader\n}\n\nexport function App({ config, components }: AppProps): ReactElement {\n // Filter the built-in 9-locale bundle by the host's whitelist. Empty /\n // omitted → expose everything; the language switcher in the header\n // collapses itself when only one locale survives the filter.\n const enabledLocales = useMemo(() => {\n if (!config.locales || config.locales.length === 0) return builtinLocales\n const codes = new Set(config.locales)\n const filtered = builtinLocales.filter((l) => codes.has(l.code))\n return filtered.length > 0 ? filtered : builtinLocales\n }, [config.locales])\n return (\n <I18nProvider\n locales={enabledLocales}\n defaultLocale={config.defaultLocale}\n fallbackLocale={config.fallbackLocale}\n metadataTranslations={config.metadataTranslations}\n >\n <ModernAdminProvider\n components={components}\n clientOptions={{\n baseUrl: config.apiUrl,\n credentials: config.credentials ?? 'include',\n headers: config.headers,\n persistDemoSession: config.persistDemoSession,\n authBasePath: config.authBasePath,\n }}\n >\n <AdminApp\n loginHint={config.loginHint}\n basePath={config.basePath}\n showSidebarResourceIds={config.showSidebarResourceIds}\n />\n </ModernAdminProvider>\n </I18nProvider>\n )\n}\n","/**\n * Imperative mounting API for embedding the admin SPA inside another React\n * tree or vanilla HTML page. Consumers call once at startup:\n *\n * import { mount } from '@modern-admin/web'\n * mount(document.getElementById('root')!, { apiUrl: 'https://api.example.com' })\n *\n * This module deliberately does **not** import the stylesheet: an app that\n * registers its own components needs to own the Tailwind root so its classes\n * get scanned, and a second root here would compile Tailwind twice. Import it\n * yourself, either directly or from your own CSS:\n *\n * import '@modern-admin/react/styles.css'\n *\n * For the prebuilt standalone bundle, `src/standalone.tsx` calls this for\n * you using `window.__MODERN_ADMIN__` and pulls in the stylesheet.\n */\n\nimport { StrictMode } from 'react'\nimport { createRoot, type Root } from 'react-dom/client'\nimport { type ComponentLoader } from '@modern-admin/react'\nimport { initTheme } from '@modern-admin/ui'\nimport { App } from './app.js'\nimport type { ModernAdminRuntimeConfig } from './runtime-config.js'\n\nexport interface MountOptions {\n config: ModernAdminRuntimeConfig\n /** Optional ComponentLoader with custom property-type components. */\n components?: ComponentLoader\n}\n\nexport interface MountedAdmin {\n /** Unmounts the admin SPA from its container. */\n unmount(): void\n}\n\n/**\n * Renders the admin SPA into `container`. Returns a handle with `unmount()`\n * for hosts that need to tear it down dynamically (rare — most users mount\n * once and never unmount).\n */\nexport function mount(container: Element, options: MountOptions): MountedAdmin {\n initTheme()\n const root: Root = createRoot(container)\n root.render(\n <StrictMode>\n <App config={options.config} components={options.components} />\n </StrictMode>,\n )\n return {\n unmount: () => root.unmount(),\n }\n}\n","/**\n * Runtime configuration for the prebuilt admin SPA.\n *\n * The standalone bundle reads this from `window.__MODERN_ADMIN__`; the\n * library `mount()` API takes it as an argument. Either way it is the\n * single source of truth for \"where is the API\", \"which translations to\n * apply\", and how the admin client should behave at runtime.\n *\n * No build-time env vars: one bundle serves any deployment.\n */\n\nimport type { MetadataTranslations } from '@modern-admin/react'\n\nexport interface ModernAdminBrand {\n /** Shown in the sidebar header and login screen. */\n title?: string\n /** Optional logo image URL. */\n logoUrl?: string\n}\n\nexport interface ModernAdminRuntimeConfig {\n /**\n * Absolute base URL of the admin API (the @modern-admin/nest backend).\n * Leave undefined to use same-origin — recommended when the SPA is served\n * by the same NestJS process that exposes the admin API.\n */\n apiUrl?: string\n /** RequestCredentials forwarded to every fetch. Defaults to 'include'. */\n credentials?: RequestCredentials\n /** Extra headers attached to every request (e.g. CSRF, custom auth). */\n headers?: Record<string, string>\n /** Helper line shown on the login screen — e.g. demo credentials. */\n loginHint?: string\n /** Initial UI locale code. Falls back to the persisted choice / 'en'. */\n defaultLocale?: string\n /** Locale used when a translation is missing. Defaults to 'en'. */\n fallbackLocale?: string\n /**\n * Whitelist of locale codes to expose in the header switcher (filters\n * the built-in 9-locale bundle: `en`, `ru`, `de`, `es`, `fr`, `it`, `ja`,\n * `pl`, `pt-BR`). Behaviour:\n * - omitted / empty array → all built-in locales available\n * - single code → switcher hides, that locale is forced\n * - multiple codes → switcher lists only those locales\n */\n locales?: string[]\n /** Optional per-resource / per-property metadata translations. */\n metadataTranslations?: MetadataTranslations\n /** Branding overrides (title, logo). */\n brand?: ModernAdminBrand\n /** Persist the demo session credentials in localStorage. */\n persistDemoSession?: boolean\n /**\n * Path under which the host mounts Better Auth's Node handler. Drives\n * the sign-in / sign-out endpoints — defaults to `/admin/api/auth`,\n * matching the canonical CLI scaffold (and `ModernAdminStaticUiModule`\n * mounted at `/admin`). Override only when the host mounts Better Auth\n * elsewhere; pass *without* a trailing slash.\n */\n authBasePath?: string\n /**\n * URL prefix where the SPA is mounted (e.g. `/admin`). Injected\n * automatically by `ModernAdminStaticUiMiddleware` from its `path` option\n * so you normally do not need to set this. The router uses it as its\n * basepath so all internal navigation and deep-link refreshes stay under\n * the correct prefix. Pass without a trailing slash.\n */\n basePath?: string\n /**\n * When true, the sidebar resource list shows the raw resource id next\n * to the localized label (e.g. \"Posts (posts)\") whenever the label\n * differs from the id. Defaults to `false` — keeping the sidebar tidy.\n * The home page tile and selector dropdowns (chart builder, etc.)\n * always show both regardless of this flag.\n */\n showSidebarResourceIds?: boolean\n}\n\ndeclare global {\n interface Window {\n __MODERN_ADMIN__?: ModernAdminRuntimeConfig\n }\n}\n\n/**\n * Reads runtime config from `window.__MODERN_ADMIN__`. Returns an empty\n * object (same-origin defaults) when nothing is injected.\n */\nexport function readWindowConfig(): ModernAdminRuntimeConfig {\n if (typeof window === 'undefined') return {}\n return window.__MODERN_ADMIN__ ?? {}\n}\n"],"mappings":";;;;;;;AA4BA,SAAgB,EAAI,EAAE,WAAQ,iBAAsC;CAUlE,OACE,kBAAC,GAAD;EACE,SARmB,QAAc;GACnC,IAAI,CAAC,EAAO,WAAW,EAAO,QAAQ,WAAW,GAAG,OAAO;GAC3D,IAAM,IAAQ,IAAI,IAAI,EAAO,OAAO,GAC9B,IAAW,EAAe,QAAQ,MAAM,EAAM,IAAI,EAAE,IAAI,CAAC;GAC/D,OAAO,EAAS,SAAS,IAAI,IAAW;EAC1C,GAAG,CAAC,EAAO,OAAO,CAGL;EACT,eAAe,EAAO;EACtB,gBAAgB,EAAO;EACvB,sBAAsB,EAAO;YAE7B,kBAAC,GAAD;GACc;GACZ,eAAe;IACb,SAAS,EAAO;IAChB,aAAa,EAAO,eAAe;IACnC,SAAS,EAAO;IAChB,oBAAoB,EAAO;IAC3B,cAAc,EAAO;GACvB;aAEA,kBAAC,GAAD;IACE,WAAW,EAAO;IAClB,UAAU,EAAO;IACjB,wBAAwB,EAAO;GAChC,CAAA;EACkB,CAAA;CACT,CAAA;AAElB;;;ACtBA,SAAgB,EAAM,GAAoB,GAAqC;CAC7E,EAAU;CACV,IAAM,IAAa,EAAW,CAAS;CAMvC,OALA,EAAK,OACH,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;EAAK,QAAQ,EAAQ;EAAQ,YAAY,EAAQ;CAAa,CAAA,EACpD,CAAA,CACd,GACO,EACL,eAAe,EAAK,QAAQ,EAC9B;AACF;;;ACoCA,SAAgB,IAA6C;CAE3D,OADI,OAAO,SAAW,MAAoB,CAAC,IACpC,OAAO,oBAAoB,CAAC;AACrC"}
@@ -1 +1 @@
1
- {"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../../src/mount.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAE1D,OAAO,6BAA6B,CAAA;AAEpC,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAA;AAEnE,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,wBAAwB,CAAA;IAChC,qEAAqE;IACrE,UAAU,CAAC,EAAE,eAAe,CAAA;CAC7B;AAED,MAAM,WAAW,YAAY;IAC3B,iDAAiD;IACjD,OAAO,IAAI,IAAI,CAAA;CAChB;AAED;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,GAAG,YAAY,CAW7E"}
1
+ {"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../../src/mount.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAG1D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAA;AAEnE,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,wBAAwB,CAAA;IAChC,qEAAqE;IACrE,UAAU,CAAC,EAAE,eAAe,CAAA;CAC7B;AAED,MAAM,WAAW,YAAY;IAC3B,iDAAiD;IACjD,OAAO,IAAI,IAAI,CAAA;CAChB;AAED;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,GAAG,YAAY,CAW7E"}
@@ -1,2 +1,2 @@
1
- import{a as e,n as t}from"./createLucideIcon-CUopU_LC.js";import{S as n,g as r,s as i}from"./with-selector-NDLhu4dT.js";import{t as a}from"./circle-alert-0EyKeic4.js";import{t as o}from"./ellipsis-lEjcTxo0.js";import{t as s}from"./zap-b3saQVKc.js";import{s as c}from"./dist-Dpg8xy_r.js";import{B as l,D as u,K as d,R as f,U as p,et as m,it as h,n as g,z as _}from"./router-wFvtgo77.js";import{A as v,D as y,E as b,M as x,O as S,S as C,T as w,gt as T,i as E,j as D,k as O,lt as k,w as A}from"./index-t4woXGa-.js";var j=e(t(),1),M=n(),N=j.forwardRef((e,t)=>(0,M.jsx)(`nav`,{ref:t,"aria-label":`breadcrumb`,...e}));N.displayName=`Breadcrumb`;var P=j.forwardRef(({className:e,...t},n)=>(0,M.jsx)(`ol`,{ref:n,className:i(`flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5`,e),...t}));P.displayName=`BreadcrumbList`;var F=j.forwardRef(({className:e,...t},n)=>(0,M.jsx)(`li`,{ref:n,className:i(`inline-flex items-center gap-1.5`,e),...t}));F.displayName=`BreadcrumbItem`;var I=j.forwardRef(({asChild:e,className:t,...n},a)=>(0,M.jsx)(e?r:`a`,{ref:a,className:i(`transition-colors hover:text-foreground`,t),...n}));I.displayName=`BreadcrumbLink`;var L=j.forwardRef(({className:e,...t},n)=>(0,M.jsx)(`span`,{ref:n,role:`link`,"aria-disabled":`true`,"aria-current":`page`,className:i(`font-normal text-foreground`,e),...t}));L.displayName=`BreadcrumbPage`;var R=({children:e,className:t,...n})=>(0,M.jsx)(`li`,{role:`presentation`,"aria-hidden":`true`,className:i(`[&>svg]:size-3.5`,t),...n,children:e??(0,M.jsx)(T,{})});R.displayName=`BreadcrumbSeparator`;var z=({className:e,...t})=>(0,M.jsxs)(`span`,{role:`presentation`,"aria-hidden":`true`,className:i(`flex size-9 items-center justify-center`,e),...t,children:[(0,M.jsx)(o,{className:`size-4`}),(0,M.jsx)(`span`,{className:`sr-only`,children:`More`})]});z.displayName=`BreadcrumbEllipsis`;function B({items:e,className:t}){return e.length===0?null:(0,M.jsx)(N,{className:t,children:(0,M.jsx)(P,{children:e.map((t,n)=>{let r=n===e.length-1,i=(0,M.jsxs)(`span`,{className:`inline-flex items-center gap-1.5`,children:[t.icon,(0,M.jsx)(`span`,{className:`truncate max-w-[8rem] sm:max-w-[16rem]`,children:t.label})]});return(0,M.jsxs)(j.Fragment,{children:[(0,M.jsx)(F,{children:r||!t.to?(0,M.jsx)(L,{children:i}):(0,M.jsx)(I,{asChild:!0,children:(0,M.jsx)(g,{to:t.to,children:i})})}),!r&&(0,M.jsx)(R,{})]},`${n}-${t.label}`)})})})}function V(e){return{label:e,to:{name:`home`},icon:(0,M.jsx)(k,{className:`size-3.5`})}}var H=(e,t)=>!t?.recordActions||t.recordActions.includes(e),U=(e,t)=>{if(!t?.recordActions)return e;let n=new Set(t.recordActions);return e.filter(e=>n.has(e.name))},W=e=>typeof e.custom?.label==`string`?e.custom.label:e.name,G=e=>{let t=[],n=new Map,r=(e,t,i)=>{if(i>=t.length)return e;let a=t[i],o=t.slice(0,i+1).map(e=>e.name).join(`>`),s=n.get(o);return s||(s={key:o,group:a,items:[]},n.set(o,s),e.push({kind:`group`,group:s})),r(s.items,t,i+1)};for(let n of e){let e=n.nesting??[];(e.length>0?r(t,e,0):t).push({kind:`action`,action:n})}return t},K=(e,t)=>e.map(e=>e.kind===`action`?(0,M.jsxs)(w,{onSelect:()=>t(e.action),children:[(0,M.jsx)(s,{className:`size-4`}),` `,W(e.action)]},e.action.name):(0,M.jsxs)(O,{children:[(0,M.jsx)(D,{children:e.group.group.name}),(0,M.jsx)(y,{children:(0,M.jsx)(v,{children:K(e.group.items,t)})})]},e.group.key));function q({actions:e,onAction:t}){let n=j.useMemo(()=>G(e),[e]);return n.length===0?null:(0,M.jsx)(M.Fragment,{children:K(n,t)})}function J({actions:e,onAction:t,t:n,trigger:r,menuLabel:i,align:a=`end`}){return j.useMemo(()=>G(e),[e]).length===0?null:(0,M.jsxs)(C,{children:[(0,M.jsx)(x,{asChild:!0,children:r??(0,M.jsxs)(c,{variant:`outline`,size:`sm`,children:[(0,M.jsx)(s,{className:`size-4`}),n(`common:actions`)]})}),(0,M.jsxs)(A,{align:a,children:[(0,M.jsx)(b,{children:i??n(`common:actions`)}),(0,M.jsx)(S,{}),(0,M.jsx)(q,{actions:e,onAction:t})]})]})}function Y(e,t){t&&e[t.type===`error`?`error`:t.type===`warning`?`warning`:t.type===`info`?`info`:`success`]({message:t.message})}function X({title:e,hint:t}){return(0,M.jsxs)(`div`,{role:`alert`,className:`flex items-start gap-3 rounded-lg border border-destructive/50 bg-destructive/10 p-4 dark:bg-destructive/15`,children:[(0,M.jsx)(a,{className:`mt-0.5 size-5 shrink-0 text-destructive`}),(0,M.jsxs)(`div`,{className:`space-y-1 text-sm`,children:[(0,M.jsx)(`p`,{className:`font-semibold text-destructive`,children:e}),t&&(0,M.jsx)(`p`,{className:`text-destructive/90`,children:t})]})]})}function Z({resourceId:e,actionName:t,recordId:n,recordIds:r,onClose:i,onSuccess:a}){let o=d(e),{components:s}=h(),{t:c}=m(),g=E(),v=o?.actions?.find(e=>e.name===t),y=v?.component??null,b=y?s?.get(y):void 0,x=u(e,t,{...n===void 0?{}:{recordId:n},...r===void 0?{}:{recordIds:r},enabled:!!v&&!!b}),S=p(e,n),C=_(e),w=l(e),T=f(e),D=C.isPending||w.isPending||T.isPending,O=j.useCallback(async(e={})=>{let i=n?await C.mutateAsync({recordId:n,actionName:t,payload:e}):r?.length?await T.mutateAsync({actionName:t,ids:r,payload:e}):await w.mutateAsync({actionName:t,payload:e});return Y(g,i.notice),a?.(),i},[n,r,t,C,T,w,g,a]);return o?v?!y||!b?(0,M.jsx)(X,{title:c(`action:componentMissing`,{component:y??t}),hint:c(`action:componentMissingHint`)}):x.isError?(0,M.jsx)(X,{title:c(`action:loadFailed`),hint:x.error instanceof Error?x.error.message:void 0}):(0,M.jsx)(b,{action:v,resource:o,resourceId:e,...n===void 0?{}:{recordId:n},...r===void 0?{}:{recordIds:r},...S.data?.record?{record:S.data.record}:{},...x.data?.records?{records:x.data.records}:{},...x.data?{data:x.data}:{},loading:x.isLoading||!!n&&S.isLoading,invoke:O,submitting:D,close:i}):(0,M.jsx)(X,{title:c(`action:notFound`,{action:t})}):(0,M.jsx)(`p`,{className:`text-muted-foreground`,children:c(`common:loading`)})}export{W as a,B as c,q as i,V as l,Y as n,H as o,J as r,U as s,Z as t};
2
- //# sourceMappingURL=action-component-host-CXIbN-8V.js.map
1
+ import{a as e,n as t}from"./createLucideIcon-CUopU_LC.js";import{S as n,g as r,s as i}from"./with-selector-NDLhu4dT.js";import{t as a}from"./circle-alert-0EyKeic4.js";import{t as o}from"./ellipsis-lEjcTxo0.js";import{t as s}from"./zap-b3saQVKc.js";import{s as c}from"./dist-Dpg8xy_r.js";import{B as l,D as u,K as d,R as f,U as p,et as m,it as h,n as g,z as _}from"./router-wFvtgo77.js";import{A as v,D as y,E as b,M as x,O as S,S as C,T as w,gt as T,i as E,j as D,k as O,lt as k,w as A}from"./index-C3MuojaM.js";var j=e(t(),1),M=n(),N=j.forwardRef((e,t)=>(0,M.jsx)(`nav`,{ref:t,"aria-label":`breadcrumb`,...e}));N.displayName=`Breadcrumb`;var P=j.forwardRef(({className:e,...t},n)=>(0,M.jsx)(`ol`,{ref:n,className:i(`flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5`,e),...t}));P.displayName=`BreadcrumbList`;var F=j.forwardRef(({className:e,...t},n)=>(0,M.jsx)(`li`,{ref:n,className:i(`inline-flex items-center gap-1.5`,e),...t}));F.displayName=`BreadcrumbItem`;var I=j.forwardRef(({asChild:e,className:t,...n},a)=>(0,M.jsx)(e?r:`a`,{ref:a,className:i(`transition-colors hover:text-foreground`,t),...n}));I.displayName=`BreadcrumbLink`;var L=j.forwardRef(({className:e,...t},n)=>(0,M.jsx)(`span`,{ref:n,role:`link`,"aria-disabled":`true`,"aria-current":`page`,className:i(`font-normal text-foreground`,e),...t}));L.displayName=`BreadcrumbPage`;var R=({children:e,className:t,...n})=>(0,M.jsx)(`li`,{role:`presentation`,"aria-hidden":`true`,className:i(`[&>svg]:size-3.5`,t),...n,children:e??(0,M.jsx)(T,{})});R.displayName=`BreadcrumbSeparator`;var z=({className:e,...t})=>(0,M.jsxs)(`span`,{role:`presentation`,"aria-hidden":`true`,className:i(`flex size-9 items-center justify-center`,e),...t,children:[(0,M.jsx)(o,{className:`size-4`}),(0,M.jsx)(`span`,{className:`sr-only`,children:`More`})]});z.displayName=`BreadcrumbEllipsis`;function B({items:e,className:t}){return e.length===0?null:(0,M.jsx)(N,{className:t,children:(0,M.jsx)(P,{children:e.map((t,n)=>{let r=n===e.length-1,i=(0,M.jsxs)(`span`,{className:`inline-flex items-center gap-1.5`,children:[t.icon,(0,M.jsx)(`span`,{className:`truncate max-w-[8rem] sm:max-w-[16rem]`,children:t.label})]});return(0,M.jsxs)(j.Fragment,{children:[(0,M.jsx)(F,{children:r||!t.to?(0,M.jsx)(L,{children:i}):(0,M.jsx)(I,{asChild:!0,children:(0,M.jsx)(g,{to:t.to,children:i})})}),!r&&(0,M.jsx)(R,{})]},`${n}-${t.label}`)})})})}function V(e){return{label:e,to:{name:`home`},icon:(0,M.jsx)(k,{className:`size-3.5`})}}var H=(e,t)=>!t?.recordActions||t.recordActions.includes(e),U=(e,t)=>{if(!t?.recordActions)return e;let n=new Set(t.recordActions);return e.filter(e=>n.has(e.name))},W=e=>typeof e.custom?.label==`string`?e.custom.label:e.name,G=e=>{let t=[],n=new Map,r=(e,t,i)=>{if(i>=t.length)return e;let a=t[i],o=t.slice(0,i+1).map(e=>e.name).join(`>`),s=n.get(o);return s||(s={key:o,group:a,items:[]},n.set(o,s),e.push({kind:`group`,group:s})),r(s.items,t,i+1)};for(let n of e){let e=n.nesting??[];(e.length>0?r(t,e,0):t).push({kind:`action`,action:n})}return t},K=(e,t)=>e.map(e=>e.kind===`action`?(0,M.jsxs)(w,{onSelect:()=>t(e.action),children:[(0,M.jsx)(s,{className:`size-4`}),` `,W(e.action)]},e.action.name):(0,M.jsxs)(O,{children:[(0,M.jsx)(D,{children:e.group.group.name}),(0,M.jsx)(y,{children:(0,M.jsx)(v,{children:K(e.group.items,t)})})]},e.group.key));function q({actions:e,onAction:t}){let n=j.useMemo(()=>G(e),[e]);return n.length===0?null:(0,M.jsx)(M.Fragment,{children:K(n,t)})}function J({actions:e,onAction:t,t:n,trigger:r,menuLabel:i,align:a=`end`}){return j.useMemo(()=>G(e),[e]).length===0?null:(0,M.jsxs)(C,{children:[(0,M.jsx)(x,{asChild:!0,children:r??(0,M.jsxs)(c,{variant:`outline`,size:`sm`,children:[(0,M.jsx)(s,{className:`size-4`}),n(`common:actions`)]})}),(0,M.jsxs)(A,{align:a,children:[(0,M.jsx)(b,{children:i??n(`common:actions`)}),(0,M.jsx)(S,{}),(0,M.jsx)(q,{actions:e,onAction:t})]})]})}function Y(e,t){t&&e[t.type===`error`?`error`:t.type===`warning`?`warning`:t.type===`info`?`info`:`success`]({message:t.message})}function X({title:e,hint:t}){return(0,M.jsxs)(`div`,{role:`alert`,className:`flex items-start gap-3 rounded-lg border border-destructive/50 bg-destructive/10 p-4 dark:bg-destructive/15`,children:[(0,M.jsx)(a,{className:`mt-0.5 size-5 shrink-0 text-destructive`}),(0,M.jsxs)(`div`,{className:`space-y-1 text-sm`,children:[(0,M.jsx)(`p`,{className:`font-semibold text-destructive`,children:e}),t&&(0,M.jsx)(`p`,{className:`text-destructive/90`,children:t})]})]})}function Z({resourceId:e,actionName:t,recordId:n,recordIds:r,onClose:i,onSuccess:a}){let o=d(e),{components:s}=h(),{t:c}=m(),g=E(),v=o?.actions?.find(e=>e.name===t),y=v?.component??null,b=y?s?.get(y):void 0,x=u(e,t,{...n===void 0?{}:{recordId:n},...r===void 0?{}:{recordIds:r},enabled:!!v&&!!b}),S=p(e,n),C=_(e),w=l(e),T=f(e),D=C.isPending||w.isPending||T.isPending,O=j.useCallback(async(e={})=>{let i=n?await C.mutateAsync({recordId:n,actionName:t,payload:e}):r?.length?await T.mutateAsync({actionName:t,ids:r,payload:e}):await w.mutateAsync({actionName:t,payload:e});return Y(g,i.notice),a?.(),i},[n,r,t,C,T,w,g,a]);return o?v?!y||!b?(0,M.jsx)(X,{title:c(`action:componentMissing`,{component:y??t}),hint:c(`action:componentMissingHint`)}):x.isError?(0,M.jsx)(X,{title:c(`action:loadFailed`),hint:x.error instanceof Error?x.error.message:void 0}):(0,M.jsx)(b,{action:v,resource:o,resourceId:e,...n===void 0?{}:{recordId:n},...r===void 0?{}:{recordIds:r},...S.data?.record?{record:S.data.record}:{},...x.data?.records?{records:x.data.records}:{},...x.data?{data:x.data}:{},loading:x.isLoading||!!n&&S.isLoading,invoke:O,submitting:D,close:i}):(0,M.jsx)(X,{title:c(`action:notFound`,{action:t})}):(0,M.jsx)(`p`,{className:`text-muted-foreground`,children:c(`common:loading`)})}export{W as a,B as c,q as i,V as l,Y as n,H as o,J as r,U as s,Z as t};
2
+ //# sourceMappingURL=action-component-host-CuzX-99S.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"action-component-host-CXIbN-8V.js","names":[],"sources":["../../../../ui/src/components/breadcrumb.tsx","../../../../react/src/breadcrumbs.tsx","../../../../react/src/action-menu.tsx","../../../../react/src/action-notice.ts","../../../../react/src/components/action-component-host.tsx"],"sourcesContent":["import * as React from 'react'\nimport { Slot } from '@radix-ui/react-slot'\nimport { ChevronRight, MoreHorizontal } from 'lucide-react'\nimport { cn } from '../lib/utils.js'\n\nexport const Breadcrumb = React.forwardRef<\n HTMLElement,\n React.ComponentPropsWithoutRef<'nav'>\n>((props, ref) => <nav ref={ref} aria-label=\"breadcrumb\" {...props} />)\nBreadcrumb.displayName = 'Breadcrumb'\n\nexport const BreadcrumbList = React.forwardRef<\n HTMLOListElement,\n React.ComponentPropsWithoutRef<'ol'>\n>(({ className, ...props }, ref) => (\n <ol\n ref={ref}\n className={cn(\n 'flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5',\n className,\n )}\n {...props}\n />\n))\nBreadcrumbList.displayName = 'BreadcrumbList'\n\nexport const BreadcrumbItem = React.forwardRef<\n HTMLLIElement,\n React.ComponentPropsWithoutRef<'li'>\n>(({ className, ...props }, ref) => (\n <li\n ref={ref}\n className={cn('inline-flex items-center gap-1.5', className)}\n {...props}\n />\n))\nBreadcrumbItem.displayName = 'BreadcrumbItem'\n\nexport const BreadcrumbLink = React.forwardRef<\n HTMLAnchorElement,\n React.ComponentPropsWithoutRef<'a'> & { asChild?: boolean }\n>(({ asChild, className, ...props }, ref) => {\n const Comp = asChild ? Slot : 'a'\n return (\n <Comp\n ref={ref}\n className={cn('transition-colors hover:text-foreground', className)}\n {...props}\n />\n )\n})\nBreadcrumbLink.displayName = 'BreadcrumbLink'\n\nexport const BreadcrumbPage = React.forwardRef<\n HTMLSpanElement,\n React.ComponentPropsWithoutRef<'span'>\n>(({ className, ...props }, ref) => (\n <span\n ref={ref}\n role=\"link\"\n aria-disabled=\"true\"\n aria-current=\"page\"\n className={cn('font-normal text-foreground', className)}\n {...props}\n />\n))\nBreadcrumbPage.displayName = 'BreadcrumbPage'\n\nexport const BreadcrumbSeparator = ({\n children,\n className,\n ...props\n}: React.ComponentPropsWithoutRef<'li'>): React.ReactElement => (\n <li\n role=\"presentation\"\n aria-hidden=\"true\"\n className={cn('[&>svg]:size-3.5', className)}\n {...props}\n >\n {children ?? <ChevronRight />}\n </li>\n)\nBreadcrumbSeparator.displayName = 'BreadcrumbSeparator'\n\nexport const BreadcrumbEllipsis = ({\n className,\n ...props\n}: React.ComponentPropsWithoutRef<'span'>): React.ReactElement => (\n <span\n role=\"presentation\"\n aria-hidden=\"true\"\n className={cn('flex size-9 items-center justify-center', className)}\n {...props}\n >\n <MoreHorizontal className=\"size-4\" />\n <span className=\"sr-only\">More</span>\n </span>\n)\nBreadcrumbEllipsis.displayName = 'BreadcrumbEllipsis'\n","// Page breadcrumb helper. Each page builds its own crumb chain (home →\n// resource → record → action) and hands it to <PageBreadcrumbs />, which\n// renders the shadcn primitives with separators, hash-router-aware Links\n// and a Home icon on the root crumb.\n\nimport * as React from 'react'\nimport {\n Breadcrumb,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n} from '@modern-admin/ui'\nimport { Home } from 'lucide-react'\nimport { Link } from './router.js'\nimport type { Route } from './router.js'\n\nexport interface BreadcrumbItemSpec {\n label: string\n /** When set, renders a hash-router link; otherwise the crumb is plain text. */\n to?: Route\n /** Optional leading icon. The home crumb gets one by default. */\n icon?: React.ReactNode\n}\n\nexport interface PageBreadcrumbsProps {\n items: BreadcrumbItemSpec[]\n className?: string\n}\n\nexport function PageBreadcrumbs({\n items,\n className,\n}: PageBreadcrumbsProps): React.ReactElement | null {\n if (items.length === 0) return null\n return (\n <Breadcrumb className={className}>\n <BreadcrumbList>\n {items.map((item, i) => {\n const isLast = i === items.length - 1\n const content = (\n <span className=\"inline-flex items-center gap-1.5\">\n {item.icon}\n <span className=\"truncate max-w-[8rem] sm:max-w-[16rem]\">{item.label}</span>\n </span>\n )\n return (\n <React.Fragment key={`${i}-${item.label}`}>\n <BreadcrumbItem>\n {isLast || !item.to ? (\n <BreadcrumbPage>{content}</BreadcrumbPage>\n ) : (\n <BreadcrumbLink asChild>\n <Link to={item.to}>{content}</Link>\n </BreadcrumbLink>\n )}\n </BreadcrumbItem>\n {!isLast && <BreadcrumbSeparator />}\n </React.Fragment>\n )\n })}\n </BreadcrumbList>\n </Breadcrumb>\n )\n}\n\n/** Default home crumb — used by every page; pass `homeLabel` to localize. */\nexport function homeCrumb(label: string): BreadcrumbItemSpec {\n return {\n label,\n to: { name: 'home' },\n icon: <Home className=\"size-3.5\" />,\n }\n}\n","import * as React from 'react'\nimport {\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuPortal,\n DropdownMenuSeparator,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuTrigger,\n} from '@modern-admin/ui'\nimport { MoreHorizontal, Zap } from 'lucide-react'\nimport type { ActionDescriptor, ActionGroup, RecordJSON } from './types.js'\n\ninterface ActionMenuGroupNode {\n key: string\n group: ActionGroup\n items: ActionMenuNode[]\n}\n\ntype ActionMenuNode =\n | { kind: 'action'; action: ActionDescriptor }\n | { kind: 'group'; group: ActionMenuGroupNode }\n\nexport interface ActionMenuProps {\n actions: ActionDescriptor[]\n onAction(action: ActionDescriptor): void\n t: (key: string, params?: Record<string, string | number>) => string\n trigger?: React.ReactElement\n menuLabel?: string\n align?: 'start' | 'center' | 'end'\n}\n\n/**\n * Whether a record-scoped action should be offered for a specific record.\n *\n * The server resolves `isVisible`/`isAccessible` per record and reports the\n * survivors in `RecordJSON.recordActions`. A record without that field made\n * it here outside the action pipeline (or from an older server) — that is\n * \"no opinion\", so everything stays visible rather than everything vanishing.\n */\nexport const isActionAllowedForRecord = (\n actionName: string,\n record: Pick<RecordJSON, 'recordActions'> | undefined,\n): boolean => !record?.recordActions || record.recordActions.includes(actionName)\n\n/** Narrow a resource's record actions down to the ones this record offers. */\nexport const visibleRecordActions = (\n actions: ActionDescriptor[],\n record: Pick<RecordJSON, 'recordActions'> | undefined,\n): ActionDescriptor[] => {\n if (!record?.recordActions) return actions\n const allowed = new Set(record.recordActions)\n return actions.filter((a) => allowed.has(a.name))\n}\n\n/** Display label for an action: the (already localized) `custom.label` set\n * by the metadata translator, falling back to the raw action name. */\nexport const getActionLabel = (action: ActionDescriptor): string =>\n typeof action.custom?.label === 'string' ? action.custom.label : action.name\n\nconst buildActionMenuTree = (actions: ActionDescriptor[]): ActionMenuNode[] => {\n const root: ActionMenuNode[] = []\n const groups = new Map<string, ActionMenuGroupNode>()\n\n const ensureGroup = (target: ActionMenuNode[], path: ActionGroup[], depth: number): ActionMenuNode[] => {\n if (depth >= path.length) return target\n const segment = path[depth]!\n const key = path\n .slice(0, depth + 1)\n .map((item) => item.name)\n .join('>')\n let group = groups.get(key)\n if (!group) {\n group = { key, group: segment, items: [] }\n groups.set(key, group)\n target.push({ kind: 'group', group })\n }\n return ensureGroup(group.items, path, depth + 1)\n }\n\n for (const action of actions) {\n const nesting = action.nesting ?? []\n const target = nesting.length > 0 ? ensureGroup(root, nesting, 0) : root\n target.push({ kind: 'action', action })\n }\n\n return root\n}\n\nconst renderNodes = (\n nodes: ActionMenuNode[],\n onAction: (action: ActionDescriptor) => void,\n): React.ReactNode =>\n nodes.map((node) => {\n if (node.kind === 'action') {\n return (\n <DropdownMenuItem\n key={node.action.name}\n onSelect={() => onAction(node.action)}\n >\n <Zap className=\"size-4\" /> {getActionLabel(node.action)}\n </DropdownMenuItem>\n )\n }\n return (\n <DropdownMenuSub key={node.group.key}>\n <DropdownMenuSubTrigger>\n {node.group.group.name}\n </DropdownMenuSubTrigger>\n <DropdownMenuPortal>\n <DropdownMenuSubContent>\n {renderNodes(node.group.items, onAction)}\n </DropdownMenuSubContent>\n </DropdownMenuPortal>\n </DropdownMenuSub>\n )\n })\n\nexport function ActionMenuItems({\n actions,\n onAction,\n}: {\n actions: ActionDescriptor[]\n onAction(action: ActionDescriptor): void\n}): React.ReactElement | null {\n const nodes = React.useMemo(() => buildActionMenuTree(actions), [actions])\n if (nodes.length === 0) return null\n return <>{renderNodes(nodes, onAction)}</>\n}\n\nexport function ActionMenu({\n actions,\n onAction,\n t,\n trigger,\n menuLabel,\n align = 'end',\n}: ActionMenuProps): React.ReactElement | null {\n const nodes = React.useMemo(() => buildActionMenuTree(actions), [actions])\n if (nodes.length === 0) return null\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n {trigger ?? (\n <Button variant=\"outline\" size=\"sm\">\n <Zap className=\"size-4\" />\n {t('common:actions')}\n </Button>\n )}\n </DropdownMenuTrigger>\n <DropdownMenuContent align={align}>\n <DropdownMenuLabel>{menuLabel ?? t('common:actions')}</DropdownMenuLabel>\n <DropdownMenuSeparator />\n <ActionMenuItems actions={actions} onAction={onAction} />\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\nexport function MoreActionsMenu({\n actions,\n onAction,\n t,\n menuLabel,\n}: Omit<ActionMenuProps, 'trigger' | 'align'>): React.ReactElement | null {\n if (actions.length === 0) return null\n return (\n <ActionMenu\n actions={actions}\n onAction={onAction}\n t={t}\n menuLabel={menuLabel}\n trigger={(\n <Button variant=\"ghost\" size=\"icon\" className=\"h-8 w-8\">\n <MoreHorizontal className=\"size-4\" />\n <span className=\"sr-only\">{t('common:openMenu')}</span>\n </Button>\n )}\n />\n )\n}\n","// Shared `notice` → toast plumbing for custom-action invocations.\n//\n// Every surface that fires a custom action (list toolbar, row menu, bulk\n// toolbar, show page, action component host) gets back the same\n// `CustomActionResponse` shape, so the mapping lives here rather than being\n// re-derived at each call site.\n\nimport type { useNotify } from './notify.js'\nimport type { CustomActionResponse } from './types.js'\n\ntype NotifyApi = ReturnType<typeof useNotify>\n\n/** Show a custom action's `notice` as the matching toast. Unrecognised\n * types fall back to a success toast; a missing notice is a no-op. */\nexport function showActionNotice(\n notify: NotifyApi,\n notice: CustomActionResponse['notice'],\n): void {\n if (!notice) return\n const type = notice.type === 'error' ? 'error'\n : notice.type === 'warning' ? 'warning'\n : notice.type === 'info' ? 'info'\n : 'success'\n notify[type]({ message: notice.message })\n}\n","// Runtime host for a custom action's UI component.\n//\n// An action declaring `component: 'MyForm'` (see `Action.component` in\n// @modern-admin/core) does not fire on click. Instead the operator lands\n// here: the named component is looked up on the ComponentLoader and rendered\n// with everything it needs to run the action — the priming `GET` response,\n// the record for record-scoped actions, and an `invoke(payload)` callback\n// that POSTs and surfaces the resulting `notice`.\n//\n// The host is presentation-neutral so the same code backs both surfaces:\n// the full `/resources/:id/actions/:name` page and the modal opened for\n// actions declaring `custom.showAs: 'dialog'`.\n\nimport * as React from 'react'\nimport { AlertCircle } from 'lucide-react'\nimport {\n useActionData,\n useInvokeBulkAction,\n useInvokeRecordAction,\n useInvokeResourceAction,\n useRecord,\n useResource,\n} from '../hooks.js'\nimport { useAdminContext } from '../provider.js'\nimport { useI18n } from '../i18n.js'\nimport { useNotify } from '../notify.js'\nimport { showActionNotice } from '../action-notice.js'\nimport type { ActionComponentProps, CustomActionResponse } from '../types.js'\n\nexport interface ActionComponentHostProps {\n resourceId: string\n actionName: string\n /** Set for record-scoped actions (`actionType: 'record'`). */\n recordId?: string\n /** Set for bulk-scoped actions (`actionType: 'bulk'`) — the selection. */\n recordIds?: string[]\n /** Dismiss the action — pop the page or close the dialog. */\n onClose(): void\n /** Fired after each successful `invoke()`. The bulk toolbar uses it to\n * clear its selection; in page mode the list has already unmounted, so\n * only dialog callers see it. */\n onSuccess?(): void\n}\n\nfunction HostError({ title, hint }: { title: string; hint?: string }): React.ReactElement {\n return (\n <div\n role=\"alert\"\n className=\"flex items-start gap-3 rounded-lg border border-destructive/50 bg-destructive/10 p-4 dark:bg-destructive/15\"\n >\n <AlertCircle className=\"mt-0.5 size-5 shrink-0 text-destructive\" />\n <div className=\"space-y-1 text-sm\">\n <p className=\"font-semibold text-destructive\">{title}</p>\n {hint && <p className=\"text-destructive/90\">{hint}</p>}\n </div>\n </div>\n )\n}\n\nexport function ActionComponentHost({\n resourceId,\n actionName,\n recordId,\n recordIds,\n onClose,\n onSuccess,\n}: ActionComponentHostProps): React.ReactElement {\n const resource = useResource(resourceId)\n const { components } = useAdminContext()\n const { t } = useI18n()\n const notify = useNotify()\n\n const action = resource?.actions?.find((a) => a.name === actionName)\n const componentName = action?.component ?? null\n const Custom = componentName ? components?.get(componentName) : undefined\n\n // Only prime once we know the action exists and its component resolved —\n // otherwise the page renders an error and the handler must not run.\n const primed = useActionData(resourceId, actionName, {\n ...(recordId !== undefined ? { recordId } : {}),\n ...(recordIds !== undefined ? { recordIds } : {}),\n enabled: Boolean(action) && Boolean(Custom),\n })\n const record = useRecord(resourceId, recordId)\n const invokeRecord = useInvokeRecordAction(resourceId)\n const invokeResource = useInvokeResourceAction(resourceId)\n const invokeBulk = useInvokeBulkAction(resourceId)\n\n const submitting = invokeRecord.isPending || invokeResource.isPending || invokeBulk.isPending\n const invoke = React.useCallback(\n async (payload: Record<string, unknown> = {}): Promise<CustomActionResponse> => {\n // Scope is decided by what the host was mounted with, not by the\n // action's declared `actionType` — a mismatch would build a request\n // the server can't route.\n const res = recordId\n ? await invokeRecord.mutateAsync({ recordId, actionName, payload })\n : recordIds?.length\n ? await invokeBulk.mutateAsync({ actionName, ids: recordIds, payload })\n : await invokeResource.mutateAsync({ actionName, payload })\n showActionNotice(notify, res.notice)\n onSuccess?.()\n return res\n },\n [recordId, recordIds, actionName, invokeRecord, invokeBulk, invokeResource, notify, onSuccess],\n )\n\n if (!resource) return <p className=\"text-muted-foreground\">{t('common:loading')}</p>\n if (!action) {\n return <HostError title={t('action:notFound', { action: actionName })} />\n }\n if (!componentName || !Custom) {\n return (\n <HostError\n title={t('action:componentMissing', { component: componentName ?? actionName })}\n hint={t('action:componentMissingHint')}\n />\n )\n }\n if (primed.isError) {\n return (\n <HostError\n title={t('action:loadFailed')}\n hint={primed.error instanceof Error ? primed.error.message : undefined}\n />\n )\n }\n\n const props: ActionComponentProps = {\n action,\n resource,\n resourceId,\n ...(recordId !== undefined ? { recordId } : {}),\n ...(recordIds !== undefined ? { recordIds } : {}),\n ...(record.data?.record ? { record: record.data.record } : {}),\n ...(primed.data?.records ? { records: primed.data.records } : {}),\n ...(primed.data ? { data: primed.data } : {}),\n loading: primed.isLoading || (Boolean(recordId) && record.isLoading),\n invoke,\n submitting,\n close: onClose,\n }\n return <Custom {...props} />\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"action-component-host-CuzX-99S.js","names":[],"sources":["../../../../ui/src/components/breadcrumb.tsx","../../../../react/src/breadcrumbs.tsx","../../../../react/src/action-menu.tsx","../../../../react/src/action-notice.ts","../../../../react/src/components/action-component-host.tsx"],"sourcesContent":["import * as React from 'react'\nimport { Slot } from '@radix-ui/react-slot'\nimport { ChevronRight, MoreHorizontal } from 'lucide-react'\nimport { cn } from '../lib/utils.js'\n\nexport const Breadcrumb = React.forwardRef<\n HTMLElement,\n React.ComponentPropsWithoutRef<'nav'>\n>((props, ref) => <nav ref={ref} aria-label=\"breadcrumb\" {...props} />)\nBreadcrumb.displayName = 'Breadcrumb'\n\nexport const BreadcrumbList = React.forwardRef<\n HTMLOListElement,\n React.ComponentPropsWithoutRef<'ol'>\n>(({ className, ...props }, ref) => (\n <ol\n ref={ref}\n className={cn(\n 'flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5',\n className,\n )}\n {...props}\n />\n))\nBreadcrumbList.displayName = 'BreadcrumbList'\n\nexport const BreadcrumbItem = React.forwardRef<\n HTMLLIElement,\n React.ComponentPropsWithoutRef<'li'>\n>(({ className, ...props }, ref) => (\n <li\n ref={ref}\n className={cn('inline-flex items-center gap-1.5', className)}\n {...props}\n />\n))\nBreadcrumbItem.displayName = 'BreadcrumbItem'\n\nexport const BreadcrumbLink = React.forwardRef<\n HTMLAnchorElement,\n React.ComponentPropsWithoutRef<'a'> & { asChild?: boolean }\n>(({ asChild, className, ...props }, ref) => {\n const Comp = asChild ? Slot : 'a'\n return (\n <Comp\n ref={ref}\n className={cn('transition-colors hover:text-foreground', className)}\n {...props}\n />\n )\n})\nBreadcrumbLink.displayName = 'BreadcrumbLink'\n\nexport const BreadcrumbPage = React.forwardRef<\n HTMLSpanElement,\n React.ComponentPropsWithoutRef<'span'>\n>(({ className, ...props }, ref) => (\n <span\n ref={ref}\n role=\"link\"\n aria-disabled=\"true\"\n aria-current=\"page\"\n className={cn('font-normal text-foreground', className)}\n {...props}\n />\n))\nBreadcrumbPage.displayName = 'BreadcrumbPage'\n\nexport const BreadcrumbSeparator = ({\n children,\n className,\n ...props\n}: React.ComponentPropsWithoutRef<'li'>): React.ReactElement => (\n <li\n role=\"presentation\"\n aria-hidden=\"true\"\n className={cn('[&>svg]:size-3.5', className)}\n {...props}\n >\n {children ?? <ChevronRight />}\n </li>\n)\nBreadcrumbSeparator.displayName = 'BreadcrumbSeparator'\n\nexport const BreadcrumbEllipsis = ({\n className,\n ...props\n}: React.ComponentPropsWithoutRef<'span'>): React.ReactElement => (\n <span\n role=\"presentation\"\n aria-hidden=\"true\"\n className={cn('flex size-9 items-center justify-center', className)}\n {...props}\n >\n <MoreHorizontal className=\"size-4\" />\n <span className=\"sr-only\">More</span>\n </span>\n)\nBreadcrumbEllipsis.displayName = 'BreadcrumbEllipsis'\n","// Page breadcrumb helper. Each page builds its own crumb chain (home →\n// resource → record → action) and hands it to <PageBreadcrumbs />, which\n// renders the shadcn primitives with separators, hash-router-aware Links\n// and a Home icon on the root crumb.\n\nimport * as React from 'react'\nimport {\n Breadcrumb,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n} from '@modern-admin/ui'\nimport { Home } from 'lucide-react'\nimport { Link } from './router.js'\nimport type { Route } from './router.js'\n\nexport interface BreadcrumbItemSpec {\n label: string\n /** When set, renders a hash-router link; otherwise the crumb is plain text. */\n to?: Route\n /** Optional leading icon. The home crumb gets one by default. */\n icon?: React.ReactNode\n}\n\nexport interface PageBreadcrumbsProps {\n items: BreadcrumbItemSpec[]\n className?: string\n}\n\nexport function PageBreadcrumbs({\n items,\n className,\n}: PageBreadcrumbsProps): React.ReactElement | null {\n if (items.length === 0) return null\n return (\n <Breadcrumb className={className}>\n <BreadcrumbList>\n {items.map((item, i) => {\n const isLast = i === items.length - 1\n const content = (\n <span className=\"inline-flex items-center gap-1.5\">\n {item.icon}\n <span className=\"truncate max-w-[8rem] sm:max-w-[16rem]\">{item.label}</span>\n </span>\n )\n return (\n <React.Fragment key={`${i}-${item.label}`}>\n <BreadcrumbItem>\n {isLast || !item.to ? (\n <BreadcrumbPage>{content}</BreadcrumbPage>\n ) : (\n <BreadcrumbLink asChild>\n <Link to={item.to}>{content}</Link>\n </BreadcrumbLink>\n )}\n </BreadcrumbItem>\n {!isLast && <BreadcrumbSeparator />}\n </React.Fragment>\n )\n })}\n </BreadcrumbList>\n </Breadcrumb>\n )\n}\n\n/** Default home crumb — used by every page; pass `homeLabel` to localize. */\nexport function homeCrumb(label: string): BreadcrumbItemSpec {\n return {\n label,\n to: { name: 'home' },\n icon: <Home className=\"size-3.5\" />,\n }\n}\n","import * as React from 'react'\nimport {\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuPortal,\n DropdownMenuSeparator,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuTrigger,\n} from '@modern-admin/ui'\nimport { MoreHorizontal, Zap } from 'lucide-react'\nimport type { ActionDescriptor, ActionGroup, RecordJSON } from './types.js'\n\ninterface ActionMenuGroupNode {\n key: string\n group: ActionGroup\n items: ActionMenuNode[]\n}\n\ntype ActionMenuNode =\n | { kind: 'action'; action: ActionDescriptor }\n | { kind: 'group'; group: ActionMenuGroupNode }\n\nexport interface ActionMenuProps {\n actions: ActionDescriptor[]\n onAction(action: ActionDescriptor): void\n t: (key: string, params?: Record<string, string | number>) => string\n trigger?: React.ReactElement\n menuLabel?: string\n align?: 'start' | 'center' | 'end'\n}\n\n/**\n * Whether a record-scoped action should be offered for a specific record.\n *\n * The server resolves `isVisible`/`isAccessible` per record and reports the\n * survivors in `RecordJSON.recordActions`. A record without that field made\n * it here outside the action pipeline (or from an older server) — that is\n * \"no opinion\", so everything stays visible rather than everything vanishing.\n */\nexport const isActionAllowedForRecord = (\n actionName: string,\n record: Pick<RecordJSON, 'recordActions'> | undefined,\n): boolean => !record?.recordActions || record.recordActions.includes(actionName)\n\n/** Narrow a resource's record actions down to the ones this record offers. */\nexport const visibleRecordActions = (\n actions: ActionDescriptor[],\n record: Pick<RecordJSON, 'recordActions'> | undefined,\n): ActionDescriptor[] => {\n if (!record?.recordActions) return actions\n const allowed = new Set(record.recordActions)\n return actions.filter((a) => allowed.has(a.name))\n}\n\n/** Display label for an action: the (already localized) `custom.label` set\n * by the metadata translator, falling back to the raw action name. */\nexport const getActionLabel = (action: ActionDescriptor): string =>\n typeof action.custom?.label === 'string' ? action.custom.label : action.name\n\nconst buildActionMenuTree = (actions: ActionDescriptor[]): ActionMenuNode[] => {\n const root: ActionMenuNode[] = []\n const groups = new Map<string, ActionMenuGroupNode>()\n\n const ensureGroup = (target: ActionMenuNode[], path: ActionGroup[], depth: number): ActionMenuNode[] => {\n if (depth >= path.length) return target\n const segment = path[depth]!\n const key = path\n .slice(0, depth + 1)\n .map((item) => item.name)\n .join('>')\n let group = groups.get(key)\n if (!group) {\n group = { key, group: segment, items: [] }\n groups.set(key, group)\n target.push({ kind: 'group', group })\n }\n return ensureGroup(group.items, path, depth + 1)\n }\n\n for (const action of actions) {\n const nesting = action.nesting ?? []\n const target = nesting.length > 0 ? ensureGroup(root, nesting, 0) : root\n target.push({ kind: 'action', action })\n }\n\n return root\n}\n\nconst renderNodes = (\n nodes: ActionMenuNode[],\n onAction: (action: ActionDescriptor) => void,\n): React.ReactNode =>\n nodes.map((node) => {\n if (node.kind === 'action') {\n return (\n <DropdownMenuItem\n key={node.action.name}\n onSelect={() => onAction(node.action)}\n >\n <Zap className=\"size-4\" /> {getActionLabel(node.action)}\n </DropdownMenuItem>\n )\n }\n return (\n <DropdownMenuSub key={node.group.key}>\n <DropdownMenuSubTrigger>\n {node.group.group.name}\n </DropdownMenuSubTrigger>\n <DropdownMenuPortal>\n <DropdownMenuSubContent>\n {renderNodes(node.group.items, onAction)}\n </DropdownMenuSubContent>\n </DropdownMenuPortal>\n </DropdownMenuSub>\n )\n })\n\nexport function ActionMenuItems({\n actions,\n onAction,\n}: {\n actions: ActionDescriptor[]\n onAction(action: ActionDescriptor): void\n}): React.ReactElement | null {\n const nodes = React.useMemo(() => buildActionMenuTree(actions), [actions])\n if (nodes.length === 0) return null\n return <>{renderNodes(nodes, onAction)}</>\n}\n\nexport function ActionMenu({\n actions,\n onAction,\n t,\n trigger,\n menuLabel,\n align = 'end',\n}: ActionMenuProps): React.ReactElement | null {\n const nodes = React.useMemo(() => buildActionMenuTree(actions), [actions])\n if (nodes.length === 0) return null\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n {trigger ?? (\n <Button variant=\"outline\" size=\"sm\">\n <Zap className=\"size-4\" />\n {t('common:actions')}\n </Button>\n )}\n </DropdownMenuTrigger>\n <DropdownMenuContent align={align}>\n <DropdownMenuLabel>{menuLabel ?? t('common:actions')}</DropdownMenuLabel>\n <DropdownMenuSeparator />\n <ActionMenuItems actions={actions} onAction={onAction} />\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\nexport function MoreActionsMenu({\n actions,\n onAction,\n t,\n menuLabel,\n}: Omit<ActionMenuProps, 'trigger' | 'align'>): React.ReactElement | null {\n if (actions.length === 0) return null\n return (\n <ActionMenu\n actions={actions}\n onAction={onAction}\n t={t}\n menuLabel={menuLabel}\n trigger={(\n <Button variant=\"ghost\" size=\"icon\" className=\"h-8 w-8\">\n <MoreHorizontal className=\"size-4\" />\n <span className=\"sr-only\">{t('common:openMenu')}</span>\n </Button>\n )}\n />\n )\n}\n","// Shared `notice` → toast plumbing for custom-action invocations.\n//\n// Every surface that fires a custom action (list toolbar, row menu, bulk\n// toolbar, show page, action component host) gets back the same\n// `CustomActionResponse` shape, so the mapping lives here rather than being\n// re-derived at each call site.\n\nimport type { useNotify } from './notify.js'\nimport type { CustomActionResponse } from './types.js'\n\ntype NotifyApi = ReturnType<typeof useNotify>\n\n/** Show a custom action's `notice` as the matching toast. Unrecognised\n * types fall back to a success toast; a missing notice is a no-op. */\nexport function showActionNotice(\n notify: NotifyApi,\n notice: CustomActionResponse['notice'],\n): void {\n if (!notice) return\n const type = notice.type === 'error' ? 'error'\n : notice.type === 'warning' ? 'warning'\n : notice.type === 'info' ? 'info'\n : 'success'\n notify[type]({ message: notice.message })\n}\n","// Runtime host for a custom action's UI component.\n//\n// An action declaring `component: 'MyForm'` (see `Action.component` in\n// @modern-admin/core) does not fire on click. Instead the operator lands\n// here: the named component is looked up on the ComponentLoader and rendered\n// with everything it needs to run the action — the priming `GET` response,\n// the record for record-scoped actions, and an `invoke(payload)` callback\n// that POSTs and surfaces the resulting `notice`.\n//\n// The host is presentation-neutral so the same code backs both surfaces:\n// the full `/resources/:id/actions/:name` page and the modal opened for\n// actions declaring `custom.showAs: 'dialog'`.\n\nimport * as React from 'react'\nimport { AlertCircle } from 'lucide-react'\nimport {\n useActionData,\n useInvokeBulkAction,\n useInvokeRecordAction,\n useInvokeResourceAction,\n useRecord,\n useResource,\n} from '../hooks.js'\nimport { useAdminContext } from '../provider.js'\nimport { useI18n } from '../i18n.js'\nimport { useNotify } from '../notify.js'\nimport { showActionNotice } from '../action-notice.js'\nimport type { ActionComponentProps, CustomActionResponse } from '../types.js'\n\nexport interface ActionComponentHostProps {\n resourceId: string\n actionName: string\n /** Set for record-scoped actions (`actionType: 'record'`). */\n recordId?: string\n /** Set for bulk-scoped actions (`actionType: 'bulk'`) — the selection. */\n recordIds?: string[]\n /** Dismiss the action — pop the page or close the dialog. */\n onClose(): void\n /** Fired after each successful `invoke()`. The bulk toolbar uses it to\n * clear its selection; in page mode the list has already unmounted, so\n * only dialog callers see it. */\n onSuccess?(): void\n}\n\nfunction HostError({ title, hint }: { title: string; hint?: string }): React.ReactElement {\n return (\n <div\n role=\"alert\"\n className=\"flex items-start gap-3 rounded-lg border border-destructive/50 bg-destructive/10 p-4 dark:bg-destructive/15\"\n >\n <AlertCircle className=\"mt-0.5 size-5 shrink-0 text-destructive\" />\n <div className=\"space-y-1 text-sm\">\n <p className=\"font-semibold text-destructive\">{title}</p>\n {hint && <p className=\"text-destructive/90\">{hint}</p>}\n </div>\n </div>\n )\n}\n\nexport function ActionComponentHost({\n resourceId,\n actionName,\n recordId,\n recordIds,\n onClose,\n onSuccess,\n}: ActionComponentHostProps): React.ReactElement {\n const resource = useResource(resourceId)\n const { components } = useAdminContext()\n const { t } = useI18n()\n const notify = useNotify()\n\n const action = resource?.actions?.find((a) => a.name === actionName)\n const componentName = action?.component ?? null\n const Custom = componentName ? components?.get(componentName) : undefined\n\n // Only prime once we know the action exists and its component resolved —\n // otherwise the page renders an error and the handler must not run.\n const primed = useActionData(resourceId, actionName, {\n ...(recordId !== undefined ? { recordId } : {}),\n ...(recordIds !== undefined ? { recordIds } : {}),\n enabled: Boolean(action) && Boolean(Custom),\n })\n const record = useRecord(resourceId, recordId)\n const invokeRecord = useInvokeRecordAction(resourceId)\n const invokeResource = useInvokeResourceAction(resourceId)\n const invokeBulk = useInvokeBulkAction(resourceId)\n\n const submitting = invokeRecord.isPending || invokeResource.isPending || invokeBulk.isPending\n const invoke = React.useCallback(\n async (payload: Record<string, unknown> = {}): Promise<CustomActionResponse> => {\n // Scope is decided by what the host was mounted with, not by the\n // action's declared `actionType` — a mismatch would build a request\n // the server can't route.\n const res = recordId\n ? await invokeRecord.mutateAsync({ recordId, actionName, payload })\n : recordIds?.length\n ? await invokeBulk.mutateAsync({ actionName, ids: recordIds, payload })\n : await invokeResource.mutateAsync({ actionName, payload })\n showActionNotice(notify, res.notice)\n onSuccess?.()\n return res\n },\n [recordId, recordIds, actionName, invokeRecord, invokeBulk, invokeResource, notify, onSuccess],\n )\n\n if (!resource) return <p className=\"text-muted-foreground\">{t('common:loading')}</p>\n if (!action) {\n return <HostError title={t('action:notFound', { action: actionName })} />\n }\n if (!componentName || !Custom) {\n return (\n <HostError\n title={t('action:componentMissing', { component: componentName ?? actionName })}\n hint={t('action:componentMissingHint')}\n />\n )\n }\n if (primed.isError) {\n return (\n <HostError\n title={t('action:loadFailed')}\n hint={primed.error instanceof Error ? primed.error.message : undefined}\n />\n )\n }\n\n const props: ActionComponentProps = {\n action,\n resource,\n resourceId,\n ...(recordId !== undefined ? { recordId } : {}),\n ...(recordIds !== undefined ? { recordIds } : {}),\n ...(record.data?.record ? { record: record.data.record } : {}),\n ...(primed.data?.records ? { records: primed.data.records } : {}),\n ...(primed.data ? { data: primed.data } : {}),\n loading: primed.isLoading || (Boolean(recordId) && record.isLoading),\n invoke,\n submitting,\n close: onClose,\n }\n return <Custom {...props} />\n}\n"],"mappings":"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"}