portal-mojo 0.1.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 (369) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +55 -0
  3. package/docs/DetailView.md +173 -0
  4. package/docs/ModelTable.md +128 -0
  5. package/docs/README.md +98 -0
  6. package/docs/admin-assistant.md +72 -0
  7. package/docs/admin-bouncer.md +120 -0
  8. package/docs/admin-cloudwatch.md +38 -0
  9. package/docs/admin-credentials.md +116 -0
  10. package/docs/admin-dashboard.md +15 -0
  11. package/docs/admin-devices-geoip.md +284 -0
  12. package/docs/admin-dns-certificates.md +122 -0
  13. package/docs/admin-dns-records.md +65 -0
  14. package/docs/admin-dns-registrar.md +33 -0
  15. package/docs/admin-dns.md +80 -0
  16. package/docs/admin-identity-users.md +103 -0
  17. package/docs/admin-incidents.md +71 -0
  18. package/docs/admin-jobs.md +426 -0
  19. package/docs/admin-members.md +112 -0
  20. package/docs/admin-messaging.md +56 -0
  21. package/docs/admin-metrics-explorer.md +63 -0
  22. package/docs/admin-monitoring.md +99 -0
  23. package/docs/admin-network-security.md +393 -0
  24. package/docs/admin-phonehub.md +27 -0
  25. package/docs/admin-push.md +36 -0
  26. package/docs/admin-rules.md +87 -0
  27. package/docs/admin-security-tickets.md +40 -0
  28. package/docs/admin-settings.md +78 -0
  29. package/docs/admin-shortlinks.md +32 -0
  30. package/docs/admin-storage.md +141 -0
  31. package/docs/auth-pages.md +212 -0
  32. package/docs/auth.md +58 -0
  33. package/docs/calendar.md +122 -0
  34. package/docs/charts.md +303 -0
  35. package/docs/client.md +206 -0
  36. package/docs/collection-multiselect.md +117 -0
  37. package/docs/collection-select.md +132 -0
  38. package/docs/combobox.md +105 -0
  39. package/docs/dataview.md +226 -0
  40. package/docs/datepicker.md +151 -0
  41. package/docs/daterange.md +170 -0
  42. package/docs/datetimepicker.md +249 -0
  43. package/docs/defineModel.md +72 -0
  44. package/docs/detail-primitives.md +174 -0
  45. package/docs/feedback.md +85 -0
  46. package/docs/file-upload-ux.md +146 -0
  47. package/docs/forms.md +435 -0
  48. package/docs/grouping-and-fmt.md +98 -0
  49. package/docs/idioms.md +112 -0
  50. package/docs/image-editor.md +101 -0
  51. package/docs/loading.md +242 -0
  52. package/docs/location-address.md +76 -0
  53. package/docs/markdown.md +203 -0
  54. package/docs/menus-and-access.md +89 -0
  55. package/docs/multiselect-dropdown.md +153 -0
  56. package/docs/params.md +73 -0
  57. package/docs/popover.md +130 -0
  58. package/docs/realtime.md +61 -0
  59. package/docs/record-feed.md +238 -0
  60. package/docs/right-panel.md +42 -0
  61. package/docs/tabs-and-form-wizard.md +60 -0
  62. package/docs/taginput.md +120 -0
  63. package/docs/timepicker.md +173 -0
  64. package/docs/timezone-select.md +147 -0
  65. package/docs/worldmap.md +302 -0
  66. package/package.json +55 -0
  67. package/src/admin/assistant/AssistantFeed.tsx +29 -0
  68. package/src/admin/assistant/AssistantPanel.tsx +65 -0
  69. package/src/admin/assistant/api.ts +17 -0
  70. package/src/admin/assistant/data.ts +63 -0
  71. package/src/admin/assistant/index.ts +21 -0
  72. package/src/admin/assistant/launchers.tsx +33 -0
  73. package/src/admin/assistant/pages.tsx +78 -0
  74. package/src/admin/assistant/streaming.ts +238 -0
  75. package/src/admin/assistant/types.ts +30 -0
  76. package/src/admin/bouncer/devices.tsx +230 -0
  77. package/src/admin/bouncer/index.ts +34 -0
  78. package/src/admin/bouncer/models.ts +199 -0
  79. package/src/admin/bouncer/signals.tsx +182 -0
  80. package/src/admin/bouncer/signatures.tsx +220 -0
  81. package/src/admin/cloudwatch/CloudWatchChart.tsx +61 -0
  82. package/src/admin/cloudwatch/CloudWatchDashboardPage.tsx +52 -0
  83. package/src/admin/cloudwatch/CloudWatchResourceDetail.tsx +33 -0
  84. package/src/admin/cloudwatch/data.ts +163 -0
  85. package/src/admin/cloudwatch/index.ts +16 -0
  86. package/src/admin/credentials/group-api-keys.tsx +433 -0
  87. package/src/admin/credentials/index.ts +4 -0
  88. package/src/admin/credentials/models.ts +244 -0
  89. package/src/admin/credentials/secret-dialog.tsx +77 -0
  90. package/src/admin/credentials/webhook-subscriptions.tsx +423 -0
  91. package/src/admin/dashboard/AdminDashboardPage.tsx +101 -0
  92. package/src/admin/dashboard/data.ts +60 -0
  93. package/src/admin/dashboard/index.ts +2 -0
  94. package/src/admin/dns/CertificateDetail.tsx +115 -0
  95. package/src/admin/dns/CertificateLifecyclePoller.tsx +141 -0
  96. package/src/admin/dns/CertificateRequestDialog.tsx +103 -0
  97. package/src/admin/dns/CertificatesPage.tsx +85 -0
  98. package/src/admin/dns/DnsRecordEditor.tsx +110 -0
  99. package/src/admin/dns/DnsRecordsPage.tsx +36 -0
  100. package/src/admin/dns/DnsRecordsPanel.tsx +71 -0
  101. package/src/admin/dns/DomainCertificatesSection.tsx +40 -0
  102. package/src/admin/dns/DomainDetail.tsx +41 -0
  103. package/src/admin/dns/DomainOnboardingDialog.tsx +27 -0
  104. package/src/admin/dns/DomainPurchaseDetail.tsx +11 -0
  105. package/src/admin/dns/DomainPurchaseWizard.tsx +112 -0
  106. package/src/admin/dns/DomainPurchasesPage.tsx +16 -0
  107. package/src/admin/dns/DomainsPage.tsx +27 -0
  108. package/src/admin/dns/ProviderCredentialsPage.tsx +304 -0
  109. package/src/admin/dns/RegistrantContactPage.tsx +42 -0
  110. package/src/admin/dns/api.ts +515 -0
  111. package/src/admin/dns/certificate-data.ts +221 -0
  112. package/src/admin/dns/data.ts +32 -0
  113. package/src/admin/dns/dns-data.ts +266 -0
  114. package/src/admin/dns/dns-integration.ts +24 -0
  115. package/src/admin/dns/domain-links.ts +37 -0
  116. package/src/admin/dns/index.ts +86 -0
  117. package/src/admin/dns/models.ts +418 -0
  118. package/src/admin/dns/purchase-data.ts +67 -0
  119. package/src/admin/dns/registrant-data.ts +52 -0
  120. package/src/admin/identity/index.ts +5 -0
  121. package/src/admin/identity/members/GroupMembersPanel.tsx +146 -0
  122. package/src/admin/identity/members/MemberDetail.tsx +226 -0
  123. package/src/admin/identity/members/MembersPage.tsx +144 -0
  124. package/src/admin/identity/members/index.ts +24 -0
  125. package/src/admin/identity/members/member-flows.tsx +132 -0
  126. package/src/admin/identity/members/models.ts +204 -0
  127. package/src/admin/identity/users/UserDetail.tsx +257 -0
  128. package/src/admin/identity/users/UsersPage.tsx +163 -0
  129. package/src/admin/identity/users/index.ts +27 -0
  130. package/src/admin/identity/users/models.ts +232 -0
  131. package/src/admin/identity/users/sections/ApiKeysSection.tsx +119 -0
  132. package/src/admin/identity/users/sections/AuditSection.tsx +190 -0
  133. package/src/admin/identity/users/sections/DevicesSection.tsx +140 -0
  134. package/src/admin/identity/users/sections/GroupsSection.tsx +64 -0
  135. package/src/admin/identity/users/sections/LoginsSection.tsx +106 -0
  136. package/src/admin/identity/users/sections/MetadataPanel.tsx +36 -0
  137. package/src/admin/identity/users/sections/NotificationsSection.tsx +117 -0
  138. package/src/admin/identity/users/sections/OAuthSection.tsx +72 -0
  139. package/src/admin/identity/users/sections/OverviewSection.tsx +109 -0
  140. package/src/admin/identity/users/sections/PermissionsSection.tsx +35 -0
  141. package/src/admin/identity/users/sections/PersonalSection.tsx +73 -0
  142. package/src/admin/identity/users/sections/ProfileSection.tsx +175 -0
  143. package/src/admin/identity/users/sections/SecuritySection.tsx +123 -0
  144. package/src/admin/identity/users/sections/actions.tsx +752 -0
  145. package/src/admin/identity/users/sections/permission-catalog.ts +138 -0
  146. package/src/admin/identity/users/sections/queries.ts +74 -0
  147. package/src/admin/identity/users/sections/shared.tsx +299 -0
  148. package/src/admin/incidents/EventDetail.tsx +43 -0
  149. package/src/admin/incidents/EventsPage.tsx +52 -0
  150. package/src/admin/incidents/IncidentDetail.tsx +68 -0
  151. package/src/admin/incidents/IncidentsPage.tsx +102 -0
  152. package/src/admin/incidents/forensics.tsx +73 -0
  153. package/src/admin/incidents/index.ts +16 -0
  154. package/src/admin/incidents/models.ts +113 -0
  155. package/src/admin/incidents/sanitize.ts +71 -0
  156. package/src/admin/index.ts +383 -0
  157. package/src/admin/jobs/JobDashboardPage.tsx +111 -0
  158. package/src/admin/jobs/JobDetail.tsx +721 -0
  159. package/src/admin/jobs/JobRunnersPage.tsx +216 -0
  160. package/src/admin/jobs/JobsTablePage.tsx +122 -0
  161. package/src/admin/jobs/RunnerDetail.tsx +576 -0
  162. package/src/admin/jobs/ScheduledTaskDetail.tsx +421 -0
  163. package/src/admin/jobs/ScheduledTasksPage.tsx +167 -0
  164. package/src/admin/jobs/columns.tsx +297 -0
  165. package/src/admin/jobs/control.ts +488 -0
  166. package/src/admin/jobs/index.ts +85 -0
  167. package/src/admin/jobs/models.ts +599 -0
  168. package/src/admin/jobs/queries.ts +272 -0
  169. package/src/admin/jobs/scheduled-task-form.tsx +493 -0
  170. package/src/admin/jobs/sections/JobChannelsPanel.tsx +142 -0
  171. package/src/admin/jobs/sections/JobOperationsSection.tsx +423 -0
  172. package/src/admin/jobs/sections/JobRunnersStrip.tsx +82 -0
  173. package/src/admin/jobs/sections/JobThroughputSection.tsx +118 -0
  174. package/src/admin/messaging/EmailDomainWizard.tsx +24 -0
  175. package/src/admin/messaging/EmailDomainsPage.tsx +17 -0
  176. package/src/admin/messaging/EmailTemplatesPage.tsx +13 -0
  177. package/src/admin/messaging/MailboxesPage.tsx +15 -0
  178. package/src/admin/messaging/PublicMessagesPage.tsx +13 -0
  179. package/src/admin/messaging/SandboxedEmailPreview.tsx +23 -0
  180. package/src/admin/messaging/SentMessagesPage.tsx +11 -0
  181. package/src/admin/messaging/api.ts +17 -0
  182. package/src/admin/messaging/data.ts +10 -0
  183. package/src/admin/messaging/index.ts +23 -0
  184. package/src/admin/messaging/models.ts +76 -0
  185. package/src/admin/messaging/push/PushPage.tsx +44 -0
  186. package/src/admin/messaging/push/api.ts +17 -0
  187. package/src/admin/messaging/push/index.ts +9 -0
  188. package/src/admin/messaging/push/models.ts +43 -0
  189. package/src/admin/messaging/sanitize.ts +44 -0
  190. package/src/admin/monitoring/LogInspector.tsx +160 -0
  191. package/src/admin/monitoring/LogsPage.tsx +135 -0
  192. package/src/admin/monitoring/MetricsExplorerPage.tsx +360 -0
  193. package/src/admin/monitoring/MetricsPermissionsPage.tsx +166 -0
  194. package/src/admin/monitoring/MetricsSourcePicker.tsx +136 -0
  195. package/src/admin/monitoring/index.ts +55 -0
  196. package/src/admin/monitoring/metrics-explorer-client.ts +229 -0
  197. package/src/admin/monitoring/metrics-explorer-data.ts +158 -0
  198. package/src/admin/monitoring/models.ts +155 -0
  199. package/src/admin/network/BlockedIPsPage.tsx +194 -0
  200. package/src/admin/network/BlockedIpDetail.tsx +262 -0
  201. package/src/admin/network/FirewallLogPage.tsx +115 -0
  202. package/src/admin/network/IPSetDetail.tsx +280 -0
  203. package/src/admin/network/IPSetEditor.tsx +191 -0
  204. package/src/admin/network/IPSetsPage.tsx +177 -0
  205. package/src/admin/network/geofence/BlocksTab.tsx +289 -0
  206. package/src/admin/network/geofence/ExemptionsTab.tsx +349 -0
  207. package/src/admin/network/geofence/GeofencingPage.tsx +119 -0
  208. package/src/admin/network/geofence/PostureHeader.tsx +184 -0
  209. package/src/admin/network/geofence/RuleEditor.tsx +176 -0
  210. package/src/admin/network/geofence/RulesTab.tsx +254 -0
  211. package/src/admin/network/geofence/SimulatorTab.tsx +281 -0
  212. package/src/admin/network/geofence/geofence-data.ts +674 -0
  213. package/src/admin/network/index.ts +68 -0
  214. package/src/admin/network/models.ts +607 -0
  215. package/src/admin/phonehub/PhoneHubPage.tsx +57 -0
  216. package/src/admin/phonehub/api.ts +47 -0
  217. package/src/admin/phonehub/data.ts +6 -0
  218. package/src/admin/phonehub/index.ts +10 -0
  219. package/src/admin/phonehub/models.ts +68 -0
  220. package/src/admin/rules/HandlerChainBuilder.tsx +51 -0
  221. package/src/admin/rules/RuleSetDetailPage.tsx +33 -0
  222. package/src/admin/rules/RuleSetsPage.tsx +33 -0
  223. package/src/admin/rules/editors.tsx +54 -0
  224. package/src/admin/rules/handler-dsl.ts +213 -0
  225. package/src/admin/rules/index.ts +13 -0
  226. package/src/admin/rules/models.ts +112 -0
  227. package/src/admin/security/devices/LoginEventDetail.tsx +123 -0
  228. package/src/admin/security/devices/LoginLocationMap.tsx +321 -0
  229. package/src/admin/security/devices/LoginLocationsPage.tsx +162 -0
  230. package/src/admin/security/devices/UserDeviceDetail.tsx +546 -0
  231. package/src/admin/security/devices/UserDeviceLocationDetail.tsx +272 -0
  232. package/src/admin/security/devices/UserDevicesPage.tsx +84 -0
  233. package/src/admin/security/devices/index.ts +40 -0
  234. package/src/admin/security/devices/models.ts +470 -0
  235. package/src/admin/security/geoip/GeoIpCachePage.tsx +135 -0
  236. package/src/admin/security/geoip/GeoIpDossier.tsx +804 -0
  237. package/src/admin/security/geoip/geoip-forms.ts +102 -0
  238. package/src/admin/security/geoip/index.ts +27 -0
  239. package/src/admin/security/geoip/models.ts +397 -0
  240. package/src/admin/security/index.ts +31 -0
  241. package/src/admin/security/models.ts +137 -0
  242. package/src/admin/security/tickets.tsx +562 -0
  243. package/src/admin/security-permissions.ts +5 -0
  244. package/src/admin/settings/SettingDetail.tsx +57 -0
  245. package/src/admin/settings/SettingEditor.tsx +157 -0
  246. package/src/admin/settings/SettingsPage.tsx +72 -0
  247. package/src/admin/settings/index.ts +19 -0
  248. package/src/admin/settings/model.ts +143 -0
  249. package/src/admin/shortlinks/ShortlinkDetail.tsx +55 -0
  250. package/src/admin/shortlinks/ShortlinkHistoryPage.tsx +18 -0
  251. package/src/admin/shortlinks/ShortlinksPage.tsx +59 -0
  252. package/src/admin/shortlinks/index.ts +18 -0
  253. package/src/admin/shortlinks/models.ts +246 -0
  254. package/src/admin/storage/BackendsPage.tsx +43 -0
  255. package/src/admin/storage/BucketDetail.tsx +77 -0
  256. package/src/admin/storage/BucketsPage.tsx +64 -0
  257. package/src/admin/storage/FileManagerDetail.tsx +36 -0
  258. package/src/admin/storage/FilePreview.tsx +54 -0
  259. package/src/admin/storage/FileUploadSurface.tsx +250 -0
  260. package/src/admin/storage/FileView.tsx +109 -0
  261. package/src/admin/storage/FilesPage.tsx +53 -0
  262. package/src/admin/storage/file-renditions.ts +99 -0
  263. package/src/admin/storage/index.ts +31 -0
  264. package/src/admin/storage/models.ts +585 -0
  265. package/src/admin/storage/storage-dialogs.tsx +80 -0
  266. package/src/charts/CircularProgress.tsx +281 -0
  267. package/src/charts/KPIStrip.tsx +162 -0
  268. package/src/charts/KPITile.tsx +125 -0
  269. package/src/charts/MetricsChart.tsx +459 -0
  270. package/src/charts/MetricsMiniWidget.tsx +588 -0
  271. package/src/charts/MiniChart.tsx +283 -0
  272. package/src/charts/PieChart.tsx +354 -0
  273. package/src/charts/SeriesChart.tsx +378 -0
  274. package/src/charts/chart-dialogs.tsx +149 -0
  275. package/src/charts/exportChart.ts +144 -0
  276. package/src/charts/index.ts +28 -0
  277. package/src/charts/pie-math.ts +127 -0
  278. package/src/charts/stats.ts +265 -0
  279. package/src/charts/worldmap/WorldMap.tsx +649 -0
  280. package/src/charts/worldmap/countryCentroids.ts +304 -0
  281. package/src/charts/worldmap/geo.ts +293 -0
  282. package/src/charts/worldmap/use-world-land.ts +47 -0
  283. package/src/charts/worldmap/world-land.ts +18 -0
  284. package/src/charts/worldmap/worldmap-data.ts +285 -0
  285. package/src/client/auth.ts +737 -0
  286. package/src/client/client.ts +268 -0
  287. package/src/client/duid.ts +31 -0
  288. package/src/client/errors.ts +33 -0
  289. package/src/client/group-context.ts +28 -0
  290. package/src/client/group.tsx +126 -0
  291. package/src/client/hooks.ts +50 -0
  292. package/src/client/index.ts +37 -0
  293. package/src/client/jwt.ts +75 -0
  294. package/src/client/location.ts +285 -0
  295. package/src/client/lookups.ts +41 -0
  296. package/src/client/markdown-fallback.tsx +115 -0
  297. package/src/client/markdown-parse.ts +386 -0
  298. package/src/client/markdown-sanitize.ts +217 -0
  299. package/src/client/markdown.ts +122 -0
  300. package/src/client/me.ts +188 -0
  301. package/src/client/mock.ts +9984 -0
  302. package/src/client/model.ts +227 -0
  303. package/src/client/params.ts +292 -0
  304. package/src/client/realtime-mock.ts +221 -0
  305. package/src/client/realtime.ts +561 -0
  306. package/src/client/record-feed.ts +329 -0
  307. package/src/client/safe-export.ts +71 -0
  308. package/src/client/types.ts +278 -0
  309. package/src/client/upload.ts +421 -0
  310. package/src/ui/AddressField.tsx +273 -0
  311. package/src/ui/AttachmentQueue.tsx +92 -0
  312. package/src/ui/CollectionMultiSelect.tsx +622 -0
  313. package/src/ui/CollectionSelect.tsx +517 -0
  314. package/src/ui/ComboBox.tsx +336 -0
  315. package/src/ui/DetailView.tsx +438 -0
  316. package/src/ui/FileDrop.tsx +194 -0
  317. package/src/ui/FileField.tsx +369 -0
  318. package/src/ui/FilterBar.tsx +269 -0
  319. package/src/ui/FormFields.tsx +77 -0
  320. package/src/ui/FormView.tsx +360 -0
  321. package/src/ui/FormWizard.tsx +336 -0
  322. package/src/ui/GroupSwitcher.tsx +202 -0
  323. package/src/ui/Guarded.tsx +16 -0
  324. package/src/ui/MarkdownView.tsx +89 -0
  325. package/src/ui/ModelTable.tsx +848 -0
  326. package/src/ui/MultiSelectDropdown.tsx +390 -0
  327. package/src/ui/Popover.tsx +204 -0
  328. package/src/ui/RecordFeed.tsx +627 -0
  329. package/src/ui/RequiresGroup.tsx +32 -0
  330. package/src/ui/RightPanel.tsx +107 -0
  331. package/src/ui/SidebarNav.tsx +330 -0
  332. package/src/ui/Tabs.tsx +161 -0
  333. package/src/ui/TagInput.tsx +381 -0
  334. package/src/ui/ThemeProvider.tsx +52 -0
  335. package/src/ui/UploadQueue.tsx +603 -0
  336. package/src/ui/armed-button.tsx +91 -0
  337. package/src/ui/date/Calendar.tsx +498 -0
  338. package/src/ui/date/DatePicker.tsx +289 -0
  339. package/src/ui/date/DateRangePicker.tsx +280 -0
  340. package/src/ui/date/DateTimePicker.tsx +587 -0
  341. package/src/ui/date/PresetRail.tsx +165 -0
  342. package/src/ui/date/TimePicker.tsx +624 -0
  343. package/src/ui/date/TimezoneSelect.tsx +267 -0
  344. package/src/ui/date/fns.ts +679 -0
  345. package/src/ui/detail/DataView.tsx +692 -0
  346. package/src/ui/detail/FlowStrip.tsx +80 -0
  347. package/src/ui/detail/KnownFieldsCard.tsx +121 -0
  348. package/src/ui/detail/MetadataSection.tsx +219 -0
  349. package/src/ui/detail/StackTraceView.tsx +150 -0
  350. package/src/ui/detail/StatusPanel.tsx +73 -0
  351. package/src/ui/detail/Timeline.tsx +52 -0
  352. package/src/ui/detail/index.ts +12 -0
  353. package/src/ui/field-registry.tsx +428 -0
  354. package/src/ui/field-wire.ts +322 -0
  355. package/src/ui/form-autosave.ts +583 -0
  356. package/src/ui/format.ts +444 -0
  357. package/src/ui/grouping.ts +150 -0
  358. package/src/ui/image-editor/ImageEditor.tsx +484 -0
  359. package/src/ui/image-editor/canvas.ts +254 -0
  360. package/src/ui/image-editor/index.ts +3 -0
  361. package/src/ui/image-editor/math.ts +416 -0
  362. package/src/ui/index.ts +58 -0
  363. package/src/ui/loading.tsx +604 -0
  364. package/src/ui/menu-registry.ts +213 -0
  365. package/src/ui/modal.tsx +297 -0
  366. package/src/ui/password.tsx +302 -0
  367. package/src/ui/schema-form-core.tsx +315 -0
  368. package/src/ui/toast.tsx +219 -0
  369. package/src/ui/ui.tsx +45 -0
