@modern-admin/web 0.3.5 → 0.4.1

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 (174) 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-BrEZDHZt.js} +2 -2
  10. package/dist/standalone/assets/action-component-host-BrEZDHZt.js.br +0 -0
  11. package/dist/standalone/assets/action-component-host-BrEZDHZt.js.gz +0 -0
  12. package/dist/standalone/assets/{action-component-host-CXIbN-8V.js.map → action-component-host-BrEZDHZt.js.map} +1 -1
  13. package/dist/standalone/assets/{action-page-OsWfKQ6C.js → action-page-Bx7kzeK7.js} +2 -2
  14. package/dist/standalone/assets/action-page-Bx7kzeK7.js.br +0 -0
  15. package/dist/standalone/assets/action-page-Bx7kzeK7.js.gz +0 -0
  16. package/dist/standalone/assets/{action-page-OsWfKQ6C.js.map → action-page-Bx7kzeK7.js.map} +1 -1
  17. package/dist/standalone/assets/{ai-assistant-widget-Bt4Z7Fmr.js → ai-assistant-widget-BbIXVrSu.js} +2 -2
  18. package/dist/standalone/assets/ai-assistant-widget-BbIXVrSu.js.br +0 -0
  19. package/dist/standalone/assets/ai-assistant-widget-BbIXVrSu.js.gz +0 -0
  20. package/dist/standalone/assets/{ai-assistant-widget-Bt4Z7Fmr.js.map → ai-assistant-widget-BbIXVrSu.js.map} +1 -1
  21. package/dist/standalone/assets/{audit-log-page-2wuGiWg-.js → audit-log-page-BoZh23oQ.js} +2 -2
  22. package/dist/standalone/assets/audit-log-page-BoZh23oQ.js.br +0 -0
  23. package/dist/standalone/assets/audit-log-page-BoZh23oQ.js.gz +0 -0
  24. package/dist/standalone/assets/{audit-log-page-2wuGiWg-.js.map → audit-log-page-BoZh23oQ.js.map} +1 -1
  25. package/dist/standalone/assets/{calendar-BZVUWakC.js → calendar-nvD_z7KQ.js} +2 -2
  26. package/dist/standalone/assets/calendar-nvD_z7KQ.js.br +0 -0
  27. package/dist/standalone/assets/calendar-nvD_z7KQ.js.gz +0 -0
  28. package/dist/standalone/assets/{calendar-BZVUWakC.js.map → calendar-nvD_z7KQ.js.map} +1 -1
  29. package/dist/standalone/assets/{chart-widget-BtJb6YXV.js → chart-widget-YeCL7jdB.js} +2 -2
  30. package/dist/standalone/assets/chart-widget-YeCL7jdB.js.br +0 -0
  31. package/dist/standalone/assets/chart-widget-YeCL7jdB.js.gz +0 -0
  32. package/dist/standalone/assets/{chart-widget-BtJb6YXV.js.map → chart-widget-YeCL7jdB.js.map} +1 -1
  33. package/dist/standalone/assets/{command-BNF_evMy.js → command-Sb0xL-se.js} +2 -2
  34. package/dist/standalone/assets/command-Sb0xL-se.js.br +0 -0
  35. package/dist/standalone/assets/command-Sb0xL-se.js.gz +0 -0
  36. package/dist/standalone/assets/{command-BNF_evMy.js.map → command-Sb0xL-se.js.map} +1 -1
  37. package/dist/standalone/assets/date-picker-C4HcgidQ.js +2 -0
  38. package/dist/standalone/assets/date-picker-C4HcgidQ.js.br +0 -0
  39. package/dist/standalone/assets/date-picker-C4HcgidQ.js.gz +0 -0
  40. package/dist/standalone/assets/date-picker-C4HcgidQ.js.map +1 -0
  41. package/dist/standalone/assets/{edit-page-DCRB9Y_q.js → edit-page-CW7-FAPC.js} +2 -2
  42. package/dist/standalone/assets/edit-page-CW7-FAPC.js.br +0 -0
  43. package/dist/standalone/assets/edit-page-CW7-FAPC.js.gz +0 -0
  44. package/dist/standalone/assets/{edit-page-DCRB9Y_q.js.map → edit-page-CW7-FAPC.js.map} +1 -1
  45. package/dist/standalone/assets/{empty-DAEAjFd3.js → empty-Cposcczv.js} +2 -2
  46. package/dist/standalone/assets/empty-Cposcczv.js.br +0 -0
  47. package/dist/standalone/assets/empty-Cposcczv.js.gz +0 -0
  48. package/dist/standalone/assets/{empty-DAEAjFd3.js.map → empty-Cposcczv.js.map} +1 -1
  49. package/dist/standalone/assets/{global-search-dialog-HMLztOho.js → global-search-dialog-DLv_nIvM.js} +2 -2
  50. package/dist/standalone/assets/global-search-dialog-DLv_nIvM.js.br +0 -0
  51. package/dist/standalone/assets/global-search-dialog-DLv_nIvM.js.gz +0 -0
  52. package/dist/standalone/assets/{global-search-dialog-HMLztOho.js.map → global-search-dialog-DLv_nIvM.js.map} +1 -1
  53. package/dist/standalone/assets/{heavy-fields-ZuqfOFMv.js → heavy-fields-BiCEgD2L.js} +2 -2
  54. package/dist/standalone/assets/heavy-fields-BiCEgD2L.js.br +0 -0
  55. package/dist/standalone/assets/heavy-fields-BiCEgD2L.js.gz +0 -0
  56. package/dist/standalone/assets/{heavy-fields-ZuqfOFMv.js.map → heavy-fields-BiCEgD2L.js.map} +1 -1
  57. package/dist/standalone/assets/{home-page-HmheewiP.js → home-page-BSz1wGk0.js} +3 -3
  58. package/dist/standalone/assets/home-page-BSz1wGk0.js.br +0 -0
  59. package/dist/standalone/assets/home-page-BSz1wGk0.js.gz +0 -0
  60. package/dist/standalone/assets/{home-page-HmheewiP.js.map → home-page-BSz1wGk0.js.map} +1 -1
  61. package/dist/standalone/assets/{icon-registry-BV44--RL.js → icon-registry-BQU4fIc4.js} +2 -2
  62. package/dist/standalone/assets/icon-registry-BQU4fIc4.js.br +0 -0
  63. package/dist/standalone/assets/{icon-registry-BV44--RL.js.gz → icon-registry-BQU4fIc4.js.gz} +0 -0
  64. package/dist/standalone/assets/{icon-registry-BV44--RL.js.map → icon-registry-BQU4fIc4.js.map} +1 -1
  65. package/dist/standalone/assets/index-BL8keDxQ.js +15 -0
  66. package/dist/standalone/assets/index-BL8keDxQ.js.br +0 -0
  67. package/dist/standalone/assets/index-BL8keDxQ.js.gz +0 -0
  68. package/dist/standalone/assets/{index-t4woXGa-.js.map → index-BL8keDxQ.js.map} +1 -1
  69. package/dist/standalone/assets/{json-editor-sGw4fDoj.js → json-editor-CAIgxYb3.js} +2 -2
  70. package/dist/standalone/assets/json-editor-CAIgxYb3.js.br +0 -0
  71. package/dist/standalone/assets/json-editor-CAIgxYb3.js.gz +0 -0
  72. package/dist/standalone/assets/{json-editor-sGw4fDoj.js.map → json-editor-CAIgxYb3.js.map} +1 -1
  73. package/dist/standalone/assets/{property-renderer-DyCwdVkh.js → property-renderer-B8Md-EqW.js} +4 -4
  74. package/dist/standalone/assets/property-renderer-B8Md-EqW.js.br +0 -0
  75. package/dist/standalone/assets/property-renderer-B8Md-EqW.js.gz +0 -0
  76. package/dist/standalone/assets/property-renderer-B8Md-EqW.js.map +1 -0
  77. package/dist/standalone/assets/{reference-Dfd804y3.js → reference-WPaf6IdD.js} +2 -2
  78. package/dist/standalone/assets/reference-WPaf6IdD.js.br +0 -0
  79. package/dist/standalone/assets/reference-WPaf6IdD.js.gz +0 -0
  80. package/dist/standalone/assets/{reference-Dfd804y3.js.map → reference-WPaf6IdD.js.map} +1 -1
  81. package/dist/standalone/assets/{revisions-button-DXZAh2Lu.js → revisions-button-0WtL6bsZ.js} +2 -2
  82. package/dist/standalone/assets/revisions-button-0WtL6bsZ.js.br +0 -0
  83. package/dist/standalone/assets/revisions-button-0WtL6bsZ.js.gz +0 -0
  84. package/dist/standalone/assets/{revisions-button-DXZAh2Lu.js.map → revisions-button-0WtL6bsZ.js.map} +1 -1
  85. package/dist/standalone/assets/{select-BWf6walM.js → select-CR4acjX0.js} +2 -2
  86. package/dist/standalone/assets/select-CR4acjX0.js.br +0 -0
  87. package/dist/standalone/assets/select-CR4acjX0.js.gz +0 -0
  88. package/dist/standalone/assets/{select-BWf6walM.js.map → select-CR4acjX0.js.map} +1 -1
  89. package/dist/standalone/assets/{settings-page-Co7sWbbL.js → settings-page-DtguXLAP.js} +2 -2
  90. package/dist/standalone/assets/settings-page-DtguXLAP.js.br +0 -0
  91. package/dist/standalone/assets/settings-page-DtguXLAP.js.gz +0 -0
  92. package/dist/standalone/assets/{settings-page-Co7sWbbL.js.map → settings-page-DtguXLAP.js.map} +1 -1
  93. package/dist/standalone/assets/{show-page-D-_vldWV.js → show-page-Dnox2NwO.js} +2 -2
  94. package/dist/standalone/assets/show-page-Dnox2NwO.js.br +0 -0
  95. package/dist/standalone/assets/show-page-Dnox2NwO.js.gz +0 -0
  96. package/dist/standalone/assets/{show-page-D-_vldWV.js.map → show-page-Dnox2NwO.js.map} +1 -1
  97. package/dist/standalone/assets/{tabs-khcHka8W.js → tabs-DUS8e6dR.js} +2 -2
  98. package/dist/standalone/assets/tabs-DUS8e6dR.js.br +0 -0
  99. package/dist/standalone/assets/tabs-DUS8e6dR.js.gz +0 -0
  100. package/dist/standalone/assets/{tabs-khcHka8W.js.map → tabs-DUS8e6dR.js.map} +1 -1
  101. package/dist/standalone/assets/{value-format-CPnbnlE6.js → value-format-Cgau0meh.js} +2 -2
  102. package/dist/standalone/assets/value-format-Cgau0meh.js.br +0 -0
  103. package/dist/standalone/assets/{value-format-CPnbnlE6.js.gz → value-format-Cgau0meh.js.gz} +0 -0
  104. package/dist/standalone/assets/{value-format-CPnbnlE6.js.map → value-format-Cgau0meh.js.map} +1 -1
  105. package/dist/standalone/assets/{wizard-create-page-CFwMs8E3.js → wizard-create-page-v2-Aw8Oj.js} +2 -2
  106. package/dist/standalone/assets/wizard-create-page-v2-Aw8Oj.js.br +0 -0
  107. package/dist/standalone/assets/wizard-create-page-v2-Aw8Oj.js.gz +0 -0
  108. package/dist/standalone/assets/{wizard-create-page-CFwMs8E3.js.map → wizard-create-page-v2-Aw8Oj.js.map} +1 -1
  109. package/dist/standalone/assets/{zod-BbfEBA4A.js → zod-BrelKLfy.js} +2 -2
  110. package/dist/standalone/assets/zod-BrelKLfy.js.br +0 -0
  111. package/dist/standalone/assets/zod-BrelKLfy.js.gz +0 -0
  112. package/dist/standalone/assets/{zod-BbfEBA4A.js.map → zod-BrelKLfy.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 +28 -6
  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 +0 -2
  136. package/dist/standalone/assets/date-picker-LfbUehd1.js.br +0 -0
  137. package/dist/standalone/assets/date-picker-LfbUehd1.js.gz +0 -0
  138. package/dist/standalone/assets/date-picker-LfbUehd1.js.map +0 -1
  139. package/dist/standalone/assets/edit-page-DCRB9Y_q.js.br +0 -0
  140. package/dist/standalone/assets/edit-page-DCRB9Y_q.js.gz +0 -0
  141. package/dist/standalone/assets/empty-DAEAjFd3.js.br +0 -0
  142. package/dist/standalone/assets/empty-DAEAjFd3.js.gz +0 -0
  143. package/dist/standalone/assets/global-search-dialog-HMLztOho.js.br +0 -0
  144. package/dist/standalone/assets/global-search-dialog-HMLztOho.js.gz +0 -0
  145. package/dist/standalone/assets/heavy-fields-ZuqfOFMv.js.br +0 -0
  146. package/dist/standalone/assets/heavy-fields-ZuqfOFMv.js.gz +0 -0
  147. package/dist/standalone/assets/home-page-HmheewiP.js.br +0 -0
  148. package/dist/standalone/assets/home-page-HmheewiP.js.gz +0 -0
  149. package/dist/standalone/assets/icon-registry-BV44--RL.js.br +0 -0
  150. package/dist/standalone/assets/index-t4woXGa-.js +0 -15
  151. package/dist/standalone/assets/index-t4woXGa-.js.br +0 -0
  152. package/dist/standalone/assets/index-t4woXGa-.js.gz +0 -0
  153. package/dist/standalone/assets/json-editor-sGw4fDoj.js.br +0 -0
  154. package/dist/standalone/assets/json-editor-sGw4fDoj.js.gz +0 -0
  155. package/dist/standalone/assets/property-renderer-DyCwdVkh.js.br +0 -0
  156. package/dist/standalone/assets/property-renderer-DyCwdVkh.js.gz +0 -0
  157. package/dist/standalone/assets/property-renderer-DyCwdVkh.js.map +0 -1
  158. package/dist/standalone/assets/reference-Dfd804y3.js.br +0 -0
  159. package/dist/standalone/assets/reference-Dfd804y3.js.gz +0 -0
  160. package/dist/standalone/assets/revisions-button-DXZAh2Lu.js.br +0 -0
  161. package/dist/standalone/assets/revisions-button-DXZAh2Lu.js.gz +0 -0
  162. package/dist/standalone/assets/select-BWf6walM.js.br +0 -0
  163. package/dist/standalone/assets/select-BWf6walM.js.gz +0 -0
  164. package/dist/standalone/assets/settings-page-Co7sWbbL.js.br +0 -0
  165. package/dist/standalone/assets/settings-page-Co7sWbbL.js.gz +0 -0
  166. package/dist/standalone/assets/show-page-D-_vldWV.js.br +0 -0
  167. package/dist/standalone/assets/show-page-D-_vldWV.js.gz +0 -0
  168. package/dist/standalone/assets/tabs-khcHka8W.js.br +0 -0
  169. package/dist/standalone/assets/tabs-khcHka8W.js.gz +0 -0
  170. package/dist/standalone/assets/value-format-CPnbnlE6.js.br +0 -0
  171. package/dist/standalone/assets/wizard-create-page-CFwMs8E3.js.br +0 -0
  172. package/dist/standalone/assets/wizard-create-page-CFwMs8E3.js.gz +0 -0
  173. package/dist/standalone/assets/zod-BbfEBA4A.js.br +0 -0
  174. package/dist/standalone/assets/zod-BbfEBA4A.js.gz +0 -0
@@ -12,7 +12,7 @@
12
12
  smoke tests), the standalone entry falls back to same-origin defaults.
