@eifi1/ui-kit 0.53.0 → 0.55.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 (902) hide show
  1. package/README.md +96 -82
  2. package/dist/account/delete-account-setting.d.ts +3 -2
  3. package/dist/account/delete-account-setting.js.map +1 -1
  4. package/dist/admin/plan-change.js +2 -2
  5. package/dist/admin/plan-change.js.map +1 -1
  6. package/dist/admin/user-roster.d.ts +6 -0
  7. package/dist/admin/user-roster.js +4 -0
  8. package/dist/admin/user-roster.js.map +1 -1
  9. package/dist/auth/auth-errors.d.ts +36 -0
  10. package/dist/auth/auth-errors.js +20 -0
  11. package/dist/auth/auth-errors.js.map +1 -1
  12. package/dist/billing/billing-labels.d.ts +26 -0
  13. package/dist/billing/billing-labels.js +9 -0
  14. package/dist/billing/billing-labels.js.map +1 -1
  15. package/dist/billing/subscription-standing.d.ts +61 -0
  16. package/dist/billing/subscription-standing.js +51 -0
  17. package/dist/billing/subscription-standing.js.map +1 -0
  18. package/dist/billing/usage-meter.d.ts +52 -0
  19. package/dist/billing/usage-meter.js +52 -0
  20. package/dist/billing/usage-meter.js.map +1 -0
  21. package/dist/chart.d.ts +1 -0
  22. package/dist/chart.js +4 -0
  23. package/dist/chart.js.map +1 -1
  24. package/dist/components/alert-banner.js +2 -3
  25. package/dist/components/alert-banner.js.map +1 -1
  26. package/dist/components/amount-input.d.ts +2 -1
  27. package/dist/components/amount-input.js +3 -15
  28. package/dist/components/amount-input.js.map +1 -1
  29. package/dist/components/autocomplete.js +2 -2
  30. package/dist/components/autocomplete.js.map +1 -1
  31. package/dist/components/bulk-action-bar.js +2 -1
  32. package/dist/components/bulk-action-bar.js.map +1 -1
  33. package/dist/components/button.d.ts +35 -1
  34. package/dist/components/button.js +5 -9
  35. package/dist/components/button.js.map +1 -1
  36. package/dist/components/calendar-heatmap.js +6 -4
  37. package/dist/components/calendar-heatmap.js.map +1 -1
  38. package/dist/components/camera-button.d.ts +89 -0
  39. package/dist/components/camera-button.js +122 -0
  40. package/dist/components/camera-button.js.map +1 -0
  41. package/dist/components/camera-capture.d.ts +66 -0
  42. package/dist/components/camera-capture.js +190 -0
  43. package/dist/components/camera-capture.js.map +1 -0
  44. package/dist/components/camera-labels.d.ts +20 -0
  45. package/dist/components/camera-labels.js +15 -0
  46. package/dist/components/camera-labels.js.map +1 -0
  47. package/dist/components/chart-edge.d.ts +9 -0
  48. package/dist/components/chart-edge.js +14 -3
  49. package/dist/components/chart-edge.js.map +1 -1
  50. package/dist/components/chart-zoom-core.js +34 -29
  51. package/dist/components/chart-zoom-core.js.map +1 -1
  52. package/dist/components/checkbox-group.js +1 -1
  53. package/dist/components/checkbox-group.js.map +1 -1
  54. package/dist/components/combobox-core.js +3 -2
  55. package/dist/components/combobox-core.js.map +1 -1
  56. package/dist/components/combobox.js +2 -2
  57. package/dist/components/combobox.js.map +1 -1
  58. package/dist/components/consent-again-notice-labels.d.ts +25 -0
  59. package/dist/components/consent-again-notice-labels.js +15 -0
  60. package/dist/components/consent-again-notice-labels.js.map +1 -0
  61. package/dist/components/consent-again-notice.d.ts +85 -0
  62. package/dist/components/consent-again-notice.js +94 -0
  63. package/dist/components/consent-again-notice.js.map +1 -0
  64. package/dist/components/country-select.js +2 -7
  65. package/dist/components/country-select.js.map +1 -1
  66. package/dist/components/currency-option-list.d.ts +2 -1
  67. package/dist/components/currency-option-list.js +10 -3
  68. package/dist/components/currency-option-list.js.map +1 -1
  69. package/dist/components/currency-select.d.ts +3 -1
  70. package/dist/components/currency-select.js +2 -2
  71. package/dist/components/currency-select.js.map +1 -1
  72. package/dist/components/danger-confirm.d.ts +22 -1
  73. package/dist/components/danger-confirm.js +52 -30
  74. package/dist/components/danger-confirm.js.map +1 -1
  75. package/dist/components/data-table-column-settings.js +2 -2
  76. package/dist/components/data-table-column-settings.js.map +1 -1
  77. package/dist/components/data-table-filter-popover.d.ts +2 -1
  78. package/dist/components/data-table-filter-popover.js +3 -2
  79. package/dist/components/data-table-filter-popover.js.map +1 -1
  80. package/dist/components/data-table-filters.d.ts +33 -3
  81. package/dist/components/data-table-filters.js +6 -4
  82. package/dist/components/data-table-filters.js.map +1 -1
  83. package/dist/components/data-table-fit.js.map +1 -1
  84. package/dist/components/data-table-virtual.js +13 -3
  85. package/dist/components/data-table-virtual.js.map +1 -1
  86. package/dist/components/date-field.js +2 -2
  87. package/dist/components/date-field.js.map +1 -1
  88. package/dist/components/date-picker.js +2 -2
  89. package/dist/components/date-picker.js.map +1 -1
  90. package/dist/components/date-range-picker.js +2 -6
  91. package/dist/components/date-range-picker.js.map +1 -1
  92. package/dist/components/dialog-frame.d.ts +5 -1
  93. package/dist/components/dialog-frame.js +6 -2
  94. package/dist/components/dialog-frame.js.map +1 -1
  95. package/dist/components/dropdown.js +7 -3
  96. package/dist/components/dropdown.js.map +1 -1
  97. package/dist/components/entity-combobox.js +2 -2
  98. package/dist/components/entity-combobox.js.map +1 -1
  99. package/dist/components/field-parts.d.ts +108 -6
  100. package/dist/components/field-parts.js +59 -6
  101. package/dist/components/field-parts.js.map +1 -1
  102. package/dist/components/field-suggestion-labels.d.ts +2 -0
  103. package/dist/components/field-suggestion-labels.js +10 -0
  104. package/dist/components/field-suggestion-labels.js.map +1 -0
  105. package/dist/components/field.js +1 -1
  106. package/dist/components/field.js.map +1 -1
  107. package/dist/components/file-button.d.ts +1 -1
  108. package/dist/components/file-button.js.map +1 -1
  109. package/dist/components/floating-panel.d.ts +7 -6
  110. package/dist/components/floating-panel.js +3 -17
  111. package/dist/components/floating-panel.js.map +1 -1
  112. package/dist/components/form-actions.d.ts +3 -2
  113. package/dist/components/form-actions.js.map +1 -1
  114. package/dist/components/hover-menu.js +3 -2
  115. package/dist/components/hover-menu.js.map +1 -1
  116. package/dist/components/icon-button.d.ts +23 -5
  117. package/dist/components/icon-button.js +6 -8
  118. package/dist/components/icon-button.js.map +1 -1
  119. package/dist/components/inline-confirm.js +2 -1
  120. package/dist/components/inline-confirm.js.map +1 -1
  121. package/dist/components/inline-entity-combobox.js +2 -2
  122. package/dist/components/inline-entity-combobox.js.map +1 -1
  123. package/dist/components/input.js +2 -2
  124. package/dist/components/input.js.map +1 -1
  125. package/dist/components/jump-calendar.js +3 -3
  126. package/dist/components/jump-calendar.js.map +1 -1
  127. package/dist/components/legal.d.ts +13 -1
  128. package/dist/components/legal.js +12 -0
  129. package/dist/components/legal.js.map +1 -1
  130. package/dist/components/legended-series-chart.d.ts +80 -0
  131. package/dist/components/legended-series-chart.js +70 -0
  132. package/dist/components/legended-series-chart.js.map +1 -0
  133. package/dist/components/lightbox.js +2 -1
  134. package/dist/components/lightbox.js.map +1 -1
  135. package/dist/components/mini-calendar.js +16 -7
  136. package/dist/components/mini-calendar.js.map +1 -1
  137. package/dist/components/modal.d.ts +48 -0
  138. package/dist/components/modal.js +26 -6
  139. package/dist/components/modal.js.map +1 -1
  140. package/dist/components/month-picker.d.ts +1 -1
  141. package/dist/components/month-picker.js +6 -6
  142. package/dist/components/month-picker.js.map +1 -1
  143. package/dist/components/multi-entity-combobox.js +2 -2
  144. package/dist/components/multi-entity-combobox.js.map +1 -1
  145. package/dist/components/multi-select.js +2 -6
  146. package/dist/components/multi-select.js.map +1 -1
  147. package/dist/components/number-input.js +2 -2
  148. package/dist/components/number-input.js.map +1 -1
  149. package/dist/components/page-header-layout.d.ts +18 -0
  150. package/dist/components/page-header-layout.js +23 -0
  151. package/dist/components/page-header-layout.js.map +1 -0
  152. package/dist/components/page-header-skeleton.d.ts +41 -0
  153. package/dist/components/page-header-skeleton.js +52 -0
  154. package/dist/components/page-header-skeleton.js.map +1 -0
  155. package/dist/components/page-header.js +4 -17
  156. package/dist/components/page-header.js.map +1 -1
  157. package/dist/components/popover.js +7 -2
  158. package/dist/components/popover.js.map +1 -1
  159. package/dist/components/release-notes-labels.d.ts +16 -0
  160. package/dist/components/release-notes-labels.js +18 -0
  161. package/dist/components/release-notes-labels.js.map +1 -0
  162. package/dist/components/release-notes.d.ts +65 -0
  163. package/dist/components/release-notes.js +65 -0
  164. package/dist/components/release-notes.js.map +1 -0
  165. package/dist/components/select.js +2 -2
  166. package/dist/components/select.js.map +1 -1
  167. package/dist/components/series-chart-labels.d.ts +9 -0
  168. package/dist/components/series-chart-labels.js +3 -1
  169. package/dist/components/series-chart-labels.js.map +1 -1
  170. package/dist/components/series-chart.d.ts +5 -0
  171. package/dist/components/series-chart.js +89 -36
  172. package/dist/components/series-chart.js.map +1 -1
  173. package/dist/components/stat-tile.d.ts +21 -2
  174. package/dist/components/stat-tile.js +4 -2
  175. package/dist/components/stat-tile.js.map +1 -1
  176. package/dist/components/toggle-group.js +3 -2
  177. package/dist/components/toggle-group.js.map +1 -1
  178. package/dist/components/treemap-layout.js.map +1 -1
  179. package/dist/components/treemap.d.ts +17 -15
  180. package/dist/components/treemap.js +167 -85
  181. package/dist/components/treemap.js.map +1 -1
  182. package/dist/currencies.d.ts +16 -2
  183. package/dist/currencies.js +2 -1
  184. package/dist/currencies.js.map +1 -1
  185. package/dist/eslint/index.js +1 -1
  186. package/dist/eslint/role-utilities.d.ts +2 -2
  187. package/dist/eslint/role-utilities.js.map +1 -1
  188. package/dist/feedback/feedback-context-labels.js +2 -1
  189. package/dist/feedback/feedback-context-labels.js.map +1 -1
  190. package/dist/feedback/feedback-context.d.ts +19 -3
  191. package/dist/feedback/feedback-context.js +12 -1
  192. package/dist/feedback/feedback-context.js.map +1 -1
  193. package/dist/feedback/feedback-diagnostics.d.ts +17 -0
  194. package/dist/feedback/feedback-diagnostics.js +27 -0
  195. package/dist/feedback/feedback-diagnostics.js.map +1 -0
  196. package/dist/feedback/feedback-dialog.d.ts +10 -0
  197. package/dist/feedback/feedback-dialog.js +6 -4
  198. package/dist/feedback/feedback-dialog.js.map +1 -1
  199. package/dist/feedback/feedback-inbox-page.d.ts +260 -0
  200. package/dist/feedback/feedback-inbox-page.js +207 -0
  201. package/dist/feedback/feedback-inbox-page.js.map +1 -0
  202. package/dist/feedback/feedback-record.d.ts +5 -0
  203. package/dist/feedback/feedback-record.js.map +1 -1
  204. package/dist/feedback/feedback-row-detail-labels.js +1 -0
  205. package/dist/feedback/feedback-row-detail-labels.js.map +1 -1
  206. package/dist/feedback/feedback-row-detail.d.ts +3 -0
  207. package/dist/feedback/feedback-row-detail.js +3 -0
  208. package/dist/feedback/feedback-row-detail.js.map +1 -1
  209. package/dist/feedback/feedback-submit-dialog.d.ts +5 -1
  210. package/dist/feedback/feedback-submit-dialog.js +15 -3
  211. package/dist/feedback/feedback-submit-dialog.js.map +1 -1
  212. package/dist/feedback/feedback-submit.d.ts +30 -2
  213. package/dist/feedback/feedback-submit.js +5 -2
  214. package/dist/feedback/feedback-submit.js.map +1 -1
  215. package/dist/feedback/feedback-vocabulary.js +2 -2
  216. package/dist/feedback/feedback-vocabulary.js.map +1 -1
  217. package/dist/feedback.d.ts +1 -0
  218. package/dist/feedback.js +6 -0
  219. package/dist/feedback.js.map +1 -1
  220. package/dist/hooks/use-debounce.d.ts +32 -5
  221. package/dist/hooks/use-debounce.js +6 -5
  222. package/dist/hooks/use-debounce.js.map +1 -1
  223. package/dist/hooks/use-file-picker.d.ts +3 -1
  224. package/dist/hooks/use-file-picker.js.map +1 -1
  225. package/dist/hooks/use-focus-trap.d.ts +19 -3
  226. package/dist/hooks/use-focus-trap.js +15 -5
  227. package/dist/hooks/use-focus-trap.js.map +1 -1
  228. package/dist/hooks/use-has-camera.d.ts +36 -0
  229. package/dist/hooks/use-has-camera.js +53 -0
  230. package/dist/hooks/use-has-camera.js.map +1 -0
  231. package/dist/hooks/use-hotkey.js +2 -1
  232. package/dist/hooks/use-hotkey.js.map +1 -1
  233. package/dist/hooks/use-overlay-history.d.ts +6 -1
  234. package/dist/hooks/use-overlay-history.js +10 -2
  235. package/dist/hooks/use-overlay-history.js.map +1 -1
  236. package/dist/i18n/defaults.js +11 -1
  237. package/dist/i18n/defaults.js.map +1 -1
  238. package/dist/i18n/german.js +78 -4
  239. package/dist/i18n/german.js.map +1 -1
  240. package/dist/i18n/kit-labels.d.ts +20 -0
  241. package/dist/i18n/kit-labels.js.map +1 -1
  242. package/dist/i18n/locales/en.js +95 -5
  243. package/dist/i18n/locales/en.js.map +3 -3
  244. package/dist/i18n/locales/es.js +74 -4
  245. package/dist/i18n/locales/es.js.map +1 -1
  246. package/dist/i18n/locales/fr.js +75 -3
  247. package/dist/i18n/locales/fr.js.map +1 -1
  248. package/dist/i18n/locales/hu.js +77 -4
  249. package/dist/i18n/locales/hu.js.map +1 -1
  250. package/dist/i18n/locales/it.js +77 -4
  251. package/dist/i18n/locales/it.js.map +1 -1
  252. package/dist/i18n/locales/zh.js +74 -4
  253. package/dist/i18n/locales/zh.js.map +1 -1
  254. package/dist/i18n/review.js +22 -1
  255. package/dist/i18n/review.js.map +1 -1
  256. package/dist/index.d.ts +27 -8
  257. package/dist/index.js +41 -2
  258. package/dist/index.js.map +1 -1
  259. package/dist/landing/landing-labels.d.ts +8 -0
  260. package/dist/landing/landing-labels.js +2 -1
  261. package/dist/landing/landing-labels.js.map +1 -1
  262. package/dist/lib/changelog.d.ts +130 -0
  263. package/dist/lib/changelog.js +137 -0
  264. package/dist/lib/changelog.js.map +1 -0
  265. package/dist/lib/clipboard-minus.js +2 -2
  266. package/dist/lib/clipboard-minus.js.map +1 -1
  267. package/dist/lib/dates.d.ts +36 -4
  268. package/dist/lib/dates.js +49 -13
  269. package/dist/lib/dates.js.map +1 -1
  270. package/dist/lib/editable-content.d.ts +33 -0
  271. package/dist/lib/editable-content.js +15 -0
  272. package/dist/lib/editable-content.js.map +1 -0
  273. package/dist/lib/focus-left.d.ts +47 -1
  274. package/dist/lib/focus-left.js +11 -2
  275. package/dist/lib/focus-left.js.map +1 -1
  276. package/dist/lib/format.d.ts +37 -2
  277. package/dist/lib/format.js +25 -1
  278. package/dist/lib/format.js.map +1 -1
  279. package/dist/lib/intl-cache.d.ts +4 -2
  280. package/dist/lib/intl-cache.js.map +1 -1
  281. package/dist/lib/server-wake.d.ts +49 -9
  282. package/dist/lib/server-wake.js +25 -14
  283. package/dist/lib/server-wake.js.map +1 -1
  284. package/dist/lib/session-refresh.d.ts +53 -0
  285. package/dist/lib/session-refresh.js +27 -1
  286. package/dist/lib/session-refresh.js.map +1 -1
  287. package/dist/lib/translation-review.d.ts +26 -0
  288. package/dist/lib/translation-review.js +14 -0
  289. package/dist/lib/translation-review.js.map +1 -1
  290. package/dist/rhf/fields.js +3 -3
  291. package/dist/rhf/fields.js.map +1 -1
  292. package/dist/shell/app-shell.d.ts +25 -1
  293. package/dist/shell/app-shell.js +3 -1
  294. package/dist/shell/app-shell.js.map +1 -1
  295. package/dist/shell/dev-menu.d.ts +3 -0
  296. package/dist/shell/dev-menu.js +4 -2
  297. package/dist/shell/dev-menu.js.map +1 -1
  298. package/dist/shell/held-changes-banner-labels.d.ts +23 -0
  299. package/dist/shell/held-changes-banner-labels.js +14 -0
  300. package/dist/shell/held-changes-banner-labels.js.map +1 -0
  301. package/dist/shell/held-changes-banner.d.ts +75 -0
  302. package/dist/shell/held-changes-banner.js +106 -0
  303. package/dist/shell/held-changes-banner.js.map +1 -0
  304. package/dist/shell/sync-status-indicator.d.ts +27 -12
  305. package/dist/shell/sync-status-indicator.js +13 -4
  306. package/dist/shell/sync-status-indicator.js.map +1 -1
  307. package/dist/shell/top-bar-parts-labels.d.ts +5 -0
  308. package/dist/shell/top-bar-parts-labels.js +3 -1
  309. package/dist/shell/top-bar-parts-labels.js.map +1 -1
  310. package/dist/shell.d.ts +2 -0
  311. package/dist/shell.js +3 -0
  312. package/dist/shell.js.map +1 -1
  313. package/dist/sync/engine.d.ts +11 -2
  314. package/dist/sync/engine.js +13 -5
  315. package/dist/sync/engine.js.map +1 -1
  316. package/dist/sync/hooks.d.ts +3 -2
  317. package/dist/sync/hooks.js +4 -2
  318. package/dist/sync/hooks.js.map +1 -1
  319. package/dist/testing/css-variables.generated.d.ts +2 -2
  320. package/dist/testing/css-variables.generated.js +1 -1
  321. package/dist/testing/css-variables.generated.js.map +1 -1
  322. package/dist/theme/chart-palette.d.ts +18 -0
  323. package/dist/theme/chart-palette.js +17 -1
  324. package/dist/theme/chart-palette.js.map +1 -1
  325. package/dist/theme/contrast-tokens.d.ts +3 -3
  326. package/dist/theme/contrast-tokens.js.map +1 -1
  327. package/dist/theme/contrast.d.ts +4 -4
  328. package/dist/theme/contrast.js.map +1 -1
  329. package/dist/tour/tour-overlay.js +2 -1
  330. package/dist/tour/tour-overlay.js.map +1 -1
  331. package/package.json +5 -9
  332. package/tokens.css +19 -54
  333. package/src/account/account-parts.ts +0 -47
  334. package/src/account/account-settings-labels.ts +0 -240
  335. package/src/account/account-settings.tsx +0 -698
  336. package/src/account/data-export-setting-labels.ts +0 -20
  337. package/src/account/data-export-setting.tsx +0 -200
  338. package/src/account/delete-account-setting-labels.ts +0 -33
  339. package/src/account/delete-account-setting.tsx +0 -341
  340. package/src/account/email-change-setting-labels.ts +0 -31
  341. package/src/account/email-change-setting.tsx +0 -443
  342. package/src/account/passkeys-setting.tsx +0 -445
  343. package/src/account/sessions-setting-labels.ts +0 -24
  344. package/src/account/sessions-setting.tsx +0 -283
  345. package/src/admin/admin-action-confirm-labels.ts +0 -19
  346. package/src/admin/admin-action-confirm.tsx +0 -509
  347. package/src/admin/admin-action-log-labels.ts +0 -83
  348. package/src/admin/admin-action-log.tsx +0 -464
  349. package/src/admin/admin-area-labels.ts +0 -114
  350. package/src/admin/admin-forbidden.tsx +0 -75
  351. package/src/admin/admin-overview.tsx +0 -258
  352. package/src/admin/admin-parts.ts +0 -68
  353. package/src/admin/admin-sections.tsx +0 -208
  354. package/src/admin/admin-tool-links.tsx +0 -90
  355. package/src/admin/invitations-panel-labels.ts +0 -31
  356. package/src/admin/invitations-panel.tsx +0 -516
  357. package/src/admin/mail-test-card-labels.ts +0 -19
  358. package/src/admin/mail-test-card.tsx +0 -189
  359. package/src/admin/plan-change-labels.ts +0 -5
  360. package/src/admin/plan-change.tsx +0 -474
  361. package/src/admin/reviewer-scope-editor-labels.ts +0 -16
  362. package/src/admin/reviewer-scope-editor.tsx +0 -224
  363. package/src/admin/role-select-labels.ts +0 -16
  364. package/src/admin/role-select.tsx +0 -282
  365. package/src/admin/transfer-ownership-dialog-labels.ts +0 -20
  366. package/src/admin/transfer-ownership-dialog.tsx +0 -162
  367. package/src/admin/user-roster-labels.ts +0 -17
  368. package/src/admin/user-roster.tsx +0 -559
  369. package/src/auth/accept-invitation-labels.ts +0 -21
  370. package/src/auth/accept-invitation.tsx +0 -210
  371. package/src/auth/auth-errors-labels.ts +0 -23
  372. package/src/auth/auth-errors.ts +0 -370
  373. package/src/auth/complete-name-dialog-labels.ts +0 -16
  374. package/src/auth/complete-name-dialog.tsx +0 -253
  375. package/src/auth/email-tag.ts +0 -75
  376. package/src/auth/forgot-password-form-labels.ts +0 -20
  377. package/src/auth/forgot-password-form.tsx +0 -221
  378. package/src/auth/form-rules.ts +0 -40
  379. package/src/auth/new-password-fields.tsx +0 -85
  380. package/src/auth/not-found-page-labels.ts +0 -12
  381. package/src/auth/not-found-page.tsx +0 -95
  382. package/src/auth/register-form-labels.ts +0 -31
  383. package/src/auth/register-form.tsx +0 -456
  384. package/src/auth/reset-password-form-labels.ts +0 -27
  385. package/src/auth/reset-password-form.tsx +0 -341
  386. package/src/auth/sign-in-form-labels.ts +0 -44
  387. package/src/auth/sign-in-form.tsx +0 -858
  388. package/src/auth/status-parts.tsx +0 -132
  389. package/src/auth/verify-email-labels.ts +0 -26
  390. package/src/auth/verify-email.tsx +0 -445
  391. package/src/billing/billing-banner.tsx +0 -258
  392. package/src/billing/billing-labels.ts +0 -381
  393. package/src/billing/billing-lock.ts +0 -108
  394. package/src/billing/billing-overview.ts +0 -157
  395. package/src/billing/billing-standing.ts +0 -80
  396. package/src/billing/cancel-request.ts +0 -97
  397. package/src/billing/cancel-subscription.tsx +0 -98
  398. package/src/billing/checkout-processing.ts +0 -394
  399. package/src/billing/checkout-return.ts +0 -90
  400. package/src/billing/pay-page.ts +0 -330
  401. package/src/billing/plan-card.tsx +0 -562
  402. package/src/billing/plan-change-labels.ts +0 -66
  403. package/src/billing/plan-limit-notice.tsx +0 -148
  404. package/src/billing/plan-limit-toast.tsx +0 -90
  405. package/src/billing/plan-limit.ts +0 -52
  406. package/src/billing/plan-price.ts +0 -98
  407. package/src/billing/plan-switch.tsx +0 -216
  408. package/src/billing/provider-page.ts +0 -84
  409. package/src/billing/subscription-actions.tsx +0 -247
  410. package/src/billing/subscription-status-chip.tsx +0 -95
  411. package/src/chart.ts +0 -103
  412. package/src/components/account-chips-labels.ts +0 -16
  413. package/src/components/account-chips.tsx +0 -474
  414. package/src/components/alert-banner.tsx +0 -670
  415. package/src/components/amount-input.tsx +0 -815
  416. package/src/components/appearance-labels.ts +0 -63
  417. package/src/components/authed-image-labels.ts +0 -11
  418. package/src/components/authed-image.tsx +0 -209
  419. package/src/components/autocomplete.tsx +0 -645
  420. package/src/components/breadcrumbs-labels.ts +0 -10
  421. package/src/components/breadcrumbs.tsx +0 -205
  422. package/src/components/bulk-action-bar-labels.ts +0 -11
  423. package/src/components/bulk-action-bar.tsx +0 -443
  424. package/src/components/button-group.tsx +0 -223
  425. package/src/components/button-look.ts +0 -256
  426. package/src/components/button.tsx +0 -339
  427. package/src/components/calculator.tsx +0 -341
  428. package/src/components/calendar-heatmap-labels.ts +0 -15
  429. package/src/components/calendar-heatmap.tsx +0 -594
  430. package/src/components/card.tsx +0 -377
  431. package/src/components/chart-edge.ts +0 -106
  432. package/src/components/chart-figure.tsx +0 -35
  433. package/src/components/chart-height.ts +0 -30
  434. package/src/components/chart-zoom-core.tsx +0 -627
  435. package/src/components/chart-zoom.tsx +0 -22
  436. package/src/components/chart.tsx +0 -554
  437. package/src/components/checkbox-group.tsx +0 -247
  438. package/src/components/checkbox.tsx +0 -346
  439. package/src/components/chip-input.tsx +0 -370
  440. package/src/components/chip-labels.ts +0 -13
  441. package/src/components/chip.tsx +0 -843
  442. package/src/components/choice-card.tsx +0 -544
  443. package/src/components/column-mapper-labels.ts +0 -38
  444. package/src/components/column-mapper.tsx +0 -655
  445. package/src/components/combobox-core.tsx +0 -1321
  446. package/src/components/combobox-suggestions.tsx +0 -131
  447. package/src/components/combobox.tsx +0 -716
  448. package/src/components/confirm-context.ts +0 -7
  449. package/src/components/confirm-dialog-labels.ts +0 -13
  450. package/src/components/confirm-dialog.tsx +0 -341
  451. package/src/components/copy-button-labels.ts +0 -13
  452. package/src/components/copy-button.tsx +0 -192
  453. package/src/components/country-select-labels.ts +0 -11
  454. package/src/components/country-select.tsx +0 -539
  455. package/src/components/currency-option-list.tsx +0 -180
  456. package/src/components/currency-select.tsx +0 -341
  457. package/src/components/danger-confirm-labels.ts +0 -20
  458. package/src/components/danger-confirm.tsx +0 -934
  459. package/src/components/data-table-cells.tsx +0 -118
  460. package/src/components/data-table-column-settings.tsx +0 -198
  461. package/src/components/data-table-filter-popover.tsx +0 -281
  462. package/src/components/data-table-filters.ts +0 -287
  463. package/src/components/data-table-fit.ts +0 -301
  464. package/src/components/data-table-groups.tsx +0 -288
  465. package/src/components/data-table-head.tsx +0 -280
  466. package/src/components/data-table-labels.ts +0 -198
  467. package/src/components/data-table-leading-row.tsx +0 -58
  468. package/src/components/data-table-mobile.tsx +0 -1117
  469. package/src/components/data-table-pagination.tsx +0 -202
  470. package/src/components/data-table-resize.ts +0 -188
  471. package/src/components/data-table-row-actions.tsx +0 -302
  472. package/src/components/data-table-row.tsx +0 -496
  473. package/src/components/data-table-selection.ts +0 -71
  474. package/src/components/data-table-shared.tsx +0 -287
  475. package/src/components/data-table-sort.ts +0 -163
  476. package/src/components/data-table-virtual.ts +0 -576
  477. package/src/components/data-table.tsx +0 -2598
  478. package/src/components/date-field.tsx +0 -684
  479. package/src/components/date-format-setting.tsx +0 -64
  480. package/src/components/date-picker.tsx +0 -404
  481. package/src/components/date-range-picker.tsx +0 -518
  482. package/src/components/description-list-labels.ts +0 -9
  483. package/src/components/description-list.tsx +0 -386
  484. package/src/components/dialog-frame-labels.ts +0 -7
  485. package/src/components/dialog-frame.tsx +0 -276
  486. package/src/components/disclosure.tsx +0 -476
  487. package/src/components/dropdown.tsx +0 -461
  488. package/src/components/empty-state.tsx +0 -136
  489. package/src/components/entity-combobox.tsx +0 -422
  490. package/src/components/error-boundary-labels.ts +0 -20
  491. package/src/components/error-boundary.tsx +0 -675
  492. package/src/components/facing-pair.ts +0 -93
  493. package/src/components/field-base-labels.ts +0 -20
  494. package/src/components/field-base.tsx +0 -656
  495. package/src/components/field-parts.tsx +0 -825
  496. package/src/components/field-strip.tsx +0 -129
  497. package/src/components/field.tsx +0 -206
  498. package/src/components/file-button.tsx +0 -295
  499. package/src/components/file-dropzone.tsx +0 -559
  500. package/src/components/floating-panel-labels.ts +0 -10
  501. package/src/components/floating-panel.tsx +0 -828
  502. package/src/components/focus-ring.ts +0 -16
  503. package/src/components/form-actions-labels.ts +0 -11
  504. package/src/components/form-actions.tsx +0 -578
  505. package/src/components/full-bleed-dialog.tsx +0 -294
  506. package/src/components/grouped-picker.tsx +0 -184
  507. package/src/components/help-popover.tsx +0 -117
  508. package/src/components/hover-menu.tsx +0 -343
  509. package/src/components/iban-input-labels.ts +0 -14
  510. package/src/components/iban-input.tsx +0 -236
  511. package/src/components/icon-button.tsx +0 -959
  512. package/src/components/icon-picker-labels.ts +0 -13
  513. package/src/components/icon-picker.tsx +0 -232
  514. package/src/components/image-grid-labels.ts +0 -11
  515. package/src/components/image-grid.tsx +0 -202
  516. package/src/components/inline-confirm.tsx +0 -116
  517. package/src/components/inline-edit-field-labels.ts +0 -11
  518. package/src/components/inline-edit-field.tsx +0 -552
  519. package/src/components/inline-entity-combobox.tsx +0 -849
  520. package/src/components/input.tsx +0 -394
  521. package/src/components/jump-calendar.tsx +0 -265
  522. package/src/components/language-select.tsx +0 -93
  523. package/src/components/legal-labels.ts +0 -105
  524. package/src/components/legal-page.tsx +0 -711
  525. package/src/components/legal.tsx +0 -668
  526. package/src/components/lightbox-labels.ts +0 -18
  527. package/src/components/lightbox.tsx +0 -399
  528. package/src/components/line-items-labels.ts +0 -14
  529. package/src/components/line-items.tsx +0 -602
  530. package/src/components/list-labels.ts +0 -12
  531. package/src/components/list.tsx +0 -870
  532. package/src/components/loading-state.tsx +0 -78
  533. package/src/components/measured-grid-labels.ts +0 -25
  534. package/src/components/measured-grid.tsx +0 -837
  535. package/src/components/menu-item.tsx +0 -241
  536. package/src/components/mini-calendar-labels.ts +0 -17
  537. package/src/components/mini-calendar.tsx +0 -789
  538. package/src/components/modal.tsx +0 -505
  539. package/src/components/money-draft.ts +0 -83
  540. package/src/components/money-field.tsx +0 -76
  541. package/src/components/month-grid.tsx +0 -229
  542. package/src/components/month-picker-labels.ts +0 -22
  543. package/src/components/month-picker.tsx +0 -1087
  544. package/src/components/multi-entity-combobox.tsx +0 -369
  545. package/src/components/multi-select.tsx +0 -376
  546. package/src/components/nav-pills.tsx +0 -198
  547. package/src/components/number-field.tsx +0 -327
  548. package/src/components/number-input.tsx +0 -558
  549. package/src/components/numeric-field.ts +0 -169
  550. package/src/components/numpad-sheet.tsx +0 -285
  551. package/src/components/one-time-code-input.tsx +0 -270
  552. package/src/components/page-contents-labels.ts +0 -9
  553. package/src/components/page-contents.tsx +0 -282
  554. package/src/components/page-header-context.ts +0 -8
  555. package/src/components/page-header.tsx +0 -298
  556. package/src/components/password-strength-labels.ts +0 -22
  557. package/src/components/password-strength.tsx +0 -241
  558. package/src/components/phone-input-labels.ts +0 -10
  559. package/src/components/phone-input.tsx +0 -282
  560. package/src/components/picker-sheet.tsx +0 -330
  561. package/src/components/pie-chart-labels.ts +0 -40
  562. package/src/components/pie-chart.tsx +0 -480
  563. package/src/components/popover-labels.ts +0 -14
  564. package/src/components/popover.tsx +0 -268
  565. package/src/components/progress-bar-labels.ts +0 -10
  566. package/src/components/progress-bar.tsx +0 -479
  567. package/src/components/qr-code.tsx +0 -76
  568. package/src/components/reauth-dialog-labels.ts +0 -13
  569. package/src/components/reauth-dialog.tsx +0 -265
  570. package/src/components/row-actions-labels.ts +0 -12
  571. package/src/components/row-actions.tsx +0 -795
  572. package/src/components/scroll-area.tsx +0 -123
  573. package/src/components/search-field.tsx +0 -192
  574. package/src/components/select.tsx +0 -294
  575. package/src/components/separator.tsx +0 -33
  576. package/src/components/series-chart-axes.ts +0 -342
  577. package/src/components/series-chart-budget.ts +0 -150
  578. package/src/components/series-chart-labels.ts +0 -33
  579. package/src/components/series-chart-thin.ts +0 -180
  580. package/src/components/series-chart-ticks.ts +0 -194
  581. package/src/components/series-chart.tsx +0 -1906
  582. package/src/components/server-wake-labels.ts +0 -11
  583. package/src/components/server-wake.tsx +0 -129
  584. package/src/components/settings-fields.tsx +0 -237
  585. package/src/components/share-card-labels.ts +0 -30
  586. package/src/components/share-card.tsx +0 -675
  587. package/src/components/signature-pad-labels.ts +0 -24
  588. package/src/components/signature-pad.tsx +0 -747
  589. package/src/components/signed-amount-labels.ts +0 -10
  590. package/src/components/signed-amount.tsx +0 -336
  591. package/src/components/skeleton.tsx +0 -80
  592. package/src/components/slider.tsx +0 -338
  593. package/src/components/sparkline-labels.ts +0 -14
  594. package/src/components/sparkline.tsx +0 -322
  595. package/src/components/spinner.tsx +0 -81
  596. package/src/components/stat-tile-labels.ts +0 -15
  597. package/src/components/stat-tile.tsx +0 -725
  598. package/src/components/status-dot.tsx +0 -170
  599. package/src/components/swatch-picker-labels.ts +0 -10
  600. package/src/components/swatch-picker.tsx +0 -283
  601. package/src/components/swipeable-row.tsx +0 -386
  602. package/src/components/switch.tsx +0 -273
  603. package/src/components/table-skeleton.tsx +0 -136
  604. package/src/components/table.tsx +0 -647
  605. package/src/components/tabs-labels.ts +0 -13
  606. package/src/components/tabs.tsx +0 -693
  607. package/src/components/text-link.tsx +0 -359
  608. package/src/components/text.tsx +0 -150
  609. package/src/components/textarea.tsx +0 -330
  610. package/src/components/tile-radio.tsx +0 -332
  611. package/src/components/time-input.tsx +0 -121
  612. package/src/components/toast-labels.ts +0 -12
  613. package/src/components/toast.tsx +0 -519
  614. package/src/components/toggle-group.tsx +0 -752
  615. package/src/components/toggle-legend.tsx +0 -394
  616. package/src/components/tooltip.tsx +0 -1149
  617. package/src/components/translation-review-editor.tsx +0 -290
  618. package/src/components/translation-review-labels.ts +0 -181
  619. package/src/components/translation-review-parts.tsx +0 -272
  620. package/src/components/translation-review.tsx +0 -1240
  621. package/src/components/tree-view.tsx +0 -589
  622. package/src/components/treemap-labels.ts +0 -47
  623. package/src/components/treemap-layout.ts +0 -210
  624. package/src/components/treemap.tsx +0 -1184
  625. package/src/components/trigger-aria.ts +0 -44
  626. package/src/components/use-anchor-dir.ts +0 -20
  627. package/src/components/use-mobile-reveal.ts +0 -62
  628. package/src/components/use-table-state.ts +0 -753
  629. package/src/components/user-avatar.tsx +0 -116
  630. package/src/components/wizard-stepper.tsx +0 -42
  631. package/src/components/write-lock-labels.ts +0 -10
  632. package/src/components/write-lock.tsx +0 -253
  633. package/src/currencies.ts +0 -70
  634. package/src/data-table.ts +0 -42
  635. package/src/demo/demo-banner.tsx +0 -153
  636. package/src/demo/demo-countdown.ts +0 -136
  637. package/src/demo/demo-ended.tsx +0 -90
  638. package/src/demo/demo-labels.ts +0 -78
  639. package/src/demo/demo-session.ts +0 -32
  640. package/src/demo/demo-start.tsx +0 -156
  641. package/src/eslint/index.ts +0 -161
  642. package/src/eslint/role-utilities.generated.ts +0 -146
  643. package/src/eslint/role-utilities.ts +0 -233
  644. package/src/feedback/feedback-attachment-download.tsx +0 -77
  645. package/src/feedback/feedback-attachment-labels.ts +0 -37
  646. package/src/feedback/feedback-attachment.tsx +0 -1239
  647. package/src/feedback/feedback-capture.ts +0 -287
  648. package/src/feedback/feedback-comment-thread.tsx +0 -353
  649. package/src/feedback/feedback-context-labels.ts +0 -10
  650. package/src/feedback/feedback-context.tsx +0 -213
  651. package/src/feedback/feedback-crash.ts +0 -547
  652. package/src/feedback/feedback-dialog-labels.ts +0 -29
  653. package/src/feedback/feedback-dialog.tsx +0 -440
  654. package/src/feedback/feedback-entry-file.tsx +0 -150
  655. package/src/feedback/feedback-inbox.tsx +0 -552
  656. package/src/feedback/feedback-labels.ts +0 -144
  657. package/src/feedback/feedback-menu-labels.ts +0 -12
  658. package/src/feedback/feedback-menu.tsx +0 -177
  659. package/src/feedback/feedback-mutation.ts +0 -20
  660. package/src/feedback/feedback-page-link.ts +0 -90
  661. package/src/feedback/feedback-public-labels.ts +0 -81
  662. package/src/feedback/feedback-public-list.tsx +0 -524
  663. package/src/feedback/feedback-public-switch.tsx +0 -102
  664. package/src/feedback/feedback-public-types.ts +0 -101
  665. package/src/feedback/feedback-record.ts +0 -423
  666. package/src/feedback/feedback-replies-context.ts +0 -9
  667. package/src/feedback/feedback-replies.tsx +0 -343
  668. package/src/feedback/feedback-rework-entry-chip.tsx +0 -18
  669. package/src/feedback/feedback-row-detail-labels.ts +0 -30
  670. package/src/feedback/feedback-row-detail.tsx +0 -858
  671. package/src/feedback/feedback-status-undo.ts +0 -109
  672. package/src/feedback/feedback-submit-dialog.tsx +0 -118
  673. package/src/feedback/feedback-submit.tsx +0 -237
  674. package/src/feedback/feedback-swipe.tsx +0 -192
  675. package/src/feedback/feedback-table-labels.ts +0 -27
  676. package/src/feedback/feedback-table.tsx +0 -912
  677. package/src/feedback/feedback-thread-labels.ts +0 -22
  678. package/src/feedback/feedback-thread-parts-labels.ts +0 -41
  679. package/src/feedback/feedback-thread.tsx +0 -905
  680. package/src/feedback/feedback-vocabulary.ts +0 -263
  681. package/src/feedback/feedback-vote-button.tsx +0 -124
  682. package/src/feedback.ts +0 -116
  683. package/src/hooks/use-anchored-panel.ts +0 -396
  684. package/src/hooks/use-anchored-rect.ts +0 -77
  685. package/src/hooks/use-announce.ts +0 -85
  686. package/src/hooks/use-authed-src.ts +0 -167
  687. package/src/hooks/use-body-scroll-lock.ts +0 -119
  688. package/src/hooks/use-breakpoint.ts +0 -133
  689. package/src/hooks/use-close-transition.ts +0 -107
  690. package/src/hooks/use-copy-to-clipboard.ts +0 -122
  691. package/src/hooks/use-debounce.ts +0 -114
  692. package/src/hooks/use-dismiss.ts +0 -371
  693. package/src/hooks/use-document-title.ts +0 -33
  694. package/src/hooks/use-file-drop.ts +0 -121
  695. package/src/hooks/use-file-picker-labels.ts +0 -28
  696. package/src/hooks/use-file-picker.tsx +0 -359
  697. package/src/hooks/use-focus-trap.ts +0 -199
  698. package/src/hooks/use-hotkey.ts +0 -189
  699. package/src/hooks/use-large-text.ts +0 -41
  700. package/src/hooks/use-long-press.ts +0 -140
  701. package/src/hooks/use-media-query.ts +0 -39
  702. package/src/hooks/use-mounted.ts +0 -39
  703. package/src/hooks/use-noindex.ts +0 -37
  704. package/src/hooks/use-overlay-history.ts +0 -714
  705. package/src/hooks/use-row-swipe.ts +0 -207
  706. package/src/hooks/use-search-param-state.ts +0 -434
  707. package/src/hooks/use-windowed-rows.ts +0 -225
  708. package/src/i18n/defaults.ts +0 -267
  709. package/src/i18n/german.ts +0 -2057
  710. package/src/i18n/kit-labels.tsx +0 -1003
  711. package/src/i18n/languages.ts +0 -349
  712. package/src/i18n/locales/de-CH.ts +0 -24
  713. package/src/i18n/locales/en.ts +0 -267
  714. package/src/i18n/locales/es.ts +0 -1997
  715. package/src/i18n/locales/fr.ts +0 -2035
  716. package/src/i18n/locales/hu.ts +0 -2002
  717. package/src/i18n/locales/it.ts +0 -2012
  718. package/src/i18n/locales/zh.ts +0 -1910
  719. package/src/i18n/plurals.ts +0 -86
  720. package/src/i18n/review.ts +0 -475
  721. package/src/i18n/shared-labels.ts +0 -138
  722. package/src/i18n/swiss.ts +0 -29
  723. package/src/index.ts +0 -1179
  724. package/src/landing/access.ts +0 -93
  725. package/src/landing/landing-actions.tsx +0 -97
  726. package/src/landing/landing-labels.ts +0 -59
  727. package/src/landing/landing-sections.tsx +0 -341
  728. package/src/landing/page-seo.ts +0 -261
  729. package/src/landing/public-header.tsx +0 -121
  730. package/src/landing/public-page.tsx +0 -106
  731. package/src/landing/routing.tsx +0 -225
  732. package/src/lib/broadcast.ts +0 -52
  733. package/src/lib/calc.ts +0 -323
  734. package/src/lib/chip-edge-snap.ts +0 -212
  735. package/src/lib/clipboard-minus.ts +0 -107
  736. package/src/lib/clipping.ts +0 -34
  737. package/src/lib/cn.ts +0 -28
  738. package/src/lib/column-mapping.ts +0 -234
  739. package/src/lib/countries.ts +0 -91
  740. package/src/lib/data-attributes.ts +0 -14
  741. package/src/lib/date-format.ts +0 -227
  742. package/src/lib/dates.ts +0 -266
  743. package/src/lib/day-format.ts +0 -96
  744. package/src/lib/decimal-marks.ts +0 -108
  745. package/src/lib/describe-failure.ts +0 -18
  746. package/src/lib/device-settings.ts +0 -147
  747. package/src/lib/direction.ts +0 -74
  748. package/src/lib/document-navigation.ts +0 -17
  749. package/src/lib/document-title.ts +0 -41
  750. package/src/lib/download.ts +0 -172
  751. package/src/lib/float-clearance.ts +0 -70
  752. package/src/lib/floating-stack.ts +0 -70
  753. package/src/lib/focus-left.ts +0 -156
  754. package/src/lib/format.ts +0 -522
  755. package/src/lib/http-status.ts +0 -38
  756. package/src/lib/iban.ts +0 -251
  757. package/src/lib/ime.ts +0 -24
  758. package/src/lib/intl-cache.ts +0 -25
  759. package/src/lib/iso-day.ts +0 -13
  760. package/src/lib/lazy-with-fallback.ts +0 -82
  761. package/src/lib/logger.ts +0 -69
  762. package/src/lib/minus.ts +0 -50
  763. package/src/lib/path-pattern.ts +0 -55
  764. package/src/lib/person-name.ts +0 -155
  765. package/src/lib/phone.ts +0 -208
  766. package/src/lib/plain-text.ts +0 -22
  767. package/src/lib/presence.ts +0 -18
  768. package/src/lib/qr-encode.ts +0 -444
  769. package/src/lib/quiet-focus.ts +0 -32
  770. package/src/lib/reduced-motion.ts +0 -22
  771. package/src/lib/refs.ts +0 -22
  772. package/src/lib/round.ts +0 -43
  773. package/src/lib/safe-storage.ts +0 -119
  774. package/src/lib/server-wake.ts +0 -315
  775. package/src/lib/session-refresh.ts +0 -472
  776. package/src/lib/share-format.ts +0 -18
  777. package/src/lib/status-tone.ts +0 -43
  778. package/src/lib/strip-fade.ts +0 -287
  779. package/src/lib/table-text.ts +0 -554
  780. package/src/lib/text-case.ts +0 -19
  781. package/src/lib/text-match.ts +0 -169
  782. package/src/lib/thenable.ts +0 -19
  783. package/src/lib/translation-review.ts +0 -717
  784. package/src/notifications/device-zone-report.ts +0 -67
  785. package/src/notifications/feedback-notification-types-labels.ts +0 -33
  786. package/src/notifications/feedback-notification-types.ts +0 -71
  787. package/src/notifications/notification-bell.tsx +0 -119
  788. package/src/notifications/notification-inbox.tsx +0 -309
  789. package/src/notifications/notification-parts.tsx +0 -81
  790. package/src/notifications/notification-types-setting.tsx +0 -406
  791. package/src/notifications/notification-types.ts +0 -89
  792. package/src/notifications/notifications-labels.ts +0 -287
  793. package/src/notifications/push-device-setting.tsx +0 -230
  794. package/src/notifications/push-devices-setting.tsx +0 -374
  795. package/src/notifications/push-worker.ts +0 -168
  796. package/src/notifications/quiet-hours-setting.tsx +0 -238
  797. package/src/notifications/unsubscribe-panel.tsx +0 -200
  798. package/src/notifications/use-push-device.ts +0 -448
  799. package/src/offline/local-first.ts +0 -100
  800. package/src/offline/pwa-update-prompt-labels.ts +0 -9
  801. package/src/offline/pwa-update-prompt.tsx +0 -78
  802. package/src/offline/query-persistence.ts +0 -391
  803. package/src/offline/service-worker-updates.ts +0 -81
  804. package/src/offline/shared-computer-labels.ts +0 -8
  805. package/src/offline/shared-computer.tsx +0 -87
  806. package/src/offline/sign-out-pending-dialog-labels.ts +0 -21
  807. package/src/offline/sign-out-pending-dialog.tsx +0 -135
  808. package/src/offline.ts +0 -37
  809. package/src/push-worker.ts +0 -25
  810. package/src/rhf/fields.tsx +0 -2187
  811. package/src/rhf/form-core.tsx +0 -324
  812. package/src/rhf/form.tsx +0 -12
  813. package/src/rhf/line-items.tsx +0 -135
  814. package/src/rhf/use-rhf-wizard-step.ts +0 -104
  815. package/src/rhf.ts +0 -21
  816. package/src/search/command-palette-labels.ts +0 -16
  817. package/src/search/command-palette.tsx +0 -624
  818. package/src/search/global-search-labels.ts +0 -13
  819. package/src/search/global-search.tsx +0 -660
  820. package/src/search/search-index.ts +0 -283
  821. package/src/search/use-command-key.ts +0 -23
  822. package/src/search.ts +0 -14
  823. package/src/settings/device-reset-setting-labels.ts +0 -20
  824. package/src/settings/device-reset-setting.tsx +0 -116
  825. package/src/settings/settings-card-classes.ts +0 -11
  826. package/src/settings/settings-catalogue.ts +0 -150
  827. package/src/settings/settings-context.ts +0 -33
  828. package/src/settings/settings-heading.tsx +0 -67
  829. package/src/settings/settings-labels.ts +0 -87
  830. package/src/settings/settings-layout.tsx +0 -471
  831. package/src/settings/settings-section.tsx +0 -108
  832. package/src/settings/use-account-appearance.ts +0 -148
  833. package/src/settings/use-account-language.ts +0 -146
  834. package/src/settings/use-settings-focus.ts +0 -128
  835. package/src/settings/use-settings-route.ts +0 -258
  836. package/src/shell/account-menu-language.tsx +0 -130
  837. package/src/shell/account-menu-text-size.tsx +0 -80
  838. package/src/shell/admin-menu.tsx +0 -183
  839. package/src/shell/app-shell-labels.ts +0 -11
  840. package/src/shell/app-shell.tsx +0 -1560
  841. package/src/shell/auth-layout.tsx +0 -186
  842. package/src/shell/company-switcher-labels.ts +0 -13
  843. package/src/shell/company-switcher.tsx +0 -136
  844. package/src/shell/dev-menu.tsx +0 -257
  845. package/src/shell/fill-height.tsx +0 -45
  846. package/src/shell/language-code.ts +0 -5
  847. package/src/shell/option-switcher-menu.tsx +0 -84
  848. package/src/shell/sync-status-indicator.tsx +0 -303
  849. package/src/shell/top-bar-actions.tsx +0 -87
  850. package/src/shell/top-bar-brand.tsx +0 -78
  851. package/src/shell/top-bar-parts-labels.ts +0 -148
  852. package/src/shell/top-bar.tsx +0 -40
  853. package/src/shell/topbar-action-menu.tsx +0 -567
  854. package/src/shell/topbar-controls.tsx +0 -269
  855. package/src/shell/tour-menu.tsx +0 -187
  856. package/src/shell.ts +0 -108
  857. package/src/sync/db.ts +0 -129
  858. package/src/sync/engine.ts +0 -1011
  859. package/src/sync/hooks.ts +0 -98
  860. package/src/sync/testing.ts +0 -73
  861. package/src/sync/wire.ts +0 -61
  862. package/src/sync.ts +0 -37
  863. package/src/table-text.ts +0 -8
  864. package/src/testing/css-variables.generated.ts +0 -112
  865. package/src/testing/css-variables.ts +0 -189
  866. package/src/testing/index.ts +0 -29
  867. package/src/testing/react.ts +0 -58
  868. package/src/testing/tour.ts +0 -41
  869. package/src/testing/viewport.ts +0 -357
  870. package/src/theme/chart-palette.ts +0 -232
  871. package/src/theme/color.ts +0 -272
  872. package/src/theme/contrast-tokens.ts +0 -268
  873. package/src/theme/contrast.ts +0 -250
  874. package/src/theme/palette-derive.ts +0 -825
  875. package/src/theme/palette-presets.ts +0 -544
  876. package/src/theme/palette-store.ts +0 -108
  877. package/src/theme/text-size.ts +0 -289
  878. package/src/theme/theme-store.ts +0 -157
  879. package/src/tour/tour-catalogue-labels.ts +0 -75
  880. package/src/tour/tour-catalogue.tsx +0 -335
  881. package/src/tour/tour-dock.ts +0 -134
  882. package/src/tour/tour-engine.ts +0 -37
  883. package/src/tour/tour-gates.ts +0 -218
  884. package/src/tour/tour-guide-button.tsx +0 -64
  885. package/src/tour/tour-guide-labels.ts +0 -15
  886. package/src/tour/tour-info.ts +0 -144
  887. package/src/tour/tour-labels.ts +0 -23
  888. package/src/tour/tour-overlay.tsx +0 -1170
  889. package/src/tour/tour.tsx +0 -750
  890. package/src/tour/use-tour-progress.ts +0 -671
  891. package/src/tour/use-tour-url-sync.ts +0 -266
  892. package/src/tour.ts +0 -21
  893. package/src/vite-env.d.ts +0 -23
  894. package/src/wizard/stepper-nav.tsx +0 -360
  895. package/src/wizard/types.ts +0 -275
  896. package/src/wizard/use-wizard.ts +0 -560
  897. package/src/wizard/validation.ts +0 -39
  898. package/src/wizard/wizard-context.tsx +0 -74
  899. package/src/wizard/wizard-labels.ts +0 -29
  900. package/src/wizard/wizard-step.tsx +0 -76
  901. package/src/wizard/wizard-summary.tsx +0 -84
  902. package/src/wizard.ts +0 -15