@@ -0,0 +1,289 @@
1
+ // DatePicker — the single-value picker SHELL over the Calendar engine.
2
+ // Ported from web-mojo src/core/forms/inputs/DatePicker.js (310 lines, read
3
+ // in full). Trigger button + popover, or `inline` (calendar rendered
4
+ // directly); one component covers day / month / year precision.
5
+ //
6
+ // React shape: the VALUE IS CONTROLLED. `value` is the canonical string at
7
+ // precision (YYYY-MM-DD / YYYY-MM / YYYY) or null; commits arrive through
8
+ // `onChange` and paint only once the owner writes them back — the source's
9
+ // in-place DOM patching (`_setValue` poking textContent + a hidden input) has
10
+ // no equivalent here and needs none.
11
+ //
12
+ // One value pipeline: the incoming prop is parsed + re-formatted ONCE into
13
+ // `canonical`, and the trigger text, the calendar, and every emitted event
14
+ // read that single derivation. A value that does not parse is treated as
15
+ // empty WITH one console.warn — never "render nothing".
16
+ //
17
+ // Structural deviation from source: source nested the clear <button> INSIDE
18
+ // the trigger <button>. That is invalid DOM (React logs a validateDOMNesting
19
+ // error), so the clear is a SIBLING absolutely positioned over the trigger.
20
+ // Same look, and clicking it still cannot open the popover — it is not
21
+ // inside the trigger at all, so there is nothing to stopPropagation from.
22
+ import { useEffect, useId, useMemo, useRef, useState } from 'react';
23
+ import { Calendar, type CalendarSelectEvent } from './Calendar';
24
+ import { formatByPrecision, formatForDisplay, parseByPrecision, type Precision } from './fns';
25
+ import { Popover } from '../Popover';
26
+
27
+ export interface DatePickerChangeEvent {
28
+ /** Canonical string at precision, or null when cleared. */
29
+ value: string | null;
30
+ /** The trigger's display text for `value` ('' when null). */
31
+ formatted: string;
32
+ /** Canonical value being replaced. */
33
+ oldValue: string | null;
34
+ }
35
+
36
+ export interface DatePickerProps {
37
+ /** Controlled canonical value: YYYY-MM-DD / YYYY-MM / YYYY, or null. */
38
+ value?: string | null;
39
+ /** Grid the picker commits at. Default 'day'. Unknown values warn + fall back. */
40
+ precision?: Precision;
41
+ /** Fires on COMMIT only (a calendar pick or the clear button), and only
42
+ * when the canonical value actually changes. */
43
+ onChange?: (event: DatePickerChangeEvent) => void;
44
+ /** Trigger text format (formatForDisplay tokens). Default per precision:
45
+ * 'MMM DD, YYYY' / 'MMM YYYY' / 'YYYY'. Tokens the precision cannot fill
46
+ * are stripped. */
47
+ displayFormat?: string;
48
+ /** Empty-state trigger text. Default 'Select date/month/year...'. */
49
+ placeholder?: string;
50
+ /** Bounds, passed through to the calendar (compared at pane precision). */
51
+ min?: string | null;
52
+ max?: string | null;
53
+ /** Individually blocked days, "YYYY-MM-DD" — day grids only. */
54
+ disabledDates?: string[];
55
+ /** First weekday: 0=Sun, 1=Mon, … Default 1. */
56
+ firstDay?: number;
57
+ /** BCP-47 locale for month/weekday names. Default 'en-US'. */
58
+ locale?: string;
59
+ /** Close the popover as soon as a value is committed. Default true. */
60
+ autoApply?: boolean;
61
+ /** Render the calendar in place instead of behind a trigger. */
62
+ inline?: boolean;
63
+ disabled?: boolean;
64
+ readOnly?: boolean;
65
+ /** Hides the clear button (a required field cannot go back to empty). */
66
+ required?: boolean;
67
+ /** Paints the error border (`is-invalid`). */
68
+ invalid?: boolean;
69
+ /** Renders a hidden input carrying the canonical value — native-form
70
+ * posts only; the package's own forms are controlled. */
71
+ name?: string;
72
+ /** Trigger id, for a label's htmlFor. */
73
+ id?: string;
74
+ className?: string;
75
+ }
76
+
77
+ const DEFAULT_DISPLAY_FORMAT: Record<Precision, string> = {
78
+ day: 'MMM DD, YYYY',
79
+ month: 'MMM YYYY',
80
+ year: 'YYYY',
81
+ };
82
+
83
+ const DEFAULT_PLACEHOLDER: Record<Precision, string> = {
84
+ day: 'Select date...',
85
+ month: 'Select month...',
86
+ year: 'Select year...',
87
+ };
88
+
89
+ const CANONICAL_SHAPE: Record<Precision, string> = {
90
+ day: 'YYYY-MM-DD',
91
+ month: 'YYYY-MM',
92
+ year: 'YYYY',
93
+ };
94
+
95
+ /** Unknown enum-ish prop values fall back WITH a warn — never render nothing. */
96
+ function normPrecision(p: Precision | undefined): Precision {
97
+ if (p == null || p === 'day' || p === 'month' || p === 'year') return p ?? 'day';
98
+ console.warn(`DatePicker: unknown precision "${String(p)}" — falling back to "day"`);
99
+ return 'day';
100
+ }
101
+
102
+ /**
103
+ * Drop tokens the precision cannot fill (e.g. DD when precision='month'),
104
+ * then trim trailing separators. Ported verbatim from source, including its
105
+ * one wart: only TRAILING separators are cleaned, so a mid-string comma
106
+ * survives ('MMM DD, YYYY' at month precision → 'MMM , YYYY'). Pass a format
107
+ * that suits the precision, or lean on the per-precision default.
108
+ */
109
+ function stripIncompatibleTokens(format: string, precision: Precision): string {
110
+ if (precision === 'day') return format;
111
+ if (precision === 'month') {
112
+ return format.replace(/\bDD\b|\bD\b/g, '').replace(/[\s\-/]+$/, '').trim();
113
+ }
114
+ if (precision === 'year') {
115
+ return format.replace(/MMMM|MMM|MM|M|DD|D/g, '').replace(/[\s\-/]+$/, '').trim() || 'YYYY';
116
+ }
117
+ return format;
118
+ }
119
+
120
+ export function DatePicker(props: DatePickerProps) {
121
+ const precision = normPrecision(props.precision);
122
+ const {
123
+ value = null,
124
+ onChange,
125
+ displayFormat,
126
+ placeholder,
127
+ min = null,
128
+ max = null,
129
+ disabledDates,
130
+ firstDay = 1,
131
+ locale = 'en-US',
132
+ autoApply = true,
133
+ inline = false,
134
+ disabled = false,
135
+ readOnly = false,
136
+ required = false,
137
+ invalid = false,
138
+ name,
139
+ id,
140
+ className,
141
+ } = props;
142
+
143
+ const [open, setOpen] = useState(false);
144
+ const triggerRef = useRef<HTMLButtonElement>(null);
145
+ const reactId = useId();
146
+ const popId = `${reactId}-cal`;
147
+
148
+ /** Disabled and readonly both make the picker inert (source parity —
149
+ * `_togglePopover` returned early for either). */
150
+ const inert = disabled || readOnly;
151
+
152
+ // Source's setEnabled/setReadonly closed an open popover on the way in;
153
+ // going inert while open is the prop equivalent.
154
+ useEffect(() => {
155
+ if (inert && open) setOpen(false);
156
+ }, [inert, open]);
157
+
158
+ // ── The one value derivation ──────────────────────────────────
159
+ // Parse + re-format so a loose-but-valid prop ("2026-3-4") canonicalizes
160
+ // before it reaches the calendar, the trigger, or an emitted event.
161
+ const canonical = useMemo(() => {
162
+ const parsed = parseByPrecision(value, precision);
163
+ return parsed ? formatByPrecision(parsed, precision) : null;
164
+ }, [value, precision]);
165
+
166
+ // Rule 4: an unparseable value renders as empty, but never silently —
167
+ // once per distinct bad value, and never during render.
168
+ const warnedRef = useRef<string | null>(null);
169
+ useEffect(() => {
170
+ if (value == null || value === '' || canonical !== null) return;
171
+ const key = `${precision}:${value}`;
172
+ if (warnedRef.current === key) return;
173
+ warnedRef.current = key;
174
+ console.warn(
175
+ `DatePicker: value "${value}" is not a valid ${precision} value ` +
176
+ `(expected ${CANONICAL_SHAPE[precision]}) — showing empty`,
177
+ );
178
+ }, [value, canonical, precision]);
179
+
180
+ const format = displayFormat ?? DEFAULT_DISPLAY_FORMAT[precision];
181
+ const displayFor = (canon: string | null): string => {
182
+ const parsed = parseByPrecision(canon, precision);
183
+ return parsed ? formatForDisplay(parsed, stripIncompatibleTokens(format, precision)) : '';
184
+ };
185
+ const displayText = displayFor(canonical);
186
+
187
+ // ── Commit ────────────────────────────────────────────────────
188
+ const commit = (raw: string | null) => {
189
+ const parsed = parseByPrecision(raw, precision);
190
+ const next = parsed ? formatByPrecision(parsed, precision) : null;
191
+ if (next === canonical) return; // source: emit only on a real change
192
+ onChange?.({ value: next, formatted: displayFor(next), oldValue: canonical });
193
+ };
194
+
195
+ const handleSelect = (event: CalendarSelectEvent) => {
196
+ commit(event.value);
197
+ // Source closed on autoApply whether or not the value changed.
198
+ if (autoApply) setOpen(false);
199
+ };
200
+
201
+ const calendar = (
202
+ <Calendar
203
+ precision={precision}
204
+ mode="single"
205
+ months={1}
206
+ value={canonical}
207
+ min={min}
208
+ max={max}
209
+ disabledDates={disabledDates}
210
+ firstDay={firstDay}
211
+ locale={locale}
212
+ onSelect={inert ? undefined : handleSelect}
213
+ />
214
+ );
215
+
216
+ const rootClass = `mojo-date-picker mojo-date-picker-${precision}${className ? ` ${className}` : ''}`;
217
+ const hidden = name ? <input type="hidden" name={name} value={canonical ?? ''} /> : null;
218
+
219
+ // ── Inline: no trigger, no popover ────────────────────────────
220
+ if (inline) {
221
+ // Source left an inline picker fully live even when disabled; here the
222
+ // props mean what they say (pointer-inert, and no commits are wired).
223
+ const inlineClass = `mojo-date-picker-inline${invalid ? ' is-invalid' : ''}`
224
+ + (inert ? ' is-inert' : '') + (disabled ? ' is-disabled' : '');
225
+ return (
226
+ <div className={rootClass}>
227
+ {hidden}
228
+ <div className={inlineClass} aria-disabled={inert || undefined}>{calendar}</div>
229
+ </div>
230
+ );
231
+ }
232
+
233
+ // ── Trigger + popover ─────────────────────────────────────────
234
+ const showClear = !required && !disabled && !readOnly;
235
+ const triggerClass = 'mojo-date-trigger'
236
+ + (invalid ? ' is-invalid' : '')
237
+ + (showClear ? ' has-clear' : '')
238
+ + (readOnly ? ' is-readonly' : '');
239
+
240
+ return (
241
+ <div className={rootClass}>
242
+ <button
243
+ type="button"
244
+ ref={triggerRef}
245
+ id={id}
246
+ className={triggerClass}
247
+ disabled={disabled}
248
+ aria-haspopup="dialog"
249
+ aria-expanded={open}
250
+ aria-controls={open ? popId : undefined}
251
+ // readOnly keeps the button focusable but actionless — that is
252
+ // aria-disabled, not aria-readonly (invalid for role=button).
253
+ aria-disabled={readOnly || undefined}
254
+ onClick={() => { if (!inert) setOpen((v) => !v); }}
255
+ >
256
+ <i className="bi bi-calendar3" aria-hidden="true" />
257
+ <span className={`mojo-date-trigger-text${canonical ? '' : ' is-empty'}`}>
258
+ {displayText || (placeholder ?? DEFAULT_PLACEHOLDER[precision])}
259
+ </span>
260
+ </button>
261
+ {showClear && (
262
+ // Source parity: the ✕ shows whenever clearing is allowed, even
263
+ // with nothing selected — clicking it then is a no-op commit.
264
+ <button
265
+ type="button"
266
+ className="mojo-date-trigger-clear"
267
+ aria-label="Clear"
268
+ tabIndex={-1}
269
+ onClick={() => commit(null)}
270
+ >
271
+ {'✕'}
272
+ </button>
273
+ )}
274
+ {hidden}
275
+ <Popover
276
+ anchorRef={triggerRef}
277
+ open={open}
278
+ onClose={() => setOpen(false)}
279
+ placement="bottom-start"
280
+ id={popId}
281
+ aria-label="Calendar"
282
+ >
283
+ {/* The shared popover shell is unpadded/unsized on purpose —
284
+ the picker content carries the source's padding + floor. */}
285
+ <div className="mojo-date-pop">{calendar}</div>
286
+ </Popover>
287
+ </div>
288
+ );
289
+ }
@@ -0,0 +1,280 @@
1
+ // DateRangePicker — range picker built on the Calendar engine and the
2
+ // shared Popover, with the "Quick range" preset rail. Ported from web-mojo
3
+ // src/core/forms/inputs/DateRangePicker.js (402 lines, read in full) +
4
+ // calendar/PresetSidebar.js (152). Three precisions ('day' | 'month' |
5
+ // 'year'); day precision defaults to two side-by-side panes.
6
+ //
7
+ // React shape: the VALUE is controlled — `start`/`end` are canonical
8
+ // strings at precision; every commit (completed calendar range, preset
9
+ // pick, clear) emits `onChange({start, end, formatted, oldStart, oldEnd})`
10
+ // and paints once the owner writes the props back. `autoApply` (default
11
+ // true) carries the source semantics exactly: values apply on EVERY commit
12
+ // — autoApply only decides whether completing a range / picking a preset
13
+ // also CLOSES the popover. There is no staged apply; mount the picker
14
+ // `inline` in a dialog (FilterBar does) when the owner wants an explicit
15
+ // Apply step around it.
16
+ //
17
+ // Deviations from source, deliberate:
18
+ // - No hidden <input>s / name/startName/endName / outputFormat /
19
+ // getFormValue surface — controlled props replace the form-DOM bridge.
20
+ // - The trigger's clear ✕ is an absolutely-positioned SIBLING button, not
21
+ // a nested <button> (source nested buttons — invalid HTML).
22
+ // - A half-picked anchor does NOT survive popover close/reopen: the anchor
23
+ // is Calendar-internal state and the popover unmounts its content on
24
+ // close (web-mojo kept one live calendar element across opens). The
25
+ // load-bearing source behavior — the anchor surviving PAGING/zooming
26
+ // mid-selection — lives in the engine and is fully preserved; reopening
27
+ // shows the committed range, the predictable state. (Revisit only if the
28
+ // engine ever grows a controlled `anchor` prop.)
29
+ // - Escape with the popover open closes the popover (Popover owns Escape
30
+ // in the capture phase); the engine's Escape-cancels-anchor applies in
31
+ // inline mode. Closing never reverts — commits were already applied.
32
+ import { useRef, useState } from 'react';
33
+ import { Calendar } from './Calendar';
34
+ import { formatForDisplay, parseByPrecision } from './fns';
35
+ import type { Precision } from './fns';
36
+ import { Popover } from '../Popover';
37
+ import { PresetRail } from './PresetRail';
38
+ import type { PresetsOption, PresetSelectEvent } from './PresetRail';
39
+
40
+ const DEFAULT_DISPLAY_FORMAT: Record<Precision, string> = {
41
+ day: 'MMM DD, YYYY',
42
+ month: 'MMM YYYY',
43
+ year: 'YYYY',
44
+ };
45
+
46
+ const DEFAULT_PLACEHOLDER: Record<Precision, string> = {
47
+ day: 'Select date range...',
48
+ month: 'Select month range...',
49
+ year: 'Select year range...',
50
+ };
51
+
52
+ export interface DateRangeChangeEvent {
53
+ /** Canonical strings at precision ('' when cleared / half-empty). */
54
+ start: string;
55
+ end: string;
56
+ /** Display text of the NEW range — what the trigger shows. */
57
+ formatted: string;
58
+ oldStart: string;
59
+ oldEnd: string;
60
+ }
61
+
62
+ export interface DateRangePickerProps {
63
+ /** Controlled canonical strings at precision (YYYY-MM-DD / YYYY-MM /
64
+ * YYYY). '' or null = empty. */
65
+ start?: string | null;
66
+ end?: string | null;
67
+ /** Fires on COMMIT (range completed, preset picked, clear) and only
68
+ * when the pair actually changes. */
69
+ onChange?: (event: DateRangeChangeEvent) => void;
70
+ /** 'day' | 'month' | 'year'. Default 'day'. Unknown values warn and
71
+ * fall back to 'day'. */
72
+ precision?: Precision;
73
+ /** Day-view panes 1 | 2. Default 2 for day precision, 1 for month/year. */
74
+ months?: 1 | 2;
75
+ /** 'default' | true → the precision's built-in list; an array → custom
76
+ * `{label, range()}` entries (`{divider: true}` separators); absent /
77
+ * false / empty array → no rail. */
78
+ presets?: PresetsOption | false | null;
79
+ /** Display tokens (YYYY YY MMMM MMM MM M DD D). Default per precision;
80
+ * tokens the precision can't honor are stripped. */
81
+ displayFormat?: string;
82
+ /** Between the two formatted ends. Default ' – '. */
83
+ separator?: string;
84
+ placeholder?: string;
85
+ /** Bounds (canonical strings) — passed through to the Calendar, which
86
+ * compares them at the pane precision being painted. */
87
+ min?: string | null;
88
+ max?: string | null;
89
+ disabled?: boolean;
90
+ readOnly?: boolean;
91
+ /** Hides the clear ✕ so the field can't be emptied from the trigger. */
92
+ required?: boolean;
93
+ /** Render the rail + calendar in place — no trigger, no popover. */
94
+ inline?: boolean;
95
+ /** Close the popover when a range completes / a preset is picked.
96
+ * Default true. Values always apply immediately either way. */
97
+ autoApply?: boolean;
98
+ className?: string;
99
+ /** id for the trigger button (external label targets). */
100
+ id?: string;
101
+ }
102
+
103
+ /** Unknown enum-ish prop values fall back WITH a warn — never render nothing. */
104
+ function normPrecision(p: Precision | undefined): Precision {
105
+ if (p == null || p === 'day' || p === 'month' || p === 'year') return p ?? 'day';
106
+ console.warn(`DateRangePicker: unknown precision "${String(p)}" — falling back to "day"`);
107
+ return 'day';
108
+ }
109
+
110
+ /** Drop display-format tokens the precision can't honor, then sweep the
111
+ * separator debris off the tail (ported verbatim from source). Private —
112
+ * DatePicker ports the same source helper; exporting twins would collide
113
+ * at the index.ts merge. */
114
+ function stripIncompatibleTokens(format: string, precision: Precision): string {
115
+ if (precision === 'day') return format;
116
+ if (precision === 'month') return format.replace(/\bDD\b|\bD\b/g, '').replace(/[\s\-\/]+$/, '').trim();
117
+ return format.replace(/MMMM|MMM|MM|M|DD|D/g, '').replace(/[\s\-\/]+$/, '').trim() || 'YYYY';
118
+ }
119
+
120
+ export function DateRangePicker(props: DateRangePickerProps) {
121
+ const precision = normPrecision(props.precision);
122
+ const {
123
+ onChange,
124
+ presets = null,
125
+ min = null,
126
+ max = null,
127
+ disabled = false,
128
+ readOnly = false,
129
+ required = false,
130
+ inline = false,
131
+ autoApply = true,
132
+ className = '',
133
+ id,
134
+ } = props;
135
+ const start = props.start ?? '';
136
+ const end = props.end ?? '';
137
+ // Same clamp as the engine so the pane-width class can't disagree with
138
+ // what the Calendar actually renders.
139
+ const months = Math.max(1, Math.min(2, (props.months ?? (precision === 'day' ? 2 : 1)) | 0)) as 1 | 2;
140
+ const twoPane = precision === 'day' && months === 2;
141
+ const displayFormat = props.displayFormat ?? DEFAULT_DISPLAY_FORMAT[precision];
142
+ const separator = props.separator ?? ' – ';
143
+ const placeholder = props.placeholder ?? DEFAULT_PLACEHOLDER[precision];
144
+
145
+ const [open, setOpen] = useState(false);
146
+ // Highlighted rail entry; -1 = none. Cleared when a manual calendar
147
+ // anchor starts (source: sidebar.setActive(-1) on range:start).
148
+ const [activePreset, setActivePreset] = useState(-1);
149
+ const wrapRef = useRef<HTMLDivElement>(null);
150
+
151
+ // Source's _hasPresets predicate: true / 'default' / non-empty array.
152
+ const railPresets: PresetsOption | null =
153
+ presets === true || presets === 'default' ? presets
154
+ : Array.isArray(presets) && presets.length > 0 ? presets
155
+ : null;
156
+
157
+ // ── Display text (source _displayText + token stripping) ─────────
158
+ const fmt = stripIncompatibleTokens(displayFormat, precision);
159
+ const displayTextFor = (s: string, e: string): string => {
160
+ const ps = s ? parseByPrecision(s, precision) : null;
161
+ const pe = e ? parseByPrecision(e, precision) : null;
162
+ if (!ps && !pe) return '';
163
+ if (ps && pe) return `${formatForDisplay(ps, fmt)}${separator}${formatForDisplay(pe, fmt)}`;
164
+ return formatForDisplay(ps ?? pe, fmt);
165
+ };
166
+ const text = displayTextFor(start, end);
167
+
168
+ // ── One commit pipeline: calendar range / preset / clear ─────────
169
+ const emitChange = (nextStart: string, nextEnd: string) => {
170
+ if (nextStart === start && nextEnd === end) return;
171
+ onChange?.({
172
+ start: nextStart,
173
+ end: nextEnd,
174
+ formatted: displayTextFor(nextStart, nextEnd),
175
+ oldStart: start,
176
+ oldEnd: end,
177
+ });
178
+ };
179
+
180
+ const closeIfAuto = () => { if (autoApply && !inline) setOpen(false); };
181
+
182
+ const handleRangeSelect = (e: { start: string; end: string }) => {
183
+ emitChange(e.start, e.end);
184
+ closeIfAuto();
185
+ };
186
+
187
+ const handleRangeStart = () => {
188
+ if (activePreset !== -1) setActivePreset(-1);
189
+ };
190
+
191
+ const handlePreset = (e: PresetSelectEvent) => {
192
+ setActivePreset(e.index);
193
+ emitChange(e.start, e.end);
194
+ closeIfAuto();
195
+ };
196
+
197
+ const clear = () => emitChange('', '');
198
+
199
+ const toggle = () => {
200
+ if (disabled || readOnly) return;
201
+ setOpen((v) => !v);
202
+ };
203
+
204
+ // ── Body — identical in the popover and inline ───────────────────
205
+ const body = (extraClass = '') => (
206
+ <div className={`mojo-daterange-body${railPresets ? ' mojo-daterange-with-presets' : ''}${extraClass}`}>
207
+ {railPresets && (
208
+ <PresetRail
209
+ precision={precision}
210
+ presets={railPresets}
211
+ activeIndex={activePreset}
212
+ onSelect={handlePreset}
213
+ />
214
+ )}
215
+ <div className={`mojo-daterange-cal${twoPane ? ' is-two-pane' : ''}`}>
216
+ <Calendar
217
+ precision={precision}
218
+ mode="range"
219
+ months={months}
220
+ startValue={start || null}
221
+ endValue={end || null}
222
+ min={min}
223
+ max={max}
224
+ onRangeSelect={handleRangeSelect}
225
+ onRangeStart={handleRangeStart}
226
+ />
227
+ </div>
228
+ </div>
229
+ );
230
+
231
+ if (inline) {
232
+ return (
233
+ <div className={`mojo-daterange mojo-daterange-${precision} mojo-daterange-inline${className ? ` ${className}` : ''}`}>
234
+ {body()}
235
+ </div>
236
+ );
237
+ }
238
+
239
+ const showClear = !required && !disabled && !readOnly;
240
+ const rootClasses = [
241
+ 'mojo-daterange',
242
+ `mojo-daterange-${precision}`,
243
+ showClear ? 'mojo-daterange-clearable' : '',
244
+ readOnly ? 'mojo-daterange-readonly' : '',
245
+ className,
246
+ ].filter(Boolean).join(' ');
247
+
248
+ return (
249
+ <div className={rootClasses} ref={wrapRef}>
250
+ <button
251
+ type="button"
252
+ id={id}
253
+ className="mojo-daterange-trigger"
254
+ disabled={disabled}
255
+ aria-haspopup="dialog"
256
+ aria-expanded={open}
257
+ onClick={toggle}
258
+ >
259
+ <i className="bi bi-calendar3-range" aria-hidden="true" />
260
+ <span className={`mojo-daterange-trigger-text${text ? '' : ' is-empty'}`}>
261
+ {text || placeholder}
262
+ </span>
263
+ </button>
264
+ {showClear && (
265
+ <button
266
+ type="button"
267
+ className="mojo-daterange-trigger-clear"
268
+ aria-label="Clear"
269
+ tabIndex={-1}
270
+ onClick={clear}
271
+ >
272
+ {'✕'}
273
+ </button>
274
+ )}
275
+ <Popover anchorRef={wrapRef} open={open} onClose={() => setOpen(false)}>
276
+ {body(' mojo-daterange-pop')}
277
+ </Popover>
278
+ </div>
279
+ );
280
+ }