13
13
  -->
14
14
  <!--MODERN_ADMIN_CONFIG-->
15
- <script type="module" crossorigin src="./assets/index-t4woXGa-.js"></script>
15
+ <script type="module" crossorigin src="./assets/index-BL8keDxQ.js"></script>
16
16
  <link rel="modulepreload" crossorigin href="./assets/createLucideIcon-CUopU_LC.js">
17
17
  <link rel="modulepreload" crossorigin href="./assets/with-selector-NDLhu4dT.js">
18
18
  <link rel="modulepreload" crossorigin href="./assets/dist-BSbFjTlr.js">
@@ -21,9 +21,9 @@
21
21
  <link rel="modulepreload" crossorigin href="./assets/router-wFvtgo77.js">
22
22
  <link rel="modulepreload" crossorigin href="./assets/dist-BcL2U6dT.js">
23
23
  <link rel="stylesheet" crossorigin href="./assets/index-95cwJ2RY.css">
24
- <link rel="prefetch" as="script" href="./assets/home-page-HmheewiP.js">
25
- <link rel="prefetch" as="script" href="./assets/icon-registry-BV44--RL.js">
26
- <link rel="prefetch" as="script" href="./assets/property-renderer-DyCwdVkh.js">
24
+ <link rel="prefetch" as="script" href="./assets/home-page-BSz1wGk0.js">
25
+ <link rel="prefetch" as="script" href="./assets/icon-registry-BQU4fIc4.js">
26
+ <link rel="prefetch" as="script" href="./assets/property-renderer-B8Md-EqW.js">
27
27
  </head>