@@ -1,1149 +0,0 @@
1
- import {
2
- cloneElement,
3
- isValidElement,
4
- useCallback,
5
- useEffect,
6
- useId,
7
- useLayoutEffect,
8
- useRef,
9
- useState,
10
- type ComponentPropsWithoutRef,
11
- type FocusEvent,
12
- type MouseEvent,
13
- type PointerEvent,
14
- type ReactElement,
15
- type ReactNode,
16
- type Ref,
17
- type RefObject,
18
- } from "react";
19
- import { createPortal } from "react-dom";
20
- import { cn } from "../lib/cn";
21
- import { useDismissLayer, useOutsideClick } from "../hooks/use-dismiss";
22
- import { useAnchoredRect, type AnchorRect } from "../hooks/use-anchored-rect";
23
- import { viewportSize } from "../hooks/use-anchored-panel";
24
- import { dirOf, type Direction } from "../lib/direction";
25
- import { hasClippingAncestor } from "../lib/clipping";
26
- import { assignRef } from "../lib/refs";
27
-
28
- /** The attribute that marks an element as a clipping container for {@link Tooltip}'s
29
- * auto-portal, whatever its computed `overflow` — `<div data-clips>` or
30
- * `<div {...{ [CLIPS_ATTRIBUTE]: "" }}>`. Put it on an app's own scroller so a test
31
- * environment without stylesheets (jsdom) portals the same tooltips the browser
32
- * does. DataTable's body and Table's wrapper already carry it. */
33
- export { CLIPS_ATTRIBUTE } from "../lib/clipping";
34
-
35
- /** Where the bubble sits. `start` / `end` follow the reading direction — `end` is the
36
- * right in LTR and the left in RTL — and are what a layout that mirrors should ask
37
- * for. `left` / `right` stay physical, for a bubble tied to something that does not
38
- * mirror (a chart axis, a map). */
39
- export type TooltipSide = "top" | "bottom" | "left" | "right" | "start" | "end";
40
-
41
- /** What a TAP — a touch or a pen press, never the mouse — on the trigger does to the
42
- * bubble. See "A tap is not a hover" on {@link Tooltip}.
43
- *
44
- * - `"auto"` (the default): a tap that activates something shows nothing; a tap that
45
- * activates nothing — on a disabled or `aria-disabled` control (a write lock's
46
- * reason), or on a trigger with no control at all (a truncated name, a badge) —
47
- * toggles the bubble, since showing it is then the tap's only answer.
48
- * - `"toggle"`: every tap toggles it — for a control that exists only to explain, such
49
- * as a "?" whose click does nothing.
50
- * - `"ignore"`: a tap never shows it. */
51
- export type TooltipTap = "auto" | "toggle" | "ignore";
52
-
53
- /** The placement the maths works in: a logical side resolved against the trigger. */
54
- type PhysicalSide = "top" | "bottom" | "left" | "right";
55
-
56
- function physicalSide(side: TooltipSide, dir: Direction): PhysicalSide {
57
- if (side === "start") return dir === "rtl" ? "right" : "left";
58
- if (side === "end") return dir === "rtl" ? "left" : "right";
59
- return side;
60
- }
61
-
62
- /** The floating bubble itself. Uses the shared surface/border/text tokens so it
63
- * reads as part of the app's chrome (like the top bar and cards) rather than the
64
- * cold slate pill it used to be.
65
- *
66
- * `w-max` keeps a short label on one line — the old `whitespace-nowrap` did that
67
- * too, but it also let a sentence-length label grow without bound, and a bubble
68
- * wider than the space beside its trigger gets clipped by whatever overflow
69
- * container it sits in. So cap it and let long text wrap instead. The cap tracks
70
- * the viewport as well, for narrow screens where 20rem is already most of it.
71
- * `side` is a preference rather than an instruction for the PORTALLED variant,
72
- * which measures the bubble and turns it round when it would not fit
73
- * (Steering Design feedback #126). The CSS-placed one never turns round, so there
74
- * `side` is still the whole of which side it is on — but since 0.14 it is slid back
75
- * along the cross axis when it would cross the viewport edge (keksdose G7), which the
76
- * viewport term in this cap is what makes possible: a bubble never wider than the glass
77
- * minus the margins always fits once slid. */
78
- const TOOLTIP_SURFACE =
79
- "w-max max-w-[min(20rem,calc(100vw-1rem))] rounded-md border border-subtle bg-surface px-2 py-1 text-xs font-medium text-primary shadow-lg";
80
-
81
- const sidePositionClass: Record<TooltipSide, string> = {
82
- top: "bottom-full left-1/2 -translate-x-1/2 mb-1",
83
- bottom: "top-full left-1/2 -translate-x-1/2 mt-1",
84
- left: "right-full top-1/2 -translate-y-1/2 mr-1",
85
- right: "left-full top-1/2 -translate-y-1/2 ml-1",
86
- // Logical insets, so CSS resolves the side from the inherited direction with no
87
- // JavaScript: `end-full` pins the bubble's END edge to the trigger's start.
88
- start: "end-full top-1/2 -translate-y-1/2 me-1",
89
- end: "start-full top-1/2 -translate-y-1/2 ms-1",
90
- };
91
-
92
- /**
93
- * `extends ComponentPropsWithoutRef<"span">` because the wrapper this renders IS a span,
94
- * and a tooltip is the component a caller most often needs to reach past: it sits
95
- * between the layout and the control, so a `data-tour` anchor, a test id or an
96
- * `aria-label` aimed at the trigger used to be swallowed by it.
97
- *
98
- * ⚠️ On the empty-label branch there is no wrapper at all, and therefore nothing for
99
- * those attributes to land on — see the note in the body.
100
- */
101
- export interface TooltipProps extends ComponentPropsWithoutRef<"span"> {
102
- label: ReactNode;
103
- side?: TooltipSide;
104
- className?: string;
105
- /**
106
- * Where the bubble lives. Left out (the default since 0.10.0), the tooltip decides for
107
- * itself: the bubble stays next to the trigger unless an ancestor clips or scrolls
108
- * (`overflow` other than `visible`, or the {@link CLIPS_ATTRIBUTE} marker), in which
109
- * case it is portalled — see "Inside a scroll container" below. `true` always portals, `false` never does; both are exactly
110
- * what they were before the default existed.
111
- */
112
- portal?: boolean;
113
- /**
114
- * Mount the IN-PLACE bubble only while it is up (hovered or focused, not dismissed),
115
- * instead of always. It still sits next to its trigger and is still placed by CSS
116
- * alone; what changes is that, while closed, it is not in the DOM — so it is not in
117
- * `getAllByRole("tooltip")`, not in an ancestor's `textContent` and not in an
118
- * ancestor's accessible name. The trigger is described (`aria-describedby`) while
119
- * the bubble is up, exactly as a portalled one is. See "Lazy in place" on
120
- * {@link Tooltip} for why this exists (keksdose F6) and why it is not the default.
121
- *
122
- * Has no effect on a bubble that is portalled (`portal`, or auto-portalled inside a
123
- * clipping container) — that one is already mounted only while up.
124
- */
125
- lazy?: boolean;
126
- /** Tag the bubble `data-private`, for a label that repeats the user's own data. */
127
- redact?: boolean;
128
- /**
129
- * What a tap (touch or pen) on the trigger does — see {@link TooltipTap} and "A tap is
130
- * not a hover" below. Default `"auto"`: a tap that activates the control shows nothing;
131
- * one that activates nothing (a disabled control, plain text) toggles the bubble.
132
- */
133
- tap?: TooltipTap;
134
- children: ReactNode;
135
- /**
136
- * The trigger WRAPPER — the `<span>` the rest of the props go to, around the caller's
137
- * child (round 0.35 R4, as Card and EmptyState took one in 0.34) — merged with its own
138
- * ref, which the bubble is anchored to and dismissed by. Not the bubble: a portalled
139
- * one exists only while it is up. On the empty-label branch there is no wrapper, so
140
- * the ref stays `null` (see the ⚠️ above).
141
- */
142
- ref?: Ref<HTMLSpanElement>;
143
- }
144
-
145
- /** What each variant below takes: the resolved `side` and `tap`, and every span attribute
146
- * the caller handed {@link Tooltip}, forwarded to that variant's own wrapper. */
147
- type TooltipVariantProps = Omit<TooltipProps, "side" | "portal" | "lazy" | "tap"> & {
148
- side: TooltipSide;
149
- tap: TooltipTap;
150
- };
151
-
152
- /**
153
- * Hover/focus label for a control.
154
- *
155
- * Two placements, and the choice matters more than it looks. In place, the bubble is
156
- * always mounted next to the trigger — so a plain render test finds it without a hover —
157
- * and shown while it is up (`display: none` otherwise, see "never widens the page"
158
- * below). Portalled, the bubble is mounted in `document.body` only while it is up,
159
- * positioned by measurement.
160
- *
161
- * ⚠️ **A bubble that repeats a value has to be redactable.** The consuming app blurs
162
- * `[data-private]` under a `demo-mode` class on `<html>` — and the portalled bubble is
163
- * mounted on `document.body`, which is INSIDE that class, so the rule reaches it as
164
- * long as the bubble is tagged. It is not tagged by default, because most labels are
165
- * UI strings; pass `redact` on the ones that repeat the user's own data (a truncated
166
- * payee, an account name, a memo). Getting this wrong is silent: the trigger blurs,
167
- * the bubble spells the value out on hover.
168
- *
169
- * ⚠️ **An empty label renders nothing at all.** `title={payee ?? ""}` is an ordinary
170
- * shape at a call site that reveals truncated text, and the native attribute answers
171
- * it by showing no tooltip. A component that faithfully rendered an empty bubble
172
- * would be a worse `title`, so the emptiness check is here rather than at every call
173
- * site that could forget it.
174
- *
175
- * ⚠️ **The bubble describes its trigger, which means cloning it.** `role="tooltip"` is
176
- * a name for a box, not a relationship — so for as long as nothing referenced the
177
- * bubble, the label reached the pointer and nobody else. That is worst on the call
178
- * sites that need it most: the kit's icon-only buttons, where the tooltip IS the
179
- * label. `aria-describedby` has to sit on the focusable element, which is the
180
- * caller's child and not this component's wrapper, so the child is CLONED to carry
181
- * it. A description the caller already set is appended to, never replaced — a field's
182
- * error text and its hint bubble both describe it. Children that cannot take props (a
183
- * fragment, a bare string, several elements) are left exactly as they were.
184
- *
185
- * ⚠️ **Escape dismisses it (WCAG 1.4.13).** Anything that appears on hover or focus has
186
- * to be dismissible without moving the pointer, and a bubble is opaque: it lands over
187
- * the row, field or figure you were reading, and the only way out of it used to be to
188
- * point somewhere else — which is precisely what you cannot do when what you need to
189
- * read is underneath it. The listener is the document's rather than the wrapper's
190
- * because the pointer opens this with the keyboard focus somewhere else entirely, and
191
- * it is subscribed only while a bubble is actually up: the in-place bubble is always
192
- * mounted, and a table of forty tooltips must not mean forty keydown listeners.
193
- *
194
- * ⚠️ **Inside a scroll container, the bubble has to be portalled — and by default it
195
- * now is.** An always-mounted bubble is absolutely positioned, but an absolutely
196
- * positioned descendant still counts towards its scroll-container ancestor's
197
- * scrollable overflow — so an invisible bubble on a control near the right edge makes
198
- * the container scroll sideways with nothing to reveal. That is what Keksdose feedback
199
- * dev#488 reported on the admin roster: 66px of horizontal scroll on a table that fit,
200
- * 44px of it owed to tooltips nobody could see. And a visible one is clipped by the
201
- * container's edge. The portalled bubble is `position: fixed` and absent until hovered,
202
- * so it adds no width and cannot be clipped.
203
- *
204
- * Until 0.10.0 the cure was `portal` at the call site, and kastlan asked for it to stop
205
- * being one: every tooltip in a table, a drawer or a scrolling card had to remember it,
206
- * and the ones that forgot were only found by someone scrolling sideways. So with
207
- * `portal` left out, the tooltip looks for a clipping ancestor itself — any element
208
- * between it and `<body>` whose computed `overflow-x` / `overflow-y` is not `visible`,
209
- * or that carries {@link CLIPS_ATTRIBUTE} (`data-clips`). The marker is what makes the
210
- * answer the same under test: jsdom computes no Tailwind, so there every scroller
211
- * reads `visible` and a table-cell tooltip used to stay in place — its always-mounted
212
- * bubble then repeated the label in the cell's accessible name and `textContent`
213
- * ("CheckingChecking"), and apps pinned `portal` to stop it. The kit's own scrollers
214
- * are marked, so a tooltip in a DataTable or Table cell portals in jsdom exactly as it
215
- * does in the browser.
216
- * It looks at MOUNT, not only on open: dev#488's phantom scroll is caused by a bubble
217
- * nobody opened, so a check that waited for the hover would find the damage already
218
- * done. It looks again on every open, for a container that started scrolling after
219
- * the tooltip mounted (a table that grew). Only the bubble changes place; the trigger
220
- * and the caller's child stay mounted, so a switch never costs a focused button its
221
- * focus. Outside any such container the in-place bubble is kept, which is still the
222
- * cheaper one and the one a plain render test can find without a hover.
223
- *
224
- * Why not simply portal everything? Because the in-place bubble is the one existing
225
- * app tests rely on (it is in the DOM without a hover), and because it follows its
226
- * trigger through a scroll or an animation for free — the portalled one re-measures.
227
- *
228
- * ⚠️ **Lazy in place: `lazy` keeps the bubble next to its trigger but out of the DOM
229
- * until it is up (keksdose F6).** `data-clips` cured the doubled text for tooltips
230
- * inside a table; it did nothing for the ones outside any clipping container, where the
231
- * always-mounted bubble is still a real `role="tooltip"` node full of text. About seven
232
- * keksdose sites — column-header tooltips, a direction toggle, an fx-estimate row, a
233
- * FlagBadge, a toast inside a `<button>` — still pinned `portal` for that alone: their
234
- * tests found two tooltips with `getAllByRole("tooltip")`, read the label twice in a
235
- * `textContent`, or got a button whose accessible name had the bubble's sentence glued
236
- * on. `portal` fixed the test and cost the browser the measured, re-positioning bubble
237
- * for no reason. `lazy` is the in-place bubble with the portalled one's lifetime: it is
238
- * rendered only while hovered or focused (and not dismissed), in the same slot, with the
239
- * same classes, so it looks and sits exactly as the default one does when it is up.
240
- *
241
- * Why unmount rather than hide? Hiding was the other option — keep the bubble mounted
242
- * with `hidden` (or `aria-hidden` plus `display: none` until `:hover` / `:focus-within`)
243
- * and let `aria-describedby` go on pointing at it, which the accname algorithm allows:
244
- * a node referenced directly by `aria-describedby` contributes its text even while
245
- * hidden. That would take the bubble out of the role queries and out of an ancestor's
246
- * accessible name — but NOT out of `textContent`, which is plain DOM and counts hidden
247
- * text too, and `textContent` is one of the three things F6 lists. Only a bubble that
248
- * is not there solves all three. The cost is the portalled variant's: the trigger is
249
- * described while the bubble is up rather than always. Focusing the trigger IS what
250
- * puts it up, and React commits the `aria-describedby` in the same task as that focus
251
- * event, so a screen reader landing on the control still hears it; what goes is the description
252
- * of a trigger that is read in browse mode without ever being focused.
253
- *
254
- * Escape still dismisses it — here by unmounting it, which takes it off the screen and
255
- * out of the accessibility tree at once — and the next hover or focus brings it back.
256
- * There is no fade to lose: neither in-place bubble has ever had a transition, only the
257
- * `opacity` switch, so appearing on a state change looks the same as appearing on
258
- * `:hover`.
259
- *
260
- * Why opt-in rather than the new default? Because the always-mounted bubble is the one
261
- * existing app tests find without a hover (see above): flipping it would turn every
262
- * `getByRole("tooltip")` written against 0.12 into a failure in all three apps at once.
263
- * Reach for `lazy` wherever a `portal` was pinned only to keep a test's DOM clean.
264
- *
265
- * ⚠️ **The in-place bubble is clamped to the viewport when it opens (keksdose G7).** The
266
- * portalled bubble has always measured itself and been pushed back onto the glass
267
- * ({@link placeTooltip}); the in-place one never learnt its own size, so `side` was the
268
- * whole of its placement and a `top` / `bottom` bubble sat centred on its trigger
269
- * whatever that cost. The cost shows on a phone: long labels live at a row's START edge
270
- * — a gcloud command in keksdose's jobs panel, a canned reply in a support thread — and
271
- * a 20rem bubble centred on a trigger 16px from the edge hangs half its text off the
272
- * screen. keksdose pinned `portal` on those sites for that alone, which gave up `lazy`'s
273
- * point (the cheap, in-place bubble) to buy a placement.
274
- *
275
- * So now, when an in-place bubble goes up (default or `lazy`), a layout effect measures
276
- * it and, if it crosses the viewport edge minus the same margin the portalled bubble
277
- * keeps, slides it back along its CROSS axis only — sideways for `top` / `bottom`, up or
278
- * down for the four side placements — with an inline margin (a `transform` until 0.25; see
279
- * {@link shiftStyle} for why that widened the page). The main axis is left alone on purpose: sliding a
280
- * `start` bubble along the main axis would slide it over its own trigger, and turning it
281
- * round is a measured-placement decision this CSS-placed bubble does not make (pass
282
- * `portal` for that). The width is already capped to the viewport (see
283
- * `TOOLTIP_SURFACE`), so a bubble can always fit once slid, and a long label wraps
284
- * instead of growing past the glass.
285
- *
286
- * Why on by default, with no prop? Because it is a no-op for every bubble that already
287
- * fits — the shift is zero unless the bubble would overflow — so the only placements it
288
- * changes are ones that were broken. The maths is in physical viewport pixels: an RTL
289
- * row puts its long label at the RIGHT edge and gets slid left by the same code, and the
290
- * reading direction only decides which edge is kept when a bubble is wider than the room
291
- * (its start, as {@link placeTooltip} keeps). Under jsdom there is no layout — every rect
292
- * is zero-sized — and a zero-sized bubble is taken to be unmeasured, so tests see no
293
- * transform at all. It is measured on open (and when the label or side changes while
294
- * open), not on every scroll: an in-place bubble follows its trigger for free, and a page
295
- * that scrolls sideways under an open tooltip is not a case worth a listener per tooltip.
296
- *
297
- * ⚠️ **An in-place bubble never widens the page (keksdose run 72, live #381).** Two
298
- * holes let it, and a page wider than the screen is worse than a bubble cut off: on a
299
- * phone Chrome then lays every `position: fixed` layer out against the WIDER page while
300
- * the glass still shows the old width. Measured on a 406px phone: the sync icon at x=254,
301
- * its `bottom` bubble at 102–422, the page 426px wide, and a FloatingPanel 17px off the
302
- * glass. First, the clamp compared against `window.innerWidth` — and on a phone that is
303
- * the layout viewport, which GROWS to the page's width once something sticks out (430 on
304
- * a 406px screen, measured in Chromium's mobile emulation), so a bubble already past the
305
- * edge found itself "inside" and stayed there. It now measures against the root's client
306
- * width, which on a phone is the screen (the initial containing block, the width `100vw`
307
- * and media queries see) whatever overflows, and on a desktop also leaves out a classic
308
- * scrollbar; the window is the fallback where there is no layout (jsdom). The portalled
309
- * bubble takes the same width, since a `fixed` bubble placed against a grown layout
310
- * viewport is off the glass for the same reason. Second, the always-mounted bubble was
311
- * `opacity: 0` while closed — invisible, but still laid out where the CSS puts it, so a
312
- * bubble nobody opened, centred on a trigger near the end edge, widened the page all by
313
- * itself (dev#488's phantom scroll, at the scale of the page instead of a scroller, where
314
- * the auto-portal cannot help: the page is not an ancestor it can portal out of). It is
315
- * now `display: none` until it is up, so a closed bubble takes no room at all, and an open
316
- * one is slid onto the glass in the same layout pass that shows it, before paint. jsdom
317
- * computes no Tailwind, so tests still find the closed bubble without a hover.
318
- *
319
- * ⚠️ **A tap is not a hover (keksdose run 72, live #379).** The bubble used to show on
320
- * `mouseenter` and on `focus`, and hide only on `mouseleave` and `blur`. A tap on a phone
321
- * fires an emulated `mouseenter` and, on a button, focuses it — and the virtual mouse and
322
- * the focus both stay on the button until the next tap somewhere else, so every
323
- * IconButton's label (the top-bar icons, the sync indicator, a row's actions) stayed up
324
- * over whatever the tap had just opened. Now a touch or pen press is told apart from the
325
- * mouse: the emulated `mouseenter` that follows a tap is ignored, the press itself closes
326
- * a bubble that was up, and a focus that arrives while the page's last input was a finger
327
- * or a pen — the tap's own, or one a closing dialog RETURNS to the icon it was opened
328
- * from — shows nothing. The next key press (a Bluetooth keyboard on a tablet) or mouse
329
- * press makes focus count again, and a mouse entering the trigger hovers as ever, so a
330
- * hybrid laptop gets both.
331
- *
332
- * Focus otherwise shows the bubble, as it always did — the keyboard, a script, a test's
333
- * `.focus()` — with one more exception: the focus a mouse press on the trigger gives it.
334
- * There it follows `:focus-visible`, so a text field still shows its bubble while you type
335
- * and a clicked button does not hold it: a desktop click behaves as it always did while
336
- * the pointer is over the button (the hover shows it) and lets it go when the pointer
337
- * leaves, where the in-place bubble used to linger until the button lost focus — the same
338
- * left-behind bubble on a smaller scale, and what the portalled bubble always did. A
339
- * keyboard user's bubble, in turn, now stays up when a mouse passes over and leaves (the
340
- * portalled one used to close). Any `pointerdown` outside the trigger closes an open
341
- * bubble too — the Safari case, where a tap elsewhere moves no focus.
342
- *
343
- * What a tap DOES show is {@link TooltipTap} (`tap`). By default a tap that activates
344
- * something shows nothing — the action is the answer — while a tap that activates nothing
345
- * toggles the bubble, because there the bubble is the only answer: a write-locked or
346
- * disabled control, whose bubble is the reason it did not respond, and a trigger with no
347
- * control at all, a truncated payee or a badge, whose bubble is the rest of the text. A
348
- * second tap, a tap anywhere else, Escape or the focus leaving closes it. "Activates" is
349
- * read off the DOM — a link, a button, a field, a label, an element with a widget role
350
- * (`button`, `link`, `checkbox`, `option`, …), or a DataTable row (`tr[tabindex]`) — from
351
- * the tapped element outwards, past the tooltip, so a glyph inside a clickable row or a
352
- * button counts as that row or button. What it cannot read is a click handler on an
353
- * element with no role (give it `role="button"`, which it needs anyway, or pass
354
- * `tap="ignore"`), and a button that exists only to explain, whose click does nothing —
355
- * FieldHint's "?" — which says so with `tap="toggle"`.
356
- *
357
- * Why not show the label on a long press, as Android does for its own icons? Because the
358
- * web gives no reliable long press on a control: browsers disagree on whether the release
359
- * that ends one still clicks the button, and the button's own long press — the context
360
- * menu, text selection, a row's drag — claims the gesture first on others. A gesture meant
361
- * to ask "what does this do" must not risk doing it. Screen readers do not need it (the
362
- * bubble is in `aria-describedby`, and an IconButton's label is its name), and a tap on
363
- * anything that does nothing already shows it. No scroll listener either: every touch scroll begins with a `pointerdown`, which
364
- * already closes the bubble, and closing on scroll would close a keyboard user's bubble
365
- * the moment Tab scrolled its control into view.
366
- */
367
- export function Tooltip({
368
- label,
369
- side = "top",
370
- className,
371
- portal,
372
- lazy = false,
373
- redact = false,
374
- tap = "auto",
375
- children,
376
- ...rest
377
- }: TooltipProps) {
378
- // No label, no bubble — and no wrapper either, so a conditional tooltip costs the
379
- // layout nothing on the branch where it does not apply. `...rest` goes with the
380
- // wrapper on this branch, which is the documented limit of the pass-through: there is
381
- // no element left to put an attribute on.
382
- if (isEmptyLabel(label)) return <>{children}</>;
383
- if (portal === true) {
384
- return (
385
- <PortalTooltip label={label} side={side} className={className} redact={redact} tap={tap} {...rest}>
386
- {children}
387
- </PortalTooltip>
388
- );
389
- }
390
- // Both variants are separate components so that `Tooltip` itself can keep calling NO
391
- // hooks: the empty-label branch above returns before either of them, and a hook after
392
- // a conditional return is a hooks-order bug rather than a style violation.
393
- return (
394
- <InPlaceTooltip
395
- label={label}
396
- side={side}
397
- className={className}
398
- redact={redact}
399
- detect={portal === undefined}
400
- lazy={lazy}
401
- tap={tap}
402
- {...rest}
403
- >
404
- {children}
405
- </InPlaceTooltip>
406
- );
407
- }
408
-
409
- /** The pointer handlers a caller may hand {@link Tooltip} that the trigger also needs:
410
- * both run, the trigger's first. (The mouse and focus handlers are the trigger's alone,
411
- * as they have always been.) */
412
- type PointerHandlers = Pick<
413
- ComponentPropsWithoutRef<"span">,
414
- "onPointerEnter" | "onPointerDown" | "onPointerUp" | "onPointerCancel"
415
- >;
416
-
417
- /** A press that is not the mouse's: a finger, or a pen on the glass. */
418
- function isTap(pointerType: string): boolean {
419
- return pointerType === "touch" || pointerType === "pen";
420
- }
421
-
422
- /**
423
- * What a tap ACTIVATES, for `tap="auto"`: the tapped element or the nearest ancestor —
424
- * inside the tooltip or round it — that a tap would do something with. The DataTable's
425
- * own list of row-owned controls (`OWN_CONTROL`), plus `tr[tabindex]` — a clickable
426
- * DataTable row is a roving tab stop, so all but one of its rows say `-1` — and the other
427
- * widget roles a tap selects or toggles.
428
- *
429
- * Two things on the DataTable's list are left off on purpose. A bare `tabindex`: a roving
430
- * tab stop is `0` on one element of a widget and `-1` on the rest, so it would make one
431
- * cell of a display-only grid "activate" and its neighbours not — and dialogs, panels and
432
- * `<main>` carry `-1` only to be focusable by script. And `gridcell` / `row`: the
433
- * CalendarHeatmap's days are display-only grid cells whose tooltip is the day's value,
434
- * and a tap is the only way to read it on a phone; a heatmap day that does select is a
435
- * `<button>`, caught above.
436
- */
437
- const ACTIVATES = [
438
- "a[href]",
439
- "button",
440
- "input",
441
- "select",
442
- "textarea",
443
- "label",
444
- "summary",
445
- '[contenteditable=""]',
446
- '[contenteditable="true"]',
447
- "tr[tabindex]",
448
- ...[
449
- "button",
450
- "link",
451
- "checkbox",
452
- "radio",
453
- "switch",
454
- "tab",
455
- "menuitem",
456
- "menuitemcheckbox",
457
- "menuitemradio",
458
- "option",
459
- "treeitem",
460
- "slider",
461
- "spinbutton",
462
- "combobox",
463
- ].map((role) => `[role="${role}"]`),
464
- ].join(",");
465
-
466
- /** Whether a tap on `target` should toggle the bubble — see {@link TooltipTap}. Under
467
- * `auto`: when the tap activates nothing, either because there is no control under it
468
- * or because the control is disabled (natively, or `aria-disabled` — a write lock). */
469
- function tapShows(tap: TooltipTap, target: EventTarget | null): boolean {
470
- if (tap !== "auto") return tap === "toggle";
471
- if (!(target instanceof Element)) return true;
472
- const control = target.closest(ACTIVATES);
473
- return control === null || control.matches(":disabled") || control.closest('[aria-disabled="true"]') !== null;
474
- }
475
-
476
- /**
477
- * The last kind of input the document saw: a `pointerdown`'s `pointerType` ("mouse",
478
- * "touch", "pen"), "keyboard" after a key press, `null` before either. ONE pair of
479
- * capturing listeners for the whole page, installed by the first trigger that mounts —
480
- * not a pair per tooltip, which a table of forty would multiply.
481
- *
482
- * Why a page-wide note and not only the trigger's own: the focus that brings a label back
483
- * after a tap is not always the tap's. A tap on a top-bar icon opens a dialog; closing it
484
- * (another tap) RETURNS the focus to the icon by script, and that focus arrives with no
485
- * press on the icon at all. On a phone it would put the label up over the page the dialog
486
- * just left, and keep it there. Keys an on-screen keyboard sends while typing
487
- * (`Unidentified`, a composition) and shortcut chords do not count as the keyboard.
488
- */
489
- let lastInput: string | null = null;
490
- let trackingInput = false;
491
-
492
- function trackInput(): void {
493
- if (trackingInput || typeof document === "undefined") return;
494
- trackingInput = true;
495
- document.addEventListener(
496
- "pointerdown",
497
- (e) => {
498
- lastInput = e.pointerType;
499
- },
500
- { capture: true, passive: true },
501
- );
502
- document.addEventListener(
503
- "keydown",
504
- (e) => {
505
- if (e.key === "Unidentified" || e.isComposing || e.ctrlKey || e.metaKey || e.altKey) return;
506
- lastInput = "keyboard";
507
- },
508
- { capture: true, passive: true },
509
- );
510
- }
511
-
512
- /**
513
- * Whether a focus should show the bubble. Not after a tap — the last input was a finger
514
- * or a pen, whoever moved the focus. Not after a mouse press on this very trigger either,
515
- * unless the browser would draw a focus ring there (`:focus-visible`: a text field, not a
516
- * button) — the hover already shows it while the pointer is there. Every other focus
517
- * shows it as every focus did before: the keyboard, a script, and a test's `.focus()` or
518
- * `fireEvent.focus` (jsdom's own `:focus-visible` guesses from whatever events the test
519
- * file fired before, so it is consulted only where a press makes the answer certain).
520
- */
521
- function focusShows(target: EventTarget, pressedByMouse: boolean): boolean {
522
- if (lastInput !== null && isTap(lastInput)) return false;
523
- if (!pressedByMouse || !(target instanceof Element)) return true;
524
- try {
525
- return target.matches(":focus-visible");
526
- } catch {
527
- return true;
528
- }
529
- }
530
-
531
- /**
532
- * Whether the bubble is up, from what the trigger hears — shared by both variants so the
533
- * in-place and the portalled bubble cannot disagree about it. See "A tap is not a hover"
534
- * on {@link Tooltip} for the rules; this is their bookkeeping.
535
- *
536
- * Three ways up — hovered by a mouse (or a hovering pen), focused (see {@link focusShows}),
537
- * and tapped (`tap`) — and Escape over all three (`dismissed`), re-armed by the next
538
- * request rather than by an effect watching the flags: coming back to a trigger is a
539
- * fresh request for its label, and an effect would also re-show the bubble under a
540
- * pointer that never left. `onArm` runs with each request, for the variant's own per-open
541
- * work (the reading direction, the clipping check).
542
- *
543
- * The refs change nothing on screen, only how the next event is read. `pressedByTouch`
544
- * is set by a touch or pen arriving or pressing — which precedes the emulated
545
- * `mouseenter` a tap produces (pointer events first, then the compatibility mouse
546
- * events) — and cleared by a mouse, or a pen that is not pressing, entering again, so a
547
- * hybrid laptop's mouse still hovers. `pressedByMouse` spans one mouse press, the window
548
- * in which that press's own focus arrives.
549
- */
550
- function useTooltipTrigger(
551
- triggerRef: RefObject<HTMLSpanElement | null>,
552
- tap: TooltipTap,
553
- passed: PointerHandlers,
554
- onArm: (el: HTMLElement) => void,
555
- ) {
556
- const [hovered, setHovered] = useState(false);
557
- const [focused, setFocused] = useState(false);
558
- const [tapped, setTapped] = useState(false);
559
- const [dismissed, setDismissed] = useState(false);
560
- const pressedByTouch = useRef(false);
561
- const pressedByMouse = useRef(false);
562
- // The tap in progress, and whether a bubble was up when it began: a tap on an open
563
- // bubble closes it (at pointerdown) and must not open it again at pointerup.
564
- const press = useRef<{ wasOpen: boolean } | null>(null);
565
- useEffect(trackInput, []);
566
- const open = (hovered || focused || tapped) && !dismissed;
567
- const close = () => {
568
- setHovered(false);
569
- setFocused(false);
570
- setTapped(false);
571
- };
572
- // A layer on the Escape stack while up (Wave 2, DUP-1): over a dialog, the first
573
- // Escape dismisses the bubble — WCAG 1.4.13, without moving focus — and the next one
574
- // the dialog. Inside a Modal the bubble used to be beyond reach of the key.
575
- // Not an overlay: a tooltip annotates what it is on, so a floating launcher with one
576
- // does not step aside for its own bubble (`hideWhenOverlay`, 0.49 KIT-11).
577
- useDismissLayer(open, () => setDismissed(true), { overlay: false });
578
- // A press anywhere else closes it — the case a blur does not cover: Safari moves no
579
- // focus on a tap, and a tap on plain text moves it nowhere.
580
- useOutsideClick(triggerRef, close, open);
581
- const arm = (el: HTMLElement) => {
582
- setDismissed(false);
583
- onArm(el);
584
- };
585
- const handlers = {
586
- onPointerEnter: (e: PointerEvent<HTMLSpanElement>) => {
587
- if (e.pointerType === "touch") pressedByTouch.current = true;
588
- else if (e.buttons === 0) pressedByTouch.current = false;
589
- passed.onPointerEnter?.(e);
590
- },
591
- onPointerDown: (e: PointerEvent<HTMLSpanElement>) => {
592
- if (isTap(e.pointerType)) {
593
- pressedByTouch.current = true;
594
- press.current = { wasOpen: open };
595
- close();
596
- } else {
597
- pressedByTouch.current = false;
598
- pressedByMouse.current = true;
599
- }
600
- passed.onPointerDown?.(e);
601
- },
602
- onPointerUp: (e: PointerEvent<HTMLSpanElement>) => {
603
- pressedByMouse.current = false;
604
- const began = press.current;
605
- press.current = null;
606
- if (began && !began.wasOpen && isTap(e.pointerType) && tapShows(tap, e.target)) {
607
- setTapped(true);
608
- arm(e.currentTarget);
609
- }
610
- passed.onPointerUp?.(e);
611
- },
612
- // The browser took the gesture over — a scroll, a pinch: not a tap.
613
- onPointerCancel: (e: PointerEvent<HTMLSpanElement>) => {
614
- pressedByMouse.current = false;
615
- press.current = null;
616
- passed.onPointerCancel?.(e);
617
- },
618
- onMouseEnter: (e: MouseEvent<HTMLSpanElement>) => {
619
- if (pressedByTouch.current) return;
620
- setHovered(true);
621
- arm(e.currentTarget);
622
- },
623
- onMouseLeave: () => setHovered(false),
624
- onFocus: (e: FocusEvent<HTMLSpanElement>) => {
625
- if (!focusShows(e.target, pressedByMouse.current)) return;
626
- setFocused(true);
627
- arm(e.currentTarget);
628
- },
629
- onBlur: (e: FocusEvent<HTMLSpanElement>) => {
630
- setFocused(false);
631
- if (e.relatedTarget instanceof Node && e.currentTarget.contains(e.relatedTarget)) return;
632
- setTapped(false);
633
- },
634
- };
635
- return { open, dismissed, handlers };
636
- }
637
-
638
- /**
639
- * The trigger wrapper's own ref, and one callback ref that hands the wrapper to the
640
- * caller's `ref` as well (round 0.35 R4).
641
- */
642
- function useTriggerRef(ref: Ref<HTMLSpanElement> | undefined) {
643
- const triggerRef = useRef<HTMLSpanElement | null>(null);
644
- const setTrigger = useCallback(
645
- (el: HTMLSpanElement | null) => {
646
- triggerRef.current = el;
647
- assignRef(ref, el);
648
- },
649
- [ref],
650
- );
651
- return { triggerRef, setTrigger };
652
- }
653
-
654
- /** The variant that lives next to its trigger, and — when `detect` is on, which is the
655
- * default — moves its bubble to `<body>` when that turns out to be inside a clipping
656
- * container (see "Inside a scroll container" on {@link Tooltip}).
657
- *
658
- * Whether the bubble is up is {@link useTooltipTrigger}'s, shared with the portalled
659
- * variant. Until 0.25 the always-mounted bubble showed itself with CSS alone —
660
- * `group-hover` / `group-focus-within` — which is exactly what a tap on a phone could not
661
- * get rid of: the tapped button keeps the focus, and `:focus-within` cannot be told that
662
- * the focus came from a finger. It is shown from the same state as every other bubble
663
- * now, and is `display: none` while closed (see "never widens the page" on
664
- * {@link Tooltip}).
665
- *
666
- * ONE component for both placements rather than a switch between the two variants: a
667
- * switch would be a different component at the same place in the tree, and React
668
- * would remount the caller's child with it — a button that loses focus the moment
669
- * its own tooltip decided where to go. Here the wrapper and the child stay put and
670
- * only the bubble's slot changes. */
671
- function InPlaceTooltip({
672
- label,
673
- side,
674
- className,
675
- redact,
676
- detect,
677
- lazy,
678
- tap,
679
- children,
680
- ref,
681
- ...rest
682
- }: TooltipVariantProps & { detect: boolean; lazy: boolean }) {
683
- const id = useId();
684
- const { triggerRef, setTrigger } = useTriggerRef(ref);
685
- const [clipped, setClipped] = useState(false);
686
- const [dir, setDir] = useState<Direction>("ltr");
687
- // The reading direction on every request (the clamp keeps the start edge of a bubble
688
- // too wide for the room); the clipping check too, for a container that began to scroll
689
- // after mount.
690
- const { open, dismissed, handlers } = useTooltipTrigger(triggerRef, tap, rest, (el) => {
691
- setDir(dirOf(el));
692
- if (detect) setClipped(hasClippingAncestor(el));
693
- });
694
- // keksdose G7: slide an open in-place bubble back onto the screen. See "The in-place
695
- // bubble is clamped" on {@link Tooltip}.
696
- const bubbleRef = useRef<HTMLSpanElement | null>(null);
697
- const { shift, measuring } = useViewportClamp(bubbleRef, open && !clipped, side, label, dir);
698
- // At mount, before the first paint: the in-place bubble inside a scroller is the
699
- // phantom-scroll bug whether or not anyone opens it.
700
- useLayoutEffect(() => {
701
- if (detect) setClipped(hasClippingAncestor(triggerRef.current));
702
- // `triggerRef` is the same object for the component's life (useTriggerRef's useRef).
703
- }, [detect, triggerRef]);
704
- return (
705
- // No role for this span: its handlers track whether the bubble is up and activate
706
- // nothing; the caller's child is the interactive element, keeps its own handlers,
707
- // and its focus shows the bubble too.
708
- <span
709
- // `...rest` first: the handlers below are what decides whether a bubble is up, and
710
- // a caller passing an `onFocus` of its own must not replace them (its pointer
711
- // handlers are called from them).
712
- {...rest}
713
- ref={setTrigger}
714
- // Clipped for the one render in which the bubble is measured (see useViewportClamp):
715
- // that layout must not reach the page's width.
716
- style={measuring ? { ...rest.style, overflow: "clip" } : rest.style}
717
- className={cn("relative inline-flex", !clipped && "group/tooltip", className)}
718
- // These track WHETHER A BUBBLE IS UP. They activate nothing — the only thing here
719
- // that can be activated is the caller's child, which keeps every handler it
720
- // arrived with — so this wrapper needs no role and no key handling of its own.
721
- {...handlers}
722
- >
723
- {/* In place the bubble is always there to point at; portalled or lazy, only while up. */}
724
- {describedBy(children, clipped || lazy ? (open ? id : undefined) : dismissed ? undefined : id)}
725
- {clipped ? (
726
- open && <PortalBubble triggerRef={triggerRef} id={id} label={label} side={side} dir={dir} redact={redact} />
727
- ) : lazy ? (
728
- // keksdose F6: the in-place slot and classes, the portalled lifetime. Mounted only
729
- // while up, so it is visible whenever it exists.
730
- open && (
731
- <span
732
- ref={bubbleRef}
733
- id={id}
734
- role="tooltip"
735
- data-private={redact ? "" : undefined}
736
- style={shiftStyle(shift)}
737
- className={cn(TOOLTIP_SURFACE, "pointer-events-none absolute z-50 opacity-100", sidePositionClass[side])}
738
- >
739
- {label}
740
- </span>
741
- )
742
- ) : (
743
- <span
744
- ref={bubbleRef}
745
- id={id}
746
- role="tooltip"
747
- style={shiftStyle(shift)}
748
- // The `hidden` ATTRIBUTE, not a class: dismissing has to take the bubble out of
749
- // the accessibility tree as well as off the screen, or a screen reader still
750
- // reads out the description of a bubble the user just closed. Merely CLOSED it
751
- // is the `hidden` CLASS — `display: none` in the browser, so it takes no room
752
- // and widens nothing (live #381), and nothing at all under jsdom, where tests
753
- // have always found this bubble without a hover. `aria-describedby` reads a
754
- // `display: none` node all the same.
755
- hidden={dismissed || undefined}
756
- data-private={redact ? "" : undefined}
757
- className={cn(
758
- TOOLTIP_SURFACE,
759
- "pointer-events-none absolute z-50",
760
- open ? "opacity-100" : "hidden",
761
- sidePositionClass[side],
762
- )}
763
- >
764
- {label}
765
- </span>
766
- )}
767
- </span>
768
- );
769
- }
770
-
771
- /** Hand `children` the bubble's id as an `aria-describedby`, if it is an element that
772
- * can hold one. `id` is undefined while there is no bubble to point at — a dangling
773
- * reference describes the trigger as nothing at all, which is worse than silence. */
774
- function describedBy(children: ReactNode, id: string | undefined): ReactNode {
775
- if (id === undefined || !isValidElement(children)) return children;
776
- const child = children as ReactElement<{ "aria-describedby"?: string }>;
777
- // Fragments, Suspense and friends are symbol-typed and take no DOM props; cloning one
778
- // with an aria attribute warns in development and drops it in production.
779
- if (typeof child.type === "symbol") return children;
780
- const own = child.props["aria-describedby"];
781
- return cloneElement(child, { "aria-describedby": own ? `${own} ${id}` : id });
782
- }
783
-
784
- /** "Would this bubble be blank." Only the values a call site actually produces when
785
- * it has nothing to say — `""`, `null`, `undefined`, `false` from a `&&` guard. A
786
- * numeric `0` is a real label and stays one. */
787
- function isEmptyLabel(label: ReactNode): boolean {
788
- return label == null || label === false || (typeof label === "string" && label.trim() === "");
789
- }
790
-
791
- const TOOLTIP_GAP = 4;
792
-
793
- /** How close to the viewport edge a bubble may sit. Not zero: a label flush
794
- * against the glass reads as clipped even when every character is on screen. */
795
- const TOOLTIP_MARGIN = 4;
796
-
797
- /** A cross-axis slide, in viewport pixels, for an in-place bubble. */
798
- interface Shift {
799
- x: number;
800
- y: number;
801
- }
802
-
803
- const NO_SHIFT: Shift = { x: 0, y: 0 };
804
-
805
- /** An in-place bubble's slide and the label, side and direction it was measured for. */
806
- interface Placement {
807
- shift: Shift;
808
- for: { side: TooltipSide; label: ReactNode; dir: Direction } | null;
809
- }
810
-
811
- const UNPLACED: Placement = { shift: NO_SHIFT, for: null };
812
-
813
- /** How far to slide the span `[low, high]` so it sits inside `[TOOLTIP_MARGIN,
814
- * extent - TOOLTIP_MARGIN]`. Zero when it already does. When the span is wider than the
815
- * room, the edge it keeps is its START — as in {@link clamp}, the start of a label is the
816
- * half worth keeping — which is the low edge unless `keepHigh` says the start is the
817
- * high one (an RTL label, along x). The width cap means that only happens on a viewport
818
- * barely wider than the margins, or one whose scrollbar the cap's `100vw` counts. */
819
- function slideInto(low: number, high: number, extent: number, keepHigh = false): number {
820
- const min = TOOLTIP_MARGIN;
821
- const max = extent - TOOLTIP_MARGIN;
822
- if (high - low > max - min) return keepHigh ? max - high : min - low;
823
- if (low < min) return min - low;
824
- if (high > max) return max - high;
825
- return 0;
826
- }
827
-
828
- // The glass a bubble has to stay on — the root's client width, not `innerWidth` (keksdose
829
- // run 72, live #381) — is `viewportSize` in use-anchored-panel.ts since 0.34 (Wave 2,
830
- // DUP-7), where every anchored panel now reads it.
831
-
832
- /**
833
- * The in-place bubble's viewport clamp (keksdose G7): while `active`, measure the bubble
834
- * in a LAYOUT effect — before paint, so there is no frame with the label off the screen —
835
- * and return the cross-axis slide that brings it inside the viewport ({@link viewportSize})
836
- * minus `TOOLTIP_MARGIN`. `top` / `bottom` slide along x; `left` / `right` / `start` / `end`
837
- * along y. {@link NO_SHIFT} while closed, so the next open measures the bubble where the
838
- * CSS alone puts it. `dir` only decides which edge survives a bubble wider than the room.
839
- *
840
- * `measuring` is true for the one render in which the bubble is measured — on open, and
841
- * again when the label, side or direction changes while open — and the caller clips its
842
- * wrapper (`overflow: clip`) for that render. Measuring means laying the bubble out where
843
- * the CSS alone puts it, which is past the edge whenever there is something to slide, and
844
- * that layout counts towards the page's scrollable overflow even though it is never
845
- * painted. Chrome on a phone grows the layout viewport to it and does not always give the
846
- * width back: in an RTL page, where overflow on the left is scrollable, a bubble measured
847
- * at −24 left the page 426 wide and scrolled by −20 after the slide had already brought it
848
- * to 4 (Chromium mobile emulation, 406px). A clipped wrapper keeps the measurement out of
849
- * every ancestor's overflow — `clip` and not `hidden`, so the wrapper never becomes a
850
- * scroll container — and it is unclipped, with the slide, in the same task, before paint.
851
- *
852
- * The rect it reads already includes the slide it applied last time (a label that
853
- * changed while open), so that slide is taken back out before deciding the new one.
854
- * A zero-sized rect means no layout (jsdom, `display: none`) and leaves the bubble put.
855
- */
856
- function useViewportClamp(
857
- bubbleRef: RefObject<HTMLSpanElement | null>,
858
- active: boolean,
859
- side: TooltipSide,
860
- label: ReactNode,
861
- dir: Direction,
862
- ): { shift: Shift; measuring: boolean } {
863
- // The slide, and what it was measured for: anything else on screen — a fresh open, a
864
- // new label, side or direction — is not measured yet, and renders clipped until it is.
865
- const [placement, setPlacement] = useState<Placement>(UNPLACED);
866
- const measured =
867
- placement.for !== null && placement.for.side === side && placement.for.label === label && placement.for.dir === dir;
868
- useLayoutEffect(() => {
869
- const el = bubbleRef.current;
870
- if (!active || !el) {
871
- setPlacement(UNPLACED);
872
- return;
873
- }
874
- if (measured) return;
875
- const r = el.getBoundingClientRect();
876
- const unlaid = r.width === 0 && r.height === 0;
877
- const horizontal = side === "top" || side === "bottom";
878
- const viewport = viewportSize();
879
- setPlacement(({ shift: previous }) => {
880
- const next = unlaid
881
- ? previous
882
- : horizontal
883
- ? { x: slideInto(r.left - previous.x, r.right - previous.x, viewport.width, dir === "rtl"), y: 0 }
884
- : { x: 0, y: slideInto(r.top - previous.y, r.bottom - previous.y, viewport.height) };
885
- const shift = next.x === previous.x && next.y === previous.y ? previous : next;
886
- return { shift, for: { side, label, dir } };
887
- });
888
- }, [bubbleRef, active, measured, side, label, dir]);
889
- return { shift: placement.shift, measuring: active && !measured };
890
- }
891
-
892
- /**
893
- * The inline style for a slide: none at all when there is nothing to slide, so a bubble
894
- * that fits renders exactly as it did before G7.
895
- *
896
- * A MARGIN, not a `transform` (0.14–0.24 used `transform: translate(…)`), because of what
897
- * Blink does with the page's width (keksdose run 72, live #381). The slide is decided after
898
- * a layout that placed the bubble where the CSS alone puts it — past the edge — and that
899
- * layout already counted it into the page's scrollable overflow. A later change to
900
- * `transform` alone re-paints but does not re-lay-out, so that overflow was never taken
901
- * back: measured in Chromium's mobile emulation, a bubble shown at 108–428 on a 406px
902
- * screen and then slid to 400 by a transform left the page 428 wide, the layout viewport
903
- * with it (`position: fixed` layers laid out against 428), until the bubble closed. The
904
- * same slide as a margin is a layout change; the page went back to 406 in the same frame.
905
- *
906
- * Physical margins, matching the physical maths: `margin-left` moves a `top` / `bottom`
907
- * bubble, whose placement is the physical `left: 50%`; `margin-top` moves the four side
908
- * placements, placed by `top: 50%`. Neither is a margin the placement classes set — they
909
- * keep their gap on the main axis (`mb-1`, `ms-1`, …).
910
- */
911
- function shiftStyle(shift: Shift) {
912
- if (shift.x === 0 && shift.y === 0) return undefined;
913
- return shift.x !== 0 ? { marginLeft: shift.x } : { marginTop: shift.y };
914
- }
915
-
916
- const portalTransformBySide: Record<PhysicalSide, string> = {
917
- right: "translate(0, -50%)",
918
- left: "translate(-100%, -50%)",
919
- top: "translate(-50%, -100%)",
920
- bottom: "translate(-50%, 0)",
921
- };
922
-
923
- /** Anchor point (viewport px) for the tooltip on the given side of `r`. Paired
924
- * with {@link portalTransformBySide}, which shifts the box onto that point. */
925
- function tooltipAnchor(r: AnchorRect, side: PhysicalSide): { left: number; top: number } {
926
- switch (side) {
927
- case "right":
928
- return { left: r.right + TOOLTIP_GAP, top: r.top + r.height / 2 };
929
- case "left":
930
- return { left: r.left - TOOLTIP_GAP, top: r.top + r.height / 2 };
931
- case "top":
932
- return { left: r.left + r.width / 2, top: r.top - TOOLTIP_GAP };
933
- case "bottom":
934
- return { left: r.left + r.width / 2, top: r.bottom + TOOLTIP_GAP };
935
- }
936
- }
937
-
938
- export interface TooltipSize {
939
- width: number;
940
- height: number;
941
- }
942
-
943
- export interface TooltipViewport {
944
- width: number;
945
- height: number;
946
- }
947
-
948
- export interface TooltipPlacement {
949
- left: number;
950
- top: number;
951
- /** Which side it ended up on, which need not be the one that was asked for. */
952
- side: PhysicalSide;
953
- }
954
-
955
- const opposite: Record<PhysicalSide, PhysicalSide> = {
956
- left: "right",
957
- right: "left",
958
- top: "bottom",
959
- bottom: "top",
960
- };
961
-
962
- /** Whether the bubble clears the viewport edge on `side` of the trigger. */
963
- function roomOn(r: AnchorRect, side: PhysicalSide, size: TooltipSize, viewport: TooltipViewport): boolean {
964
- switch (side) {
965
- case "left":
966
- return r.left - TOOLTIP_GAP - size.width >= TOOLTIP_MARGIN;
967
- case "right":
968
- return r.right + TOOLTIP_GAP + size.width <= viewport.width - TOOLTIP_MARGIN;
969
- case "top":
970
- return r.top - TOOLTIP_GAP - size.height >= TOOLTIP_MARGIN;
971
- case "bottom":
972
- return r.bottom + TOOLTIP_GAP + size.height <= viewport.height - TOOLTIP_MARGIN;
973
- }
974
- }
975
-
976
- function sameRoom(
977
- a: { size: TooltipSize; viewport: TooltipViewport },
978
- b: { size: TooltipSize; viewport: TooltipViewport },
979
- ): boolean {
980
- return (
981
- a.size.width === b.size.width &&
982
- a.size.height === b.size.height &&
983
- a.viewport.width === b.viewport.width &&
984
- a.viewport.height === b.viewport.height
985
- );
986
- }
987
-
988
- function clamp(value: number, low: number, high: number): number {
989
- // `high` first, so a bubble taller or wider than the viewport is pinned to the
990
- // top-left corner rather than to the bottom-right one — the start of a label
991
- // is the half worth keeping.
992
- return Math.max(low, Math.min(value, high));
993
- }
994
-
995
- /**
996
- * Where the bubble actually goes, given how big it turned out to be.
997
- *
998
- * Two rules, and they are separate because they fix separate failures.
999
- *
1000
- * **Turn round when the preferred side has no room.** `side` says which side of
1001
- * the trigger the label reads best on, and on a form near the left edge of the
1002
- * window that side is off the screen — the capped bubble can only wrap, not
1003
- * move, so what the reader gets is a sentence with its first half outside the
1004
- * glass. Flipped only when the *other* side is genuinely better: a trigger in a
1005
- * viewport too narrow for the bubble either way keeps the side it asked for, and
1006
- * the clamp below does what it can.
1007
- *
1008
- * **Then clamp both axes.** The cross axis is the one that needs it — a `top`
1009
- * bubble is centred on the trigger, so a trigger near the left edge pushes half
1010
- * the label off even though the side it is on is right — and clamping the main
1011
- * axis too costs nothing and covers the flip having nowhere to land.
1012
- *
1013
- * Pure, and measured in viewport pixels throughout, so it can be tested without
1014
- * a layout: the caller supplies the trigger's rect, the bubble's own size and
1015
- * the window.
1016
- */
1017
- export function placeTooltip(
1018
- r: AnchorRect,
1019
- side: PhysicalSide,
1020
- size: TooltipSize,
1021
- viewport: TooltipViewport,
1022
- ): TooltipPlacement {
1023
- const chosen = roomOn(r, side, size, viewport) || !roomOn(r, opposite[side], size, viewport) ? side : opposite[side];
1024
- const point = tooltipAnchor(r, chosen);
1025
- const box =
1026
- chosen === "left"
1027
- ? { left: point.left - size.width, top: point.top - size.height / 2 }
1028
- : chosen === "right"
1029
- ? { left: point.left, top: point.top - size.height / 2 }
1030
- : chosen === "top"
1031
- ? { left: point.left - size.width / 2, top: point.top - size.height }
1032
- : { left: point.left - size.width / 2, top: point.top };
1033
- return {
1034
- left: clamp(box.left, TOOLTIP_MARGIN, viewport.width - size.width - TOOLTIP_MARGIN),
1035
- top: clamp(box.top, TOOLTIP_MARGIN, viewport.height - size.height - TOOLTIP_MARGIN),
1036
- side: chosen,
1037
- };
1038
- }
1039
-
1040
- function PortalTooltip({ label, side, className, redact, tap, children, ref, ...rest }: TooltipVariantProps) {
1041
- const { triggerRef, setTrigger } = useTriggerRef(ref);
1042
- // The trigger's reading direction, read when the bubble is asked for (an event, not a
1043
- // render): it resolves `start` / `end`, and the portalled bubble — which has left the
1044
- // subtree it would have inherited `dir` from — carries it too.
1045
- const [dir, setDir] = useState<Direction>("ltr");
1046
- const id = useId();
1047
- // Escape closes it outright, since this variant's bubble only exists while it is
1048
- // shown. The next mouseenter/focus brings it back, which is the behaviour WCAG
1049
- // 1.4.13 asks for: dismissible now, still available when you ask again.
1050
- const { open, handlers } = useTooltipTrigger(triggerRef, tap, rest, (el) => setDir(dirOf(el)));
1051
-
1052
- return (
1053
- <>
1054
- {/* As in `InPlaceTooltip`, no role: the handlers only show and hide the bubble; the
1055
- caller's child is the interactive element, and focusing it shows the bubble. */}
1056
- <span
1057
- // As in `InPlaceTooltip`: the caller's attributes first, the handlers that run
1058
- // this component after them. The BUBBLE is deliberately not given them — it
1059
- // is portalled to `<body>`, and an id or a tour anchor duplicated onto a node
1060
- // that only exists while hovered would match twice or match nothing. So, too,
1061
- // the caller's `ref` (round 0.35 R4).
1062
- {...rest}
1063
- ref={setTrigger}
1064
- className={cn("relative inline-flex", className)}
1065
- {...handlers}
1066
- >
1067
- {describedBy(children, open ? id : undefined)}
1068
- </span>
1069
- {open && <PortalBubble triggerRef={triggerRef} id={id} label={label} side={side} dir={dir} redact={redact} />}
1070
- </>
1071
- );
1072
- }
1073
-
1074
- /** The measured, `position: fixed` bubble on `<body>`, mounted only while it is up.
1075
- * Shared by {@link PortalTooltip} and the in-place variant's clipped mode, so the two
1076
- * cannot place a bubble differently. */
1077
- function PortalBubble({
1078
- triggerRef,
1079
- id,
1080
- label,
1081
- side,
1082
- dir,
1083
- redact,
1084
- }: {
1085
- triggerRef: RefObject<HTMLSpanElement | null>;
1086
- id: string;
1087
- label: ReactNode;
1088
- side: TooltipSide;
1089
- dir: Direction;
1090
- redact: boolean | undefined;
1091
- }) {
1092
- const bubbleRef = useRef<HTMLSpanElement | null>(null);
1093
- // The measure + scroll/resize-tracking lifecycle is owned by useAnchoredRect;
1094
- // here we only map the rect to a side-specific anchor point.
1095
- const rect = useAnchoredRect(triggerRef, true);
1096
- // The bubble's own size and the window it has to fit in — neither of which is
1097
- // knowable in render: the width is whatever the label wrapped to inside the
1098
- // cap, and reading `window` while rendering is not a pure thing to do. Both
1099
- // are taken in a LAYOUT effect, so the correction lands before the browser
1100
- // paints and there is no frame in which the label sits off the screen.
1101
- const [room, setRoom] = useState<{ size: TooltipSize; viewport: TooltipViewport } | null>(null);
1102
- useLayoutEffect(() => {
1103
- const measured = bubbleRef.current?.getBoundingClientRect();
1104
- setRoom((previous) => {
1105
- if (!measured) return null;
1106
- const next = {
1107
- size: { width: measured.width, height: measured.height },
1108
- // The glass, not the window: see viewportSize (live #381).
1109
- viewport: viewportSize(),
1110
- };
1111
- // Only publish what actually CHANGED: every re-measure allocates a fresh
1112
- // object, and a new object on every scroll event would re-render the
1113
- // bubble forever.
1114
- return previous && sameRoom(previous, next) ? previous : next;
1115
- });
1116
- }, [rect, label]);
1117
-
1118
- const physical = physicalSide(side, dir);
1119
- const point = rect ? tooltipAnchor(rect, physical) : null;
1120
- // Unmeasured on the very first pass, where the anchor point plus the side's
1121
- // own transform is exactly what this always did. One layout effect later the
1122
- // size is known and the placement is decided properly.
1123
- const placed = rect && room ? placeTooltip(rect, physical, room.size, room.viewport) : null;
1124
-
1125
- if (!point || typeof document === "undefined") return null;
1126
- return createPortal(
1127
- <span
1128
- ref={bubbleRef}
1129
- id={id}
1130
- role="tooltip"
1131
- dir={dir}
1132
- data-private={redact ? "" : undefined}
1133
- style={
1134
- placed
1135
- ? { position: "fixed", left: placed.left, top: placed.top }
1136
- : {
1137
- position: "fixed",
1138
- left: point.left,
1139
- top: point.top,
1140
- transform: portalTransformBySide[physical],
1141
- }
1142
- }
1143
- className={cn(TOOLTIP_SURFACE, "pointer-events-none z-50")}
1144
- >
1145
- {label}
1146
- </span>,
1147
- document.body,
1148
- );
1149
- }