@rapidmx/web-client 0.10.0 → 0.11.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 (310) hide show
  1. package/README.md +183 -2
  2. package/apps/admin/_layout.tsx +8 -2
  3. package/apps/admin/index.tsx +7 -1
  4. package/apps/admin/ingest-queue/index.tsx +2 -1
  5. package/apps/admin/mailboxes/[uid].tsx +23 -10
  6. package/apps/admin/quarantine/index.tsx +2 -1
  7. package/apps/admin/signing-certificates/index.tsx +343 -0
  8. package/apps/escrow/_layout.tsx +8 -2
  9. package/apps/shared/appearance/AppearanceForm.tsx +392 -0
  10. package/apps/shared/appearance/AppearanceHead.tsx +49 -0
  11. package/apps/shared/appearance/AppearanceProvider.tsx +470 -0
  12. package/apps/shared/appearance/ColorField.tsx +107 -0
  13. package/apps/shared/appearance/appearanceCache.ts +106 -0
  14. package/apps/shared/appearance/appearanceContext.ts +77 -0
  15. package/apps/shared/appearance/bootScript.ts +35 -0
  16. package/apps/shared/appearance/color.ts +169 -0
  17. package/apps/shared/appearance/photo.ts +88 -0
  18. package/apps/shared/appearance/resolvedTheme.ts +67 -0
  19. package/apps/shared/appearance/theme.ts +355 -0
  20. package/apps/shared/components/admin/layout/AdminShell.tsx +22 -10
  21. package/apps/shared/components/admin/mailboxes/ShareAccessCard.tsx +107 -60
  22. package/apps/shared/components/admin/settings/BrandingForm.tsx +24 -0
  23. package/apps/shared/components/admin/settings/PluginsManager.tsx +228 -25
  24. package/apps/shared/components/admin/setup/SetupWizard.tsx +2 -1
  25. package/apps/shared/components/calendar/CalendarListSidebar.tsx +4 -5
  26. package/apps/shared/components/contacts/ContactForm.tsx +11 -5
  27. package/apps/shared/components/contacts/ContactsSidebar.tsx +5 -3
  28. package/apps/shared/components/contacts/ContactsToolbar.tsx +25 -52
  29. package/apps/shared/components/contacts/layout/ContactsShell.tsx +10 -5
  30. package/apps/shared/components/escrow/layout/EscrowShell.tsx +13 -6
  31. package/apps/shared/components/layout/AppShell.tsx +98 -32
  32. package/apps/shared/components/layout/BrandingChrome.tsx +200 -13
  33. package/apps/shared/components/layout/RailIcon.tsx +100 -0
  34. package/apps/shared/components/layout/ResponsiveToolbar.tsx +315 -0
  35. package/apps/shared/components/layout/ThemeSwitch.tsx +84 -0
  36. package/apps/shared/components/layout/UserMenu.tsx +75 -6
  37. package/apps/shared/components/mail/ConversationList.tsx +4 -2
  38. package/apps/shared/components/mail/ConversationThreadPane.tsx +487 -475
  39. package/apps/shared/components/mail/LazyReadingPane.tsx +14 -16
  40. package/apps/shared/components/mail/MailSelectionBar.tsx +2 -10
  41. package/apps/shared/components/mail/MessageDetailPane.tsx +366 -287
  42. package/apps/shared/components/mail/OutboxBadge.tsx +44 -0
  43. package/apps/shared/components/mail/OutboxRowStatus.tsx +31 -0
  44. package/apps/shared/components/mail/compose/ComposeContext.tsx +35 -2
  45. package/apps/shared/components/mail/compose/ComposeWindow.tsx +239 -437
  46. package/apps/shared/components/mail/compose/encryptionRequirement.ts +86 -0
  47. package/apps/shared/components/mail/layout/MailShell.tsx +39 -25
  48. package/apps/shared/components/mail/reading/EncryptedBody.tsx +65 -0
  49. package/apps/shared/components/mail/reading/EncryptedPreview.tsx +35 -0
  50. package/apps/shared/components/mail/reading/MessageBody.tsx +240 -0
  51. package/apps/shared/components/mail/reading/MessageCard.tsx +174 -0
  52. package/apps/shared/components/mail/reading/bodyContent.ts +100 -0
  53. package/apps/shared/components/mail/reading/bodyHtml.ts +298 -0
  54. package/apps/shared/components/mail/reading/color.ts +245 -0
  55. package/apps/shared/components/mail/reading/frameControl.ts +142 -0
  56. package/apps/shared/components/mail/reading/frameDocument.ts +123 -0
  57. package/apps/shared/components/mail/reading/safeDocument.ts +24 -0
  58. package/apps/shared/components/mail/reading/themeAdaptation.ts +221 -0
  59. package/apps/shared/components/mail/reading/themeSurface.ts +90 -0
  60. package/apps/shared/components/mail/reading/viewOriginal.ts +38 -0
  61. package/apps/shared/components/settings/SigningCertificateCard.tsx +344 -0
  62. package/apps/shared/components/settings/layout/SettingsShell.tsx +1 -0
  63. package/apps/shared/components/sharing/PrincipalPicker.tsx +145 -0
  64. package/apps/shared/components/tasks/TasksSidebar.tsx +5 -3
  65. package/apps/shared/components/tasks/layout/TasksShell.tsx +9 -5
  66. package/apps/shared/mail/folderCounts.ts +9 -3
  67. package/apps/shared/mail/folderTree.ts +143 -0
  68. package/apps/shared/mail/outbox/UnlockBridge.tsx +23 -0
  69. package/apps/shared/mail/outbox/composeBridge.ts +69 -0
  70. package/apps/shared/mail/outbox/outboxState.ts +88 -0
  71. package/apps/shared/mail/outbox/pendingSends.ts +146 -0
  72. package/apps/shared/mail/outbox/sendDecision.ts +157 -0
  73. package/apps/shared/mail/outbox/sendJob.ts +427 -0
  74. package/apps/shared/mail/outbox/sendOutcomes.ts +146 -0
  75. package/apps/shared/mail/outbox/sendState.ts +35 -0
  76. package/apps/shared/mail/outbox/useOutboxStatus.ts +61 -0
  77. package/apps/shared/mail/useMailConnection.ts +81 -28
  78. package/apps/shared/mail/useMailLiveUpdates.ts +55 -10
  79. package/apps/shared/mail/useNewMailNotifications.ts +47 -32
  80. package/apps/shared/navigation/AppRouter.tsx +2 -2
  81. package/apps/shared/notifications/NotificationCenter.tsx +215 -0
  82. package/apps/shared/notifications/NotificationHistoryDialog.tsx +90 -0
  83. package/apps/shared/notifications/apiErrors.ts +98 -0
  84. package/apps/shared/notifications/headerOffset.ts +35 -0
  85. package/apps/shared/notifications/pushStatus.ts +56 -0
  86. package/apps/shared/notifications/store.ts +549 -0
  87. package/apps/shared/notifications/systemErrors.ts +63 -0
  88. package/apps/shared/notifications/useNotifications.ts +41 -0
  89. package/apps/shared/signing/enrollmentStorage.ts +33 -0
  90. package/apps/shared/signing/enrollmentTracker.ts +385 -0
  91. package/apps/shared/signing/enrollmentView.ts +251 -0
  92. package/apps/shared/signing/signingInfo.ts +58 -0
  93. package/apps/shared/signing/useNow.ts +19 -0
  94. package/apps/shared/signing/useSigningEnrollmentWatcher.ts +90 -0
  95. package/apps/shared/styles/app.css +80 -0
  96. package/apps/www/_layout.tsx +8 -2
  97. package/apps/www/_routes.ts +1 -0
  98. package/apps/www/calendar/index.tsx +9 -14
  99. package/apps/www/contacts/[uid].tsx +5 -8
  100. package/apps/www/contacts/index.tsx +587 -584
  101. package/apps/www/index.tsx +38 -27
  102. package/apps/www/messages/[uid].tsx +3 -1
  103. package/apps/www/settings/appearance/index.tsx +21 -0
  104. package/apps/www/settings/auto-reply/index.tsx +3 -7
  105. package/apps/www/settings/encryption/index.tsx +151 -116
  106. package/apps/www/settings/filters/[uid].tsx +3 -1
  107. package/apps/www/settings/filters/new/index.tsx +3 -1
  108. package/apps/www/settings/privacy/index.tsx +3 -8
  109. package/apps/www/settings/read-receipts/index.tsx +3 -7
  110. package/apps/www/settings/sharing/index.tsx +44 -65
  111. package/apps/www/settings/signatures/[uid].tsx +3 -1
  112. package/apps/www/settings/signatures/new/index.tsx +3 -2
  113. package/apps/www/tasks/index.tsx +11 -16
  114. package/dist/apps/admin/_layout.d.ts +5 -1
  115. package/dist/apps/admin/_layout.js +3 -2
  116. package/dist/apps/admin/index.js +3 -2
  117. package/dist/apps/admin/ingest-queue/index.js +2 -1
  118. package/dist/apps/admin/mailboxes/[uid].js +9 -6
  119. package/dist/apps/admin/quarantine/index.js +2 -1
  120. package/dist/apps/admin/signing-certificates/index.d.ts +3 -0
  121. package/dist/apps/admin/signing-certificates/index.js +161 -0
  122. package/dist/apps/escrow/_layout.d.ts +5 -1
  123. package/dist/apps/escrow/_layout.js +3 -2
  124. package/dist/apps/shared/appearance/AppearanceForm.d.ts +7 -0
  125. package/dist/apps/shared/appearance/AppearanceForm.js +131 -0
  126. package/dist/apps/shared/appearance/AppearanceHead.d.ts +15 -0
  127. package/dist/apps/shared/appearance/AppearanceHead.js +31 -0
  128. package/dist/apps/shared/appearance/AppearanceProvider.d.ts +32 -0
  129. package/dist/apps/shared/appearance/AppearanceProvider.js +390 -0
  130. package/dist/apps/shared/appearance/ColorField.d.ts +22 -0
  131. package/dist/apps/shared/appearance/ColorField.js +40 -0
  132. package/dist/apps/shared/appearance/appearanceCache.d.ts +45 -0
  133. package/dist/apps/shared/appearance/appearanceCache.js +80 -0
  134. package/dist/apps/shared/appearance/appearanceContext.d.ts +45 -0
  135. package/dist/apps/shared/appearance/appearanceContext.js +29 -0
  136. package/dist/apps/shared/appearance/bootScript.d.ts +16 -0
  137. package/dist/apps/shared/appearance/bootScript.js +33 -0
  138. package/dist/apps/shared/appearance/color.d.ts +65 -0
  139. package/dist/apps/shared/appearance/color.js +132 -0
  140. package/dist/apps/shared/appearance/photo.d.ts +17 -0
  141. package/dist/apps/shared/appearance/photo.js +79 -0
  142. package/dist/apps/shared/appearance/resolvedTheme.d.ts +21 -0
  143. package/dist/apps/shared/appearance/resolvedTheme.js +55 -0
  144. package/dist/apps/shared/appearance/theme.d.ts +111 -0
  145. package/dist/apps/shared/appearance/theme.js +251 -0
  146. package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +1 -1
  147. package/dist/apps/shared/components/admin/layout/AdminShell.js +16 -6
  148. package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.d.ts +10 -5
  149. package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +49 -34
  150. package/dist/apps/shared/components/admin/settings/BrandingForm.d.ts +2 -0
  151. package/dist/apps/shared/components/admin/settings/BrandingForm.js +7 -1
  152. package/dist/apps/shared/components/admin/settings/PluginsManager.js +106 -20
  153. package/dist/apps/shared/components/admin/setup/SetupWizard.js +2 -1
  154. package/dist/apps/shared/components/calendar/CalendarListSidebar.js +8 -8
  155. package/dist/apps/shared/components/contacts/ContactForm.js +1 -1
  156. package/dist/apps/shared/components/contacts/ContactsSidebar.js +4 -2
  157. package/dist/apps/shared/components/contacts/ContactsToolbar.d.ts +3 -1
  158. package/dist/apps/shared/components/contacts/ContactsToolbar.js +24 -9
  159. package/dist/apps/shared/components/contacts/layout/ContactsShell.js +4 -1
  160. package/dist/apps/shared/components/escrow/layout/EscrowShell.js +7 -4
  161. package/dist/apps/shared/components/layout/AppShell.d.ts +6 -1
  162. package/dist/apps/shared/components/layout/AppShell.js +59 -13
  163. package/dist/apps/shared/components/layout/BrandingChrome.d.ts +69 -10
  164. package/dist/apps/shared/components/layout/BrandingChrome.js +140 -12
  165. package/dist/apps/shared/components/layout/RailIcon.d.ts +21 -0
  166. package/dist/apps/shared/components/layout/RailIcon.js +85 -0
  167. package/dist/apps/shared/components/layout/ResponsiveToolbar.d.ts +55 -0
  168. package/dist/apps/shared/components/layout/ResponsiveToolbar.js +199 -0
  169. package/dist/apps/shared/components/layout/ThemeSwitch.d.ts +9 -0
  170. package/dist/apps/shared/components/layout/ThemeSwitch.js +54 -0
  171. package/dist/apps/shared/components/layout/UserMenu.d.ts +7 -1
  172. package/dist/apps/shared/components/layout/UserMenu.js +41 -7
  173. package/dist/apps/shared/components/mail/ConversationList.js +3 -2
  174. package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +2 -1
  175. package/dist/apps/shared/components/mail/ConversationThreadPane.js +42 -20
  176. package/dist/apps/shared/components/mail/LazyReadingPane.js +9 -8
  177. package/dist/apps/shared/components/mail/MailSelectionBar.d.ts +3 -4
  178. package/dist/apps/shared/components/mail/MailSelectionBar.js +2 -3
  179. package/dist/apps/shared/components/mail/MessageDetailPane.d.ts +18 -7
  180. package/dist/apps/shared/components/mail/MessageDetailPane.js +86 -41
  181. package/dist/apps/shared/components/mail/OutboxBadge.d.ts +17 -0
  182. package/dist/apps/shared/components/mail/OutboxBadge.js +18 -0
  183. package/dist/apps/shared/components/mail/OutboxRowStatus.d.ts +10 -0
  184. package/dist/apps/shared/components/mail/OutboxRowStatus.js +19 -0
  185. package/dist/apps/shared/components/mail/compose/ComposeContext.d.ts +8 -0
  186. package/dist/apps/shared/components/mail/compose/ComposeContext.js +28 -3
  187. package/dist/apps/shared/components/mail/compose/ComposeWindow.d.ts +2 -1
  188. package/dist/apps/shared/components/mail/compose/ComposeWindow.js +229 -328
  189. package/dist/apps/shared/components/mail/compose/encryptionRequirement.d.ts +49 -0
  190. package/dist/apps/shared/components/mail/compose/encryptionRequirement.js +35 -0
  191. package/dist/apps/shared/components/mail/layout/MailShell.d.ts +2 -0
  192. package/dist/apps/shared/components/mail/layout/MailShell.js +20 -14
  193. package/dist/apps/shared/components/mail/reading/EncryptedBody.d.ts +21 -0
  194. package/dist/apps/shared/components/mail/reading/EncryptedBody.js +22 -0
  195. package/dist/apps/shared/components/mail/reading/EncryptedPreview.d.ts +18 -0
  196. package/dist/apps/shared/components/mail/reading/EncryptedPreview.js +22 -0
  197. package/dist/apps/shared/components/mail/reading/MessageBody.d.ts +32 -0
  198. package/dist/apps/shared/components/mail/reading/MessageBody.js +136 -0
  199. package/dist/apps/shared/components/mail/reading/MessageCard.d.ts +62 -0
  200. package/dist/apps/shared/components/mail/reading/MessageCard.js +58 -0
  201. package/dist/apps/shared/components/mail/reading/bodyContent.d.ts +38 -0
  202. package/dist/apps/shared/components/mail/reading/bodyContent.js +84 -0
  203. package/dist/apps/shared/components/mail/reading/bodyHtml.d.ts +47 -0
  204. package/dist/apps/shared/components/mail/reading/bodyHtml.js +267 -0
  205. package/dist/apps/shared/components/mail/reading/color.d.ts +63 -0
  206. package/dist/apps/shared/components/mail/reading/color.js +201 -0
  207. package/dist/apps/shared/components/mail/reading/frameControl.d.ts +33 -0
  208. package/dist/apps/shared/components/mail/reading/frameControl.js +120 -0
  209. package/dist/apps/shared/components/mail/reading/frameDocument.d.ts +52 -0
  210. package/dist/apps/shared/components/mail/reading/frameDocument.js +98 -0
  211. package/dist/apps/shared/components/mail/reading/safeDocument.d.ts +3 -0
  212. package/dist/apps/shared/components/mail/reading/safeDocument.js +12 -0
  213. package/dist/apps/shared/components/mail/reading/themeAdaptation.d.ts +56 -0
  214. package/dist/apps/shared/components/mail/reading/themeAdaptation.js +144 -0
  215. package/dist/apps/shared/components/mail/reading/themeSurface.d.ts +17 -0
  216. package/dist/apps/shared/components/mail/reading/themeSurface.js +83 -0
  217. package/dist/apps/shared/components/mail/reading/viewOriginal.d.ts +9 -0
  218. package/dist/apps/shared/components/mail/reading/viewOriginal.js +32 -0
  219. package/dist/apps/shared/components/settings/SigningCertificateCard.d.ts +32 -0
  220. package/dist/apps/shared/components/settings/SigningCertificateCard.js +98 -0
  221. package/dist/apps/shared/components/settings/layout/SettingsShell.js +1 -0
  222. package/dist/apps/shared/components/sharing/PrincipalPicker.d.ts +26 -0
  223. package/dist/apps/shared/components/sharing/PrincipalPicker.js +77 -0
  224. package/dist/apps/shared/components/tasks/TasksSidebar.js +4 -2
  225. package/dist/apps/shared/components/tasks/layout/TasksShell.js +3 -1
  226. package/dist/apps/shared/mail/folderCounts.d.ts +1 -1
  227. package/dist/apps/shared/mail/folderCounts.js +9 -3
  228. package/dist/apps/shared/mail/folderTree.d.ts +55 -0
  229. package/dist/apps/shared/mail/folderTree.js +116 -0
  230. package/dist/apps/shared/mail/outbox/UnlockBridge.d.ts +5 -0
  231. package/dist/apps/shared/mail/outbox/UnlockBridge.js +18 -0
  232. package/dist/apps/shared/mail/outbox/composeBridge.d.ts +34 -0
  233. package/dist/apps/shared/mail/outbox/composeBridge.js +32 -0
  234. package/dist/apps/shared/mail/outbox/outboxState.d.ts +41 -0
  235. package/dist/apps/shared/mail/outbox/outboxState.js +42 -0
  236. package/dist/apps/shared/mail/outbox/pendingSends.d.ts +39 -0
  237. package/dist/apps/shared/mail/outbox/pendingSends.js +109 -0
  238. package/dist/apps/shared/mail/outbox/sendDecision.d.ts +80 -0
  239. package/dist/apps/shared/mail/outbox/sendDecision.js +94 -0
  240. package/dist/apps/shared/mail/outbox/sendJob.d.ts +68 -0
  241. package/dist/apps/shared/mail/outbox/sendJob.js +319 -0
  242. package/dist/apps/shared/mail/outbox/sendOutcomes.d.ts +3 -0
  243. package/dist/apps/shared/mail/outbox/sendOutcomes.js +141 -0
  244. package/dist/apps/shared/mail/outbox/sendState.d.ts +25 -0
  245. package/dist/apps/shared/mail/outbox/sendState.js +8 -0
  246. package/dist/apps/shared/mail/outbox/useOutboxStatus.d.ts +11 -0
  247. package/dist/apps/shared/mail/outbox/useOutboxStatus.js +49 -0
  248. package/dist/apps/shared/mail/useMailConnection.d.ts +9 -8
  249. package/dist/apps/shared/mail/useMailConnection.js +64 -22
  250. package/dist/apps/shared/mail/useMailLiveUpdates.d.ts +17 -4
  251. package/dist/apps/shared/mail/useMailLiveUpdates.js +36 -10
  252. package/dist/apps/shared/mail/useNewMailNotifications.d.ts +4 -10
  253. package/dist/apps/shared/mail/useNewMailNotifications.js +43 -23
  254. package/dist/apps/shared/navigation/AppRouter.js +2 -2
  255. package/dist/apps/shared/notifications/NotificationCenter.d.ts +19 -0
  256. package/dist/apps/shared/notifications/NotificationCenter.js +90 -0
  257. package/dist/apps/shared/notifications/NotificationHistoryDialog.d.ts +10 -0
  258. package/dist/apps/shared/notifications/NotificationHistoryDialog.js +36 -0
  259. package/dist/apps/shared/notifications/apiErrors.d.ts +19 -0
  260. package/dist/apps/shared/notifications/apiErrors.js +86 -0
  261. package/dist/apps/shared/notifications/headerOffset.d.ts +8 -0
  262. package/dist/apps/shared/notifications/headerOffset.js +33 -0
  263. package/dist/apps/shared/notifications/pushStatus.d.ts +9 -0
  264. package/dist/apps/shared/notifications/pushStatus.js +54 -0
  265. package/dist/apps/shared/notifications/store.d.ts +136 -0
  266. package/dist/apps/shared/notifications/store.js +416 -0
  267. package/dist/apps/shared/notifications/systemErrors.d.ts +9 -0
  268. package/dist/apps/shared/notifications/systemErrors.js +58 -0
  269. package/dist/apps/shared/notifications/useNotifications.d.ts +17 -0
  270. package/dist/apps/shared/notifications/useNotifications.js +18 -0
  271. package/dist/apps/shared/signing/enrollmentStorage.d.ts +10 -0
  272. package/dist/apps/shared/signing/enrollmentStorage.js +33 -0
  273. package/dist/apps/shared/signing/enrollmentTracker.d.ts +73 -0
  274. package/dist/apps/shared/signing/enrollmentTracker.js +307 -0
  275. package/dist/apps/shared/signing/enrollmentView.d.ts +57 -0
  276. package/dist/apps/shared/signing/enrollmentView.js +214 -0
  277. package/dist/apps/shared/signing/signingInfo.d.ts +11 -0
  278. package/dist/apps/shared/signing/signingInfo.js +48 -0
  279. package/dist/apps/shared/signing/useNow.d.ts +2 -0
  280. package/dist/apps/shared/signing/useNow.js +18 -0
  281. package/dist/apps/shared/signing/useSigningEnrollmentWatcher.d.ts +16 -0
  282. package/dist/apps/shared/signing/useSigningEnrollmentWatcher.js +87 -0
  283. package/dist/apps/shared/styles/app.css +80 -0
  284. package/dist/apps/www/_layout.d.ts +5 -1
  285. package/dist/apps/www/_layout.js +3 -2
  286. package/dist/apps/www/_routes.js +1 -0
  287. package/dist/apps/www/calendar/index.js +3 -4
  288. package/dist/apps/www/contacts/[uid].js +5 -7
  289. package/dist/apps/www/contacts/index.js +14 -21
  290. package/dist/apps/www/index.js +37 -19
  291. package/dist/apps/www/messages/[uid].js +3 -1
  292. package/dist/apps/www/settings/appearance/index.d.ts +4 -0
  293. package/dist/apps/www/settings/appearance/index.js +13 -0
  294. package/dist/apps/www/settings/auto-reply/index.js +3 -6
  295. package/dist/apps/www/settings/encryption/index.js +103 -95
  296. package/dist/apps/www/settings/filters/[uid].js +3 -1
  297. package/dist/apps/www/settings/filters/new/index.js +3 -1
  298. package/dist/apps/www/settings/privacy/index.js +5 -8
  299. package/dist/apps/www/settings/read-receipts/index.js +3 -6
  300. package/dist/apps/www/settings/sharing/index.js +19 -33
  301. package/dist/apps/www/settings/signatures/[uid].js +3 -1
  302. package/dist/apps/www/settings/signatures/new/index.js +3 -2
  303. package/dist/apps/www/tasks/index.js +8 -14
  304. package/package.json +2 -2
  305. package/apps/shared/components/mail/NewMailToasts.tsx +0 -141
  306. package/apps/shared/components/mail/compose/SendFailureAlert.tsx +0 -48
  307. package/dist/apps/shared/components/mail/NewMailToasts.d.ts +0 -19
  308. package/dist/apps/shared/components/mail/NewMailToasts.js +0 -55
  309. package/dist/apps/shared/components/mail/compose/SendFailureAlert.d.ts +0 -14
  310. package/dist/apps/shared/components/mail/compose/SendFailureAlert.js +0 -11
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The inline script the page layouts put in `<head>`, after the stylesheets and before the body, so the user's theme is in place before
3
+ * the first paint. It runs before any of this app's code and must not depend on it, which is why it is a plain ES5 string:
4
+ *
5
+ * - it reads the cache `AppearanceProvider` keeps in `localStorage` (`appearanceCache.ts`): finished stylesheet text, the mode, the key
6
+ * of the preferences they were made from and the time they were last changed (`t`);
7
+ * - it does nothing when the cache belongs to another account (`<html data-uid>` names the signed-in one, when the page knows it), or
8
+ * when the server already rendered a stylesheet (`<style data-key data-t>`) for *different* preferences that are at least as new: the
9
+ * server's copy can be a minute old, so it only wins when it is not older than the browser's;
10
+ * - otherwise it puts the cached stylesheet in the page (replacing the server's, which for the same preferences lacks the measured
11
+ * lightness of the background image), or removes the server's when the cache says nothing is chosen, and sets `<html data-theme>` for
12
+ * an explicit "Light" or "Dark" (removing it for "System", whose stylesheet follows the operating system by media query).
13
+ *
14
+ * Any failure (storage blocked, bad JSON) leaves the page as it was.
15
+ */
16
+ export declare const APPEARANCE_BOOT_SCRIPT: string;
@@ -0,0 +1,33 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import { APPEARANCE_STYLE_ID } from "./theme.js";
6
+ import { APPEARANCE_CACHE_KEY } from "./appearanceCache.js";
7
+ /**
8
+ * The inline script the page layouts put in `<head>`, after the stylesheets and before the body, so the user's theme is in place before
9
+ * the first paint. It runs before any of this app's code and must not depend on it, which is why it is a plain ES5 string:
10
+ *
11
+ * - it reads the cache `AppearanceProvider` keeps in `localStorage` (`appearanceCache.ts`): finished stylesheet text, the mode, the key
12
+ * of the preferences they were made from and the time they were last changed (`t`);
13
+ * - it does nothing when the cache belongs to another account (`<html data-uid>` names the signed-in one, when the page knows it), or
14
+ * when the server already rendered a stylesheet (`<style data-key data-t>`) for *different* preferences that are at least as new: the
15
+ * server's copy can be a minute old, so it only wins when it is not older than the browser's;
16
+ * - otherwise it puts the cached stylesheet in the page (replacing the server's, which for the same preferences lacks the measured
17
+ * lightness of the background image), or removes the server's when the cache says nothing is chosen, and sets `<html data-theme>` for
18
+ * an explicit "Light" or "Dark" (removing it for "System", whose stylesheet follows the operating system by media query).
19
+ *
20
+ * Any failure (storage blocked, bad JSON) leaves the page as it was.
21
+ */
22
+ export const APPEARANCE_BOOT_SCRIPT = "(function(){try{" +
23
+ `var c=JSON.parse(localStorage.getItem("${APPEARANCE_CACHE_KEY}")||"null"),d=document,h=d.documentElement;` +
24
+ 'if(!c||c.v!==1||typeof c.css!=="string"||typeof c.key!=="string")return;' +
25
+ 'var u=h.getAttribute("data-uid");if(u&&c.uid&&u!==c.uid)return;' +
26
+ `var s=d.getElementById("${APPEARANCE_STYLE_ID}");` +
27
+ 'if(s&&s.getAttribute("data-key")!==c.key&&Number(s.getAttribute("data-t")||0)>=(c.t||0))return;' +
28
+ "if(c.css){" +
29
+ `if(!s){s=d.createElement("style");s.id="${APPEARANCE_STYLE_ID}";d.head.appendChild(s)}` +
30
+ 's.setAttribute("data-key",c.key);s.textContent=c.css' +
31
+ "}else if(s){s.parentNode.removeChild(s)}" +
32
+ 'if(c.mode==="light"||c.mode==="dark")h.setAttribute("data-theme",c.mode);else h.removeAttribute("data-theme")' +
33
+ "}catch(e){}})();";
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The small amount of colour maths theming needs, with no dependency: parsing and formatting `#rrggbb`, mixing, WCAG relative
3
+ * luminance and contrast, and deriving a readable text colour or a whole shade scale from one chosen colour. Pure - nothing here
4
+ * touches the DOM.
5
+ */
6
+ /** A colour as 0-255 red, green and blue. */
7
+ export interface Rgb {
8
+ r: number;
9
+ g: number;
10
+ b: number;
11
+ }
12
+ export declare const BLACK: Rgb;
13
+ export declare const WHITE: Rgb;
14
+ /** WCAG AA for normal-size text. */
15
+ export declare const AA_CONTRAST = 4.5;
16
+ /** `#rrggbb` (or, forgivingly, `rrggbb`, `#rgb` and `rgb`, in any case) as a colour; `undefined` for anything else. */
17
+ export declare function parseHex(input: string): Rgb | undefined;
18
+ /** The colour as lower-case `#rrggbb` (channels are rounded and clamped). */
19
+ export declare function toHex({ r, g, b }: Rgb): string;
20
+ /** The lower-case `#rrggbb` form of a hex colour typed any of the forgiving ways `parseHex()` accepts; `undefined` if it isn't one. */
21
+ export declare function normalizeHex(input: string): string | undefined;
22
+ /** `true` for a strict `#rrggbb` (what the server stores). */
23
+ export declare function isHexColor(input: string): boolean;
24
+ /** The colour `amount` of the way from `from` to `to` (0 is `from`, 1 is `to`), mixed in sRGB. */
25
+ export declare function mix(from: Rgb, to: Rgb, amount: number): Rgb;
26
+ /** WCAG 2 relative luminance: 0 for black, 1 for white. */
27
+ export declare function relativeLuminance({ r, g, b }: Rgb): number;
28
+ /** The gray (0-255) whose relative luminance is `luminance` - the inverse of `relativeLuminance()` for a gray. */
29
+ export declare function grayOfLuminance(luminance: number): number;
30
+ /** WCAG 2 contrast ratio between two colours: 1 (identical) to 21 (black on white). Symmetric. */
31
+ export declare function contrastRatio(a: Rgb, b: Rgb): number;
32
+ /** The contrast ratio of two `#rrggbb` colours, `undefined` when either doesn't parse. */
33
+ export declare function contrastOfHex(a: string, b: string): number | undefined;
34
+ /** The app's own two text colours, for text that must sit on a colour of unknown lightness. */
35
+ export declare const DARK_TEXT: Rgb;
36
+ export declare const LIGHT_TEXT: Rgb;
37
+ /**
38
+ * The app's dark or light text colour for text on `background`: `prefer` (light by default, as on the primary colour; the accent
39
+ * colour prefers dark) whenever that reaches AA contrast, else whichever of the two reads better.
40
+ */
41
+ export declare function readableOn(background: Rgb, prefer?: Rgb): Rgb;
42
+ /**
43
+ * `foreground`, moved toward black or white - whichever the background is further from - just far enough to reach `minimum`
44
+ * contrast against `background`; unchanged when it already does. Where even black or white can't (a mid-gray background can't reach
45
+ * 7:1), the best of the two.
46
+ */
47
+ export declare function ensureContrast(foreground: Rgb, background: Rgb, minimum?: number): Rgb;
48
+ /** The shades the app's tokens come in, derived from one colour. */
49
+ export interface PrimaryScale {
50
+ base: string;
51
+ dark: string;
52
+ darker: string;
53
+ darkest: string;
54
+ light: string;
55
+ lighter: string;
56
+ lightest: string;
57
+ /** Text that reads on `base`: `preferOnBase` (white by default) when that reaches AA contrast, else the better of the app's two. */
58
+ onBase: string;
59
+ }
60
+ /**
61
+ * The seven-step scale (`base`, three darker shades, three lighter ones) plus the text colour for a button of `base`, from the single
62
+ * `#rrggbb` a user picked. Shades are mixes with black and white, so the hue stays exactly the one chosen. `undefined` if `hex` isn't
63
+ * a colour.
64
+ */
65
+ export declare function deriveScale(hex: string, preferOnBase?: Rgb): PrimaryScale | undefined;
@@ -0,0 +1,132 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ /**
6
+ * The small amount of colour maths theming needs, with no dependency: parsing and formatting `#rrggbb`, mixing, WCAG relative
7
+ * luminance and contrast, and deriving a readable text colour or a whole shade scale from one chosen colour. Pure - nothing here
8
+ * touches the DOM.
9
+ */
10
+ export const BLACK = { r: 0, g: 0, b: 0 };
11
+ export const WHITE = { r: 255, g: 255, b: 255 };
12
+ /** WCAG AA for normal-size text. */
13
+ export const AA_CONTRAST = 4.5;
14
+ const HEX_6 = /^#?([0-9a-f]{6})$/i;
15
+ const HEX_3 = /^#?([0-9a-f])([0-9a-f])([0-9a-f])$/i;
16
+ /** `#rrggbb` (or, forgivingly, `rrggbb`, `#rgb` and `rgb`, in any case) as a colour; `undefined` for anything else. */
17
+ export function parseHex(input) {
18
+ const text = input.trim();
19
+ let digits;
20
+ const long = HEX_6.exec(text);
21
+ if (long) {
22
+ digits = long[1];
23
+ }
24
+ else {
25
+ const short = HEX_3.exec(text);
26
+ if (!short) {
27
+ return undefined;
28
+ }
29
+ digits = short[1] + short[1] + short[2] + short[2] + short[3] + short[3];
30
+ }
31
+ const value = parseInt(digits, 16);
32
+ return { r: (value >> 16) & 255, g: (value >> 8) & 255, b: value & 255 };
33
+ }
34
+ function channel(value) {
35
+ return Math.min(255, Math.max(0, Math.round(value)));
36
+ }
37
+ /** The colour as lower-case `#rrggbb` (channels are rounded and clamped). */
38
+ export function toHex({ r, g, b }) {
39
+ return "#" + [r, g, b].map((value) => channel(value).toString(16).padStart(2, "0")).join("");
40
+ }
41
+ /** The lower-case `#rrggbb` form of a hex colour typed any of the forgiving ways `parseHex()` accepts; `undefined` if it isn't one. */
42
+ export function normalizeHex(input) {
43
+ const rgb = parseHex(input);
44
+ return rgb ? toHex(rgb) : undefined;
45
+ }
46
+ /** `true` for a strict `#rrggbb` (what the server stores). */
47
+ export function isHexColor(input) {
48
+ return /^#[0-9a-f]{6}$/i.test(input);
49
+ }
50
+ /** The colour `amount` of the way from `from` to `to` (0 is `from`, 1 is `to`), mixed in sRGB. */
51
+ export function mix(from, to, amount) {
52
+ const t = Math.min(1, Math.max(0, amount));
53
+ return { r: from.r + (to.r - from.r) * t, g: from.g + (to.g - from.g) * t, b: from.b + (to.b - from.b) * t };
54
+ }
55
+ function linear(value) {
56
+ const s = value / 255;
57
+ return s <= 0.04045 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
58
+ }
59
+ /** WCAG 2 relative luminance: 0 for black, 1 for white. */
60
+ export function relativeLuminance({ r, g, b }) {
61
+ return 0.2126 * linear(r) + 0.7152 * linear(g) + 0.0722 * linear(b);
62
+ }
63
+ /** The gray (0-255) whose relative luminance is `luminance` - the inverse of `relativeLuminance()` for a gray. */
64
+ export function grayOfLuminance(luminance) {
65
+ const l = Math.min(1, Math.max(0, luminance));
66
+ const s = l <= 0.0031308 ? l * 12.92 : 1.055 * Math.pow(l, 1 / 2.4) - 0.055;
67
+ return channel(s * 255);
68
+ }
69
+ /** WCAG 2 contrast ratio between two colours: 1 (identical) to 21 (black on white). Symmetric. */
70
+ export function contrastRatio(a, b) {
71
+ const la = relativeLuminance(a);
72
+ const lb = relativeLuminance(b);
73
+ return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
74
+ }
75
+ /** The contrast ratio of two `#rrggbb` colours, `undefined` when either doesn't parse. */
76
+ export function contrastOfHex(a, b) {
77
+ const first = parseHex(a);
78
+ const second = parseHex(b);
79
+ return first && second ? contrastRatio(first, second) : undefined;
80
+ }
81
+ /** The app's own two text colours, for text that must sit on a colour of unknown lightness. */
82
+ export const DARK_TEXT = { r: 0x1c, g: 0x25, b: 0x26 };
83
+ export const LIGHT_TEXT = { r: 0xff, g: 0xff, b: 0xff };
84
+ /**
85
+ * The app's dark or light text colour for text on `background`: `prefer` (light by default, as on the primary colour; the accent
86
+ * colour prefers dark) whenever that reaches AA contrast, else whichever of the two reads better.
87
+ */
88
+ export function readableOn(background, prefer = LIGHT_TEXT) {
89
+ if (contrastRatio(prefer, background) >= AA_CONTRAST) {
90
+ return prefer;
91
+ }
92
+ return contrastRatio(DARK_TEXT, background) >= contrastRatio(LIGHT_TEXT, background) ? DARK_TEXT : LIGHT_TEXT;
93
+ }
94
+ /**
95
+ * `foreground`, moved toward black or white - whichever the background is further from - just far enough to reach `minimum`
96
+ * contrast against `background`; unchanged when it already does. Where even black or white can't (a mid-gray background can't reach
97
+ * 7:1), the best of the two.
98
+ */
99
+ export function ensureContrast(foreground, background, minimum = AA_CONTRAST) {
100
+ if (contrastRatio(foreground, background) >= minimum) {
101
+ return foreground;
102
+ }
103
+ const target = contrastRatio(BLACK, background) >= contrastRatio(WHITE, background) ? BLACK : WHITE;
104
+ for (let step = 1; step <= 20; step++) {
105
+ const candidate = mix(foreground, target, step / 20);
106
+ if (contrastRatio(candidate, background) >= minimum) {
107
+ return candidate;
108
+ }
109
+ }
110
+ return target;
111
+ }
112
+ /**
113
+ * The seven-step scale (`base`, three darker shades, three lighter ones) plus the text colour for a button of `base`, from the single
114
+ * `#rrggbb` a user picked. Shades are mixes with black and white, so the hue stays exactly the one chosen. `undefined` if `hex` isn't
115
+ * a colour.
116
+ */
117
+ export function deriveScale(hex, preferOnBase = LIGHT_TEXT) {
118
+ const base = parseHex(hex);
119
+ if (!base) {
120
+ return undefined;
121
+ }
122
+ return {
123
+ base: toHex(base),
124
+ dark: toHex(mix(base, BLACK, 0.2)),
125
+ darker: toHex(mix(base, BLACK, 0.4)),
126
+ darkest: toHex(mix(base, BLACK, 0.55)),
127
+ light: toHex(mix(base, WHITE, 0.2)),
128
+ lighter: toHex(mix(base, WHITE, 0.4)),
129
+ lightest: toHex(mix(base, WHITE, 0.6)),
130
+ onBase: toHex(readableOn(base, preferOnBase)),
131
+ };
132
+ }
@@ -0,0 +1,17 @@
1
+ import type { PhotoRange } from "./theme.js";
2
+ /** The side, in pixels, of the square the picture is shrunk to for measuring. */
3
+ export declare const MEASURE_SIZE = 32;
4
+ /**
5
+ * The 5th and 95th percentile of relative luminance across RGBA pixel data, as the grays that have them - the darkest and lightest
6
+ * a panel's text may sit over. `undefined` when no pixel is opaque enough to count.
7
+ */
8
+ export declare function rangeOfPixels(rgba: ArrayLike<number>): PhotoRange | undefined;
9
+ /**
10
+ * Loads `url` and measures it. `undefined` where it can't be done - no canvas, the picture doesn't load, or it comes from another
11
+ * origin without CORS and taints the canvas - in which case the caller assumes the worst.
12
+ */
13
+ export declare function measureImage(url: string): Promise<PhotoRange | undefined>;
14
+ /** A measured range as the two gray levels the cache stores. */
15
+ export declare function rangeToLevels(range: PhotoRange): [number, number];
16
+ /** The range for two stored gray levels; `undefined` unless both are numbers from 0 to 255. */
17
+ export declare function rangeFromLevels(levels: unknown): PhotoRange | undefined;
@@ -0,0 +1,79 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ /**
6
+ * How light and how dark a background photo is, measured in the browser, so the panels over it are only as opaque as its busiest
7
+ * places demand (see `panelAlpha()`). Nothing is uploaded: the picture is drawn once at 32 x 32 pixels on a canvas and its lightest and
8
+ * darkest twentieth are taken.
9
+ */
10
+ import { grayOfLuminance, relativeLuminance } from "./color.js";
11
+ /** The side, in pixels, of the square the picture is shrunk to for measuring. */
12
+ export const MEASURE_SIZE = 32;
13
+ /** Pixels more transparent than this don't count - a transparent PNG margin is whatever is behind it, not the picture. */
14
+ const MIN_ALPHA = 128;
15
+ function gray(level) {
16
+ return { r: level, g: level, b: level };
17
+ }
18
+ /**
19
+ * The 5th and 95th percentile of relative luminance across RGBA pixel data, as the grays that have them - the darkest and lightest
20
+ * a panel's text may sit over. `undefined` when no pixel is opaque enough to count.
21
+ */
22
+ export function rangeOfPixels(rgba) {
23
+ const values = [];
24
+ for (let i = 0; i + 3 < rgba.length; i += 4) {
25
+ if (rgba[i + 3] >= MIN_ALPHA) {
26
+ values.push(relativeLuminance({ r: rgba[i], g: rgba[i + 1], b: rgba[i + 2] }));
27
+ }
28
+ }
29
+ if (values.length === 0) {
30
+ return undefined;
31
+ }
32
+ values.sort((a, b) => a - b);
33
+ const at = (fraction) => values[Math.min(values.length - 1, Math.floor(values.length * fraction))];
34
+ return { lo: gray(grayOfLuminance(at(0.05))), hi: gray(grayOfLuminance(at(0.95))) };
35
+ }
36
+ /**
37
+ * Loads `url` and measures it. `undefined` where it can't be done - no canvas, the picture doesn't load, or it comes from another
38
+ * origin without CORS and taints the canvas - in which case the caller assumes the worst.
39
+ */
40
+ export async function measureImage(url) {
41
+ if (typeof document === "undefined" || typeof Image === "undefined") {
42
+ return undefined;
43
+ }
44
+ try {
45
+ const image = new Image();
46
+ image.decoding = "async";
47
+ const loaded = new Promise((resolve, reject) => {
48
+ image.onload = () => resolve();
49
+ image.onerror = () => reject(new Error("The image did not load."));
50
+ });
51
+ image.src = url;
52
+ await loaded;
53
+ const canvas = document.createElement("canvas");
54
+ canvas.width = MEASURE_SIZE;
55
+ canvas.height = MEASURE_SIZE;
56
+ const context = canvas.getContext("2d", { willReadFrequently: true });
57
+ if (!context) {
58
+ return undefined;
59
+ }
60
+ context.drawImage(image, 0, 0, MEASURE_SIZE, MEASURE_SIZE);
61
+ return rangeOfPixels(context.getImageData(0, 0, MEASURE_SIZE, MEASURE_SIZE).data);
62
+ }
63
+ catch {
64
+ return undefined;
65
+ }
66
+ }
67
+ /** A measured range as the two gray levels the cache stores. */
68
+ export function rangeToLevels(range) {
69
+ return [range.lo.r, range.hi.r];
70
+ }
71
+ /** The range for two stored gray levels; `undefined` unless both are numbers from 0 to 255. */
72
+ export function rangeFromLevels(levels) {
73
+ if (!Array.isArray(levels) ||
74
+ levels.length !== 2 ||
75
+ !levels.every((level) => typeof level === "number" && Number.isFinite(level) && level >= 0 && level <= 255)) {
76
+ return undefined;
77
+ }
78
+ return { lo: gray(levels[0]), hi: gray(levels[1]) };
79
+ }
@@ -0,0 +1,21 @@
1
+ /** The colour scheme the app is showing right now: what the user chose, or - for "System" - what the operating system prefers. */
2
+ export type ResolvedTheme = "light" | "dark";
3
+ /** The media query that says the operating system prefers a dark scheme. */
4
+ export declare const DARK_SCHEME_QUERY = "(prefers-color-scheme: dark)";
5
+ /**
6
+ * The scheme in effect: an explicit `data-theme="light" | "dark"` on `<html>` (set by `AppearanceProvider` for every choice
7
+ * including "System", and by a page's own `<html data-theme>`) wins, else the operating system's preference, else `"light"`.
8
+ * `"light"` where there is no document (server-side rendering).
9
+ */
10
+ export declare function readResolvedTheme(): ResolvedTheme;
11
+ /**
12
+ * `"light"` or `"dark"`: the scheme the app is showing now, kept live - it changes when the user picks another in Settings >
13
+ * Appearance and, while they follow the system, when the operating system's preference flips. For anything that must match the
14
+ * app's scheme without reading CSS (a message body rendered in its own document, a canvas, a chart). Needs no provider: it reads
15
+ * `<html data-theme>` and `matchMedia` itself. Renders `"light"` on the server and for the hydrating render, then the real value.
16
+ */
17
+ export declare function useResolvedTheme(): ResolvedTheme;
18
+ export { useAppearance } from "./appearanceContext.js";
19
+ export type { AppearanceApi, AppearancePatch } from "./appearanceContext.js";
20
+ /** `true` while the operating system prefers a dark scheme - only the media query, never `<html data-theme>` (which `AppearanceProvider` sets from this). */
21
+ export declare function useSystemPrefersDark(): boolean;
@@ -0,0 +1,55 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import { useSyncExternalStore } from "react";
6
+ /** The media query that says the operating system prefers a dark scheme. */
7
+ export const DARK_SCHEME_QUERY = "(prefers-color-scheme: dark)";
8
+ /**
9
+ * The scheme in effect: an explicit `data-theme="light" | "dark"` on `<html>` (set by `AppearanceProvider` for every choice
10
+ * including "System", and by a page's own `<html data-theme>`) wins, else the operating system's preference, else `"light"`.
11
+ * `"light"` where there is no document (server-side rendering).
12
+ */
13
+ export function readResolvedTheme() {
14
+ if (typeof document === "undefined") {
15
+ return "light";
16
+ }
17
+ const override = document.documentElement.getAttribute("data-theme");
18
+ if (override === "light" || override === "dark") {
19
+ return override;
20
+ }
21
+ return typeof window.matchMedia === "function" && window.matchMedia(DARK_SCHEME_QUERY).matches ? "dark" : "light";
22
+ }
23
+ /** Calls `onChange` when the operating system's scheme or `<html data-theme>` changes; returns the unsubscribe. */
24
+ function subscribe(onChange) {
25
+ const media = typeof window.matchMedia === "function" ? window.matchMedia(DARK_SCHEME_QUERY) : undefined;
26
+ media?.addEventListener("change", onChange);
27
+ const observer = new MutationObserver(onChange);
28
+ observer.observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme"] });
29
+ return () => {
30
+ media?.removeEventListener("change", onChange);
31
+ observer.disconnect();
32
+ };
33
+ }
34
+ /**
35
+ * `"light"` or `"dark"`: the scheme the app is showing now, kept live - it changes when the user picks another in Settings >
36
+ * Appearance and, while they follow the system, when the operating system's preference flips. For anything that must match the
37
+ * app's scheme without reading CSS (a message body rendered in its own document, a canvas, a chart). Needs no provider: it reads
38
+ * `<html data-theme>` and `matchMedia` itself. Renders `"light"` on the server and for the hydrating render, then the real value.
39
+ */
40
+ export function useResolvedTheme() {
41
+ return useSyncExternalStore(subscribe, readResolvedTheme, () => "light");
42
+ }
43
+ export { useAppearance } from "./appearanceContext.js";
44
+ function subscribeSystem(onChange) {
45
+ const media = typeof window.matchMedia === "function" ? window.matchMedia(DARK_SCHEME_QUERY) : undefined;
46
+ media?.addEventListener("change", onChange);
47
+ return () => media?.removeEventListener("change", onChange);
48
+ }
49
+ function readSystemDark() {
50
+ return typeof window.matchMedia === "function" && window.matchMedia(DARK_SCHEME_QUERY).matches;
51
+ }
52
+ /** `true` while the operating system prefers a dark scheme - only the media query, never `<html data-theme>` (which `AppearanceProvider` sets from this). */
53
+ export function useSystemPrefersDark() {
54
+ return useSyncExternalStore(subscribeSystem, readSystemDark, () => false);
55
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Turns a user's `AppearancePreferences` into CSS - pure functions, no DOM (`AppearanceProvider` applies the result).
3
+ *
4
+ * **How the theme reaches the app.** The app's colours are custom properties (`--rr-color-*`, mapped to Tailwind's colours in
5
+ * `app.css`'s `@theme`), with a light set on `:root` and a dark set for `prefers-color-scheme: dark` and `[data-theme="dark"]`. A
6
+ * deployment's branding stylesheet re-points the same properties. The user's choices are emitted as one more set of those
7
+ * properties, `!important` and for both schemes (`appearanceCss()`), so precedence is: built-in defaults < the branding stylesheet <
8
+ * the user. A custom property declared `!important` on `<html>` cannot be beaten by any ordinary rule, however specific - but a
9
+ * branding stylesheet's own *rules* (say `nav { background: ... }`) are untouched and still decide their element, exactly as they
10
+ * always did; only the tokens are the user's.
11
+ *
12
+ * **Background.** With a background set, `--color-surface` and `--color-surface-alt` (what `bg-surface` and `bg-surface-alt` read)
13
+ * become the scheme's surface colour at an alpha, so the rail and header are translucent panels over the fixed background layer,
14
+ * and the content region (`#app-content`) is one such panel itself, its own `bg-surface` children transparent so panels never stack
15
+ * into an opaque slab; menus, dialogs and pop-ups reset both tokens to the opaque colour again (see `app.css`). The alpha is chosen
16
+ * so text stays at AA contrast over the photo (`panelAlpha()`).
17
+ */
18
+ import type { AppearanceBackground, AppearanceColors, AppearancePreferences } from "@rapidmx/react-shared/appearance/preferencesApi.js";
19
+ import { Rgb } from "./color.js";
20
+ import type { ResolvedTheme } from "./resolvedTheme.js";
21
+ /** What each scheme's tokens are without any customisation - `app.css`'s own values. */
22
+ export interface SchemePalette {
23
+ surface: string;
24
+ text: string;
25
+ muted: string;
26
+ }
27
+ export declare const DEFAULT_PALETTE: Record<ResolvedTheme, SchemePalette>;
28
+ /** The colours in effect for a scheme: the user's surface and text where chosen, else the defaults. */
29
+ export interface EffectivePalette {
30
+ surface: Rgb;
31
+ text: Rgb;
32
+ muted: Rgb;
33
+ /** The user chose a surface. */
34
+ surfaceChosen: boolean;
35
+ /** The user chose a text colour, or the default one could not be read on their surface and was replaced. */
36
+ textAdjusted: boolean;
37
+ }
38
+ export declare function effectivePalette(colors: AppearanceColors | undefined, scheme: ResolvedTheme): EffectivePalette;
39
+ /**
40
+ * The custom properties a user's colours set for one scheme - only those they changed (and what derives from them): the primary
41
+ * colour's seven-step scale and the text on it, the accent and its shades, and the surface / text family. Empty when they chose
42
+ * no colours.
43
+ */
44
+ export declare function colorVariables(colors: AppearanceColors | undefined, scheme: ResolvedTheme): Record<string, string>;
45
+ /** The lightest and darkest a background can be behind a panel, as colours. */
46
+ export interface PhotoRange {
47
+ lo: Rgb;
48
+ hi: Rgb;
49
+ }
50
+ /** What is assumed of a photo nobody has measured: black in one corner and white in another. */
51
+ export declare const WORST_CASE_PHOTO: PhotoRange;
52
+ /** The panels are never more see-through than this ... */
53
+ export declare const PANEL_ALPHA_MIN = 0.5;
54
+ /** ... nor less than this (still a hint of the picture). */
55
+ export declare const PANEL_ALPHA_MAX = 0.96;
56
+ /**
57
+ * The opacity (0.5 to 0.96) of the surface panels over a background, the lowest for which text and muted text keep AA contrast
58
+ * against the worst place the picture can be. What is behind a panel is the picture under the dim (`dim` of the surface colour laid
59
+ * over it); the panel is `alpha` of the surface over that, so the picture's share of what the text sits on is `(1 - alpha) *
60
+ * (1 - dim)`. A wide-ranging (busy) photo therefore gets more opaque panels than a plain one, and more dim allows less opaque
61
+ * panels.
62
+ */
63
+ export declare function panelAlpha(palette: Pick<EffectivePalette, "surface" | "text" | "muted">, dim: number, photo: PhotoRange): number;
64
+ /** What a background shows behind the panels - `undefined` for none: a colour, or an image of unknown or measured lightness. */
65
+ export declare function backgroundPhoto(background: AppearanceBackground | undefined, measured?: PhotoRange): PhotoRange | undefined;
66
+ /** The dim actually laid over the background: a colour has no picture to dim. */
67
+ export declare function effectiveDim(background: AppearanceBackground): number;
68
+ /**
69
+ * The custom properties for one scheme: the user's colours, plus - when a background is set - the translucent panel colours and the
70
+ * transparent frame. `measured` is the image's measured lightness range, when known (`photo.ts`).
71
+ */
72
+ export declare function themeVariables(prefs: AppearancePreferences, scheme: ResolvedTheme, measured?: PhotoRange): Record<string, string>;
73
+ /** The element `appearanceCss()` writes into `<head>`, so an update replaces rather than duplicates it. */
74
+ export declare const APPEARANCE_STYLE_ID = "rr-appearance";
75
+ /** A string as a CSS `url()` argument: quoted, with what could end the string or the declaration escaped. */
76
+ export declare function cssUrl(url: string): string;
77
+ /**
78
+ * The rules for the background layer, which is two pseudo-elements of `<html>` rather than any markup - so it exists from the
79
+ * first byte of a server-rendered page (no flash), follows a live preview by rewriting one stylesheet, and needs no component.
80
+ * `::before` is the picture (or colour): `position: fixed`, behind everything (`z-index: -1` puts it above the page's own canvas
81
+ * colour and below every in-flow box), its box grown by twice the blur so the blurred edge falls outside the window. `::after` is the
82
+ * dim: the scheme's surface colour at the chosen opacity. Neither takes a click, and neither prints.
83
+ */
84
+ export declare function backgroundCss(background: AppearanceBackground | undefined, imageUrl?: string): string;
85
+ export interface AppearanceCssOptions {
86
+ /** The image's measured lightness range, when known (`photo.ts`); an unmeasured photo is assumed the worst case. */
87
+ measured?: PhotoRange;
88
+ /** Where the background image comes from - the server's URL for the stored version, or a `blob:` URL while an upload is in flight. */
89
+ imageUrl?: string;
90
+ }
91
+ /**
92
+ * The stylesheet text for the user's preferences, or `""` when they change nothing. Three rules per set of properties so each
93
+ * scheme is right in every case: the light set for everything except an explicit dark (`data-theme="dark"`) - which also covers
94
+ * a page with no `data-theme` at all; the dark set for a dark operating system on a page with no `data-theme` yet (before the
95
+ * provider ran, or a server-rendered page following "System"); and the dark set for an explicit `data-theme="dark"`. All three are
96
+ * `html:root...` (0,2,1) so the later dark rules win the equal-specificity tie against the light one. Then the background layer
97
+ * (`backgroundCss()`).
98
+ */
99
+ export declare function appearanceCss(prefs: AppearancePreferences | undefined, options?: AppearanceCssOptions): string;
100
+ /** A pair of colours that is hard to read, as `ContrastWarning`s describe them. */
101
+ export interface ContrastWarning {
102
+ id: "text-surface" | "accent-text";
103
+ ratio: number;
104
+ message: string;
105
+ }
106
+ /**
107
+ * The colour pairs the user's choices make hard to read (contrast below 4.5:1, WCAG AA for normal text) in `scheme`: the text on the
108
+ * surface, and the text on an accent-coloured button. A warning, never a block - it is their app. Nothing for a pair they haven't
109
+ * touched (the app's own pairs are the app's business).
110
+ */
111
+ export declare function contrastWarnings(colors: AppearanceColors | undefined, scheme: ResolvedTheme): ContrastWarning[];