28
28
  <body>
29
29
  <div id="root"></div>
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modern-admin/web",
3
- "version": "0.3.5",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "Pre-built React admin SPA. Mount it into a host page or let @modern-admin/nest serve the standalone bundle.",
@@ -35,6 +35,10 @@
35
35
  "types": "./dist/lib/index.d.ts",
36
36
  "default": "./dist/lib/index.js"
37
37
  },
38
+ "./vite": {
39
+ "types": "./dist/config/vite.d.ts",
40
+ "default": "./dist/config/vite.js"
41
+ },
38
42
  "./standalone/*": "./dist/standalone/*",
39
43
  "./package.json": "./package.json"
40
44
  },
@@ -48,23 +52,30 @@
48
52
  "types": "./dist/lib/index.d.ts",
49
53
  "default": "./dist/lib/index.js"
50
54
  },
55
+ "./vite": {
56
+ "types": "./dist/config/vite.d.ts",
57
+ "default": "./dist/config/vite.js"
58
+ },
51
59
  "./standalone/*": "./dist/standalone/*",
52
60
  "./package.json": "./package.json"
53
61
  }
54
62
  },
55
63
  "peerDependencies": {
56
64
  "react": "^19.2.6",
57
- "react-dom": "^19.2.6"
65
+ "react-dom": "^19.2.6",
66
+ "@tailwindcss/vite": "^4.2.4",
67
+ "@vitejs/plugin-react": "^6.0.1",
68
+ "vite": "^8.0.10"
58
69
  },
59
70
  "dependencies": {
60
- "@modern-admin/core": "^0.3.5",
71
+ "@modern-admin/core": "^0.4.1",
61
72
  "@modern-admin/i18n": "^0.3.4",
62
- "@modern-admin/react": "^0.3.5",
63
- "@modern-admin/ui": "^0.3.4",
73
+ "@modern-admin/react": "^0.4.1",
74
+ "@modern-admin/ui": "^0.4.1",
64
75
  "@tanstack/react-query": "^5.100.9"
65
76
  },
66
77
  "devDependencies": {
67
- "@modern-admin/tsconfig": "0.3.0",
78
+ "@modern-admin/tsconfig": "0.4.0",
68
79
  "@tailwindcss/vite": "^4.2.4",
69
80
  "@types/react": "^19.2.14",
70
81
  "@types/react-dom": "^19.2.3",
@@ -75,5 +86,16 @@
75
86
  "typescript": "^6.0.3",
76
87
  "vite": "^8.0.10",
77
88
  "vite-plugin-dts": "^5.0.1"
89
+ },
90
+ "peerDependenciesMeta": {
91
+ "@tailwindcss/vite": {
92
+ "optional": true
93
+ },
94
+ "@vitejs/plugin-react": {
95
+ "optional": true
96
+ },
97
+ "vite": {
98
+ "optional": true
99
+ }
78
100
  }
79
101
  }
package/src/mount.tsx CHANGED
@@ -5,15 +5,21 @@
5
5
  * import { mount } from '@modern-admin/web'
6
6
  * mount(document.getElementById('root')!, { apiUrl: 'https://api.example.com' })
7
7
  *
8
+ * This module deliberately does **not** import the stylesheet: an app that
9
+ * registers its own components needs to own the Tailwind root so its classes
10
+ * get scanned, and a second root here would compile Tailwind twice. Import it
11
+ * yourself, either directly or from your own CSS:
12
+ *
13
+ * import '@modern-admin/react/styles.css'
14
+ *
8
15
  * For the prebuilt standalone bundle, `src/standalone.tsx` calls this for
9
- * you using `window.__MODERN_ADMIN__`.
16
+ * you using `window.__MODERN_ADMIN__` and pulls in the stylesheet.
10
17
  */
11
18
 
12
19
  import { StrictMode } from 'react'
13
20
  import { createRoot, type Root } from 'react-dom/client'
14
21
  import { type ComponentLoader } from '@modern-admin/react'
15
22
  import { initTheme } from '@modern-admin/ui'
16
- import '@modern-admin/ui/styles.css'
17
23
  import { App } from './app.js'
18
24
  import type { ModernAdminRuntimeConfig } from './runtime-config.js'
19
25
 
@@ -7,6 +7,7 @@
7
7
  * No build-time configuration — one bundle works for any deployment.
8
8
  */
9
9
 
10
+ import '@modern-admin/react/styles.css'
10
11
  import { mount } from './mount.js'
11
12
  import { readWindowConfig } from './runtime-config.js'
12
13
 
package/src/vite.ts ADDED
@@ -0,0 +1,219 @@
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
+
27
+ import path from 'node:path'
28
+ import { promises as fs } from 'node:fs'
29
+ import { promisify } from 'node:util'
30
+ import zlib from 'node:zlib'
31
+ import type { ConfigEnv, Plugin, PluginOption, UserConfig } from 'vite'
32
+ import react from '@vitejs/plugin-react'
33
+ import tailwindcss from '@tailwindcss/vite'
34
+ import type { ModernAdminRuntimeConfig } from './runtime-config.js'
35
+
36
+ const brotli = promisify(zlib.brotliCompress)
37
+ const gzip = promisify(zlib.gzip)
38
+
39
+ /** Layout StaticUiMiddleware resolves as `<webPackage>/dist/standalone`. */
40
+ const STANDALONE_OUT_DIR = 'dist/standalone'
41
+
42
+ export interface AdminAppConfigOptions {
43
+ /** Dev server port. Defaults to `WEB_PORT`, then 3000. */
44
+ port?: number
45
+ /**
46
+ * Origin of the NestJS admin API, proxied during `vite dev` so the SPA and
47
+ * the API stay same-origin and session cookies work (e.g.
48
+ * `http://localhost:3011`). Omit if you serve both yourself.
49
+ */
50
+ apiProxy?: string
51
+ /**
52
+ * Path prefixes forwarded to `apiProxy`. Defaults to the REST/auth prefix
53
+ * every `@modern-admin/nest` controller lives under plus the socket.io
54
+ * endpoint used by `@modern-admin/realtime`.
55
+ */
56
+ apiProxyPath?: string | string[]
57
+ /**
58
+ * Runtime config for `vite dev` only. In production the host server
59
+ * injects `window.__MODERN_ADMIN__` into the shell, so there is nothing to
60
+ * configure at build time — this fills the same slot while developing.
61
+ */
62
+ devConfig?: ModernAdminRuntimeConfig
63
+ /** Directory the standalone build writes to. Rarely worth changing. */
64
+ outDir?: string
65
+ /** Extra plugins, appended after the built-in ones. */
66
+ plugins?: PluginOption[]
67
+ }
68
+
69
+ /**
70
+ * Emits `.br` and `.gz` siblings next to every compressible standalone
71
+ * asset. StaticUiMiddleware serves whichever variant the browser's
72
+ * `Accept-Encoding` allows and falls back to the plain file, so this is an
73
+ * optimisation rather than a correctness requirement.
74
+ */
75
+ export function precompressPlugin(outDir = STANDALONE_OUT_DIR): Plugin {
76
+ const COMPRESSIBLE = new Set(['.js', '.mjs', '.css', '.html', '.svg', '.json'])
77
+ const MIN_SIZE = 1024
78
+ return {
79
+ name: 'modern-admin:precompress',
80
+ apply: 'build',
81
+ async writeBundle(options) {
82
+ const dir = options.dir ?? path.resolve(process.cwd(), outDir)
83
+ const walk = async (d: string): Promise<string[]> => {
84
+ const entries = await fs.readdir(d, { withFileTypes: true })
85
+ const nested = await Promise.all(
86
+ entries.map((e) => (e.isDirectory() ? walk(path.join(d, e.name)) : [path.join(d, e.name)])),
87
+ )
88
+ return nested.flat()
89
+ }
90
+ const files = (await walk(dir)).filter((f) => COMPRESSIBLE.has(path.extname(f)))
91
+ await Promise.all(
92
+ files.map(async (file) => {
93
+ const source = await fs.readFile(file)
94
+ if (source.length < MIN_SIZE) return
95
+ const [br, gz] = await Promise.all([
96
+ brotli(source, { params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 11 } }),
97
+ gzip(source, { level: zlib.constants.Z_BEST_COMPRESSION }),
98
+ ])
99
+ await Promise.all([fs.writeFile(`${file}.br`, br), fs.writeFile(`${file}.gz`, gz)])
100
+ }),
101
+ )
102
+ },
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Injects low-priority `<link rel="prefetch">` hints for the lazy chunks a
108
+ * fresh session is almost certain to need right after the shell renders:
109
+ * the dashboard landing page, the full icon registry (sidebar), and the
110
+ * resource list page + property renderer (first sidebar click). The browser
111
+ * fetches them during idle time instead of on first navigation, without
112
+ * competing with the critical-path entry chunk. Relative `./assets/` hrefs
113
+ * are rewritten to the mount path by StaticUiMiddleware like every other
114
+ * asset reference.
115
+ */
116
+ export function prefetchHintsPlugin(): Plugin {
117
+ const PREFETCH_CHUNKS = new Set(['home-page', 'icon-registry', 'list-page', 'property-renderer'])
118
+ return {
119
+ name: 'modern-admin:prefetch-hints',
120
+ apply: 'build',
121
+ transformIndexHtml: {
122
+ order: 'post',
123
+ handler(_html, ctx) {
124
+ const chunks = Object.values(ctx.bundle ?? {}).filter(
125
+ (out) => out.type === 'chunk' && out.name && PREFETCH_CHUNKS.has(out.name),
126
+ )
127
+ return chunks.map((chunk) => ({
128
+ tag: 'link',
129
+ attrs: { rel: 'prefetch', as: 'script', href: `./${chunk.fileName}` },
130
+ injectTo: 'head' as const,
131
+ }))
132
+ },
133
+ },
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Injects `window.__MODERN_ADMIN__` while developing. In production the same
139
+ * slot is filled per-request by the host server (StaticUiMiddleware replaces
140
+ * the `<!--MODERN_ADMIN_CONFIG-->` marker, or falls back to `</head>`), so
141
+ * this plugin is dev-only and never affects the built bundle.
142
+ */
143
+ export function devRuntimeConfigPlugin(config: ModernAdminRuntimeConfig): Plugin {
144
+ return {
145
+ name: 'modern-admin:dev-runtime-config',
146
+ apply: 'serve',
147
+ transformIndexHtml: {
148
+ order: 'pre',
149
+ handler() {
150
+ // `</script>` inside the JSON would close the tag early.
151
+ const json = JSON.stringify(config).replace(/</g, '\\u003c')
152
+ return [
153
+ {
154
+ tag: 'script',
155
+ children: `window.__MODERN_ADMIN__ = ${json};`,
156
+ injectTo: 'head-prepend' as const,
157
+ },
158
+ ]
159
+ },
160
+ },
161
+ }
162
+ }
163
+
164
+ /**
165
+ * `ws: true` so the realtime gateway's socket.io upgrade survives the proxy;
166
+ * `changeOrigin` keeps the session cookie's Origin check happy.
167
+ */
168
+ function buildProxy(
169
+ target: string,
170
+ paths: string | string[],
171
+ ): Record<string, { target: string; changeOrigin: boolean; ws: boolean }> {
172
+ const list = Array.isArray(paths) ? paths : [paths]
173
+ return Object.fromEntries(list.map((p) => [p, { target, changeOrigin: true, ws: true }]))
174
+ }
175
+
176
+ /**
177
+ * Builds the Vite config for a custom admin SPA. Pass the result straight to
178
+ * `export default` — it resolves `vite dev` and `vite build` itself, so no
179
+ * `--mode` flag is required.
180
+ */
181
+ export function defineAdminAppConfig(
182
+ options: AdminAppConfigOptions = {},
183
+ ): (env: ConfigEnv) => UserConfig {
184
+ const {
185
+ apiProxy,
186
+ apiProxyPath = ['/admin/api', '/socket.io'],
187
+ devConfig,
188
+ outDir = STANDALONE_OUT_DIR,
189
+ plugins: extraPlugins = [],
190
+ } = options
191
+ return ({ command }) => {
192
+ const shared: PluginOption[] = [react(), tailwindcss()]
193
+ if (command === 'build') {
194
+ return {
195
+ plugins: [...shared, precompressPlugin(outDir), prefetchHintsPlugin(), ...extraPlugins],
196
+ // Relative base so one bundle can be mounted under any path — the
197
+ // host server rewrites `./assets/` to its own mount prefix.
198
+ base: './',
199
+ build: {
200
+ outDir,
201
+ emptyOutDir: true,
202
+ sourcemap: true,
203
+ },
204
+ }
205
+ }
206
+ return {
207
+ plugins: [
208
+ ...shared,
209
+ ...(devConfig ? [devRuntimeConfigPlugin(devConfig)] : []),
210
+ ...extraPlugins,
211
+ ],
212
+ server: {
213
+ port: options.port ?? Number(process.env.WEB_PORT ?? 3000),
214
+ host: true,
215
+ ...(apiProxy ? { proxy: buildProxy(apiProxy, apiProxyPath) } : {}),
216
+ },
217
+ }
218
+ }
219
+ }
package/vite.config.ts CHANGED
@@ -7,61 +7,23 @@
7
7
  * `mode` is set by Vite from the `--mode` CLI flag. Other modes (dev,
8
8
  * preview) get the standalone SPA config — that's what you serve from
9
9
  * `bun run dev`.
10
+ *
11
+ * The standalone/dev half is `defineAdminAppConfig()` from `src/vite.ts`,
12
+ * i.e. the very config we hand to apps that build their own bundle
13
+ * (`@modern-admin/web/vite`). Dogfooding it here is what stops the two from
14
+ * drifting apart.
10
15
  */
11
16
 
12
- import { defineConfig, type Plugin, type UserConfig } from 'vite'
17
+ import { defineConfig, type UserConfig } from 'vite'
13
18
  import react from '@vitejs/plugin-react'
14
19
  import tailwindcss from '@tailwindcss/vite'
15
20
  import dts from 'vite-plugin-dts'
16
21
  import { fileURLToPath } from 'node:url'
17
22
  import path from 'node:path'
18
- import { promises as fs } from 'node:fs'
19
- import { promisify } from 'node:util'
20
- import zlib from 'node:zlib'
23
+ import { defineAdminAppConfig } from './src/vite.js'
21
24
 
22
25
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
23
26
 
24
- const brotli = promisify(zlib.brotliCompress)
25
- const gzip = promisify(zlib.gzip)
26
-
27
- /**
28
- * Emits `.br` and `.gz` siblings next to every compressible standalone
29
- * asset. `@modern-admin/nest`'s StaticUiMiddleware picks whichever variant
30
- * the browser's `Accept-Encoding` allows — no runtime compression cost.
31
- * Sourcemaps are skipped (fetched only with devtools open) and so are
32
- * files that compression wouldn't meaningfully shrink.
33
- */
34
- function precompressPlugin(): Plugin {
35
- const COMPRESSIBLE = new Set(['.js', '.mjs', '.css', '.html', '.svg', '.json'])
36
- const MIN_SIZE = 1024
37
- return {
38
- name: 'modern-admin:precompress',
39
- apply: 'build',
40
- async writeBundle(options) {
41
- const dir = options.dir ?? path.resolve(__dirname, 'dist/standalone')
42
- const walk = async (d: string): Promise<string[]> => {
43
- const entries = await fs.readdir(d, { withFileTypes: true })
44
- const nested = await Promise.all(
45
- entries.map((e) => (e.isDirectory() ? walk(path.join(d, e.name)) : [path.join(d, e.name)])),
46
- )
47
- return nested.flat()
48
- }
49
- const files = (await walk(dir)).filter((f) => COMPRESSIBLE.has(path.extname(f)))
50
- await Promise.all(
51
- files.map(async (file) => {
52
- const source = await fs.readFile(file)
53
- if (source.length < MIN_SIZE) return
54
- const [br, gz] = await Promise.all([
55
- brotli(source, { params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 11 } }),
56
- gzip(source, { level: zlib.constants.Z_BEST_COMPRESSION }),
57
- ])
58
- await Promise.all([fs.writeFile(`${file}.br`, br), fs.writeFile(`${file}.gz`, gz)])
59
- }),
60
- )
61
- },
62
- }
63
- }
64
-
65
27
  // External deps for the library build — host apps provide their own copy
66
28
  // (deduped by the workspace / package manager). The standalone SPA bundle
67
29
  // inlines all of these so the served `<script>` is self-contained.
@@ -101,7 +63,7 @@ const libConfig: UserConfig = {
101
63
  entryRoot: 'src',
102
64
  outDirs: 'dist/lib',
103
65
  include: ['src/**/*.ts', 'src/**/*.tsx'],
104
- exclude: ['src/standalone.tsx'],
66
+ exclude: ['src/standalone.tsx', 'src/vite.ts'],
105
67
  }),
106
68
  ],
107
69
  // Don't copy public/ (favicon.svg etc.) into the library output — those
@@ -122,62 +84,9 @@ const libConfig: UserConfig = {
122
84
  },
123
85
  }
124
86
 
125
- /**
126
- * Injects low-priority `<link rel="prefetch">` hints for the lazy chunks a
127
- * fresh session is almost certain to need right after the shell renders:
128
- * the dashboard landing page, the full icon registry (sidebar), and the
129
- * resource list page + property renderer (first sidebar click). The browser
130
- * fetches them during idle time instead of on first navigation, without
131
- * competing with the critical-path entry chunk. Relative `./assets/` hrefs
132
- * are rewritten to the mount path by StaticUiMiddleware like every other
133
- * asset reference.
134
- */
135
- function prefetchHintsPlugin(): Plugin {
136
- const PREFETCH_CHUNKS = new Set(['home-page', 'icon-registry', 'list-page', 'property-renderer'])
137
- return {
138
- name: 'modern-admin:prefetch-hints',
139
- apply: 'build',
140
- transformIndexHtml: {
141
- order: 'post',
142
- handler(_html, ctx) {
143
- const chunks = Object.values(ctx.bundle ?? {}).filter(
144
- (out) => out.type === 'chunk' && out.name && PREFETCH_CHUNKS.has(out.name),
145
- )
146
- return chunks.map((chunk) => ({
147
- tag: 'link',
148
- attrs: { rel: 'prefetch', as: 'script', href: `./${chunk.fileName}` },
149
- injectTo: 'head' as const,
150
- }))
151
- },
152
- },
153
- }
154
- }
155
-
156
- const standaloneConfig: UserConfig = {
157
- plugins: [react(), tailwindcss(), precompressPlugin(), prefetchHintsPlugin()],
158
- // Relative base so the same bundle can be mounted under any path
159
- // (e.g. `/admin/`) — the server rewrites asset URLs in index.html.
160
- base: './',
161
- build: {
162
- outDir: 'dist/standalone',
163
- emptyOutDir: true,
164
- sourcemap: true,
165
- rollupOptions: {
166
- input: path.resolve(__dirname, 'index.html'),
167
- },
168
- },
169
- }
170
-
171
- const devConfig: UserConfig = {
172
- plugins: [react(), tailwindcss()],
173
- server: {
174
- port: Number(process.env.WEB_PORT ?? 3000),
175
- host: true,
176
- },
177
- }
87
+ // `vite build --mode standalone` arrives as command 'build'; `vite` and
88
+ // `vite preview` as 'serve'. Rollup's input defaults to `<root>/index.html`,
89
+ // which is exactly the shell this package ships.
90
+ const adminApp = defineAdminAppConfig()
178
91
 
179
- export default defineConfig(({ mode }) => {
180
- if (mode === 'lib') return libConfig
181
- if (mode === 'standalone') return standaloneConfig
182
- return devConfig
183
- })
92
+ export default defineConfig((env) => (env.mode === 'lib' ? libConfig : adminApp(env)))
@@ -1,2 +0,0 @@
1
- import{a as e,n as t}from"./createLucideIcon-CUopU_LC.js";import{S as n,s as r}from"./with-selector-NDLhu4dT.js";import{n as i}from"./refresh-cw-DHvUQTMb.js";import{s as a}from"./dist-Dpg8xy_r.js";import{a as o,i as s,n as c,s as l,t as u}from"./calendar-BZVUWakC.js";import{a as d,i as f,n as p}from"./useQueries-BCUhWYX4.js";import{Y as m}from"./index-t4woXGa-.js";var h=e(t(),1),g=n(),_=`yyyy-MM-dd`,v=`yyyy-MM-dd'T'HH:mm`,y=`yyyy-MM-dd HH:mm`;function b(e){if(!e)return;let t=e.length<=10?s(e,_,new Date):c(e);return Number.isNaN(t.getTime())?void 0:t}function x(e,t){let n=e.trim();if(n===``)return;let r=t===`datetime`?[v,y,`yyyy-MM-dd'T'HH:mm:ss`,_]:[_];for(let e of r){let t=s(n,e,new Date);if(l(t))return t}let i=c(n);return l(i)?i:null}function S(e,t){return e?o(e,t===`datetime`?y:_):``}function C(e,t){return o(e,t===`datetime`?v:_)}function w({value:e,onChange:t,mode:n=`date`,disabled:s,placeholder:c,className:l,inputClassName:_,ariaLabel:v,openCalendarLabel:y=`Open calendar`,timeLabel:w=`Time`}){let[T,E]=h.useState(!1),D=b(e),[O,k]=h.useState(()=>S(D,n)),A=h.useRef(S(D,n));h.useEffect(()=>{let e=S(D,n);e!==A.current&&(A.current=e,k(e))},[e,n]);let j=D?o(D,`HH:mm`):`00:00`,M=e=>{if(!e){A.current=``,k(``),t(``);return}if(n===`datetime`){let[t,n]=j.split(`:`).map(Number);e.setHours(t??0,n??0,0,0)}let r=S(e,n);A.current=r,k(r),t(C(e,n))},N=e=>{if(!D)return;let[r,i]=e.split(`:`).map(Number),a=new Date(D);a.setHours(r??0,i??0,0,0);let o=S(a,n);A.current=o,k(o),t(C(a,n))},P=e=>{let r=e.target.value;k(r);let i=x(r,n);i===void 0?(A.current=``,t(``)):i&&(A.current=S(i,n),t(C(i,n)))},F=()=>{if(x(O,n)===null){let e=S(D,n);A.current=e,k(e)}},I=c??(n===`datetime`?`YYYY-MM-DD HH:MM`:`YYYY-MM-DD`);return(0,g.jsxs)(p,{open:T,onOpenChange:E,children:[(0,g.jsxs)(`div`,{className:r(`relative w-full`,l),children:[(0,g.jsx)(m,{type:`text`,inputMode:`numeric`,value:O,onChange:P,onBlur:F,placeholder:I,disabled:s,"aria-label":v,className:r(_,`pr-10`)}),(0,g.jsx)(d,{asChild:!0,children:(0,g.jsx)(a,{type:`button`,variant:`ghost`,size:`icon`,disabled:s,"aria-label":y,className:`absolute right-1 top-1/2 size-8 -translate-y-1/2 text-muted-foreground hover:text-foreground`,children:(0,g.jsx)(i,{className:`size-4`})})})]}),(0,g.jsxs)(f,{className:`w-auto p-0`,align:`start`,children:[(0,g.jsx)(u,{mode:`single`,selected:D,defaultMonth:D,onSelect:e=>{M(e),n===`date`&&E(!1)},autoFocus:!0}),n===`datetime`&&(0,g.jsxs)(`div`,{className:`flex items-center gap-2 border-t border-border p-3`,children:[(0,g.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:w}),(0,g.jsx)(m,{type:`time`,value:j,disabled:!D,onChange:e=>N(e.target.value),className:`h-8 w-32`})]})]})]})}w.displayName=`DatePicker`;export{w as t};
2
- //# sourceMappingURL=date-picker-LfbUehd1.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"date-picker-LfbUehd1.js","names":[],"sources":["../../../../ui/src/components/date-picker.tsx"],"sourcesContent":["// Popover-driven date / datetime input. Single picker handles both modes:\n// pass `mode=\"datetime\"` to surface an HH:MM time input below the calendar.\n//\n// Value is an ISO-ish string (`yyyy-MM-dd` for dates, `yyyy-MM-ddTHH:mm` for\n// datetime — same shape <input type=\"date\">/<input type=\"datetime-local\">\n// produce, so callers can stay format-stable). The trigger is a real text\n// input so users can also type the date manually; clicks on the trailing\n// calendar icon open the popover with the inline picker.\n\nimport * as React from 'react'\nimport { format, isValid, parse, parseISO } from 'date-fns'\nimport { Calendar as CalendarIcon } from 'lucide-react'\nimport { cn } from '../lib/utils.js'\nimport { Button } from './button.js'\nimport { Calendar } from './calendar.js'\nimport { Input } from './input.js'\nimport { Popover, PopoverContent, PopoverTrigger } from './popover.js'\n\nexport type DatePickerMode = 'date' | 'datetime'\n\nexport interface DatePickerProps {\n value: string | null | undefined\n onChange(next: string): void\n mode?: DatePickerMode\n disabled?: boolean\n placeholder?: string\n /** Applied to the outer wrapper div (controls width, etc.). */\n className?: string\n /** Applied to the inner text Input — use to override height / font size. */\n inputClassName?: string\n /** ARIA label forwarded to the trigger input (mobile users / screen readers). */\n ariaLabel?: string\n /** ARIA label for the calendar icon button. Default: \"Open calendar\". */\n openCalendarLabel?: string\n /** Label for the time input shown in datetime mode. Default: \"Time\". */\n timeLabel?: string\n}\n\nconst DATE_FMT = 'yyyy-MM-dd'\nconst DATETIME_FMT = \"yyyy-MM-dd'T'HH:mm\"\n// Friendlier display format for the datetime input — space instead of `T`.\nconst DATETIME_DISPLAY_FMT = 'yyyy-MM-dd HH:mm'\n\nfunction parseValue(value: string | null | undefined): Date | undefined {\n if (!value) return undefined\n const direct = value.length <= 10 ? parse(value, DATE_FMT, new Date()) : parseISO(value)\n return Number.isNaN(direct.getTime()) ? undefined : direct\n}\n\n/**\n * Try to parse a manually-typed string in any of the supported shapes.\n * Returns `undefined` on blank, `null` on invalid (non-blank) input so the\n * caller can distinguish \"cleared\" from \"typo\".\n */\nfunction parseTyped(raw: string, mode: DatePickerMode): Date | undefined | null {\n const trimmed = raw.trim()\n if (trimmed === '') return undefined\n const candidates =\n mode === 'datetime'\n ? [DATETIME_FMT, DATETIME_DISPLAY_FMT, \"yyyy-MM-dd'T'HH:mm:ss\", DATE_FMT]\n : [DATE_FMT]\n for (const fmt of candidates) {\n const parsed = parse(trimmed, fmt, new Date())\n if (isValid(parsed)) return parsed\n }\n // Last resort: ISO with timezone, etc.\n const iso = parseISO(trimmed)\n return isValid(iso) ? iso : null\n}\n\nfunction formatForInput(date: Date | undefined, mode: DatePickerMode): string {\n if (!date) return ''\n return format(date, mode === 'datetime' ? DATETIME_DISPLAY_FMT : DATE_FMT)\n}\n\nfunction formatForApi(date: Date, mode: DatePickerMode): string {\n return format(date, mode === 'datetime' ? DATETIME_FMT : DATE_FMT)\n}\n\nexport function DatePicker({\n value,\n onChange,\n mode = 'date',\n disabled,\n placeholder,\n className,\n inputClassName,\n ariaLabel,\n openCalendarLabel = 'Open calendar',\n timeLabel = 'Time',\n}: DatePickerProps): React.ReactElement {\n const [open, setOpen] = React.useState(false)\n const date = parseValue(value)\n // Local draft for the text input — keeps user typing intact even when\n // intermediate strings don't yet parse.\n const [draft, setDraft] = React.useState(() => formatForInput(date, mode))\n // Re-sync draft whenever the canonical value changes from outside (e.g.\n // calendar selection or form reset). Compare via formatted shape so a noop\n // update doesn't clobber what the user is typing.\n const lastFormatted = React.useRef(formatForInput(date, mode))\n React.useEffect(() => {\n const next = formatForInput(date, mode)\n if (next !== lastFormatted.current) {\n lastFormatted.current = next\n setDraft(next)\n }\n }, [value, mode]) // eslint-disable-line react-hooks/exhaustive-deps\n\n const time = date ? format(date, 'HH:mm') : '00:00'\n\n const commitDate = (next: Date | undefined): void => {\n if (!next) {\n lastFormatted.current = ''\n setDraft('')\n onChange('')\n return\n }\n if (mode === 'datetime') {\n const [h, m] = time.split(':').map(Number)\n next.setHours(h ?? 0, m ?? 0, 0, 0)\n }\n const formatted = formatForInput(next, mode)\n lastFormatted.current = formatted\n setDraft(formatted)\n onChange(formatForApi(next, mode))\n }\n\n const setTime = (raw: string): void => {\n if (!date) return\n const [h, m] = raw.split(':').map(Number)\n const next = new Date(date)\n next.setHours(h ?? 0, m ?? 0, 0, 0)\n const formatted = formatForInput(next, mode)\n lastFormatted.current = formatted\n setDraft(formatted)\n onChange(formatForApi(next, mode))\n }\n\n const handleInputChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {\n const text = e.target.value\n setDraft(text)\n const parsed = parseTyped(text, mode)\n if (parsed === undefined) {\n // cleared\n lastFormatted.current = ''\n onChange('')\n } else if (parsed) {\n lastFormatted.current = formatForInput(parsed, mode)\n onChange(formatForApi(parsed, mode))\n }\n // parsed === null → keep draft, don't fire onChange yet\n }\n\n const handleInputBlur: React.FocusEventHandler<HTMLInputElement> = () => {\n // On blur, if the draft is invalid, snap back to the canonical value.\n const parsed = parseTyped(draft, mode)\n if (parsed === null) {\n const restored = formatForInput(date, mode)\n lastFormatted.current = restored\n setDraft(restored)\n }\n }\n\n const inputPlaceholder =\n placeholder ?? (mode === 'datetime' ? 'YYYY-MM-DD HH:MM' : 'YYYY-MM-DD')\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <div className={cn('relative w-full', className)}>\n <Input\n type=\"text\"\n inputMode=\"numeric\"\n value={draft}\n onChange={handleInputChange}\n onBlur={handleInputBlur}\n placeholder={inputPlaceholder}\n disabled={disabled}\n aria-label={ariaLabel}\n className={cn(inputClassName, 'pr-10')}\n />\n <PopoverTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n disabled={disabled}\n aria-label={openCalendarLabel}\n className=\"absolute right-1 top-1/2 size-8 -translate-y-1/2 text-muted-foreground hover:text-foreground\"\n >\n <CalendarIcon className=\"size-4\" />\n </Button>\n </PopoverTrigger>\n </div>\n <PopoverContent className=\"w-auto p-0\" align=\"start\">\n <Calendar\n mode=\"single\"\n selected={date}\n defaultMonth={date}\n onSelect={(d) => {\n commitDate(d)\n if (mode === 'date') setOpen(false)\n }}\n autoFocus\n />\n {mode === 'datetime' && (\n <div className=\"flex items-center gap-2 border-t border-border p-3\">\n <span className=\"text-xs text-muted-foreground\">{timeLabel}</span>\n <Input\n type=\"time\"\n value={time}\n disabled={!date}\n onChange={(e) => setTime(e.target.value)}\n className=\"h-8 w-32\"\n />\n </div>\n )}\n </PopoverContent>\n </Popover>\n )\n}\nDatePicker.displayName = 'DatePicker'\n"],"mappings":"oYAsCM,EAAW,aACX,EAAe,qBAEf,EAAuB,mBAE7B,SAAS,EAAW,EAAoD,CACtE,GAAI,CAAC,EAAO,OACZ,IAAM,EAAS,EAAM,QAAU,GAAK,EAAM,EAAO,EAAU,IAAI,IAAM,EAAI,EAAS,CAAK,EACvF,OAAO,OAAO,MAAM,EAAO,QAAQ,CAAC,EAAI,IAAA,GAAY,CACtD,CAOA,SAAS,EAAW,EAAa,EAA+C,CAC9E,IAAM,EAAU,EAAI,KAAK,EACzB,GAAI,IAAY,GAAI,OACpB,IAAM,EACJ,IAAS,WACL,CAAC,EAAc,EAAsB,wBAAyB,CAAQ,EACtE,CAAC,CAAQ,EACf,IAAK,IAAM,KAAO,EAAY,CAC5B,IAAM,EAAS,EAAM,EAAS,EAAK,IAAI,IAAM,EAC7C,GAAI,EAAQ,CAAM,EAAG,OAAO,CAC9B,CAEA,IAAM,EAAM,EAAS,CAAO,EAC5B,OAAO,EAAQ,CAAG,EAAI,EAAM,IAC9B,CAEA,SAAS,EAAe,EAAwB,EAA8B,CAE5E,OADK,EACE,EAAO,EAAM,IAAS,WAAa,EAAuB,CAAQ,EADvD,EAEpB,CAEA,SAAS,EAAa,EAAY,EAA8B,CAC9D,OAAO,EAAO,EAAM,IAAS,WAAa,EAAe,CAAQ,CACnE,CAEA,SAAgB,EAAW,CACzB,QACA,WACA,OAAO,OACP,WACA,cACA,YACA,iBACA,YACA,oBAAoB,gBACpB,YAAY,QAC0B,CACtC,GAAM,CAAC,EAAM,GAAA,EAAiB,SAAS,EAAK,EACtC,EAAO,EAAW,CAAK,EAGvB,CAAC,EAAO,GAAA,EAAkB,aAAe,EAAe,EAAM,CAAI,CAAC,EAInE,EAAA,EAAsB,OAAO,EAAe,EAAM,CAAI,CAAC,EAC7D,EAAM,cAAgB,CACpB,IAAM,EAAO,EAAe,EAAM,CAAI,EAClC,IAAS,EAAc,UACzB,EAAc,QAAU,EACxB,EAAS,CAAI,EAEjB,EAAG,CAAC,EAAO,CAAI,CAAC,EAEhB,IAAM,EAAO,EAAO,EAAO,EAAM,OAAO,EAAI,QAEtC,EAAc,GAAiC,CACnD,GAAI,CAAC,EAAM,CACT,EAAc,QAAU,GACxB,EAAS,EAAE,EACX,EAAS,EAAE,EACX,MACF,CACA,GAAI,IAAS,WAAY,CACvB,GAAM,CAAC,EAAG,GAAK,EAAK,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM,EACzC,EAAK,SAAS,GAAK,EAAG,GAAK,EAAG,EAAG,CAAC,CACpC,CACA,IAAM,EAAY,EAAe,EAAM,CAAI,EAC3C,EAAc,QAAU,EACxB,EAAS,CAAS,EAClB,EAAS,EAAa,EAAM,CAAI,CAAC,CACnC,EAEM,EAAW,GAAsB,CACrC,GAAI,CAAC,EAAM,OACX,GAAM,CAAC,EAAG,GAAK,EAAI,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM,EAClC,EAAO,IAAI,KAAK,CAAI,EAC1B,EAAK,SAAS,GAAK,EAAG,GAAK,EAAG,EAAG,CAAC,EAClC,IAAM,EAAY,EAAe,EAAM,CAAI,EAC3C,EAAc,QAAU,EACxB,EAAS,CAAS,EAClB,EAAS,EAAa,EAAM,CAAI,CAAC,CACnC,EAEM,EAAiE,GAAM,CAC3E,IAAM,EAAO,EAAE,OAAO,MACtB,EAAS,CAAI,EACb,IAAM,EAAS,EAAW,EAAM,CAAI,EAChC,IAAW,IAAA,IAEb,EAAc,QAAU,GACxB,EAAS,EAAE,GACF,IACT,EAAc,QAAU,EAAe,EAAQ,CAAI,EACnD,EAAS,EAAa,EAAQ,CAAI,CAAC,EAGvC,EAEM,MAAmE,CAGvE,GADe,EAAW,EAAO,CAC7B,IAAW,KAAM,CACnB,IAAM,EAAW,EAAe,EAAM,CAAI,EAC1C,EAAc,QAAU,EACxB,EAAS,CAAQ,CACnB,CACF,EAEM,EACJ,IAAgB,IAAS,WAAa,mBAAqB,cAE7D,OACE,EAAA,EAAA,KAAA,CAAC,EAAD,CAAe,OAAM,aAAc,WAAnC,EACE,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAG,kBAAmB,CAAS,WAA/C,EACE,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,KAAK,OACL,UAAU,UACV,MAAO,EACP,SAAU,EACV,OAAQ,EACR,YAAa,EACH,WACV,aAAY,EACZ,UAAW,EAAG,EAAgB,OAAO,CACtC,CAAA,GACD,EAAA,EAAA,IAAA,CAAC,EAAD,CAAgB,QAAA,aACd,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,KAAK,SACL,QAAQ,QACR,KAAK,OACK,WACV,aAAY,EACZ,UAAU,yGAEV,EAAA,EAAA,IAAA,CAAC,EAAD,CAAc,UAAU,QAAU,CAAA,CAC5B,CAAA,CACM,CAAA,CACb,KACL,EAAA,EAAA,KAAA,CAAC,EAAD,CAAgB,UAAU,aAAa,MAAM,iBAA7C,EACE,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,KAAK,SACL,SAAU,EACV,aAAc,EACd,SAAW,GAAM,CACf,EAAW,CAAC,EACR,IAAS,QAAQ,EAAQ,EAAK,CACpC,EACA,UAAA,EACD,CAAA,EACA,IAAS,aACR,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,8DAAf,EACE,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAU,yCAAiC,CAAgB,CAAA,GACjE,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,KAAK,OACL,MAAO,EACP,SAAU,CAAC,EACX,SAAW,GAAM,EAAQ,EAAE,OAAO,KAAK,EACvC,UAAU,UACX,CAAA,CACE,GAEO,GACT,GAEb,CACA,EAAW,YAAc"}